Avoid Duplicating Code by Using ASP.NET Core Filters
A filter in ASP.NET Core allows us to run code before and after the action execution (e.g. our application code in an API controller or endpoint), as shown in Figure 1. In .NET Nakama (2020, December), we have seen how we could use Middlewares to inject our application logic (code) in the request pipeline to create reusable and modular code.
Filters run in their own filter pipeline (action invocation pipeline) after the ASP.NET Core selects the action to be executed. Probably you have already used a filter (spoiler alert 😛). For example, the
[Authorize] attribute is an action type filter that limits access to the applied component to any authenticated user.
In this article, we will start by comparing Filters with Middlewares to distinguish when we have to use the one in comparison to the other. Additionally, we will investigate the different filter types to understand a) the cases in which we could use them, b) how to use them and c) their limitations.
As we have already seen, Filters and Middlewares provide similar functionalities. Both use a pipeline and both can be used to inject code in our application to:
- Modify the incoming request,
- Modify the produced response, and
- Generate a response.
In this way, we can create reusable and modular components and thus, follow the Don’t Repeat Yourself (DRY) principle. Hmmm, so… there are cases in which we can use either of them, but how can we decide which one to use? The main difference between them is their access to different contexts.
- Middlewares have access only to the
HttpContext, which allows us to perform actions on the HTTP Request and Response. Middlewares run on every request, regardless of which controller or action is called.
- Filters have full access to the
MVC context, meaning that we have access to the: routing data, current controller, ModelState, etc. For example, we could create a filter that validates the input model. Filters can run on specified controllers or/and action methods, but can also be registered globally in Startup.
Consequently, the selection of Filters vs Middlewares is based on:
- The needed context (i.e. do I need MVC context?) and
- In which cases our code needs to run (i.e. on every request or on specified actions/ controllers?).
In the filter pipeline, filters are executed in a specific order based on their type (Figure 2). It is important to notice that not all filters support the before-after execution. From Figure 2, it is clear that
Action Filters and
Result Filters can run code after a specific stage of the pipeline.
In case multiple filters of the same type exist (e.g. multiple resource filters), the default execution order is based on their scope as follows:
- Global filters.
- Controller filters.
- Action method filters.
To override the default order we can set the
Order property with an integer number. The filters with a higher value are executed first. The
Order property is exposed to the filter’s attribute by implementing the
ASP.NET Core supports both
asynchronous filters. Their main difference when implementing such filters is the selection of the appropriate interface. We will see more information in the following article.
In the next sections, we will investigate each filter type (e.g. their interfaces, limitations, etc.) to understand the cases in which we could use them.
Authorization filters are the first filters run in the filter pipeline which determine whether the user is authorized for the request. If the request is not authorized, the Authorization filters short-circuit the pipeline. As we can see in Figure 2, this kind of filters does not have an after method.
Resource filters run after authorization filters and support before-and-after methods. As we can see in Figure 2, this means that the before-method is executed only for authorized users and the after-method will be executed last in the filter-pipeline, having access to the final response.
For that reasons, this kind of filters is very useful in cases such as caching, in which we can short-circuit the request (at the before-method to return existing cache) or cache the result in the after-method.
OnResourceExecuting(ResourceExecutingContext): Run before the rest of the filter pipeline.
OnResourceExecuted(ResourceExecutedContext): Run after the rest of the pipeline has completed.
OnResourceExecutionAsync(ResourceExecutingContext, ResourceExecutionDelegate): Run before the rest of the pipeline.
Action filters run immediately before and after an action method to be called. As we can see in Figure 2, action filters are executed after the Model-Binding phase. This means that if an input (source) is found but can’t be converted into the target type, the model state is flagged as invalid and if the
[ApiController] attribute is used, an automatic HTTP 400 response error will be returned. In such a case, action filters will not run.
Action filters can be used to:
- Change the arguments passed into the action method.
- Change the result returned from the action method.
- Perform additional validation in the model state (e.g. business validations) and return an error respectively.
OnActionExecuting(ActionExecutingContext): Called before the action execution and after the model binding is complete.
OnActionExecuted(ActionExecutedContext): Called after the action execution and before the action result.
OnActionExecutionAsync(ActionExecutingContext, ActionExecutionDelegate): Called asynchronously before the action and after model binding is complete.
Exception filters handle the unhandled exceptions that occur in controller creation, model binding, action filters, and action methods. They do not catch exceptions that occur in resource filters, result filters, and MVC result execution.
To handle an exception, set the ExceptionHandled property to
true or write a response (e.g.
context.Result = new RedirectResult("myErrorPage.html")). In general, Exception filters are not as flexible as error handling using middlewares, but it can be used in cases where the error handling is different based on which action method is called.
Result filters run code immediately before and after the
Result Execution step (see Figure 2), only when the action method has executed successfully. At the
Result Execution step (i.e. executing the action results), the response is sent to the client.
Result filters are useful when we want to add logic (e.g. a formatter) to the final response. For example, in an API we could perform some serialization to the result before its execution.
OnResultExecuting(ResultExecutingContext): Called before the action result executes.
OnResultExecuted(ResultExecutedContext): Called after the action result executes. Meaning that the response has probably already been sent to the client and thus, it cannot be changed.
OnResultExecutionAsync(ResultExecutingContext, ResultExecutionDelegate): Called asynchronously before the action result.
In ASP.NET Core, we can create custom filters which are based on built-in filter attributes. Meaning that there are filter attribute classes that can be used as base classes for our custom filters. The following list shows the available filter attributes:
The FormatFilterAttribute class is not used as a base class but it creates an instance of a FormatFilter (implements a Resource filter and Result filter), which is responsible to use the format value (provided in the route data or in a query string) and to set accordingly the content type in the result returned from the action method.
When a filter attribute it’s dependent on other services via Dependency Injection (DI), then one of the following has been used for its creation:
- IFilterFactory: An interface implemented on the Attribute (class).
- Create multiple filter types in the same class.
- Apply the filter by instance.
- Access to the service container (i.e. DI) via the
As we have already spoiled, a filter can be added to the filter pipeline either by using an Attribute (on a controller or an action method) or by applying it globally (to all controllers and action methods) in Startup as shown in the following example.
As we can see in the previous example, we are applying the filter but we cannot set input parameters. By using attributes we are:
- Allowing filters to accept arguments and,
- Applying filters to specific controllers or/and action methods.
Attribute based filters, can be added to the filter pipeline in the following two ways:
- By type (e.g.
[ServiceFilter(typeof(MyCustomFilter))]). An instance of the filter is created for each request. In addition, we can use constructor Dependency Injection (DI).
- By instance (e.g.
[MyCustomFilter]). The same instance will be used for every request. We cannot use constructor DI.
The next code example presents how to apply a filter for the following cases:
- As an Attribute on the Controller (which will be applied to all action methods of this controller). This means that for this example, all endpoints of this controller will return a
headergeneralheader with the value
- As a
ServiceFilterby type on an action method.
- As an Attribute (based on
IFilterFactory) on an action method.
It is very useful for .NET developers to know what an ASP.NET Core Filter Attribute is and how to use it to avoid duplicating code. ASP.NET Core Filters are similar to Middlewares in the sense that both use a pipeline and both can be used to inject code in the application to modify the incoming request, modify the produced response, and generate a response.
Filters are executed in a specific flow based on their type, before and in some case after a specific stage. There are five main filter types, which can be used for a specific context: Authorization, Resources, Actions, Exceptions and Results. ASP.NET Core supports both
asynchronous filters. Their main difference when implementing such filters is the selection of the appropriate interface.
Filters that are used via attributes allow them to accept arguments and be used to specific controllers or/and action methods. Also, filters can be applied globally to all controllers and action methods. Applying a filter to the filter pipeline (i.e. by type or by instance) is depending on the selected Built-In Filter Attribute and if the filter is dependent on other services via Dependency Injection (DI) or not.
Filters are a great way to inject code in the filter-pipeline (aka invocation pipeline) to extract common repetitive code out of the action methods. By this way, we can follow the Don’t Repeat Yourself (DRY) principle by creating a common abstraction. In the next article, we will learn how to create and test filter attributes, so stay tuned!