FilterAttribute Property Injection in Autofac MVC 3 Integration

by Alex Meyer-Gleaves 24 March 2011 - 8:56 PM

The current mechanism for performing property injection on FilterAttribute instances via the ExtensibleActionInvoker had to be removed recently due to a rather nasty bug. These are the notes that Nick provided outlining the problem he discovered (possibly with the help of the exciting new Whitebox profiler).

Because the filters passed from the base action invoker also include the controller, property injection happens on the controller itself several times as the filters are processed.

The filter attributes also included in the collection may also be singletons cached by MVC, and so it is quite likely that dependencies may be overwritten with those from a concurrently executing request.

In all this behaviour is probably too risky to reliably support.

Removed property injection routine. (Breaking change.)

I have replaced the old mechanism using an approach that leverages the improved dependency injection support added to MVC 3 (this will be in the next release). To make use of property injection for your filter attributes all you will need to do is call the RegisterFilterProvider method on the ContainerBuilder before building your container and providing it to the AutofacDependencyResolver.

ContainerBuilder builder = new ContainerBuilder();

builder.RegisterControllers(Assembly.GetExecutingAssembly());
builder.Register(c => new Logger()).As<ILogger>().InstancePerHttpRequest();
builder.RegisterFilterProvider();

IContainer container = builder.Build();
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));

Then you can add properties to your filter attributes and any matching dependencies that are registered in the container will be injected into the properties. For example, the action filter below will have the ILogger instance that was registered above injected. Note that the attribute itself does not need to be registered in the container.

public class CustomActionFilter : ActionFilterAttribute
{
    public ILogger Logger { get; set; }

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        Logger.Log("OnActionExecuting");
    }
}

The same simple approach applies to the other filter attribute types such as authorization attributes.

public class CustomAuthorizeAttribute : AuthorizeAttribute
{
    public ILogger Logger { get; set; }

    protected override bool AuthorizeCore(HttpContextBase httpContext)
    {
        Logger.Log("AuthorizeCore");
        return true;
    }
}

After applying the attributes to your actions as required your work is done.

[CustomActionFilter]
[CustomAuthorizeAttribute]
public ActionResult Index()
{
    // ...
}

To make this work I added a custom FilterAttributeFilterProvider implementation. The custom filter provider delegates the job of collecting the filters to the base class. Once the filters have been retrieved by the base class, the ILifetimeScope for the current HTTP request is retrieved and used to perform property injection on the filters. The false passed to the base FilterAttributeProvider constructor sets the cacheAttributeInstances parameter to ensure that attribute instances are not cached. Allowing the attribute instances to be cached would result in race conditions and other unexpected behaviour.

/// <summary>
/// Defines a filter provider for filter attributes that performs property injection.
/// </summary>
public class AutofacFilterAttributeFilterProvider : FilterAttributeFilterProvider
{
    /// <summary>
    /// Initializes a new instance of the <see cref="AutofacFilterAttributeFilterProvider"/> class.
    /// </summary>
    /// <remarks>
    /// The <c>false</c> constructor parameter passed to base here ensures that attribute instances are not cached.
    /// </remarks>
    public AutofacFilterAttributeFilterProvider() : base(false)
    {
    }

    /// <summary>
    /// Aggregates the filters from all of the filter providers into one collection.
    /// </summary>
    /// <param name="controllerContext">The controller context.</param>
    /// <param name="actionDescriptor">The action descriptor.</param>
    /// <returns>
    /// The collection filters from all of the filter providers with properties injected.
    /// </returns>
    public override IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
    {
        var filters = base.GetFilters(controllerContext, actionDescriptor).ToArray();
        var lifetimeScope = AutofacDependencyResolver.Current.RequestLifetimeScope;

        if (lifetimeScope != null)
            foreach (var filter in filters)
                lifetimeScope.InjectProperties(filter.Instance);

        return filters;
    }
}

The RegisterFilterProvider method has been added to the ContainerBuilder using an extension method. This method will register the AutofacFilterAttributeFilterProvider using the IFilterProvider interface that MVC uses when asking the dependency resolver for filter providers. Following the instructions outlined in Brad Wilson’s post on the subject of dependency injection and filters, I made sure that the default FilterAttributeFilterProvider instance is removed from the static collection of providers.

/// <summary>
/// Registers the <see cref="AutofacFilterAttributeFilterProvider"/>.
/// </summary>
/// <param name="builder">The container builder.</param>
public static void RegisterFilterProvider(this ContainerBuilder builder)
{
    if (builder == null) throw new ArgumentNullException("builder");

    foreach (var provider in FilterProviders.Providers.OfType<FilterAttributeFilterProvider>().ToArray())
        FilterProviders.Providers.Remove(provider);

    builder.RegisterType<AutofacFilterAttributeFilterProvider>()
        .As<IFilterProvider>()
        .SingleInstance();
}

If you were using the old mechanism you will have breaking changes to contend with, but as you can see it should be easy to get back on track again.

Tags: ,

Autofac | Web Development

Comments (5) -

Nicholas Blumhardt
Nicholas Blumhardt
25 March 2011 - 2:07 PM #

Aha! Nice job. I should have looked deeper into the available options this morning - a price paid for rushing things through! Smile Looks like a great solution.

Nick

Reply

Jesus Garza
Jesus Garza Mexico
28 April 2011 - 9:49 AM #

Alex, do you know when these changes will be officially released?

Regards,

Jesús Garza

Reply

Alex Meyer-Gleaves
Alex Meyer-Gleaves Australia
5 May 2011 - 1:14 AM #

Hi Jesús,

The changes will be included in the next release, but I am not sure when that will actually be. It usually isn't too long between releases.

Cheers,

Alex.

Reply

Luke
Luke Australia
27 May 2011 - 10:40 AM #

Hi Alex,

In one project we've built from source and are using this new implementation, and it all works fine, thanks Smile

Do you have any indication when the next release to include this will be?

Cheers,

Luke

Reply

Alex Meyer-Gleaves
Alex Meyer-Gleaves Australia
2 August 2011 - 11:04 PM #

Thanks for the feedback Luke. The 2.5.1 build is now available and includes the changes.

http://code.google.com/p/autofac/downloads/list

Reply

Add comment

  Country flag

biuquote
  • Comment
  • Preview
Loading

About the author

Alex Meyer-Gleaves I'm a Technical Architect living in Australia (that island like continent in the southern hemisphere). I love Microsoft .NET and C#. I hate early mornings, slow drivers and Lotus Notes.

Twitter

Google Shared

 

Month List

Recent Comments

Comment RSS

Links

Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

© Copyright 2010