StructureMap, Action Filters and Dependency Injection in ASP.NET MVC 3

Continuing on from the basics and model binding, and continuing to kinda make it up as I go along, here we will discuss how to hook up action filters with dependency injection using StructureMap and the new dependency injection hooks in ASP.NET MVC 3.

IFilterProvider

Your key extensibility hook is the IFilterProvider interface. Implement that on a class, setup that with your IoC setup and boom, you should get automatic DI for your filters. Great, huh? In reality, of course, it’s not nearly that straightforward. So…how should we do this?

Options

One of the nice new features in ASP.NET MVC 3 is the ability to register global filters. On the one hand, it said that “At this point in time, there is no way to register a global filter with the service locator”. At least that was true as of the CTP. But on the other hand, even though I have heard of no changes since then, from looking at teh codez it appears there might be something there. We'll come back to this in a sec.

I’ve seen two good DI implementations for filters, the first by Javier Lozano. I don’t think Javier’s solution for filters will work with global filters because I’m pretty sure his base class from the framework, FilterAttributeFilterProvider, only pulls the attribute-based filters (otherwise the framework class would be poorly named).

There is also a short but fine series by K. Scott Allen here, here and here. He seems to solve the whole global filter problem by completely bypassing the normal global filter registration process. That’s clearly okay by me anyway because that’s essentially what I did to get model binders setup for dependency injection.

So what to do? For this blog post I’m going to just go the same route as Javier and inherit from FilterAttributeFilterProvider and mull over the idea of how to hook global filters into this.

Implementation

First we’ll start with our basic hijacking of the FilterAttributeFilterProvider. We’ll go ahead and begin with a little constructor injection of our provider as it will need some reference to our StructureMap container.


using System;
using System.Collections.Generic;
using System.Web.Mvc;
using StructureMap;
namespace Mvc3StructureMapIoc
{
    public class StructureMapFilterProvider : FilterAttributeFilterProvider
    {
        public StructureMapFilterProvider(IContainer container)
        {
            _container = container;
        }
        private IContainer _container;
        public override IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
        {
            return base.GetFilters(controllerContext, actionDescriptor);
        }
    }
}

This does not complete our implementation but this gives us our provider. To hook this up, we will register IFilterProvider with StructureMap, which will supply it to the MVC 3 runtime because of the dependency resolver we have already implemented.


protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();
    RegisterGlobalFilters(GlobalFilters.Filters);
    RegisterRoutes(RouteTable.Routes);
    var modelBinderTypeMappingDictionary = new ModelBinderTypeMappingDictionary();
    modelBinderTypeMappingDictionary.Add(typeof(AThing), typeof(AThingModelBinder));
    IContainer container = new Container(x =>
    {
        x.For<IControllerActivator>().Use<StructureMapControllerActivator>();
        x.For<IModelBinderProvider>().Use<StructureMapModelBinderProvider>();
        x.For<ModelBinderTypeMappingDictionary>().Use(modelBinderTypeMappingDictionary);
        x.For<IFilterProvider>().Use<StructureMapFilterProvider>();
        x.For<IBar>().Use<Bar>();
    });
    DependencyResolver.SetResolver(new StructureMapDependencyResolver(container));
}

Our only new line there is “x.For<IFilterProvider>().Use<StructureMapFilterProvider>();” and we have our filter provider ready to go.

In ASP.NET MVC v1 and v2, action filters were always attributes. In v3 they don’t have to be but in this case but we’ll keep on going that direction for now since this will get us covered for all previous-version ASP.NET MVC filter implementations. We will also do the incredibly lame and redundant demo here of creating a filter for logging.

Up to this point all our dependency injection has been through constructors. This is what I prefer as it conveys through the api provided that the class MUST have those dependencies to function. We don’t really get that luxury with attribute-based filters because they are attributes, and the runtime really controls the beginning of an attributes lifecycle. So instead, we’ll use property injection.

So first I’ll create a logger…


public class Logger : ILogger
{
    public void LogMessage(string message)
    {
        //Put some logging logic here, yo.
    }
}
public interface ILogger
{
    void LogMessage(string message);
}

And then I’ll create my filter.


public class LoggingFilter : ActionFilterAttribute
{
    public ILogger Logger { get; set; }
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        base.OnActionExecuting(filterContext);
        Logger.LogMessage("Yay!");
    }
}

If you put that attribute on an action and view it on the site, you’ll get a nice little object reference error because the dependency, ILogger, hasn’t been injected yet. To do that we have to tell StructureMap to inject these dependencies, so back to Application_Start in the global.asax.


protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();
    RegisterGlobalFilters(GlobalFilters.Filters);
    RegisterRoutes(RouteTable.Routes);
    var modelBinderTypeMappingDictionary = new ModelBinderTypeMappingDictionary();
    modelBinderTypeMappingDictionary.Add(typeof(AThing), typeof(AThingModelBinder));
    IContainer container = new Container(x =>
    {
        x.For<IControllerActivator>().Use<StructureMapControllerActivator>();
        x.For<IModelBinderProvider>().Use<StructureMapModelBinderProvider>();
        x.For<ModelBinderTypeMappingDictionary>().Use(modelBinderTypeMappingDictionary);
        x.For<IFilterProvider>().Use<StructureMapFilterProvider>();
        x.For<IBar>().Use<Bar>();
        x.For<ILogger>().Use<Logger>();
        x.SetAllProperties(p =>
            {
                p.OfType<ILogger>();
            });
    });
    DependencyResolver.SetResolver(new StructureMapDependencyResolver(container));
}

Now if you run it…it is still broken. One more thing to do. In your filter provider, get the filters and use the container to inject them. It looks like this.


public class StructureMapFilterProvider : FilterAttributeFilterProvider
{
    public StructureMapFilterProvider(IContainer container)
    {
        _container = container;
    }
    private IContainer _container;
    public override IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
    {
        var filters = base.GetFilters(controllerContext, actionDescriptor);
        foreach (var filter in filters)
        {
            _container.BuildUp(filter.Instance);
        }
        return filters;
    }
}

Conclusion

And we are done. The code has been updated so you can get the working bits if you would like. Next time we’ll tackle something else.