Middleware and Action Filter... what to use when ?
Khanh Nguyen • 15 October 2021 •
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.
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#
- The purpose of middleware have nothing to do with business layer.
- Every Http Request need some well defined, easy to access data structure standardize by Http Request information.
- Every Http Request need some extra information satisfied business process requirement.
- We need much more free spaces to do complex stuff.
Some example of good reasons that make use of middleware:
- Logging out the request, including all params, header, http request,...
- Count API reaching
- Authentication
- Authorization
- Filter User blacklist
- Request redirecting based on some global condition.
Action Filters#
- Those naming was actually so good to define usecases
- Logic should be tied with controller.
- Logic should be affected to some specific controller or action endpoint.
- Logic purpose was simple and clear (like add header, handle exception,...)
How was the implementation ?#
Middleware#
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"));
// 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#
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!
}
}
// Registering stage
services.AddScoped<SampleActionFilter>();
// Using stage
[ServiceFilter(typeof(SampleActionFilter))]
public class SampleController : Controller
{
[HttpGet]
[ServiceFilter(typeof(OtherSampleActionFilter))]
public IActionResult Sample()
{
return Ok();
}
}
services.AddControllers(cfg => cfg.Filters.Add(new SampleActionFilter()));