Injectable, Configurable Action Filters

Thursday, January 20, 2011

One of the advantages to using a custom IFilterProvider is how you gain total control over the instantiation of action filters in ASP.NET MVC 3. When you combine an IoC container with a filter provider and a custom IDependencyResolver (see Brad's posts for details),  then all the pieces fall into place. Building on the code from the last post, we can change our filter provider to utilize Ninject for action filter instantiation.

public class ConfiguredFilterProvider : IFilterProvider
{
    private readonly IKernel _kernel;

    public ConfiguredFilterProvider(IKernel  kernel)
    {
        _kernel = kernel;
    }

    public IEnumerable<Filter> GetFilters(
        ControllerContext controllerContext, 
        ActionDescriptor actionDescriptor)
    {
        var filters = FiltersSettings.Settings.Filters;
        foreach (var filter in filters.Cast<FilterAction>())
        {
            var filterType = Type.GetType(filter.Type);
            yield return new Filter(
                    _kernel.Get(filterType),
                    FilterScope.Global, order:null          
                );
        }
    }
}

Using Ninject to create the filters makes it trivially easy to inject dependencies into the filter. In case you didn't know, action filters in MVC 3 no longer need to derive from an attribute derived base class. All you need is a class that implements one or more of the action filter interfaces.

public class LoggingFilter : IActionFilter, 
                             IExceptionFilter, 
                             IResultFilter, 
                             IAuthorizationFilter
{

    public LoggingFilter(ISomeDependency dependency)
    {
            
    }

    // ...
}

If you are using Ninject, you'll also want to use a custom IDependencyResolver based on Ninject. It's easy to write your own, or you can add one already implemented for you from the NuGet gallery.

image

With the custom dependency resolver in place, you no longer need to register your filter provider in the FilterProvider.Providers collection. Instead, you can just register the provider as another service in the container.

public static class AppStart_NinjectMVC3
{
    public static void RegisterServices(IKernel kernel)
    {
        kernel.Bind<IFilterProvider>()
              .To<ConfiguredFilterProvider>();
        kernel.Bind<ISomeDependency>()
              .To<SomeDependency>();
    }

    public static void Start()
    {
        IKernel kernel = new StandardKernel();
        RegisterServices(kernel);
        DependencyResolver.SetResolver(
            new NinjectServiceLocator(kernel));
    }
}

In previous versions of MVC, filters were notoriously difficult for injection, and mostly inflexible. MVC 3 is a great improvement.


Comments
gravatar Nicholas Piasecki Thursday, January 20, 2011
So in the ASP.NET MVC 3 release notes I noticed this little blurb:

"In previous versions of ASP.NET MVC, action filters are create per request except in a few cases. This behavior was never a guaranteed behavior but merely an implementation detail and the contract for filters was to consider them stateless. In ASP.NET MVC 3, filters are cached more aggressively. Therefore, any custom action filters which improperly store instance state might be broken."

Let's say that in my ASP.NET MVC 2 app I have some action filters that do store things like an NHibernate ISession as a property, and that that ISession is injected by Autofac (or Ninject or whatever) on an instance-per-request basis.

So what I'm getting is that the hooks provided by ASP.NET MVC 3 that you discuss in the post means that the container has the capability (doesn't necessarily, but has the capability) to still provide filters (and, as a result, members) that are instanced per request, making that blurb in the release notes no longer relevant.

Is that right? Or should my property be doing the Context.Items["DaSession"] shindig because the filter caching happens at a higher level than the container can deal with?

Thanks for any insight.
gravatar scott Friday, January 21, 2011
@Nicholas - I believe if you have your own filter provider you'll be ok. It looks like it is the built-in providers that perform some of the caching.
Comments are now closed.
by K. Scott Allen K.Scott Allen
My Pluralsight Courses
The Podcast!