In this post we're going to see what middleware in .NET are as well as filters (FilterAttribute
), we'll see their differences and how to implement them;
Index
1 - What is a middleware in .NET?
Before we can see the differences between middleware and filters, we need to understand what each one is.
First, let me say that I really dislike the name "middleware", because it doesn't clearly represent where it is located, I mean, yes, in the middle. But personally it doesn't say much to me.
In .NET, a middleware is a class that allows us to manipulate an HTTP request (or response).
A middleware acts on every request that reaches our application, therefore it is part of the request pipeline.
And they do the same with the response, but in reverse order.
Since the arrival of .NET Core, the use of middleware is very common, so it is very important to understand the logic behind them and how they work.
It's important to note that order matters, since as I've indicated they are executed one after the other.
Keep in mind that we can create middleware that, under certain conditions, returns a response directly. This would prevent the execution of the middlewares that come after it.
1.1 - Middleware examples in ASP.NET Core
We have examples of middlewares: when we create an API with the Visual Studio template, we get the instruction app.UseHttpsRedirection();
which internally adds a middleware to redirect all HTTP requests to HTTPS.
CORS
or routing
are other very common functionalities which are also implemented through middleware.
Or for example, if we have app.UseStaticFiles()
enabled, which we usually place first, if the request is "get file" such as a CSS or JavaScript file, we will detect that the file is in the static files folder and return it, without running the rest of the middlewares.
1.2 - Creating middleware in .NET
To create middleware in .NET all we need to do is create a regular class, with the exception of two rules
- The constructor must receive
RequestDelegate
. - A method called
Invoke
that takesHttpContext
as a parameter.
public class EjemploMiddleware
{
private readonly RequestDelegate _next;
public EjemploMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
await _next(context);
}
}
And in the class we can do whatever we want, we can even access the dependency container via context.RequestServices.GetService<T>()
or via normal dependency injection through the constructor.
Now let's add logic. In this example it's simple, we log the time taken by the request, but we can make it as complex as we want.
public class EjemploMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger _logger;
public EjemploMiddleware(RequestDelegate next, ILoggerFactory loggerFactory)
{
_next = next;
_logger = loggerFactory.CreateLogger(typeof(EjemploMiddleware));
}
public async Task Invoke(HttpContext context)
{
//Este id es para simular un ID que viene del FrontEnd/Container
//Con el cual mantenemos "trace" de toda la acción del usuario
//Antes de la request
Guid traceId = Guid.NewGuid();
_logger.LogDebug($"Request {traceId} iniciada");
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
await _next(context);
//Despues de la request
stopWatch.Stop();
TimeSpan ts = stopWatch.Elapsed;
string elapsedTime = String.Format("{0:00}:{1:00}:{2:00}.{3:00}",
ts.Hours, ts.Minutes, ts.Seconds, ts.Milliseconds / 10);
_logger.LogDebug($"La request {traceId} ha llevado {elapsedTime} ");
}
}
To activate our middleware, we need to do it in the Configure
method inside startup.cs
and for that we only need to include the following code.
app.UseMiddleware<EjemploMiddleware>();
- Note: in Blazor WA we can't use middlewares because there is no pipeline.
And with this, our middleware is activated and working. If we make a request we can see the result:
2 - What is a filter in .NET
First of all, when we say filter we refer to an ActionFilterAttribute
.
And the way it works is similar to middleware, but in this case we use it when we want more control or to be more precise with what we can do.
Whereas in middleware we only have access to the HttpContext
, in ActionFilterAttribute
we have access to much more information, such as the model and of course also to HttpContext
.
If we were to put middleware and filter pipelines in an image, the filters pipeline would be after middleware's, but unlike middleware, with filters we can specify where we want to use them.
This is why we say they are more specific, because we may want an endpoint to have a filter or not:
As we can see in the image, filter 2 is common to both endpoints, while filter 1 and filter 3 are exclusive to one of the endpoints.
- Note:
ActionFilterAttribute
is not the only filter in .NET, like ExceptionFilter which triggers if there's an exception.
2.1 - Filter examples in ASP.NET
The most common examples are Authorize
, which is an action filter that allows us to restrict access to an endpoint depending on the user or their role.
- Note: I will be creating a post about this soon.
Another very common example is OutPutCache
which keeps the endpoint's response in cache for the amount of time we specify.
Of course, the filter pipeline works the same way as middleware's: we can return a response from one of them. The most common case is Authorize
. With the following directive [Authorize(Policy = "PuedeConsultarUsuarios")]
the logged user needs that policy to access the endpoint.
2.3 - Implementing a filter in ASP.NET
To create a filter is very simple, first we need to create a class that implements any of the filters .NET provides, in our case ActionFilterAttribute
.
Just as with middleware, we have access to dependency injection from the constructor.
After that, we override the methods we need: OnActionExecuting
will run before the endpoint, and OnActionExecuted
runs after.
Here's an example:
public class EjemploFiltro : ActionFilterAttribute
{
private readonly ILogger<EjemploFiltro> _logger;
public EjemploFiltro(ILogger<EjemploFiltro> logger)
{
_logger = logger;
}
public override void OnActionExecuting(ActionExecutingContext context)
{
_logger.LogInformation($"antes del método {context.ActionDescriptor.DisplayName}");
base.OnActionExecuting(context);
}
public override void OnActionExecuted(ActionExecutedContext context)
{
_logger.LogInformation($"después del método {context.ActionDescriptor.DisplayName}");
base.OnActionExecuted(context);
}
}
In this example, it just logs the method we're executing.
2.4 - Implementing an Async filter in ASP.NET
If we're accessing an async method, we need to use IAsyncActionFilter
and override the OnActionExecutionAsync
method.
public class EjemploFiltro : IAsyncActionFilter
{
private readonly ILogger<EjemploFiltro> _logger;
public EjemploFiltro(ILogger<EjemploFiltro> logger)
{
_logger = logger;
}
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
_logger.LogInformation($"Antes del método {context.ActionDescriptor.DisplayName}");
await next();
_logger.LogInformation($"Después del método {context.ActionDescriptor.DisplayName}");
}
}
As we can see, it looks very similar to middleware.
Now we need to add some configuration to use it. First, we must add it to the dependency container
services.AddScoped<EjemploFiltro>();
- Note: We need to inject it since we're injecting the logger.
And then, in the method/endpoint where we want to use it, we indicate it with [ServiceFilter(typeof(EjemploFiltro))]
as follows:
[ServiceFilter(typeof(EjemploFiltro))]
[HttpGet("{userName}")]
public async Task<ResultDto<PersonalProfileDto>> Get(string userName)
{
return (await GetProfile(userName)).MapDto(x=>x);
}
- Note: If we weren't injecting the logger, we could call the filter directly with `[EjemploFiltro]`.
Conclusion
In this post we've seen
- What a middleware is and how to create middlewares in .NET
- What a Filter attribute is and how to create one in .NET
- How the request pipeline works in .NET
- Code examples for Middlewares in .NET
- Code examples for filters in .NET
If there is any problem you can add a comment bellow or contact me in the website's contact form