Filters in ASP.NET Core
Filters act as a pipeline, similarly to middleware. They can be used both with MVC and Razor Pages. They can be applied to individual controllers, actions or globally, to all of them. Filters run as part of Endpoint middleware.
Comparison with the Middleware
Filters is like a small middleware. It hooks into the lifecycle of a request-response procedure.
Differences:
- Middleware runs for ALL requests, filters run for selected requests (or globally as well).
- Filters have access to
HttpContext
and MVC constructs such asModelState
andIActionResult
. Middleware has access only toHttpContext
.
Middleware is more general and it’s designed to solve concerns that are cross-cutting throughout the app.
Types
There are 5 types of filters, each one running at a different stage (MVC):
- Authorization (request only) - short-circuits the request if it’s unauthorized
- Resource (2-ways) - they can have many use-cases, depending on needs. It runs before model binding.
- Action (2-ways) - runs after model binding. We can manipulate action’s arguments; or they can modify action’s response
- Exception - catch exceptions allowing us to prepare a better response. It catches exceptions in:
- model binding and validation
- actions execution
- action/page filter execution
- Result (2-way) - run before and after
IActionResult
is executed, we can control that execution within a filter.
Filters registered globally will be executed for both MVC and Razor Pages.
Implementation
Non-global filters are implemented as attributes. The order of attributes application dictates the order of filters execution.
Filters can be sync or async. Each filter type has two interfaces for that reason. For example, the Authorization filter has:
IAuthorizationFilter
IAsyncAuthorizationFilter
The synchronous filters that execute multiple times (twice or 3 times) have separate methods in their interface for each of the executions. The asynchronous ones have one method and delegates that we need to execute between request/response operations.
Here’s an example of a synchronous filter:
Here’s an example of an asynchronous filter:
Application
Filters can be applied:
- globally
- to MVC:
- controllers
- actions
- to Razor Pages
PageModel
(not individual methods)
Global
Both MVC and Razor Pages:
Just the Razor Pages:
MVC
Page Model
Order
Filters are executed in the following order for a request:
- global filters
- controller filters
- base controller filters
- action filters
IOrderFilter
We can change the default order of execution by implementing the IOrderFilter
interface, which has just one property - int Order
. Lower value of Order
moves filter up in the queue. Filters with the same Order
execute using the
framework’s ordering rules, presented before.
Dependency Injection
Since filters are attributes (unless global), it’s not easy to inject services into them. Filters are singletons. One approach would be to use the service locator pattern, but nobody likes that.
Instead, a filter could be split into two classes:
- one implementing the
I*Filter
- it would contain the DI and filter functionality - another one implementing either
TypeFilterAttribute
orSerivceFilterAttribute
.
The solution is based on IFilterFactory
that ASP.NET Core “understands” and it
knows how to handle it.