Action filters, service filters and type filters in ASP.NET 5 and MVC 6

Today, let's have a look at he area of filters in ASP.NET MVC 6 – because it actually contains quite a few interesting changes compared to classic MVC and Web API filter pipelines.

Let's leave the specialized filters (error filters, authorization filters) on a side for now, and focus instead on the functional, aspect oriented, filters. Aside from the good old action filters, known from both MVC and from Web API, there are two new types of filters (or rather filter factories, but we’ll get there) that you can use – ServiceFilters and TypeFilters.

Action filters

Action filters in MVC 6 play the same role as they did in both old MVC and Web API – allow you to run some code before or after the action is executed. They are defined by the two interfaces below (sync and async):

Additionally there is a concept of a result filter (which did not existed in Web API, but should be familiar to MVC users), which allows you to run some code just before or just after the execution of the result of your action.

The default ActionFilterAttribute that you will be probably using most commonly implements all 4 of these interfaces.

At this point, if you have been looking carefully, you might have noticed one thing – none of the interfaces allow you to order your filters. In Web API, indeed, there was no support for filter ordering (which was a big issue if you ask me), but it has always been there in MVC.

MVC 6 still supports filter ordering – it's done through yet another interface IOrderedFilter, whose sole purpose is to introduce an Order property into your filter. It is also implemented by the common ActionFilterAttribute.

So, overall, no big difference on that front, and the usage seems pretty straight forward too. Consider the following example:

In this case, the filter will validate the model state prior to action execution, and respond with a 400 response in case the model state is invalid (i.e. required parameters are missing or other validation conditions have not been satisified).

And here is the action filter in (pun intended) action. Notice that there is no longer the need for any model state checks inside the action, as this logic has been externalized into the action filter.

Of course this is nothing new for anyone – the behavior is virtually identical to that from both MVC 5 (and earlier) and Web API.

Service filters

Service filters are a bit more exciting. They allow you to resolve a filter instance directly from the IoC container. This means:

  • the filter must be registered in the container in the first place
  • the filter can have a lifetime that you control yourself (through the IoC container)
  • constructor injection is now supported into those filters

The last point is especially important, as for example in Web API this was not the case – the filters were cached, and having per request or transient dependencies injected into the filter was virtually impossible (at least not without manually rewriting the majority of the filter pipeline). As a result Web API had horrendous service locator patterns like these scattered all over the place:

In MVC 6 the same code can be nicely rewritten into a class using proper dependency injection.

At this point you may ask, how does this tie into the ServiceFilter, since our filter above is just a regular ActionFilterAttribute?

Well, you can think of ServiceFilter as a provider of filters (or technically, a factory). In fact, ServiceFilter is an implementation of a simple IFilterFactory interface:

The role of a filter factory is to provide an instance of an IFilter which can be used within the MVC 6 pipeline. It is a bit of a "filterception" since IFilterFactory is an IFilter itself 🙂 This way the filters and filter factories can be used interchangeably in the pipeline. The default filter provider in MVC 6 will then attempt to cast each filter to IFilterFactory and if that succeeds, invoke the CreateInstance method – otherwise, it will simply treat the filter as a general IFilter.

So going back to ServiceFilter, its entire definition is shown below:

As you can see, all that's needed is a type of a real filter that you want to use to be provided through the constructor, and it will then be resolved from the ASP.NET 5 IoC container – through IServiceProvider.

So, with our earlier LogFilter example, in practice, it would look like this in a controller:

In other words, we are using ServiceFilter as a gateway to get the actual filter we want to use (in this case above it's LogFilter) to be resolved from the IoC container.

One final missing piece is that LogFilter needs to be registered in the IoC container for this setup to work. This has to happen in your Startup class, inside ConfigureServices.

Type filters

Finally, you can choose to use type filters, which are quite similar to service filters. Type filters are also implementations of IFilterFactory and can allow you to have dependencies injected into your filter.

The definition is shown below:

The difference between service filters and type filters is that Types that are resolved through type filters do not get resolved directly from an IoC container, but rather use ObjectFactory for instantiation. This allows you to use TypeFilterAttribute with filters that – contrary to our earlier service filter example – have not been registered in the IoC container.

So the example with LogFilter would look like this when used in conjunction with TypeFilterAttribute:

Which is identical as when using service filters, accept it will just work straight away – without us having to explicitly register the filter in IoC container.

Additionally, TypeFilter also allows you to pass in extra arguments to the filter, directly from the controller. Consider we modify our filter definition to be as follows – adding an extra constructor parameter that would not be resolved from IoC container:

We could now change our type filter usage in the controller to explicitly pass the string value into the constructor.

Ultimately the entire structure gives you quite a lot of flexibility in terms of what you can do with the filter pipeline, and how do you wish your filters to be “brought to life”.