Adding MvcContrib SubControllers to your ASP.NET MVCProject

After a little bit of work with MVC, you get infected with the spirit of clean codeand begin to desire even more ways of eliminating repetition. You’ve got partialsand html helpers. Still you are hungry. SubControllers are the dish that will fillyou up.

Why SubControllers?

There’s a design decision here. The question is why are we designing with subcontrollers?To understand the rationale, let’s look at the qualities of the various sub-view options.

  • Partial Views – partial views are probably the most useful method for re-using viewoutput. They are very easy to set up, and are very versatile. However, they don’tcontain logic. They are intended entirely as a slave to the Action Controller. Theyget their model from the controller and simply render it. This means that the controllerneeds to know and provide everything the partial view needs. Not good for somethinglike a login status control, which is an separate concern from most controllers.
  • HTML Helpers – HTML helpers get used a lot, and they are very helpful for creatingyour own ‘controls’ to use in your pages. They do not, however, support using a viewtemplate. This means you’ve got to create them and test them with tests for emittedmarkup. This adds a lot of complexity if you are trying to do something more thancreate a html rendering function. Not a good place to insert complex view logic, likea shopping cart status widget, or anything with a table or list. The HTML helper alsostill gets it’s data from the master view/controller, so it fails at separating concerns.
  • Html.RenderAction() – RenderAction is the once and future solution to a number ofproblems. Or so I hear. I have trouble getting excited about using it right now. Currently,it is not baked into MVC, but rather is in the separate ‘MVC Futures’ assembly, whereit is unsupported. Rumor is it is buggy and unsecure. In the future it may be changedor dropped or renamed or anything. ScottGu has promised in a blog comment that itis due for inclusion in MVC 2. However, I don’t program to unreleased Microsoft products.Maybe later, but for now…
  • MvcContrib SubController – Jeff Palermo implemented subcontrollers for MvcContribto give us a working solution—now—for reusable view/controller code. Subcontrollershave their own model, ViewData, are nestable, and can use view templates for theiroutput. If you have a job that goes beyond a partial view, or a html helper, SubControllersare a peach.

Setting Up Your Project

1) Add a reference to MvcContrib.

2) Create a class called StructureMapSubControllerBinder. This is only required ifyou’re using StructureMap to do your IOC for you. You can use the base SubControllerBinderfrom MvcContrib, or create your own version for your IOC tool.

    public class StructureMapSubControllerBinder: SubControllerBinder{public override object CreateSubController(TypedestinationType){object instance = ObjectFactory.GetInstance(destinationType);if (instance == null){throw new InvalidOperationException(destinationType+ " not registered with StructureMap");}return instance;}}

     

    3) Make the SubControllerBinder your default binder.

    ModelBinders.Binders.DefaultBinder = new StructureMapSubControllerBinder();
    4) Add a reference to the MvcContrib namespace to the Pages section of Web.Config.This will save you putting a lot of namespace tags in your views.
    <add namespace="MvcContrib"/>

    Creating a SubController

    1) Create a ~/Controllers/SubControllers folder. This is completely optional. If youhave a bigger project you might want to make multiple subcontrollers folders for differentareas.

    2) Create a SubController class. The action needs to have the ‘same’ name as the class.It also subclasses from SubController.

    using System.Web.Mvc;using MvcContrib;namespace NWIS.Business.Web.Controllers.SubControllers{public class DemoSubController: SubController{public ViewResult Demo(){return View();}}}

    3) Create a View subfolder. Using the ‘Add View’ context menu from the controllerwon’t work because the ‘sub’ in the class name. Just create the folder yourself. ~/Views/Demo.

    4) Create a View. Right click on the ~/Views/Demo folder. Select Add->View. Namethe view ‘Demo’. Make it a partial view (.ascx). Go ahead an add some markup to theview. Whatever you like.

    Using the SubController in your View

    1) Add an attribute to your Controller class.

    [SubControllerActionToViewDataAttribute]

    2) Add the subcontroller as a parameter to the action you want to use it in.

    public ViewResultIndex(DemoSubController mySubCont)

    3) Place the subcontroller output into your view.

    <% ViewData.Get<Action>("mySubCont").Invoke();%>
    The name you use here is the name of the parameter to the controller action.

    That should be it. You subcontroller can output into your view, and you can useit in many, many controllers and actions. You can add dependencies directly to thesubcontroller in it’s constructor. You can also pass information from the action tothe subcontroller using properties.

    Resources

    StructureMap: IRepository and Test Data Injection

    I Love StructureMap! It’s wonderful. What a way to compose your code together easily,precisely, and consolidatedly (!!). When I put a sentence like that together, I wonderif I even know what I’m talking about. The problem with StructureMap, IoC, and dependencyinjection really seems to be that the jargon for the patterns is so manifestlytrue that once you learn what the hell you are doing, you are completelyunable to stop talking about it in shorthand. And that shorthand makes absolutelyno sense to someone who hasn’t absorbed the patterns. Keep plugging, people. Onceyou do it, you’ll get it. Then you’ll be there and not be able toexplain to other people why you’re so right. It’s like being Tom Cruise and needingto explain scientology.

    Anyways.

    I learned two things today. The first is how to hook all of my concrete generic repositorytypes together with their interfaces. I had been adding a line of configuration foreach repository. Now my test code looks like this:

    ForRequestedType(typeof(IRepository<>)).TheDefaultIsConcreteType(typeof(ListRepository<>));

    And my production code:

    ForRequestedType(typeof(IRepository<>)).TheDefaultIsConcreteType(typeof(LlblRepository<>));

    That’s easy!

    The other thing I learned is that I can happily and easily inject data into my objectregistry for testing purposes. I have an in-memory repository implementation built.All it needs is data.

    public class MemoryDataSource{private Dictionary<Type, IQueryable> data;public MemoryDataSource(Dictionary<Type, IQueryable>data){this.data = data;}public IQueryable GetQueryable(Type type){return this.data[type];}}public class ListRepository<T>: IRepository<T>{private MemoryDataSource source;public ListRepository(MemoryDataSource source){this.source = source;}public IQueryable<T> GetSource(){return ((IQueryable<T>)source.GetQueryable(typeof(T)));}public void SaveEntity(Tentity){return;}}

    Now all I need to do is build a Dictionary<> keyed on the entity object typeand fill it up with data. Once I’ve done that, I just pass it into the StructureMapregistry like this:

    ObjectFactory.Inject<Dictionary<Type, IQueryable>>(dataSource);

    Now I can have ObjectFactory construct my object under test and it’s got just thedata I need it to have.

    Adding MVC to an existing ASP.NET Application

    ASP.NET MVC Has added a very useful new programming model to developing websites in.NET. There is hype and debate all throughout the interwebs on how great MVC is. Andit all can be yours, with nothing but a ‘File->New’ in Visual Studio….unless youalready have an ASP.NET application. In which case, you need to do a bit more work.

    1. Add project references to:
      System.Web.Abstractions
      System.Web.Mvc
      System.Web.Routing
    2. Create /Controllers, /Views, and /Views/Shared folders in your project
    3. Update web.config
      <?xml version="1.0" encoding="utf-8" ?><configuration><system.web><pages><namespaces><add namespace="System.Web.Mvc"/><add namespace="System.Web.Mvc.Ajax"/><add namespace="System.Web.Mvc.Html" /><add namespace="System.Web.Routing"/><add namespace="System.Linq"/><add namespace="System.Collections.Generic"/></namespaces></pages><compilation><assemblies><add assembly="System.Core,Version=3.5.0.0,Culture=neutral,PublicKeyToken=B77A5C561934E089"/><add assembly="System.Web.Mvc,Version=1.0.0.0,Culture=neutral,PublicKeyToken=31bf3856ad364e35" /><add assembly="System.Web.Abstractions,Version=3.5.0.0,Culture=neutral,PublicKeyToken=31BF3856AD364E35"/><add assembly="System.Web.Routing,Version=3.5.0.0,Culture=neutral,PublicKeyToken=31BF3856AD364E35"/></assemblies></compilation><httpModules><add name="UrlRoutingModule"type="System.Web.Routing.UrlRoutingModule,System.Web.Routing,Version=3.5.0.0,Culture=neutral,PublicKeyToken=31BF3856AD364E35" /></httpModules></system.web><system.webServer><validation validateIntegratedModeConfiguration="false"/><modules runAllManagedModulesForAllRequests="true"><remove name="ScriptModule" /><remove name="UrlRoutingModule" /><add name="ScriptModule" preCondition="managedHandler"type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/><add name="UrlRoutingModule"type="System.Web.Routing.UrlRoutingModule,System.Web.Routing, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" /></modules><handlers><remove name="WebServiceHandlerFactory-Integrated"/><remove name="ScriptHandlerFactory" /><remove name="ScriptHandlerFactoryAppServices" /><remove name="ScriptResource" /><remove name="MvcHttpHandler" /><remove name="UrlRoutingHandler" /><add name="ScriptHandlerFactory" verb="*" path="*.asmx"preCondition="integratedMode"type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/><add name="ScriptHandlerFactoryAppServices" verb="*"path="*_AppService.axd" preCondition="integratedMode"type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/><add name="ScriptResource" preCondition="integratedMode"verb="GET,HEAD" path="ScriptResource.axd"type="System.Web.Handlers.ScriptResourceHandler, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" /><add name="MvcHttpHandler" preCondition="integratedMode"verb="*" path="*.mvc" type="System.Web.Mvc.MvcHttpHandler, System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/><add name="UrlRoutingHandler"preCondition="integratedMode" verb="*" path="UrlRouting.axd"type="System.Web.HttpForbiddenHandler,System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" /></handlers></system.webServer></configuration>
    4. Add the following to Global.asax.cs (create a Global.asax if you don’t already haveone)
      public static void RegisterRoutes(RouteCollectionroutes){routes.IgnoreRoute("{resource}.axd/{*pathInfo}");routes.IgnoreRoute("{resource}.aspx/{*pathInfo}");routes.MapRoute("Default", //Route name"{controller}/{action}/{id}", //URL with parametersnew { controller = "Home",action = "Index", id = "" } //Parameter defaults);}protected void Application_Start(){RegisterRoutes(RouteTable.Routes);}

      The above is the “standard” routing for a MVC site. It will work, but it might causeyou trouble if you want the root of your website to still go to a ‘default.aspx’.Try this line instead:

      routes.MapRoute("Default", "MVC/{controller}/{action}/{id}", new { controller = "Home",action = "Index", id = "" }
    5. Edit your .csproj file by hand
      <ProjectTypeGuids>{603c0e0b-db56-11dc-be95-000d561079b0};{349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}</ProjectTypeGuids>

      This will tell Visual Studio to act like this is an MVC project. You will getthe context menu items for MVC in your Controllers and Views directories.
      Note: I’ve had trouble at this point with some Visual Studio installs.If you’re having trouble with “The project type is not supported by this installation.”messages here, it may be time for a clean install in a fresh VM.
    6. Set Up IIS. If you’re using IIS7, the System.Webserver settings in your Web.Configfile should have taken care of this step. If you’re going to need to map the wildcardURL to aspnet_isapi.dll in order to get all of the MVC routing magic to work.

    7. You might want to copy of the /Views/web.config file from an existing MVC projectto prevent your views from being viewed directly, rather than through their controllers.
    8. You might also wish to create a /Scripts folder and copy over the contents ofthe /Scripts folder from native MVC project. It has the jquery and MS AJAX javascriptsthat you may be looking for if you’re following a MVC book or tutorial.

    Boy howdy. That was a good chunk of work. But now you have the infinite pleasure ofdeveloping in MVC, and your old web site should continue to work under you. Deliciousincremental development goodness.

    Resources

    Windows Forms Eventing: Generic Pub/Sub

    In my lastpost, I covered how you can implement a publish/subscribe system for sending eventsbetween components of your Windows Forms application. Using this model, it is easyto create separate components that don’t rely on each other’s implementation detailsin order to provide a consistent experience for the user. A button on the toolbarcan be disabled by an action on the data entry screen…without hard references betweenthe two.

    However, the implementation from last time required a new aggregator and set of interfacesfor each message type that needed to be passed around. Let’s fix that with generics.

    Listener Interface

    First we replace the IEventReciever interface from last time with a generic IListenerinterface that uses a generic type for the message object.

    public interface IListener<T>{void Handle(T message);}
    All your subscribers now implement an IListener<Message> for each messagetype they can handle. (This is great because one class can listen for multiple typesof messages!)

    Event Aggregator Interface

    Similarly, the Event Aggregator needs a generics reset. Notice that the Add and Removemethods now accept any old object.

    public interface IEventAggregator{void SendMessage<T>(T message);void AddListener(object listener);void RemoveListener(object listener);}

    Event Aggregator

    public class EventAggregator: IEventAggregator{private readonly List<object>listeners = new List<object>();#region IEventAggregator Memberspublic void SendMessage<T>(Tmessage){listeners.CallOnEach<IListener<T>>(x => { x.Handle(message); });}public void AddListener(object listener){if (listeners.Contains(listener)) return;listeners.Add(listener);}public void RemoveListener(object listener){listeners.Remove(listener);}#endregion}

    Not much new here….except for that CallOnEach method. Where did that come from?

    Extension Methods

    We need to add a few utility methods to the IEnumerables so that we can send our messages:

    public static void CallOnEach<T>(this IEnumerableenumerable, Action<T> action) where T : class{foreach (object o in enumerable){o.CallOn(action);}}public static void CallOn<T>(this object target,Action<T> action) where T : class{var subject = target as T;if (subject != null){action(subject);}}

    Put those in a likely static class somewhere.

    Are we there yet?

    This Event Aggregator is getting powerful. We should kill it before it develops languageskills.

    With the code we have so far, we can easily create a new message in the system withoutmodifying the aggregator code. I like adding the methods as child classes of theirreceivers (when they are receiver-specific).

    There is, however, still a problem. In the type of application that needs this eventingsystem, you are likely going to need to do some background threading to keep the UIresponsive. The Event Aggregator we have doesn’t do anything to keep itself or therest of the application synchronized.

    What happens when a background thread sends a message that the receiver needs to acton by talking to the UI thread? Do you write a bunch of Invoke() code everywhere?

    As it turns out, there is a better way. And my next post will show you how to upgradeyour EventAggregator to be the thread master!

    Windows Forms Eventing: Adding Pub/Sub

    In my lastpost, I described the creeping problem I’ve been having with wiring my large WindowsForms application up with EventHandler delegates. In short, the design becomes brittle—it’shard to write and hard to change. Luckily, the solution is an easy one: Publish/Subscribe.

    The Event Aggregator is a singleton. You can either manage this yourself, or you canhave your Inversion of Control container do it for you. When an Event Receiver iscreated, it registers itself with the Event Aggregator, which stores a reference tothe receiver. When a Event Sender sends a message to the Event Aggregator, the aggregatorloops through all the registered receivers and calls a method on them to handle theevent. This is facilitated by having the receivers implementing an interface. Here’sa simple implementation:

    public class EventMessage{public string MessageText{ get; set; }}public interface IEventReceiver{void Handle(EventMessage message);}public interface IEventAggregator{void SendMessage(EventMessage message);void AddReceiver(IEventReceiver receiver);void RemoveReceiver(IEventReceiver receiver);}public class EventAggregator: IEventAggregator{private readonly List<IEventReceiver>receivers = new List<IEventReceiver>();#region IEventAggregator Memberspublic void SendMessage(EventMessagemessage){receivers.ForEach(r => r.Handle(message));}public void AddReceiver(IEventReceiverreceiver){if (receivers.Contains(receiver)) return;receivers.Add(receiver);}public void RemoveReceiver(IEventReceiverreceiver){receivers.Remove(receiver);}#endregion}public class EventReceiver: IEventReceiver{public EventReceiver(IEventAggregator aggregator){aggregator.AddReceiver(this);}#region IEventReceiver Memberspublic void Handle(EventMessagemessage){Console.WriteLine(message.MessageText);}#endregion}public class EventSender{private IEventAggregator aggregator;public EventSender(IEventAggregator aggregator){this.aggregator = aggregator;}public void SendErrorMessage(string message){aggregator.SendMessage(new EventMessage { MessageText= message });}}

    This is a big improvement over manual event wiring through an application. No longerdo the targets of a message need to know (have references to) the senders of the messagein order to make the connection. You can add a new target (receiver) without changingthe code of the sender. You can add a new sender without changing the code of thereceivers. And you don’t have to manually map things out in some sort of registryclass. This is simple easy and it will work.

    It’s always something with you, isn’t it?

    Ok. I like it. I’m happy. But how many of these things am I going to have floatingaround? It seems like a lot of code to write to hand one type of event between somecomponents. I’m going to have to hire some Indian outsourcing company to write allof these singleton aggregators.

    This sounds like a job for generics! My next post will rewrite the Event Aggregatorso that a single aggregator can handle every message your application has.

    Windows Forms Eventing: The Problem

    Anxiety. This is a feeling I’ve been learning to recognize as a SIGN when I’m coding.The feeling you get when the camera gets really close on the face of the pretty girlas she walks around the empty, and silent, house. Is she going to turn around suddenlyand be given flowers? Or is the disemboweling only moments away. You don’t know, butyou’re cringing either way.

    I’ve felt this in the past about data access layers. This is the feeling I’ve beenaccumulating around my Windows Forms application lately. I don’t want to code. I’mafraid to code. It’s going to be painful. I’m not sure what’s going to go wrong. Butsomething will, and I’ll be sucked into the unproductive death pit.

    Having spent a bit of time feeling around the problem, I’m pretty sure what I don’tlike is having to deal with events. Let’s take a look at the reason why:

    Windows forms makes things easy.

    Click click click! I have a button and I have an event linked to it. Amazingly productive.My app will be running in no time.

    Event Handlers are teh awesome.

    It’s so easy to link an action to an event handler. Well. It’s pretty easy. It’s veryeasy within one class. Not so bad if you’ve got a reference to the object that containsthe event handler. A little bit of encapsulation makes it harder. Chained event handlerscan fix that though……

    All is happy with one form per task.

    This form is easy. Add a few buttons and it’s great. If you’re prepared to make yourapplication out of a bunch of forms that the user will work with one-by-one, there’sno problem.

    However….sovereign apps don’t look like that.

    If the user is going to be in one application all day long, it will have to provideeasy access to a number of different types of functionality. The application can’tconsolidate all of the interface elements required for a single task into a singleplace. Rather there’s a spaghetti monster of functionality, reaching it’s noodleyappendages everywhere.

    Events flying every which way, and everything needs a hard reference to everythingelse.

    Can you actually wire all this up with EventHandler references? Sure. Of course youcan. However, you end with a lot of tightly coupled code that is a real mess to createand to maintain.

    Hope. The eternal struggle.

    A application shell is a complicated beast. But problems have solutions. In my nextpost, I’ll show how you can integrate all these separate components without stranglingyourself in wiring.

    Adding ELMAH to an ASP.NET Site

    ELMAH (Error Logging Modules and Handlers)is a fantastic library that provides error logging and troubleshooting support toan ASP.NET web site. You practically just drop it in and BOOM, you’ve got great exceptionreporting.

    Step 1: Add A Reference

    Add a reference to the ELMAH DLL in your ASP.NET project. (They tell me it just needsto be dropped in the BIN folder, but that almost seems like more work to me.)

    Step 2: Add Config Sections

    In web.config, add the following lines to <configSections>

    <configSections>
    <sectionGroup name="elmah">
    <section name="security" requirePermission="false" type="Elmah.SecuritySectionHandler,Elmah"/>
    <section name="errorLog" requirePermission="false" type="Elmah.ErrorLogSectionHandler,Elmah" />
    <section name="errorMail" requirePermission="false" type="Elmah.ErrorMailSectionHandler,Elmah" />
    <section name="errorFilter" requirePermission="false" type="Elmah.ErrorFilterSectionHandler,Elmah"/>
    </sectionGroup>
    </configSections>

    Step 3: Add the ELMAH Section

    <elmah>
    <security allowRemoteAccess="0" />
    <errorLog type="Elmah.XmlFileErrorLog,Elmah" logPath="|DataDirectory|" />
    </elmah>

    Step 4: System.web—httpModules and httpHandlers

    <system.web>
    <httpModules>
    <add name="ErrorLog" type="Elmah.ErrorLogModule,Elmah"/>
    </httpModules>
    <httpHandlers>
    <add verb="POST,GET,HEAD" path="elmah.axd" type="Elmah.ErrorLogPageFactory,Elmah" />
    </httpHandlers>
    </system.web>

    Step 5: (II7 Only) Configure system.webServer

    <system.webServer>
    <validation validateIntegratedModeConfiguration="false" />
    <modules>
    <add name="Elmah.ErrorLog" type="Elmah.ErrorLogModule,Elmah" preCondition="managedHandler" />
    <add name="Elmah.ErrorFilter" type="Elmah.ErrorFilterModule" preCondition="managedHandler" />
    <add name="Elmah.ErrorMail" type="Elmah.ErrorMailModule" preCondition="managedHandler" />
    </modules>
    <handlers>
    <add name="Elmah" path="elmah.axd" verb="POST,GET,HEAD" type="Elmah.ErrorLogPageFactory,Elmah" preCondition="integratedMode" />
    </handlers>
    </system.webServer>

    Step 6: Secure remote access using ASP.NET membership

    <location path="elmah.axd">
    <system.web>
    <authorization>
    <deny users="?"/>
    </authorization>
    </system.web>
    </location>

    Resources: