Action Filtering is one of the “hidden parts” of ASP.NET MVC. When checking page execution time, it’s common to measure controller action or view generation, but it’s often uncommon to monitor filters (as for routing). After a few performance reviews, I now think it’s really important to not forget this part.
Just to remember, MVC filters are custom attributes that you put onto action methods/controllers/globally to add common functionality, with pre- and post-processing behaviors.
How does the filter work? A good starting point is ASP.NET MVC source code and especially ControllerActionInvoker.
We all love great debugging tools and ASP.NET MVC MiniProfiler is one of them. However it does not come with a native way to instrument filter execution. But that’s however the tool we will use to report execution timings.
In order to evaluate performance of filters, a typical ASP.NET developer could create a new base filter attribute, and use this attribute for every filter in the target solution.
A basic implementation could be :
public void OnActionExecuted(ActionExecutedContext filterContext)
{
using (MiniProfiler.StepStatic("Filter: " + actionFilter.GetType().Name + ".OnActionExecuted", ProfileLevel.Info))
{
actionFilter.OnActionExecuted(filterContext);
}
}
Even if it’s a valid OOP approach, I don’t like to change the default base class for all of the custom filters. Obviously, this will not work for third party filters coming from my favorites Nuget packages. The problem is not in this implementation but how we plug it into ASP.NET MVC. However, I keep the code for the future.
An experienced ASP.NET MVC developer knows it is relatively easy to customize the default ControllerActionInvoker
. As suggested in this post . The next step is to create an ActionInvoker
that will wrap all ActionFilterAttribute
s with our wrapper class.
Especially, we will override the GetFilters
method as follows:
protected override FilterInfo GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
{
FilterInfo baseFilters = base.GetFilters(controllerContext, actionDescriptor);
WrapFilters(baseFilters.ActionFilters);
WrapFilters(baseFilters.ResultFilters);
return baseFilters;
}
Prior to ASP.NET MVC 4, the only way to switch out the action invoker was to write a custom controller factory. That’s not so complicated but it’s so long and annoying…
Since ASP.NET MVC 4, you can now simply inject an IActionInvoker
using the dependency resolver. To understand how IoC/DI works in ASP.NET MVC, you could read the ASP.NET tutorial here. http://www.asp.net/mvc/tutorials/hands-on-labs/aspnet-mvc-4-dependency-injection
Using Ninject.MVC Nuget package, the final code is:
private static void RegisterServices(IKernel kernel)
{
kernel.Bind<IActionInvoker>().To<ProfiledActionInvoker>();
}
That’s all, we did it! Here is the result for the standard MVC 4 template and two custom global filters.

The complete Gist is available here.
OK, I found a problem, how to solve it ?
The code in the filter is generally easy to understand and you should use standard tools (Visual Studio Profiler). However there are two things you’ve got to be carefully taught:
Using Filter Attributes, there is only a single attribute instance per application. Use an appropriate store for your filter state (see this post from Brad Wilson).
Using FilterProviders, the filter provider will have a chance to decide whether a particular filter applies to the current request. If so, a new instance of the filter will be created each time.
Canceling Filter Execution
According to MSDN, You can cancel filter execution in the OnActionExecuting
and OnResultExecuting
methods by setting the Result
property to a non-null value. I’ve seen many cases where useless filters are invoked; remember everything has a cost.
What about Glimps ?
In Glimpse, the Execution tab shows the ASP.NET MVC execution pipeline of actions, action results, and action filters (including child actions) required to respond to the HTTP request. However, I find the execution tab not clear (too much data) and I’m also less confident in Glimpse for production.
Because you have nothing to do, it’s not so funny