StructureMap, Global Action Filters and Dependency Injection in ASP.NET MVC 3
As you can tell by now, I’m not one who feels he has to avoid long titles for blog posts. We are continuing our work on exploring our options for dependency injection in ASP.NET MVC 3, with StructureMap as our IoC tool of choice. In our first post we got started with IDependencyResolver, in the second post we setup model binders and in the third we setup non-global action filters. In this post we will discuss global filters and the normal caveat applies: I have seen no clear guidance on this so I am kinda making this up as I go along. It works, but that is about all that I can promise you.
I had hoped in the last post that I might be able to find a nice dependency injection hook in the framework for global filters. I failed to find one, either because there isn’t one or because I missed it. C’est la vie. So what you have below is an attempt to get around this limitation in a reasonable manner. Here goes.
The Easy Way
Here is the easy way, which is the wrong way to do it in my opinion.
GlobalFilters.Filters.Add(new MyCustomGlobalActionFilter(container));
That’s easy and rather straightforward. I won’t do that though because it makes every global filter have to reference and call the StructureMap container directly, which pushes that code to a whole lot more places than I want it to go. It would be better to have a more generic registration mechanism that would instantiate the filters using StructureMap, injecting their dependencies for me, whenever the filter is needed. So let’s abandon this way and make up something that will work better.
StructureMapGlobalFilterProvider
We are going to create a StructureMapGlobalFilterProvider class. This is in addition to the StructureMapFilterProvider class that we created in the last post. You can actually register as many filter providers as you would like as the filters provided by them will be aggregated by the framework. So we will first create the shell of that now. It would look something like this:
public class StructureMapGlobalFilterProvider : IFilterProvider
{
public StructureMapGlobalFilterProvider(IContainer container)
{
_container = container;
}
private IContainer _container;
public IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor
actionDescriptor)
{
return new List<Filter>();
}
}
Since we will need the container, we will go ahead and pass that in to the constructor. Of course one of our previous steps will go ahead and wire this constructor injection up for us. We’ll come back and augment this class a bit in a minute.
Of course we need a filter. Let’s create a nice, superfluous global filter. It will write out an insult to your mom into an Html comment with every request at the head. I mean, sure it writes it before the html tag and makes your markup invalid, but why wouldn’t you do this? :) And just to prove that the dependency injection is working, we’ll throw IBar in on the constructor and use it to build our message.
public class YourMomGlobalFilter : IActionFilter
{
public YourMomGlobalFilter(IBar bar)
{
_bar = bar;
}
private IBar _bar;
public void OnActionExecuted(ActionExecutedContext filterContext)
{
string message = String.Format("<!-- Your mom is so fat, she makes viewstate look lightweight. {0} -->",
_bar.IPityTheFoo());
filterContext.HttpContext.Response.Write(message);
}
public void OnActionExecuting(ActionExecutingContext filterContext)
{
//We won't do anything here.
}
}
That’s a pretty awesome filter. Now, we need to register this with our global filter provider. Here is where I leave the beaten path and make it up as I go. Recommendations and critiques are welcome.
When I create these global filters on each request, I will need to know two things: the type and the order value that the runtime will use to order the execution. I think I’ll put that in a list and register that with container so I can use all this collected metadata in my provider. So here is my metadata type and my list:
public class GlobalFilterRegistration
{
public Type Type { get; set; }
public int? Order { get; set; }
}
public class GlobalFilterRegistrationList : List<GlobalFilterRegistration>
{
}
Now in my global.asax I will create an instance of the list, add data for my global filter, and register the list with StructureMap. I will also register my new FilterProvider. Here is my Application_Start as it has been built up during the series with my new bits:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
var modelBinderTypeMappingDictionary = new ModelBinderTypeMappingDictionary();
modelBinderTypeMappingDictionary.Add(typeof(AThing), typeof(AThingModelBinder));
var globalFilterRegistrationList = new GlobalFilterRegistrationList();
globalFilterRegistrationList.Add(new GlobalFilterRegistration { Type = typeof(YourMomGlobalFilter), Order
= 2 });
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<IFilterProvider>().Use<StructureMapGlobalFilterProvider>();
x.For<GlobalFilterRegistrationList>().Use(globalFilterRegistrationList);
x.For<IBar>().Use<Bar>();
x.For<ILogger>().Use<Logger>();
x.SetAllProperties(p =>
{
p.OfType<ILogger>();
});
});
DependencyResolver.SetResolver(new StructureMapDependencyResolver(container));
}
Now the only remaining work to be done is to finish our implementation of the StructureMapGlobalFilterProvider. We need to change the constructor signature to now take the list we just configured but that’s easy. Slightly more confusing is that, like we could see in use in our last post, the IFilterProvider returns a List<Filter>, not a list of say IActionFilter. This means we will have to get our filters from the container, then wrap them in this other class, then return the list. Why there is this other class I do not know but here is my fully implemented filter provider.
public class StructureMapGlobalFilterProvider : IFilterProvider
{
public StructureMapGlobalFilterProvider(IContainer container, GlobalFilterRegistrationList filterList)
{
_container = container;
_filterList = filterList;
}
private IContainer _container;
private GlobalFilterRegistrationList _filterList;
public IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor
actionDescriptor)
{
var filters = new List<Filter>();
if (_filterList == null || _filterList.Count == 0)
return filters;
foreach (GlobalFilterRegistration registration in _filterList)
{
var actionFilter = _container.GetInstance(registration.Type);
var filter = new Filter(actionFilter, FilterScope.Global, registration.Order);
filters.Add(filter);
}
return filters;
}
}
And that’s it. I’ve updated the library and sample site up on GitHub. Disclaimer: Once again, this is me making stuff up. But it works fine for me and makes setting it all up not too painful. What would you do differently?