April 9, 2020

1194 words 6 mins read

MiddlewareFilter asp .net core

One of my favourites features of ASP .NET Core compared to ASP .NET is the application pipeline or request pipeline. The request pipeline allows you to specify what should be executed when an HTTP request arrives.

The idea is simple when an HTTP request is processed, the middlewares are executed in order. Each middleware has the responsibility to call the next middleware in the pipeline or do not call it, short-circuiting the pipeline. This is a powerful way to configure the behaviour of your application.

The request pipeline is configured adding middlewares to IApplicationBuilder instance in Configure method within Startup.cs. It can look something like this:

public void Configure(IApplicationBuilder app) {
    app
        .UseRouting()
        .UseCors()
        .UseAuthentication()
        .UseAuthorization()
        .UseMvc();
}

Creating your Middleware

There are different ways to create and add a middleware to the request pipeline. But my preferred option to create a middleware is using a class. This class only needs to receive the RequestDelegate parameter in the constructor and has an Invoke method. As an example here is a simple Middleware.

public class ExampleMiddleware {
    private readonly RequestDelegate next;

    public ExampleMiddleware(RequestDelegate next) {
        this.next = next;
    }

    public async Task Invoke(HttpContext httpContext)
    {
        // Do whatever you want before call the next middleware
        await next(httpContext);
        // Do whatever when the next middleware already treated the request
    }
}

To continue with the request pipeline you only need to call to the RequestDelegate passing the HttpContext. You can do whatever you want before and after calling the RequestDelegate. Even you can decide not to call the RequestDelegate and return a custom response, short-circuiting the pipeline. This is how several middlewares like SwaggerUi or HealthChecks works.

Normally an extension method is provided to simplify the usage of the middleware. The extension method for our example middleware could be something like this.

public static class ExampleMiddlewareExtension {
    public static IApplicationBuilder UseExample(this IApplicationBuilder app) {
        return app.UseMiddleware<ExampleMiddleware>();
    }
}

And that’s all, simply adding a call to this extension method within the Configure method in Startup class, every HTTP request will trigger a call to the Invoke method of your middleware.

But, what happens if you don’t want your middleware to be invoked in every request? what if you want some logic to be executed only in certain controllers? For this case you have another option, use a Filter.

A little about filters

Disclaimer: I am not going to explain all about filters in this post, so if you are interested in this topic, I suggest you check the Microsoft documentation.

Filters have a different lifecycle, as shown in the following figure they are executed within MVC action. This is after ASP .NET Core has selected the action to execute.

Image from Microsoft documentation

Image from Microsoft documentation

Filters are very powerful but that power has a price, the complexity. There are several filters types, with different methods to be implemented, all of these methods are called in different moments of the requests.

But not always all this complexity is needed, so there is another way to get the advantages of a filter avoiding such complexity.

Using middleware as a Filter

There is another tool that allows us to use a Middleware as a filter, this is the MiddlewareFilterAttribute. The middleware will be invoked in the filter pipeline, in the runtime execution, giving us some of the advantages of the filters with the simplicity of middlewares.

To use MiddlewareFilterAttribute we need to create a new class, this class must meet the following requisites:

  • Have a parameterless constructor
  • A public method Configure that receives IApplicationBuilder as a parameter

Resuming our previous example, the class could look something like the following code. Note that the same logic that the previously ExampleMiddlewareExtension.

public class ExampleMiddlewareBuilder {
    public void Configure(IApplicationBuilder app) {
        app.UseMiddleware<ExampleMiddleware>();
    }
}

Once we have this class we can use the MiddlewareFilterAttribute.

[Route("api/values")]
[ApiController]
[MiddlewareFilter(typeof(ExampleMiddlewareBuilder))]
public class ValuesController : ControllerBase {

    [HttpGet]
    public ICollection<string> Get() {
        return new List<string>{ "1", "2" };
    }
}

And we have it, every HTTP request to ValuesController will trigger the execution of our ExampleMiddleware.

Some advantages of using MiddlewareFilterAttribute

But there is more, executing the middleware within the filter pipeline gives us some extra possibilities in the middleware, like access to the context of the action execution using the IActionContextAccessor, that can be declared as a dependency within the middleware constructor.

Let’s put a use case example, imagine that you want to log the body of the HTTP requests. That can be easily done in a middleware, just reading the body. Let’s do it a little more complicated, if you have any sensitive data in the body that you must not log it, in this case, we can not log all the request body.

Our idea was to use an Attribute in the Data Transfer Objects (DTO) properties to specify which data must not be logged. Once we have the properties we can log and which not, our idea is to intercept the request and log the information that is possible.

The first try was to use a middleware in the request pipeline. The problem with this approach is that it is not possible to know what is the DTO is expected by the controller. So we need to know what action is going to be executed in order to know the DTO type.

Filters solve that problem, due to being executed in a different lifecycle where the framework has already selected the action to process the request. We created an ActionFilter where we had:

  • The body of the request

  • A way to access the DTO expected by the controller

To log the body, in the first place we deserialized the body to the DTO, and then we can log the DTO without the properties that have the Attribute we created before.

The problem that we had using a filter is that it was a clever code, with several code paths for different cases, difficult to understand and to maintain.

And then the MiddlewareFilter comes to the rescue. To use it we only need to create a middleware that receives IActionContextAccessor and use the same logic that the filter we created before. The result can look something similar to this:

public class LoggingMiddleware {
    private readonly RequestDelegate next;
    private readonly IActionContextAccessor contextAccessor;

    public ExampleMiddleware(RequestDelegate next, IActionContextAccessor contextAccessor) {
        this.next = next;
        this.contextAccessor = contextAccessor;
    }

    public async Task Invoke(HttpContext httpContext)
    {
        // We can get the DTO type from contextAccessor and log it.
        await next(httpContext);
    }
}

There is only one step more to have that code working, we need to add IActionContextAccessor to the dependency injection container. It can be done in the ConfigureServices method in your StartUp.cs.

public void ConfigureServices(IServiceCollection services) {
    // Your configuration
    services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
    // More configuration
}

And now we are ready to use it in our controllers as we saw previously in the Using middleware as a Filter section.

Taking this use case, if we need to log the body of all requests. We can register it globally like any other filter. Avoiding to repeat it in every controller class.

public void ConfigureServices(IServiceCollection services) {
    // ...
    services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
    services
        .AddMvcCore(mvcConfig => {
            mvcConfig.Filters.Add(new MiddlewareFilterAttribute(typeof(LoggingMiddleware)));
        })
    // ...
}