I should have known something was up when I caught Jeffrey Palermo (my boss at Headspring) browsing the ASP.NET MVC Preview 5 source.

We're working on a enterprise web application using ASP.NET MVC.  That week one of our tasks was to create a rather complex page full of robust components that did intricate little things like field-level security.  We wanted the ability to push behavior decisions to smaller and smaller pieces, and reuse those pieces on other pages.  We needed testability, dependency injection and minimal friction.  We needed composition.

Since I have been (hardly) working on components of CodeCampServer and hanging out with others interested in MVC I had heard about these SubController things.  "We need SubControllers!" and "When they finally put in SubControllers we can do X!" "RenderAction is troublesome because of Y and partials are limited for reasons of Z!"

Admittedly I had no idea what they were talking about, but I sort of fell in line and waited for something cool to come out of Redmond.

Still, since he's literally writing the book on ASP.NET MVC, I shouldn't have been surprised when after a few hours of keyboard clicking Jeffrey looked up and said, "Hey, I just committed SubControllers."

Then a few days later he wrote a sample application and sent some info to the MvcContrib mailing list.  He's been heads-down on our project and I've seen a few questions online about how to create reusable components in ASP.NET MVC, so I asked Jeffrey if I could do a write-up here.  To be clear, I didn't invent, write, or even imagine SubControllers.. it's all Jeffrey… but I have been using them with success for a few weeks.

You may want to check out the sample before I dive into the details. 

Here's a few screenshots of the sample in action.  The rendered page (each component there is rendered by an isolated SubController's action) :

image

A nested SubController:

image 

What is this "SubController"?

  • Derives from SubController which derives from Controller and implements an ISubController interface.  But it is a controller, so it:
    • has its own view - works just like regular controller with standard conventions
    • has siloed ViewData - it does not share with other controllers
    • has access to query string and the rest of the controller context
  • Provides a System.Action (a delegate) that can be placed into ViewData and rendered in the host view with a call to Invoke()
  • Is a parameter on action methods in your hosting controllers
  • Can be infinitely nested
  • Is constructed with an IModelBinder that spins up an instance from the container

So let's take each of these in turn.

How it works

First, a look at the ISubController interface:

public interface ISubController : IController
{
    Action GetResult(ControllerBase parentController);
}

Pretty straight forward.  It's a method named GetResult that takes a ControllerBase parameter and returns an Action.

Here is the SubController class' implementation of that method:

public virtual Action GetResult(ControllerBase parentController)
{
    RequestContext requestContext = GetNewRequestContextFromController(parentController);
    return () => Execute(requestContext);
}

Ok.  Now we are getting somewhere.  This System.Action is a delegate with no arguments and a return type of void.  What it's actually doing is encapsulating a regular Controller's Execute method.  Which pops off the controller action method: stuffs ViewData and renders the View and all that.  The parameterless delegate Action is new in .NET 3.5 (though Action<T> was introduced in .NET 2.0).

Anyway, that's the real meaty bit of SubControllers: providing a delegate that can be put into ViewData.

So the next thing that has to happen is the delegate has to be created to represent the SubController's action method (that returns an ActionResult). This is where we start using the convention of naming the SubController's action method in an obvious way. We'll come back to this convention.

Let's take a look at the rest of the SubController class:

public RequestContext GetNewRequestContextFromController(ControllerBase parentController)
{
    RouteData parentRouteData = parentController.ControllerContext.RouteData;
    var routeData = new RouteData(parentRouteData.Route, parentRouteData.RouteHandler);
    string controllerName = GetControllerName();
    routeData.Values["controller"] = controllerName;
    routeData.Values["action"] = controllerName;
    return new RequestContext(parentController.ControllerContext.HttpContext, routeData);
}

public string GetControllerName()
{
    return GetType().Name.ToLowerInvariant().Replace("subcontroller", "").Replace("controller", "");
}

The important thing to note is that the GetControllerName() method returns the part of the controller name minus the word "controller" and "subcontroller".  So if your SubController is named ProductDetailSubController that method would return "ProductDetail".

The other important thing to note is that in the GetNewRequestContextFromController(…) method the action key of the routeData is set to this same value.  In our example, "ProductDetail" is the action.

When the SubController's delegate is eventually invoked it will execute a method on the SubController with the name "ProductDetail".

Therefore the convention is to use an action method with the same name as the SubController (minus the text "subcontroller" or "controller").

So far we see that SubControllers give us a delegate (System.Action) that represents an invocation of its method with the conventional name.

Hello SubControllers

Let's take a quick detour to see what a SubController might look like:

public class FirstLevelSubController : SubController
{
    public ViewResult FirstLevel()
    {
        ViewData["text"] = "I am a first level controller";
        return View();
    }
}

This is straight out of Jeffrey's example.  Looks just like a regular controller except it derives from SubController and has one action method with the conventional name. FirstLevelSubController's GetResult() will return a delegate that represents the FirstLevel() method.

By the way, you test SubControllers exactly the same way as you would regular controllers: execute the action method and assert that the ActionResult is in the proper state with the correct ViewData and ViewName.

Using SubControllers from host Controllers

Now that we have the System.Action delegate we need to get it into ViewData.  This is how I started out doing it:

public class HomeController : Controller
{
    public ActionResult Index(FirstLevelSubController firstLevel)
    {
        ViewData["firstLevel"] = firstLevel.GetResult(this);
        return View();
    }
}

… and so on, and so on, for each SubController in the parameter list.  This works fine.  The System.Action gets put in ViewData and we can invoke it from the view just like we expect.  Testing it is straightforward as well, just mock the SubController and expect that its action gets put in ViewData:

[Test]
public void Index_puts_subcontroller_action_into_viewdata()
{
    var controller = new HomeController();
    var firstLevelSubController = MockRepository.GenerateStub<FirstLevelSubController>();
    Action stubAction = () => { };
    firstLevelSubController.Stub(sub => sub.GetResult(controller)).Return(stubAction);

    var result = controller.Index(firstLevelSubController) as ViewResult;

    Assert.That(result.ViewData.Get<Action>("firstLevel"), Is.EqualTo(stubAction));
}

Yeah, straight forward as in straight forward but not as in easy or concise (especially when it comes to actions that depend on many SubControllers - imagine 5 or 7 or 10 stubs in there..)..

Going frictionless

Enter the SubControllerActionToViewDataAttribute!  This ActionFilterAttribute cycles through the host action's parameters (filterContext.ActionParameters - a neat trick) and puts each SubController parameter's System.Action into ViewData:

public class SubControllerActionToViewDataAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        foreach (var pair in filterContext.ActionParameters)
        {
            object value = pair.Value;
            if(value == null)
            {
                continue;
            }

            if (typeof(ISubController).IsAssignableFrom(value.GetType()))
            {
                var controller = (ISubController) value;
                filterContext.Controller.ViewData.Add(pair.Key, controller.GetResult(filterContext.Controller));
            }
        }

        base.OnActionExecuting(filterContext);
    }
}

We have added a second convention: the SubController's parameter variable name (pair.Key above) is the key of the respective System.Action in ViewData.

The concern now becomes testing that the attribute works and that the your controllers have the attribute, not testing that individual SubController's System.Actions are put in ViewData. These tests are in MvcContrib. This frees developers from the weight of having to write cumbersome tests.

In fact, the SubController base class in MvcContrib has this attribute allowing for infinite nesting.

"Calling" the SubController from the view

Once we pop that System.Action delegate into ViewData we can invoke it straight from the host action's view:

<% ViewData.Get<Action>("firstLevel").Invoke(); %>

The strong-typed Get<T>() method is another feature built into MvcContrib.

Dependency injection

Finally, we definitely need rich dependency injection support in these SubControllers. MVC Preview 5 introduced the IModelBinder interface that is responsible for constructing action parameters.  We can add them in Global.asax or wherever the controllers are constructed:

protected void Application_Start()
{
    RegisterRoutes(RouteTable.Routes);
    ModelBinders.DefaultBinder = new SubControllerBinder();
}

Here is the basic implementation of SubControllerBinder in MvcContrib:

public class SubControllerBinder : DefaultModelBinder
{
    protected override object ConvertType(CultureInfo culture, object value, Type destinationType)
    {
        if (typeof (ISubController).IsAssignableFrom(destinationType))
        {
            object instance = CreateSubController(destinationType);
            if (instance == null)
            {
                throw new InvalidOperationException(destinationType + " not created properly.");
            }

            return instance;
        }

        return base.ConvertType(culture, value, destinationType);
    }

    ///<summary>
    /// Creates the subcontroller given its type.  Override this method to wire into an IoC container
    ///</summary>
    ///<param name="destinationType">The type of subcontroller</param>
    ///<returns>an object instance</returns>
    public virtual object CreateSubController(Type destinationType)
    {
        return Activator.CreateInstance(destinationType, true);
    }
}

And you can wire this up to a container with ease:

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

        return instance;
    }
}

So there you have it.  You can download and explore a complete working sample from MvcContrib.  If you have a suggestion for improvement MvcContrib is accepting patches.  I have a few more posts in the queue about some intermediate usages and testing.