Middleware and Action Filter... what to use when ?

Khanh Nguyen • 15 October 2021 • 

ASP.NET Core.Net CoreMiddlewareAction Filter

A piece of code that would affected every requests on application level was such a common things worth take consider in every web server framework, including ASP.NET Core. But instead of providing one single united way to do the one specific thing. As developer at .Net world, we have more.

But why should that be separate ?

Definition#

Http Pipeline#

First, we need to scratch a bit on surface.

Http pipeline

In the most common scenarios, a Http request will be send on client (through browser, application,...) then passing through a journey of router and server, then arrive at our Kestrel server. The request know would pass down to actual application, which would flow through a collection of middleware, by the order that we design it to.

That collection of middlewares in specific order, can be consider a Http Pipeline.

What does it do ?#

Middleware is a specific block in the collection. Action Filter stick with Controller, which is only a block of the whole pipeline.

So, middleware are much more widely and stronger than Action Filters ?

Not really, since if every request should arrive on the Controller, then every request pass through them too. And actually every thing middleware can do, Action Filter can too. Well... some what confuse...

Using purpose#

Middleware#

Some example of good reasons that make use of middleware:

Action Filters#

How was the implementation ?#

Middleware#

Simple form - AKA inline middleware - Startup.cs - Configure()
app.Run(async ctx => ctx.Request.Headers.Add("X-Correlation-Id", Guid.NewGuid().ToString()));
// assume we have some page on that relative path
app.Run(async ctx =>
{
    if (ctx.Request.RouteValues.ContainsKey("someForbbidenPath"))
      ctx.Response.Redirect("403", true);
});
// If the response was wrote, any middlewares below it won't get executed.
app.Run(async context => await context.Response.WriteAsync("The pipeline ended here!")));
// If the response was wrote, any middlewares below it won't get executed.
app.Run(async ctx => ctx.Request.Headers.Add("X-Application-Id", "Some cool thing"));
Full fledged Form
// Defining stage, put the class somewhere
public class ExampleMiddleware
{
  private readonly RequestDelegate _next;

  public ExampleMiddleware(RequestDelegate next)
  {
    _next = next;
  }

  public async Task Invoke(HttpContext context)
  {
    context.Request.Headers.Add("X-Correlation-Id", Guid.NewGuid().ToString());
    await _next.Invoke(context);
  }
}

// registering stage, Startup.cs | Configure()
app.UseMiddleware<ExampleMiddleware>();

Action Filters#

Declareing stage
public class SampleActionFilter : IActionFilter
{
    public void OnActionExecuting(ActionExecutingContext context)
    {
        // If ModelState is not valid, write response, the request won't pass down to controller
        if(context.ModelState.IsValid)
        {
            context.Result = new BadRequestObjectResult("Oops, the request was not valid!");
        }
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
      // Do some stuff here if needed!
    }
}
Using at specific Controller | Action Endpoint
// Registering stage
services.AddScoped<SampleActionFilter>();

// Using stage
[ServiceFilter(typeof(SampleActionFilter))]
public class SampleController : Controller
{
  [HttpGet]
  [ServiceFilter(typeof(OtherSampleActionFilter))]
  public IActionResult Sample()
  {
      return Ok();
  }
}
Using globally
services.AddControllers(cfg => cfg.Filters.Add(new SampleActionFilter()));

Khanh Nguyen

Web developer & .Net lover