Dependency Injection in ASP.NET MVC: Views

Other posts in this series:

And now for a bit more controversial shift.  While most folks doing DI in ASP.NET MVC see the benefit of the ability to provide injection around the controller-side of things (filters, action results, controllers etc.), I’ve also seen a lot of benefit from injection on the view side.  But before delve into the how, let’s first look at the why.

The responsibility of a view is to render a model.  Pretty cut and dry, until you start to try and do more interesting things in the view.  Up until now, the CODE extensibility points of a view included:

  • XyzHelper extensions (UrlHelper, AjaxHelper, HtmlHelper)
  • Custom base view class with custom services

I’m leaving out markup extensibility points such as MVC 2 templated helpers, master pages, partials, render action and so on.  If we want our custom helper extension method to use a service, such as a custom Url resolver, localization services, complex HTML builders and so on, we’re left with service location, looking something like this:

public static MvcHtmlString Text<TModel>(this HtmlHelper<TModel> helper, string key)
{
    var provider = ObjectFactory.GetInstance<ILocalizationProvider>();

    var text = provider.GetValue(key);

    return MvcHtmlString.Create(text);
}

We started seeing this sort of cruft all over the place.  It became clear quite quickly that HtmlHelper extensions are only appropriate for small, procedural bits of code.  But as we started building customized input builders (this was before MVC 2’s templated helpers and MVC Contrib’s input builders), the view started becoming much, much more intelligent about building HTML.  Its responsibilities were still just “build HTML from the model”, but we took advantage of modern OO programming and conventions to drastically reduce the amount of duplication in our views.

But all of this was only possible if we could inject services into the view.  Since MVC isn’t really designed with DI everywhere in mind, we have to use quite a bit of elbow grease to squeeze out the powerful designs we wanted.

Building an injecting view engine

Our overall strategy for injecting services into the view was:

  • Create a new base view class layer supertype
  • Expose services as read/write properties
  • Utilize property injection to build up the view

Since we’re using the WebFormsViewEngine, we don’t really have any control over view instantiation.  We have to use property injection instead.  That’s not a big hurdle for us here as it was in other place.  We’re not instantiating views in unit tests, which are a big source of confusion when doing property injection.

First, we need to subclass the existing view engine and plug in to its existing behavior:

public class NestedContainerViewEngine : WebFormViewEngine
{
    public override ViewEngineResult FindView(
        ControllerContext controllerContext, 
        string viewName, string masterName, bool useCache)
    {
        var result = base.FindView(controllerContext, viewName, masterName, useCache);

        return CreateNestedView(result, controllerContext);
    }

    public override ViewEngineResult FindPartialView(
        ControllerContext controllerContext, 
        string partialViewName, bool useCache)
    {
        var result = base.FindPartialView(controllerContext, partialViewName, useCache);

        return CreateNestedView(result, controllerContext);
    }

We’re going to use the base view engine to do all of the heavy lifting for locating views.  When it finds a view, we’ll create our ViewEngineResult based on that.  The CreateNestedView method becomes:

private ViewEngineResult CreateNestedView(
    ViewEngineResult result, 
    ControllerContext controllerContext)
{
    if (result.View == null)
        return result;

    var parentContainer = controllerContext.HttpContext.GetContainer();

    var nestedContainer = parentContainer.GetNestedContainer();

    var webFormView = (WebFormView)result.View;

    var wrappedView = new WrappedView(webFormView, nestedContainer);

    var newResult = new ViewEngineResult(wrappedView, this);

    return newResult;
}

We want to create a nested container based on the calling controller’s nested container.  Our previous controller factory used a static gateway to store the outermost nested container in HttpContext.Items.  To make it visible to our view engine (which is SINGLETON), we have no choice but to build a little extension method in GetNestedContainer for HttpContextBase to retrieve our nested container.

Once we have the outermost nested container, we create a new, child nested container from it.  Containers can nest as many times as we like, inheriting the parent container configuration.

From there, we then need to build up our own IView instance, a WrappedView object.  Unfortunately, the extension points in the WebFormView class do not exist for us to seamlessly extend it to provide injection.  However, since MVC is open source, we have a great starting point.

After we build our WrappedView, we create our ViewEngineResult and our custom view engine is complete.  Before we look at the WrappedView class, let’s look at how our views will be built.

Layer supertype to provide injection

To provide injection of services, we’ll need a layer supertype between our actual views and the normal MVC ViewPage and ViewPage<T>:

public abstract class ViewPageBase<TModel> : ViewPage<TModel>
{
    public IHtmlBuilder<TModel> HtmlBuilder { get; set; }
}

public abstract class ViewPageBase : ViewPageBase<object>
{
}

Here, we include our custom IHtmlBuilder service that will do all sorts of complex HTML building.  We can include any other service we please, but we just need to make sure that it’s a mutable property on our base view layer supertype.  The HtmlBuilder implementation does nothing interesting, but includes a set of services we want to have injected:

public class HtmlBuilder<TModel> : IHtmlBuilder<TModel>
{
    private readonly HtmlHelper<TModel> _htmlHelper;
    private readonly AjaxHelper<TModel> _ajaxHelper;
    private readonly UrlHelper _urlHelper;

    public HtmlBuilder(
        HtmlHelper<TModel> htmlHelper, 
        AjaxHelper<TModel> ajaxHelper, 
        UrlHelper urlHelper)
    {
        _htmlHelper = htmlHelper;
        _ajaxHelper = ajaxHelper;
        _urlHelper = urlHelper;
    }

When we configure our container, we want any service used to be available.  By configuring the various helpers, we allow our helpers to be injected instead of passed around everywhere in method arguments.  This is MUCH MUCH cleaner than passing context objects around everywhere we go, regardless if they’re actually used or not.

Wrapping WebFormView to provide injection

As I mentioned before, we can’t subclass WebFormView directly, but we can instead wrap its behavior with our own.  Composition over inheritance, but we still have to duplicate more behavior than I would have liked.  But, it’s about the cleanest and lowest-impact implementation I’ve come up with, and gets around any kind of sub-optimal poor-man’s dependency injection.

First, our WrappedView definition:

public class WrappedView : IView, IDisposable
{
    private bool _disposed;

    public WrappedView(WebFormView baseView, IContainer container)
    {
        BaseView = baseView;
        Container = container;
    }

    public WebFormView BaseView { get; private set; }
    public IContainer Container { get; private set; }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (_disposed)
            return;

        if (Container != null)
            Container.Dispose();

        _disposed = true;
    }

We accept the base view (a WebFormView created from the original WebFormsViewEngine), as well as our nested container.  We need to dispose of our container properly, so we implement IDisposable properly.

Now, the next part is large, but only because I had to duplicate the existing MVC code to add in what I needed:

public void Render(ViewContext viewContext, TextWriter writer)
{
    if (viewContext == null)
    {
        throw new ArgumentNullException("viewContext");
    }

    object viewInstance = BuildManager.CreateInstanceFromVirtualPath(BaseView.ViewPath, typeof(object));
    if (viewInstance == null)
    {
        throw new InvalidOperationException(
            String.Format(
                CultureInfo.CurrentUICulture,
                "The view found at '{0}' was not created.",
                BaseView.ViewPath));
    }

    ////////////////////////////////
    // This is where our code starts
    ////////////////////////////////
    var viewType = viewInstance.GetType();
    var isBaseViewPage = viewType.Closes(typeof (ViewPageBase<>));

    Container.Configure(cfg =>
    {
        cfg.For<ViewContext>().Use(viewContext);
        cfg.For<IViewDataContainer>().Use((IViewDataContainer) viewInstance);

        if (isBaseViewPage)
        {
            var modelType = GetModelType(viewType);
            var builderType = typeof (IHtmlBuilder<>).MakeGenericType(modelType);
            var concreteBuilderType = typeof (HtmlBuilder<>).MakeGenericType(modelType);

            cfg.For(builderType).Use(concreteBuilderType);
        }
    });

    Container.BuildUp(viewInstance);
    ////////////////////////////////
    // This is where our code ends
    ////////////////////////////////

    var viewPage = viewInstance as ViewPage;
    if (viewPage != null)
    {
        RenderViewPage(viewContext, viewPage);
        return;
    }

    ViewUserControl viewUserControl = viewInstance as ViewUserControl;
    if (viewUserControl != null)
    {
        RenderViewUserControl(viewContext, viewUserControl);
        return;
    }

    throw new InvalidOperationException(
        String.Format(
            CultureInfo.CurrentUICulture,
            "The view at '{0}' must derive from ViewPage, ViewPage<TViewData>, ViewUserControl, or ViewUserControl<TViewData>.",
            BaseView.ViewPath));
}

I’m going to ignore the other pieces except what’s between those comment blocks.  We have our ViewPageBase<TModel>, and we need to configure various services for our views, including:

  • ViewContext
  • Anything needed by the helpers (IViewDataContianer)
  • Custom services, like IHtmlBuilder<TModel>

Just like our previous nested container usage, we take advantage of StructureMap’s ability to configure a container AFTER it’s been created.  We first configure ViewContext and IViewDataContainer (needed for HtmlHelper).  Finally, we determine the TModel model type of our view, and configure IHtmlBuilder<TModel> against HtmlBuilder<TModel>.  If TModel is of type Foo, we configure IHtmlBuilder<Foo> to use HtmlBuilder<Foo>.

Finally, we use the BuildUp method to perform property injection.  Just as we explicitly configured property injection for our filter’s services, we need to do the same for the view’s services:

SetAllProperties(c =>
{
    c.OfType<IActionInvoker>();
    c.OfType<ITempDataProvider>();
    c.WithAnyTypeFromNamespaceContainingType<ViewPageBase>();
    c.WithAnyTypeFromNamespaceContainingType<LogErrorAttribute>();
});

The view services are all contained in the namespace of the ViewPageBase class.  With that in place, we just have one more piece to deal with: services in master pages.

Dealing with master pages

In the RenderViewPage method, we add a piece to deal with master pages and enable injection for them as well:

private void RenderViewPage(ViewContext context, ViewPage page)
{
    if (!String.IsNullOrEmpty(BaseView.MasterPath))
    {
        page.MasterLocation = BaseView.MasterPath;
    }

    page.ViewData = context.ViewData;

    page.PreLoad += (sender, e) => BuildUpMasterPage(page.Master);

    page.RenderView(context);
}

Because master pages do not flow through the normal view engine, we have to hook in to their PreLoad event to do our property injection in a BuildUpMasterPage method:

private void BuildUpMasterPage(MasterPage master)
{
    if (master == null)
        return;

    var masterContainer = Container.GetNestedContainer();

    masterContainer.BuildUp(master);

    BuildUpMasterPage(master.Master);
}

If we needed any custom configuration for master pages, this is where we could do it.  In my example, I don’t, so I just create a new default nested container from the parent container.  Also, master pages can have nesting, so we recursively build up all of the master pages in the hierarchy until we run out of parent master pages.

Finally, we need to hook up our custom view engine in the global.asax:

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();

    RegisterRoutes(RouteTable.Routes);

    StructureMapConfiguration.Initialize();

    var controllerFactory = new StructureMapControllerFactory(ObjectFactory.Container);

    ControllerBuilder.Current.SetControllerFactory(controllerFactory);

    ViewEngines.Engines.Clear();
    ViewEngines.Engines.Add(new NestedContainerViewEngine());
}

And that’s it!  With our nested container view engine in place, we can now inject complex UI services in to our views, allowing us to create powerful UI content builders without resorting to gateways or service location.

Conclusion

It was a bit of work, but we were able to inject services into not only views, but partials, master pages, even MVC 2 templated helpers!  By using nested containers, we were able to configure all of the contextual pieces so that services built for each view got the correct contextual item (the right HtmlHelper, ViewContext, IViewDataContainer, etc.)

This is a quite powerful tool now, we don’t need to resort to ugly usage of static gateways or service location.  We can now build UI services that depend on an HtmlHelper or ViewContext, and feel confident that our services get the correct instance.  In the past, we’d need to pass around our ViewContext EVERYWHERE in order to get back at these values.  Not very fun, especially when you start to see interfaces that accept everything under the sun “just in case”.

For those folks that don’t want to inject services in to their views, it’s all about responsibilities.  I can create encapsulated, cohesive UI services that still only create HTML from a model, but I’m now able to use actual OO programming without less-powerful static gateways or service location to do so.

So looking back, we were able to inject services into our controllers, filters, action results and views.  Using nested containers, we were able to provide contextual injection of all those context objects that MVC loves to use everywhere.  But now we can let our services use them only when needed through dependency injection, providing a much cleaner API throughout.

You can find code for this example on my github:

http://github.com/jbogard/blogexamples

Related Articles:

Post Footer automatically generated by Add Post Footer Plugin for wordpress.

About Jimmy Bogard

I'm a technical architect with Headspring in Austin, TX. I focus on DDD, distributed systems, and any other acronym-centric design/architecture/methodology. I created AutoMapper and am a co-author of the ASP.NET MVC in Action books.
This entry was posted in ASP.NET MVC, Dependency Injection. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • Arnis L.

    Pure awesomeness. Thanks for sharing.

  • Steve O

    This looks like just what I need. But when trying your code:

    controllerContext.HttpContext.GetContainer()

    I don’t seem to have a GetContainer() on HttpContext. Any idea what I’m missing?

  • http://www.lostechies.com/members/bogardj/default.aspx bogardj

    @Steve

    That was an extension method I added, that pulls the IContainer out of HttpContext.Items. Check out my github, I put the full code example up there ifyalike :)

  • Christophe Dubray

    I’m curious how this approach looks like for ASP.NET MVC 3 and the Razor viewengine. Any thoughts?

  • http://erasmusrios.webnode.com/ AaronRiggs

    For ASP.NET or Active Server Pages, ASP.NET MVC does not include anything that directly corresponds to a page. In an ASP.NET MVC application, there is not a page on disk that corresponds to the path in the URL that you type into the address bar of your browser. The closest thing to a page in an ASP.NET MVC application is something called a view.