Dependency Injection in ASP.NET MVC: Filters


So far, we’ve looked at extending the advantages of dependency injection to our controllers and its various services.  We started with a basic controller factory that merely instantiates controllers to one that takes advantage of the modern container feature of nested/child containers to provide contextual, scoped injection of services.  With a child container, we can do things like scope a unit of work to a request, without needing to resort to an IHttpModule (and funky service location issues).

Having the nested container in place gives us a nice entry point for additional services that the base controller class builds up, including filters.  Right after controllers, filters are one of the earliest extension points of ASP.NET MVC that we run into where we want to start injecting dependencies.

However, we quickly run into a bit of a problem.  Out of the box, filters in ASP.NET MVC are instances of attributes.  That means that we have absolutely no hook at all into the creation of our filter classes.  If we have a filter that uses a logger implementation:

public class LogErrorAttribute : FilterAttribute, IExceptionFilter
{
    private readonly ILogger _logger;

    public LogErrorAttribute(ILogger logger)
    {
        _logger = logger;
    }

We’ll quickly find that our code using the attribute won’t compile.  You then begin to see some rather heinous use of poor man’s dependency injection to fill the dependencies.  But we can do better, we can keep our dependencies inverted, without resorting to various flavors of service location or, even worse, poor man’s DI.

Building Up Filters

We’ve already established that we do not have a window into the instantiation of filter attributes.  Unless we come up with an entirely new way of configuring filters for controllers that doesn’t involve attributes, we still need a way to supply dependencies to already-built-up instances.  Luckily for us, modern IoC containers already support this ability.

Instead of constructor injection for our filter attribute instance, we’ll use property injection instead:

public class LogErrorAttribute : FilterAttribute, IExceptionFilter
{
    public ILogger Logger { get; set; }

    public void OnException(ExceptionContext filterContext)
    {
        var controllerName = filterContext.Controller.GetType().Name;
        var message = string.Format("Controller {0} generated an error.", controllerName);

        Logger.LogError(filterContext.Exception, message);
    }
}

The LogErrorAttribute’s dependencies are exposed as properties, instead of through the constructor.  Normally, I don’t like doing this.  Property injection is usually reserved for optional dependencies, backed by the null object pattern.  In our case, we don’t really have many choices.  To get access to the piece in the pipeline that deals with filters, we’ll need to extend some behavior in the default ControllerActionInvoker:

public class InjectingActionInvoker : ControllerActionInvoker
{
    private readonly IContainer _container;

    public InjectingActionInvoker(IContainer container)
    {
        _container = container;
    }

    protected override FilterInfo GetFilters(
        ControllerContext controllerContext, 
        ActionDescriptor actionDescriptor)
    {
        var info = base.GetFilters(controllerContext, actionDescriptor);

        info.AuthorizationFilters.ForEach(_container.BuildUp);
        info.ActionFilters.ForEach(_container.BuildUp);
        info.ResultFilters.ForEach(_container.BuildUp);
        info.ExceptionFilters.ForEach(_container.BuildUp);

        return info;
    }
}

In our new injecting action invoker, we’ll first want to take a dependency on an IContainer.  This is the piece we’ll use to build up our filters.  Next, we override the GetFilters method.  We call the base method first, as we don’t want to change how the ControllerActionInvoker locates filters.  Instead, we’ll go through each of the kinds of filters, calling our container’s BuildUp method.

The BuildUp method in StructureMap takes an already-constructed object and performs setter injection to push in configured dependencies into that object.  We still need to manually configure the services to be injected, however.  StructureMap will only use property injection on explicitly configured types, and won’t try just to fill everything it finds.  Our new StructureMap registration code becomes:

For<IActionInvoker>().Use<InjectingActionInvoker>();
For<ITempDataProvider>().Use<SessionStateTempDataProvider>();
For<RouteCollection>().Use(RouteTable.Routes);

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

We made two critical changes here.  First, we now configure the IActionInvoker to use our InjectingActionInvoker.  Next, we configure the SetAllProperties block to include any type in the namespace containing our LogErrorAttribute.  We can then add all of our custom filters to the same namespace, and they will automatically be injected.

Typically, we have a few namespaces that our services are contained, so we don’t have to keep configuring this block too often.  Unfortunately, StructureMap can’t distinguish between regular attribute properties and services, so we have to be explicit in what StructureMap should fill.

The other cool thing about our previous work with controller injection is that we don’t need to modify our controllers to get a new action invoker in place.  Instead, we work with our normal DI framework, and the controller is unaware of how the IActionInvoker gets resolved, or which specific implementation is used.

Additionally, since our nested container is what’s resolved in our InjectedActionInvoker (StructureMap automatically resolves IContainer to itself, including in nested containers), we can use all of our contextual items in our filters.  Although I would have preferred to use constructor injection on my filters, this design is a workable compromise that doesn’t force me to resort to less-than-ideal patterns such as global registries, factories, service location, or poor man’s DI.

Attribute lifecycle