Control the execution order of your filters in ASP.NET Web API - StrathWeb

Strath

Control the execution order of your filters in ASP.NET Web API

Because attributes no longer have to run unordered

One of the few minor annoyances in ASP.NET Web API is that you lack the ability to control the order in which the attributes/filters are executed. If you have several of those applied to one action, they wil not run in the order they have been declared in the code, and that sometimes may cause a lot of problems.

In fact, it has even been submitted as an issue to the ASP.NET team on Codeplex. In the meantime, let’s tackle this problem and see how you can easily regain control over the execution order of the attributes.

The problem

First, let’s look at the problem. Let’s declare three ActionFilterAttributes and apply them to a ApiController action.

To be able to see in which order they are executed, I will write to the Response object in each one of them. This is obviously totally against pretty much everything ASP.NET Web API stands for, but it will allow us to monitor the execution order very easily – and that’s the only thing we are interested in.

So we have three attributes, each one of which will write an identifying string to the Response. Note that due to the nature how ActionFilterAttributes work, the first one to hit OnActionExecuting will be the last to hit OnActionExecuted.

Let’s decorate a Web API action with those three attributes:

This, one would expect, should yield the following result:

Right? Well, not really.

Solution

The solution to the problem is to introduce a parameter which determines the attribute order, and then implement a custom IFilterProvider which will respect that.

The easiest way to go about it would be to extend the System.Web.Http.Filters.FilterInfo class, because that’s the one that IFilterProvider spits out. Unfortunately, that class is sealed so there is not much we can do with it. Plan B then, is to implement a mirror class, CustomFilterInfo, order the attributes there, and then convert the IEnumerable of CustomFilterInfo to IEnumerable of FilterInfo in the CustomFilterProvider.

Let’s go about it.

To be able to sort, we need an interface first.

Position will bne the property that determines the sort order. Now we need a class which will accept the Position parameter in the constructor, so that we can decorate our attributes accordingly.

This class, inherits from ActionFilterAttribute and implements BaseAttribute. If the attribute is created without explicitly set position, it would get position = 0 (first in the queue to execute), otherwise position will be set by the developer.

Note, that in this case we are creating a base inheriting from ActionFilterAttribute. You could/should create the same base class for AuthorizationAttribute and ExceptionFilterAttribute if you plan on sorting these as well, but since it’s duplicate code I will skip them for now.

CustomFilterInfo

Now, we need a class that will be a mirror to the aforementioned, sealed FilterInfo. Instances of this class will also be sortable, so it will implement IComparable.

Let me reiterate the plan – we will retrieve all the relevant filters for a given action, store them in a collection of CustomFilterInfo, sort there, and convert o a collection of FilterInfo, maintaining the order.

This object has two properties (just like FilterInfo) – Instance – which holds the actual Filter and Scope, which determines whether the filter is scoped globally, on controller level or on action level.

The CompareTo method will be used for sorting and first checks whether the compared item is CustomFilterInfo, then if the Instance property contains an object implementing IBaseAttribute (because if it does, it will contain our Position property).

Finally we include a simple method to convert from FilterInfo to CustomFilterInfo.

FilterProvider

Now that we have all our help classes in turn, let’s focus on the heart of it, CustomFilterProvider.

Surprisingly, it is really simple:

What we do here, is we read all the controller-scoped filters, and create an enumerable of CustomFilterInfo. Then we do the same for the action-scoped filters.

Finally, we merge the two enumerables, order them (very easy, since we implemented IComparable) and convert back to FilterInfo).

Notice we set the scope of all filters to “Controller”. The reason is we want to be able to apply sorting not only to action filters but also to controller ones. So that if action filter has a higher “Position” than controller filter, it will be executed first. But, if you don’t like that you can keep the scopes intact.

Plugging it in

The final thing to do is to plug our custom provider into the pipeline, and remove the default one. That’s done in the Global.asax.

First we add our custom one, then we find the default ActionDescriptorFilterProvider and get rid of it.

Testing the functionality

We can now test the code from the beginning again. Let’s modify the attributes to match our current situation:

We need to inherit now not directly from ActionFilterAttribute, but from BaseActionFilterAttribute. That’s not going to change anything – except introduce the Position property.

Now, this is the same code as before, but it will now execute in the proper order.

If you put [CustomAttribute2(Position=2)] on the controller instead, the order remains the same! That is because controller-scoped attributes and action-scoped attributes are being sorted together.

Note, these were all ActionFilterAttributes. As mentioned, you could make the same base class for Authorization and Exception attributes (these are part of the source code that is attached to this post).

Remember, not setting a position at all, will cause the element to be treated as if it had Position=0, so will be executed first.

Summary and source

Hopefully this will be useful in your projects – I am sure that ultimately, there will come a time when you need an order in the attributes, and this solution should provide you with an easy out – as long as your filters would inherit from the base classes as shown here.

With that said – see you next time!

source code on github

Be Sociable, Share!

  • Tom

    Genius! This has saved me many hours.

    Thanks Filip

  • Pingback: The Morning Brew - Chris Alcock » Afternoon Tea - Sunday 10th June 2012

  • Pingback: Dew Drop – June 11, 2012 | Alvin Ashcraft's Morning Dew

  • http://beletsky.net Alexander Beletsky

    Hm, why not to use ‘Order’ attribute of filter:

    http://msdn.microsoft.com/en-us/library/system.web.mvc.filter.order(v=vs.98).aspx

    It’s designed exactly for this purpose. It works perfect for MVC controllers, doesn’t it work for WebAPI controllers?

    • Filip W

      No :)

      MVC filters are of System.Web.Mvc.Filter type (http://msdn.microsoft.com/en-us/library/system.web.mvc.filter(v=vs.98).aspx), while Web API filters are of System.Web.Http.Filters.Filter type (http://msdn.microsoft.com/en-us/library/system.web.http.filters.filter(v=vs.108).aspx) and they don’t have the “Order” property.

      • http://weblogs.asp.net/ricardoperes Ricardo Peres

        That’s precisely what’s wrong with them… Microsoft, why do you keep duplicating things? Why not join all of this MVC/HTTP/Web stuff?

        • David Barrett

          Because Web Api began as a completely separate effort built on top of a simplified WCF stack. It wasn’t until later that the codebase was ported to mirror the MVC implementation. I would be surprised if they weren’t reconciled in the future. At least now the principles and model are very, very similar.

  • http://Whataboutglobalfilters? brad laney

    This works for attribute filters, but what about global ones?

  • Pingback: Friday links 34 « A Programmer with Microsoft tools

  • David Barrett

    Fantastic article — thank you!

    • Filip W

      Thanks! Really appreciate it!

  • Francesco

    I posted a request do add the possibility to control Filter execution order on the next release of asp.net web.api. Please vote it here: http://aspnet.uservoice.com/forums/147201-asp-net-web-api/suggestions/3346720-execution-order-of-mvc4-webapi-action-filters

  • velocity too

    Great help, thank you! One silly question: why did you name the property “Position” instead of “Order” as it is named in the mirrored System.Web.Mvc.FilterAttribute class? That way, if it ever is moved over to a new release of WebAPI, I don’t need to change the property name throughout my code?

    • http://www.strathweb.com/ Filip W

      I wish I had an answer to this question :)

  • Chris Padgett

    A great post, Filip.
    I believe the actionDescriptor.GetFilters() call should select a collection of CustomFilterInfos that are scoped to FilterScope.Action, not FilterScope.Controller.
    Is that right?
    Regards, Chris

    • http://www.strathweb.com/ Filip W

      yes that’s right – typo :)

  • Richard Lawley

    In its current form, this will prevent any global filters from being applied. The FilterProvider must be modified to also include configuration.Filters as well.

  • JordanM

    Brilliant – thank you so much for sharing this code. I would never have had time to investigate this myself this week. Thanks again.