.NET Nakama

Improving your .NET skills

Avoid Duplicating Code by Using ASP.NET Core Filters

February 04, 2021 (~12 Minute Read)
BASICS FILTER ATTRIBUTE ASP.NET CORE FILTER PIPELINE

Introduction

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.

The filter pipeline in relation to the middleware pipeline.
Figure 1. - The filter pipeline in relation to the middleware pipeline.

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.

Filters vs Middleware

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:

  1. Modify the incoming request,
  2. Modify the produced response, and
  3. 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:

  1. The needed context (i.e. do I need MVC context?) and
  2. In which cases our code needs to run (i.e. on every request or on specified actions/ controllers?).

Filter Types and Execution Order

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 Resource Filters, Action Filters and Result Filters can run code after a specific stage of the pipeline.

The execution flow on the filter pipeline for the different filter types
Figure 2. - The execution flow on the filter pipeline for the different filter types (Source).

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:

  1. Global filters.
  2. Controller filters.
  3. 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 IOrderedFilter.

[MyCustomFilter(Order = 1)]

ASP.NET Core supports both synchronous and 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

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.

In general, implementing custom authorization filters is not recommended because it will require a custom authorization framework. Nowadays, authentication and authorization are an important and a very complex problem, especially regarding security issues in combination with the different client-types (a server, an MVC app, JavaScript-framework app, native mobile app, etc.). Alternatively, check if the existing frameworks and protocols can be applied in your case, such as Basic Authentication, LDAP, OAuth 2.0, SSO, etc. and configure relatively the .NET Core Authentication and Authorization.

Resource Filters

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.

A resource filter implements either the IResourceFilter or IAsyncResourceFilter interface.

  • IResourceFilter
    • OnResourceExecuting(ResourceExecutingContext): Run before the rest of the filter pipeline.
    • OnResourceExecuted(ResourceExecutedContext): Run after the rest of the pipeline has completed.
  • IAsyncResourceFilter
    • OnResourceExecutionAsync(ResourceExecutingContext, ResourceExecutionDelegate): Run before the rest of the pipeline.

Action Filters

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.

It is important to notice that action filters are not supported in Razor Pages. Razor Pages supports IPageFilter and IAsyncPageFilter, which we will not be covered in this article.

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.

An action filter implements either the IActionFilter or IAsyncActionFilter interface.

  • IActionFilter
    • 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.
  • IAsyncActionFilter
    • OnActionExecutionAsync(ActionExecutingContext, ActionExecutionDelegate): Called asynchronously before the action and after model binding is complete.

Exception Filters

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.

Exception filters do not support before-after methods, but support the OnException or OnExceptionAsync method, depending on the implemented interface IExceptionFilter or IAsyncExceptionFilter.

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

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.

A result filter implements either the IResultFilter or IAsyncResultFilter interface and optionally the IAlwaysRunResultFilter or IAsyncAlwaysRunResultFilter.

  • IResultFilter
    • 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.
  • IAsyncResultFilter
    • OnResultExecutionAsync(ResultExecutingContext, ResultExecutionDelegate): Called asynchronously before the action result.

Built-In Filter Attributes

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.

Filters Dependency Injection

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:

The IFilterFactory exposes the CreateInstance method which accepts the IServiceProvider argument as constructor DI and creates an IFilterMetadata instance. The IFilterFactory provides the ability to:

  • Create multiple filter types in the same class.
  • Apply the filter by instance.
  • Access to the service container (i.e. DI) via the IServiceProvider.

Applying a Filter

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.

// Apply the ‘MyCustomFilter’ for all controllers and action methods.
public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers(options =>
    {
        options.Filters.Add(typeof(MyCustomFilter));
    });
}

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:

  1. 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 headergeneral header with the value myValue1.
  2. As a ServiceFilter by type on an action method.
  3. As an Attribute (based on IFilterFactory) on an action method.
[ApiController]
[Route("[controller]")]
[AddHeader("headergeneral", "myValue1")]
public class WeatherForecastController : ControllerBase
{
    [HttpGet]
    [ServiceFilter(typeof(MyActionFilterAttribute))]
    public IEnumerable<WeatherForecast> GetAll(){
    //
    }

    [HttpGet("{Id}")]
    [AddHeaderWithFactory()]
    public WeatherForecast GetById(int id) (){
    //
    }
}

Summary

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 synchronous and 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!

If you liked this article (or not), do not hesitate to leave comments, questions, suggestions, complaints, or just say Hi in the section below. Don't be a stranger 😉!

Dont't forget to follow my feed and be a .NET Nakama. Have a nice day 😁.