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

Β· 1456 words Β· 7 minutes to read

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):

public interface IActionFilter : IFilter  
{  
void OnActionExecuting([NotNull] ActionExecutingContext context);

void OnActionExecuted([NotNull] ActionExecutedContext context);  
}

public interface IAsyncActionFilter : IFilter  
{  
Task OnActionExecutionAsync([NotNull] ActionExecutingContext context, [NotNull] ActionExecutionDelegate next);  
}  

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.

public interface IResultFilter : IFilter  
{  
void OnResultExecuting([NotNull] ResultExecutingContext context);

void OnResultExecuted([NotNull] ResultExecutedContext context);  
}

public interface IAsyncResultFilter : IFilter  
{  
Task OnResultExecutionAsync([NotNull] ResultExecutingContext context, [NotNull] ResultExecutionDelegate next);  
}  

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:

public class ValidateModelStateAttribute : ActionFilterAttribute  
{  
public override void OnActionExecuting(ActionExecutingContext actionContext)  
{  
if (!actionContext.ModelState.IsValid)  
{  
actionContext.Result = new BadRequestObjectResult(actionContext.ModelState);  
}  
}  
}  

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.

[HttpPut("{id}")]  
[ValidateModelState]  
public void Put(int id, Item item)  
{  
contact.ContactId = id;  
repository.Update(contact);  
}  

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:

public class LogFilter : ActionFilterAttribute  
{  
public override void OnActionExecuting(HttpActionContext actionContext)  
{  
var logger = actionContext.Request.GetDependencyScope().GetService(typeof(ILog)) as ILog;  
if (logger != null)  
{  
logger.Log(actionContext.Request);  
}  
}  
}  

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

public class LogFilter : ActionFilterAttribute  
{  
private readonly ILog logger;

public LogFilter(ILog logger)  
{  
this.logger = logger;  
}

public override void OnActionExecuting(ActionExecutingContext actionContext)  
{  
this.logger.Log(actionContext.HttpContext.Request);  
}  
}  

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:

public interface IFilterFactory : IFilter  
{  
IFilter CreateInstance([NotNull] IServiceProvider serviceProvider);  
}  

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:

public class ServiceFilterAttribute : Attribute, IFilterFactory, IOrderedFilter  
{  
public ServiceFilterAttribute([NotNull] Type type)  
{  
ServiceType = type;  
}

public Type ServiceType { get; private set; }

public int Order { get; set; }

public IFilter CreateInstance([NotNull] IServiceProvider serviceProvider)  
{  
var service = serviceProvider.GetRequiredService(ServiceType);

var filter = service as IFilter;  
if (filter == null)  
{  
throw new InvalidOperationException(Resources.FormatFilterFactoryAttribute_TypeMustImplementIFilter(  
typeof(ServiceFilterAttribute).Name,  
typeof(IFilter).Name));  
}

return filter;  
}  
}  

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:

[Route("[controller]")]  
public class ItemController : ApiController  
{  
private readonly IItemRepository repository;

public ItemController(IItemRepository repository)  
{  
this.repository = repository;  
} 

[ServiceFilter(typeof(LogFilter))]  
[HttpGet("")]  
public IEnumerable<Item> Get()  
{  
return this.repository.GetAll();  
}  
}  

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.

{  
services.AddSingleton<IItemRepository, DefaultItemRepository>();  
services.AddSingleton<LogFilter>();

services.AddMvc()  
}  

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:

public class TypeFilterAttribute : Attribute, IFilterFactory, IOrderedFilter  
{  
private ObjectFactory factory;

public TypeFilterAttribute([NotNull] Type type)  
{  
ImplementationType = type;  
}

public object[] Arguments { get; set; }

public Type ImplementationType { get; private set; }

public int Order { get; set; }

public IFilter CreateInstance([NotNull] IServiceProvider serviceProvider)  
{  
if (this.factory == null)  
{  
var argumentTypes = Arguments?.Select(a => a.GetType())?.ToArray();

this.factory = ActivatorUtilities.CreateFactory(ImplementationType, argumentTypes ?? Type.EmptyTypes);  
}

return (IFilter)this.factory(serviceProvider, Arguments);  
}  
}  

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:

[Route("[controller]")]  
public class ItemController : ApiController  
{  
private readonly IItemRepository repository;

public ItemController(IItemRepository repository)  
{  
this.repository = repository;  
} 

[TypeFilter(typeof(LogFilter))]  
[HttpGet("")]  
public IEnumerable<Item> Get()  
{  
return this.repository.GetAll();  
}  
}  

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:

public class LogFilter : ActionFilterAttribute  
{  
private readonly ILog logger;  
private readonly string logName;

public LogFilter(ILog logger, string logName)  
{  
this.logger = logger;  
this.logName = logName;  
}

public override void OnActionExecuting(ActionExecutingContext actionContext)  
{  
this.logger.Log(actionContext.HttpContext.Request, this.logName);  
}  
}  

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

[TypeFilter(typeof(LogFilter), Arguments = new object[] {"myLogger"})]  
[HttpGet("")]  
public IEnumerable<Item> Get()  
{  
return this.repository.GetAll();  
}  

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”.

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 and on Mastodon.

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