En este post vamos a ver que son los middleware en .NET así como los filtros (FilterAttribute
), veremos su diferencia y cómo implementarlos;
Índice
1 - Qué es un middleware en .NET?
Para poder ver las diferencias entre middleware y filtros, debemos entender que es cada cosa.
Primero decir que el nombre de “middleware” yo lo odio, porque no representa claramente donde está ubicado, osea si, en el medio. Pero a mi personalmente no me dice nada.
En .NET un middleware es una clase que permite manipular una petición (o respuesta) HTTP.
Un middleware actúa en cada request que llega a nuestra aplicación, por lo tanto es parte de la request pipeline.
Y hacen lo propio con la respuesta, pero en orden inverso.
Desde la llegada de .NET Core el uso de middleware es muy común, por lo que es muy importante saber la lógica que tienen detrás y su funcionamiento.
Cabe destacar que el orden es importante, ya que como he indicado se van ejecutando uno detrás de otro.
Hay que tener en cuenta, que podemos crear un middleware que en ciertas condiciones nos crea una respuesta directamente, esta acción lo que haría sería evitar que los middleware que vienen después no se ejecuten.
1.1 - Ejemplos de middleware en ASP.NET Core
Tenemos ejemplos de middlewares cuando creamos una API con el template de visual studio nos viene con la instrucción app.UseHttpsRedirection();
que lo que hace por detrás es añadir un middleware para redireccionar todas las peticiones HTTP a HTTPS.
CORS
o routing
son otras funcionalidades muy comunes las cuales también se implementan a través de middlewares.
O por ejemplo si tenemos activado app.UseStaticFiles()
, el cual ubicamos normalmente el primero, si la request es “get fichero” como un css o javascript, detectaremos que el fichero está en la carpeta de ficheros estáticos y lo devolveremos, sin ejecutar el resto de los middlewares.
1.2 - Crear un middleware en .NET
Para crear un middleware en .NET lo único que tenemos que hacer es una clase normal, con la excepción de dos reglas
- El constructor debe recibir
RequestDelegate
. - Un método llamado
Invoke
que recibaHttpContext
como parámetro.
public class EjemploMiddleware
{
private readonly RequestDelegate _next;
public EjemploMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
await _next(context);
}
}
Y en la clase podemos hacer lo que queramos, incluso podemos acceder al contendor de dependenicas a través de context.RequestServices.GetService<T>()
o a través de la inyeccion de dependencias normal a través del constructor.
Ahora añadimos lógica, en el ejemplo es sencillo, loguear el tiempo que tarda la request, pero lo podemos complicar tanto como queramos.
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} ");
}
}
Para activar nuestro middleware debemos hacerlo en el método Configure
dentro de startup.cs
y para ello únicamente incluiremos el siguiente código.
app.UseMiddleware<EjemploMiddleware>();
- Nota: en Blazor WA no podemos indicar middlewares porque no hay pipeline.
Y con esto ya tenemos nuestro middleware activado y funcionando, si hacemos una request podemos ver el resultado:
2 - Qué es un filtro en .NET
Primero de todo decir que cuando decimos filtro nos referimos a un ActionFilterAttribute
.
Y el funcionamiento es similar al del middleware, pero en este caso lo utilizaremos cuando queremos un mayor control o ser más preciso con lo que podemos hacer.
Mientras que en el middleware únicamente tenemos acceso al HttpContext
, en el ActionFilterAttribute
tendremos acceso a mucha más información, como puede ser el modelo y por supuesto también al HttpContext
.
Si pusiéramos en una imagen la pipeline de los middleware y la de los filtros la de los filtros iría después de la de los middleware, pero a diferencia de en los middleware, en los filtros podemos especificar donde queremos utilizarlos.
Por ello indicamos que son más específicos porque puede ser que queramos que un endpoint tenga un filtro o no:
Como podemos ver en la imagen, el filtro 2 es común para ambos endpoints, mientras que el filtro 1 y el filtro 3 son exclusivos de uno de los endpoints.
- Nota:
ActionFilterAttribute
no es el único filtro en .NET, como ExceptionFilter que se activa al tener una excepción.
2.1 - Ejemplos de filtros en ASP.NET
Los ejemplos más comunes sonAuthorize
, el cual es un action filter que nos permite restringir el acceso a un endpoint dependiendo del usuario o del rol del mismo.
- Nota: crearé un post sobre el pronto.
Otro ejemplo muy común es OutPutCache
el cual mantiene en cache la respuesta del endpoint por el tiempo que especifiquemos.
Por supuesto la pipeline de los filtros funciona igual que la de los middleware, podemos devolver la respuesta desde uno de ellos. El caso más común es Authorize
. Con la siguiente directiva [Authorize(Policy = "PuedeConsultarUsuarios")]
el usuario logueado neceista esa policy para poder acceder al endpoint.
2.3 Implementar un filtro en ASP.NET
Para crear un filtro es muy sencillo, primero debemos crear una clase que implemente cualquiera de los filtros que proporciona .NET, en nuestro caso ActionFilterAttribute
.
Igual que en el middleware tenemos acceso a la inyección de dependencias desde el constructor.
Y posteriormente sobreescribimos los metodos que neceistemos, OnActionExecuting
se ejecutará antes del endpoint, y OnActionExecuted
después.
Aquí puedes ver un ejemplo:
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);
}
}
En el ejemplo únicamente hace un log del método que estamos ejecutando.
2.4 - Implementar un filtro Async en ASP.NET
Si estamos accediendo a un método asíncrono debemos utilizar IAsyncActionFilter
del cual sobreescribiremos el método OnActionExecutionAsync
.
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}");
}
}
Como vemos luce muy similar al middleware.
Ahora nos queda añadir un poco de configuración para poder utilizarlo. Primero debemos añadirlo al contenedor de dependencias
services.AddScoped<EjemploFiltro>();
- Nota: Necesitamos inyectarlo porque estamos inyectando en el mismo el logger.
Y posteriormente en el método/Endpoint donde lo queremos utilizar lo indicamos con [ServiceFilter(typeof(EjemploFiltro))]
quedando de la siguiente manera:
[ServiceFilter(typeof(EjemploFiltro))]
[HttpGet("{userName}")]
public async Task<ResultDto<PersonalProfileDto>> Get(string userName)
{
return (await GetProfile(userName)).MapDto(x=>x);
}
- Nota: si no estuviéramos inyectando el logger, podríamos llamar al filtro directamente con `[EjemploFiltro]`
Conclusión
En este post hemos visto
- Qué es y cómo crear middlewares en .NET
- Qué es y cómo crear un Filter attribute en .NET
- Cómo funciona la request pipeline en .NET
- Ejemplos de código para Middlewares en .NET
- Ejemplos de código para filtors en .NET