Overriding filters in ASP.NET MVC 6

Β· 1065 words Β· 5 minutes to read

There are many posts out there, including on this blog, about what’s in ASP.NET MVC 6 and how to use it. This one however, will be about what’s not in the framework, or at least not in the same way as you might be used to it from MVC 5/Web API 2 - the ability to override filters. I was recently working on an MVC 6 project and ran into this exact problem.

In MVC 5 and Web API 2, there was a built in way to do it, and even though it was not very extensible, it proved to be very handy (at least for me).

IN MVC 6, these override filters are gone, so at first glance, filter overriding is quite difficult. In reality, that’s not the case, you just need to know what to do - let’s have a look.

Background πŸ”—

Override filters, as a rather successful piece of the old framework, were kind of expected to make it to MVC 6 - in fact, there was even a github issue made for that specific purpose. However, it was pushed to the backlog and will not be a part of the upcoming RTM release of MVC 6 framework.

If you look at the issue I linked to, I actually followed up on the status of it, and, as usually, Yishai from the MVC team showed up with some helpful pieces of advice.

Action Descriptor route πŸ”—

Initially my approach was to use a custom action descriptor to restrict MVC to use only specific filters for sepcific actions. However, this approach, even though a good idea at first glance, has a bunch flaws that Yishai pointed out - mainly the fact that filter providers can create extra filters that this approach would not account for.

FilterProviders in MVC6 πŸ”—

The way to support a flexible way of filter overriding in MVC 6 is to implement a custom INestedProvider. Even though there is a base class with virtual members - DefaultFilterProvider - and extending it seems like a good you idea, you should still implement the interface directly. The reason for that is that ASP.NET 5 uses a Russian doll model for providers, allowing you to register multiple providers which can then be called one after the other (hence the Action callNext in the interface). If you are familiar with Web API delegating handlers or OWIN middleware the concept here is similar.

namespace Microsoft.Framework.DependencyInjection  
{  
public interface INestedProvider<T>  
{  
int Order { get; }

void Invoke(T context, Action callNext);  
}  
}  

The filter provider is executed on every request and can be used to filter out filters (pun intended) according to whatever needs you have.

This is particularly useful, as in ASP.NET 5 we deal with a number if different types of filters - TypeFilters, ServiceFilters and then, on top of that, all of the typical filters you might be used to from MVC/Web API (action, authorization and so on).

After a bit of back and forth, what Yishai suggested, was to use the FilterProvider infrastructure to override filters. This turns out to be really easy as this approach can deal with all these filter types in one go.

Let’s imagine we introduce the following filter, with a Type property, allowing you to indicate which type of filter you are willing to override. It will not introduce any behavior - we’ll use it as a marker class only.

public class OverrideFilter : ActionFilterAttribute  
{  
public Type Type { get; set; }  
}  

This class defines a Type of filters that should be overridden (or excluded) from processing. Mind you, overriding by type has its limitations so you really need to know what you are doing here.

Now, let’s introduce a filter provider that will respect our marker OverrideFilter.

public class OverrideFriendlyFilterProvider : INestedProvider<FilterProviderContext>  
{  
//all framework providers have negative orders, so ours will come later  
public int Order => 0;

public override void Invoke(FilterProviderContext context, Action callNext)  
{  
//let the whole provider pipeline run first  
if (callNext != null)  
{  
callNext();  
}

if (context.ActionContext.ActionDescriptor.FilterDescriptors != null)  
{  
var overrideFilters = context.Results.Where(filterItem => filterItem.Filter is OverrideFilter).ToArray();  
foreach (var overrideFilter in overrideFilters)  
{  
context.Results.RemoveAll(filterItem =>  
filterItem.Descriptor.Filter.GetType() == ((OverrideFilter)overrideFilter.Filter).Type &&  
filterItem.Descriptor.Scope <= overrideFilter.Descriptor.Scope); } } } }

What happens here is that any time the action is executed, we check the filter pipeline (context.Results) for the presence of our OverrideFilter. If it’s there, we’ll try to remove from the pipeline all Types of filter that our instance of OverrideFilter specifes. In this particular example, I am also comparing the Scope of the filters - so that only lower level scope can override a higher level scope. This means an action level filter can override a controller or global filter, but not vice versa. This is obviously not mandatory but it is a very intuitive behavior.

What’s worth noting is that contrary to the approach taken in MVC 5 / Web API 2, we do not need to create separate override filters for different filter types - by the time we reach the provider and its Invoke method, the entire filter pipeline will be exposed to us. This will also include filters that are created by a filter provider other than yours - those would not be available on the action descriptor.

One thing you might be worried about at this point is performance - the code above will execute on every request to your endpoint. Should you be worried about that, there is nothing stopping your from caching the result of the filtering in a static field.

Registration πŸ”—

The final step is to register our provider. This is done using the built in ASP.NET 5 dependency injection. Since the filter provider model is a nested model, we need to register our OverrideFriendlyFilterProvider as such:

{  
//other stuff

services.AddTransient<INestedProvider<FilterProviderContext>, OverrideFriendlyFilterProvider>();  
}  

Summary πŸ”—

It is really easy to support filter overriding in MVC 6 through a relatively high level abstraction. We do not have to turn the framework upside down to achieve that goal.

The example shown here overrides filter by a predefined type - which might not be the way that works for you best. Please remember that you may decide to override however you want - by the type of the filter itself or by some ID - what matters is to be aware of technique discussed here, and how easily you can get up and running with it.

About


Hi! I'm Filip W., a cloud architect from ZΓΌrich πŸ‡¨πŸ‡­. I like Toronto Maple Leafs πŸ‡¨πŸ‡¦, Rancid and quantum computing. Oh, and I love the Lowlands 🏴󠁧󠁒󠁳󠁣󠁴󠁿.

You can find me on Github, on Mastodon and on Bluesky.

My Introduction to Quantum Computing with Q# and QDK book
Microsoft MVP