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

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 will not run in the order they have been declared in the code, and that sometimes may cause a lot of problems.

In fact, as a member of the ASP.NET Web API Advisory Group I already brought that up during our meetings, not to mention it has already 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