Custom Ordering of Action Filters in ASP.NET MVC

I recently stumbled across something with ASP.NET MVC action filters where they weren’t being executed in quite the order I was expecting. More specifically, I had made a poor assumption that filters defined at the Controller level would all execute prior to any action-level filters. After all, that was the behavior I thought I had seen before, but it turned out that it worked a bit differently. In this post I will explore exactly how ASP.NET MVC finds and orders filters, and what your options are for working with it.

Introduction to ActionFilterAttribute

I’ll start off with a quick introduction to ActionFilterAttribute, in case you are not familiar with it. ActionFilterAttribute is an sbtract class you can extend to gain access to different injection points in the execution of an action method. The attribute can decorate either an action method or an entire controller. In the case of decorating the controller, the filter will get applied to any action method inside that controller. If an action method in that controller also defines the same filter, and that filter does not allow for multiple instances, the filter on the action gets overridden by the one on the controller. In this example, both FilterOne and FilterTwo will be applied to the Index() action:

[FilterOne()]
public class HomeController : CustomController
{
	[FilterTwo()]
	public ActionResult Index()
	{
		return View();
	}
}

For more information on the basics of using filters, check out this article.

Keeping Your Filters In Order

What happens when you need your filters to execute in a certain order? Fortunately, the attribute provides an Order property for just such an occasion, but it comes with certain behaviors to keep in mind. First, it’s important to remember that filters that implement OnActionExecuting or OnActionExecuted will fire before any filters that do not, simply because those events happen first. You are also restricted to entering values of 0 or greater. Otherwise, filters are applied in ascending order according to the value of their Order property.

If no value is provided, the default value for Order is -1. This is exactly where my original confusion came in: any filter that does not specify a value for Order will fire before any filter that does, since -1 is less than 0. One I introduced a strict order for the filters on my base controller, I started seeing all the other filters executing before them, which was definitely not ideal.

Implementation Details

Since ASP.NET MVC is open source, I figured I would take a look to see how it was implemented to help see what my options were for working around my problem. The Controller class has a property called ActionInvoker, defined as:

public IActionInvoker ActionInvoker {
	get {
		if (_actionInvoker == null) {
			_actionInvoker = CreateActionInvoker();
		}
		return _actionInvoker;
	}
	set {
		_actionInvoker = value;
	}
}

protected virtual IActionInvoker CreateActionInvoker() {
	return new ControllerActionInvoker();
}

By default, it holds an instance of ControllerActionInvoker. In that class, there are a few methods that dictate how filters are discovered and applied for an action. The main starting point in here is the InvokeAction method, which as the name implies, tries to begin invoking a specific action on a controller. It retrieves a list of filters on the action by calling GetFilters(ControllerContext, ActionDescriptor):

protected virtual FilterInfo GetFilters(
    ControllerContext controllerContext,
    ActionDescriptor actionDescriptor) {
	FilterInfo filters = actionDescriptor.GetFilters();

	// if the current controller implements one of the filter interfaces, it should be added to the list at position 0
	ControllerBase controller = controllerContext.Controller;
	AddControllerToFilterList(controller, filters.ActionFilters);
	AddControllerToFilterList(controller, filters.ResultFilters);
	AddControllerToFilterList(controller, filters.AuthorizationFilters);
	AddControllerToFilterList(controller, filters.ExceptionFilters);

	return filters;
}

That method gets a list of filters from the action and then segments them off into different lists based on their type. You’ll see why this happens soon. The actual ordering of the filters happens in actionDescriptor.GetFilters():

internal static FilterInfo GetFilters(MethodInfo methodInfo) {
	// Enumerable.OrderBy() is a stable sort, so this method preserves scope ordering.
	FilterAttribute[] typeFilters =
            (FilterAttribute[])methodInfo
                .ReflectedType
                .GetCustomAttributes(typeof(FilterAttribute), true);
	FilterAttribute[] methodFilters =
            (FilterAttribute[])methodInfo
                .GetCustomAttributes(typeof(FilterAttribute), true);
	List<FilterAttribute> orderedFilters =
            RemoveOverriddenFilters(
                typeFilters.Concat(methodFilters)
            ).OrderBy(attr => attr.Order).ToList();

	FilterInfo filterInfo = new FilterInfo();
	MergeFiltersIntoList(orderedFilters, filterInfo.ActionFilters);
	MergeFiltersIntoList(orderedFilters, filterInfo.AuthorizationFilters);
	MergeFiltersIntoList(orderedFilters, filterInfo.ExceptionFilters);
	MergeFiltersIntoList(orderedFilters, filterInfo.ResultFilters);
	return filterInfo;
}

It grabs filters from the controller and action method, removes overridden filters based on the rules I mentioned earlier, and then orders them based on the Order property. If we jump back to the InvokeAction method, we can see that it executes authorization filters first, and then moves on to execute the remaining filters:

AuthorizationContext authContext =
    InvokeAuthorizationFilters(
        controllerContext,
        filterInfo.AuthorizationFilters,
        actionDescriptor);

if (authContext.Result != null) {
	// the auth filter signaled that we should let it short-circuit the request
	InvokeActionResult(controllerContext, authContext.Result);
}
else {
	if (controllerContext.Controller.ValidateRequest) {
		ValidateRequest(controllerContext);
	}

	IDictionary<string, object> parameters =
            GetParameterValues(controllerContext, actionDescriptor);
	ActionExecutedContext postActionContext =
            InvokeActionMethodWithFilters(
                controllerContext, filterInfo.ActionFilters,
                actionDescriptor, parameters);
	InvokeActionResultWithFilters(
            controllerContext, filterInfo.ResultFilters,
            postActionContext.Result);
}

Customization Options

So now we have seen how filters are applied, and the underlying implementation of it. What options are there for achieving the filter order you want?

In this case, the simplest solution is probably the best one – it just requires being aware of the traps we’ve talked about to avoid running into them. If you have define some filter order ranges, you can be sure that certain filters will be applied before others are run. The big catch of course is that it means every time you add a filter to an action or a controller you need to specify an order, to make sure it doesn’t get executed before the filters that should go first.

That said, ASP.NET MVC is full of extensibility points where you can plug in pretty much any behavior you want, so this seemed like a good chance to try that out again. For this example we’ll augment the existing ordering by also providing a way of prioritizing certain filters. To get started, let’s define two test filters that simply write out to the response stream when they get executed:

public class FilterOne : ActionFilterAttribute
{
	public override void OnActionExecuting(ActionExecutingContext filterContext)
	{
		filterContext.HttpContext.Response.Write("One<br />");
	}
}

public class FilterTwo : ActionFilterAttribute
{
	public override void OnActionExecuting(ActionExecutingContext filterContext)
	{
		filterContext.HttpContext.Response.Write("Two<br />");
	}
}

And then we’ll define a controller and action method for testing those filters:

[FilterOne(Order = 1), FilterTwo(Order = 2)]
public class HomeController
{
	public ContentResult Index()
	{
		return Content("");
	}
}

If you run it, you’ll see that FilterOne executes before FilterTwo, as expected. Now let’s build up a small framework for prioritizing filters. First, here is an extension method for IList I’ll use later to fluently add all items in a collection to a list:

public static class ListExtensions
{
	public static IList<T> AddAll<T>(this IList<T> destination, IEnumerable<T> source)
	{
		foreach (T item in source)
		{
			destination.Add(item);
		}

		return destination;
	}
}

For this example, a filter either is or isn’t prioritized, so there won’t be different levels of priority. To say a filter should get prioritized, it should implement the IPriorityFilter interface:

public interface IPriorityFilter
{
}

FilterTwo should be updated so that it is marked as a priority:

public class FilterTwo : ActionFilterAttribute, IPriorityFilter

Now we need to define our own custom IActionInvoker, which is where all the real logic goes. Since the default MVC ControllerActionInvoker does almost everything we want, we can leverage that and just swap out the part we want to change:

public class PrioritizedControllerActionInvoker : ControllerActionInvoker
{
	protected override FilterInfo GetFilters(
            ControllerContext controllerContext,
            ActionDescriptor actionDescriptor)
	{
		FilterInfo filterInfo = actionDescriptor.GetFilters();

		prioritizeFilters(filterInfo.ActionFilters);
		prioritizeFilters(filterInfo.AuthorizationFilters);
		prioritizeFilters(filterInfo.ExceptionFilters);
		prioritizeFilters(filterInfo.ResultFilters);

		return filterInfo;
	}

	private void prioritizeFilters<T>(IList<T> filters)
	{
		IList<T> originalFilters = filters.ToList();
		IEnumerable<T> priorityFilters =
                    originalFilters
                       .Where(filter => (filter as IPriorityFilter) != null);

		filters.Clear();

		filters
			.AddAll(priorityFilters)
			.AddAll(
				originalFilters.Except(priorityFilters)
			);
	}
}

It uses the default implementation of ActionDescriptor.GetFilters() so we don’t need to worry about all the details, and then just rearranges the order of the filters based on the presence of IPriorityFilter. Next we’ll need a custom controller that uses it:

public abstract class CustomController : Controller
{
	protected override IActionInvoker CreateActionInvoker()
	{
		return new PrioritizedControllerActionInvoker();
	}
}

Finally, update HomeController so that it uses the custom controller instead of the default one:

public class HomeController : CustomController

And that’s it! If you fire the page up again, you’ll see FilterTwo processed before FilterOne, even though the default ordering says otherwise.

comments powered by Disqus
Navigation