Los filtros o filters nos permiten ejecutar código antes o después de determinadas fases en el procesamiento de una solicitud HTTP. Es decir podes interferir esta solicitud antes o después de que llego a nuestro Controller.
Podemos crear filtros personalizados para diferentes cuestiones, como por ejemplo el control de errores, almacenamiento cache , configuración, autorización y registro, entre otras. ¿Qué ganamos con esto? Evitamos la duplicación de código en aquellas instancias donde se deben aplicar los mismos procedimientos para muchos métodos del Controller.
Cada tipo de filtro es ejecutado en una fase diferente de la solicitud, es decir tienen un órden de ejecución según su responsabilidad. La manera de saber que fase va a tener nuestro filtra será según la implementación de la interface IFilterMetadata
del espacio de nombres Microsoft.AspNetCore.Mvc.Filters
.
Tipo | Interface | Descripción |
---|---|---|
Autorización | IAuthorizationFilter | Se utiliza para aplicar la política de autorización y seguridad |
Acción | IActionFilter | Se utiliza para realizar un trabajo específico inmediatamente antes o después de realizar un método de acción. |
Resultado | IResultFilter | Se utiliza para realizar un trabajo específico inmediatamente antes o después de que se procese el resultado de un método de acción |
Excepción | IExceptionFilter | Se usa para manejar excepciones |
Estos se utilizan con el fin de autenticar y crear politicas de seguridad para nuestro aplicación web. Se ejecutan antes que cualquier otro filtro y permiten evitar llegar al controller en caso de no cumplir con las politicas de seguridad. Estos estan comprendidos en la interface IAuthorizationFilter
namespace Microsoft.AspNetCore.Mvc.Filters {
public interface IAuthorizationFilter : IFilterMetadata {
void OnAuthorization(AuthorizationFilterContext context);
}
}
El método OnAuthorization
se utiliza para escribir el código para que el filtro pueda autorizar la solicitud.
El parámetro AuthorizationFilterContext context
, recibe los datos del contexto que describen la solicitud. Este objeto contiene una propiedad llamada Result
del tipo IActionResult
que se utiliza para alterar la respuesta en el caso de ser necesario. Por ejemplo, si no esta autorizado se responde un 401: Unauthorized
. Esto evita que se llame al controller y el resto de los filtros.
Para este ejemplo nos crearemos un filtro de autorización que simplemente verifique en el encabezado que contenga un identificador.
Utilizaremos una clase llamada Auth
que nos permitira manejar la lógica del manejo de usuarios. Esta clase esta dentro del proyecto ej-filters.auth
que fue creado por nosotros para darle completitud a este ejemplo.
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using ej_filters.auth;
namespace ej_filters.api.Filters
{
public class ExampleAuthorizationFilter : Attribute, IAuthorizationFilter
{
private Auth auth;
private readonly string msg;
public ExampleAuthorizationFilter(string message)
{
msg = message;
auth = new Auth();
}
public void OnAuthorization(AuthorizationFilterContext context)
{
string token = context.HttpContext.Request.Headers["auth"];
if (token == null)
{
context.Result = new ContentResult()
{
StatusCode = 401,
Content = msg + "no esta logueado."
};
return;
}
if (!auth.IsLogued(token))
{
context.Result = new ContentResult()
{
StatusCode = 403,
Content = msg + "no esta identificado correctamente."
};
return;
}
}
}
}
Vemos en que en el constructor de este filtro recibimos un atributo string message
. Esto se debe a que los filtros aceptan parametros de tipo primitivos. Es decir, cuando nosotros indicamos en el método del Controller el filtro a utilzar podemos pasarle parametros a este, que haremos referencia en el contructor del filtro.
Vemos que en los casos que no se cumple con la politica de seguridad se le asigna un ContentResult
al resultado de la solicitud, y automaticamente se responde la solicitud no llegando de esta manera al método del Controller.
Los filtros de acción se ejecutan después de los filtros de autorización. Se llaman justo antes de que se llame un método del Controller y justo después de que se termina un método del Controller. Se derivan de la interface de IActionFilter
.
namespace Microsoft.AspNetCore.Mvc.Filters {
public interface IActionFilter : IFilterMetadata {
void OnActionExecuting(ActionExecutingContext context);
void OnActionExecuted(ActionExecutedContext context);
}
}
Al aplicar un filtro de acción a un método del controller, se llama al método OnActionExecuting
justo antes de que se invoque el método del controller, y se llama al método OnActionExecuted
justo después de que el método del controller haya terminado de ejecutarse.
El método OnActionExecuting
tiene un parámetro del tipo ActionExecutingContext
. Que destacaremos la siguientes propiedades de este:
Nombre | Descripción |
---|---|
Controller | El nombre del controlador cuyo método está a punto de ser invocado. |
Result | Esta propiedad es de tipo IActionResult . Si esta propiedad establece un valor de este tipo, se sobreescribe el resultado del método del Controller. |
El método OnActionExecuted
tiene un parámetro del tipo ActionExecutedContext
. Que destacaremos las siguientes propiedades:
Nombre | Descripción |
---|---|
Controller | El nombre del controlador cuyo método fue invocado. |
Exception | Esta propiedad contiene la excepción que ocurrió en el método del Controller. |
ExceptionHandled | Cuando establece su propiedad en true, las excepciones no se propagarán más. |
Result | Esta propiedad devuelve el IActionResult devuelto por el método del Controller, y puede cambiarlo o reemplazarlo si lo necesita. |
En este ejemplo vamos a crear un filtro de acción que simplemente controle el tiempo de ejecución del método del Controller. Para esto vamos a iniciar un temporizador antes de la ejecución y lo vamos a parar luego de la ejecución para devolver el timepo que duró esta ejecución.
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using System.Diagnostics;
namespace ej_filters.api.Filters
{
public class ExampleActionFilter : Attribute, IActionFilter
{
private Stopwatch timer;
public void OnActionExecuting(ActionExecutingContext context)
{
timer = Stopwatch.StartNew();
}
public void OnActionExecuted(ActionExecutedContext context)
{
timer.Stop();
string result = " Tiempo de ejecucion: " + $"{timer.Elapsed.TotalMilliseconds} ms";
((ObjectResult)context.Result).Value = result;
}
}
}
Para esto inicializamos este cronómetro en el método OnActionExecuting
y lo detenemos en el método OnActionExecuted
, cambiando en este último el valor de la respuesta por el tiempo de ejecución.
Los filtros de resultados se ejecutan antes y después de que se procese el resultado del método del Controller. Se ejecutan justo después de los filtros de acción. Se derivan de la interface IResultFilter
. Cabe destacar que los filtros de resultados tienen el mismo patrón que los filtros de acción.
namespace Microsoft.AspNetCore.Mvc.Filters {
public interface IResultFilter : IFilterMetadata {
void OnResultExecuting(ResultExecutingContext context);
void OnResultExecuted(ResultExecutedContext context);
}
}
La interface IResultFilter
tiene 2 métodos. El método OnResultExecuting
se llama justo antes de que se procese el resultado del método del Controller, mientras que el método OnResultExecuted
se llama justo después de que se procesa el resultado del método del Controller.
El método OnResultExecuting tiene un parámetro del tipo ResultExecutingContext
. Que destacaremos las siguientes propiedades:
Nombre | Descripción |
---|---|
Controller | El nombre del controlador cuyo método se invoca. |
Result | Esta propiedad es de tipo IActionResult y contiene el objeto IActionResult devuelto por el método del Controller. |
Cancel | Establecer esta propiedad en verdadero detendrá el procesamiento del resultado de la acción y dará una respuesta 404. |
El método OnResultExecuted
tiene un parámetro del tipo ResultExecutedContext
. Que destacaremos las siguientes propiedades:
Nombre | Descripción |
---|---|
Controller | El nombre del controlador cuyo método se invoca. |
Canceled | Una propiedad de solo lectura que indica si la solicitud fue cancelada. |
Exception | Contiene las excepciones lanzadas en el método del Controller. |
ExceptionHandled | Cuando esta propiedad se establece en true, las excepciones no se propagan más. |
Result | Una propiedad de solo lectura que contiene el IActionResult generado por el método del Controller. |
En este ejemplo vamos a crear un filtro de resultado que concatene a la respuesta un valor a la respuesta del método del Controller. Y luego registre en un log en texto plano la fecha y hora de la ejecución.
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using ej_filters.api.Logic;
namespace ej_filters.api.Filters
{
public class ExampleResultFilter : Attribute, IResultFilter
{
public void OnResultExecuting(ResultExecutingContext context)
{
string result = (string)((ObjectResult)context.Result).Value;
context.Result = new ObjectResult("Hola " + result);
}
public void OnResultExecuted(ResultExecutedContext context)
{
ServiceLogic.EntryLog();
}
}
}
Cabe destacar que el método EntryLog
es de una clase creada por nosotros a modo de ejemplo para darle completitud al ejemplo.
Los filtros de excepciones permiten capturar excepciones sin tener que escribir el bloque try & catch. Para esto implementaremos la interface IExceptionFilter
.
namespace Microsoft.AspNetCore.Mvc.Filters {
public interface IExceptionFilter : IFilterMetadata {
void OnException(ExceptionContext context);
}
}
Para esta interface, los datos de contexto se proporcionan a través de la clase ExceptionContext
, que es un parámetro del método OnException
. Destacaremos las siguientes propiedades:
Nombre | Descripción |
---|---|
Exception | La propiedad contiene las excepciones que se lanzan |
ExceptionDispatchInfo | Contiene los detalles de seguimiento de la pila de la excepción. |
ExceptionHandled | Una propiedad de solo lectura que indica si se maneja la excepción |
Result | Esta propiedad establece el IActionResult que se usará para generar la respuesta |
En este ejemplo vamos a capturar las excepciones que salten y vamos a devolver con una respuesta personalizada la solicitud.
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
namespace ej_filters.api.Filters
{
public class ExampleExceptionFilter : Attribute, IExceptionFilter
{
public void OnException(ExceptionContext context)
{
context.Result = new ContentResult()
{
StatusCode = 500,
Content = "Se lanzo una excepcion con el siguiente mensaje: " + context.Exception.Message
};
}
}
}
Obtenemos la excepción que se ejecutó y anexamos el mensaje a la respuesta. Es de gran utilidad para cuando repetimos muchos try & catch en los métodos de los Controllers.
Para esto fue que agregamos la clase Attribute
en los filtros de la siguiente manera:
public class ExampleAuthorizationFilter : Attribute, IAuthorizationFilter
Esto nos permite poder utilizar los filtros de la siguiente manera:
[ExampleAuthorizationFilter("No puede ver chistes porque ")]
[ExampleActionFilter]
[ExampleResultFilter]
[HttpGet]
public IActionResult GetAll([FromHeader(Name = "auth")]string token)
{
return Ok(ServiceLogic.GetJokes());
}
Cabe destacar que no solo podemos poner filtros por método sino que también lo podemos aplicar a la clase, de la siguiente forma:
[ExampleExceptionFilter]
[ApiController]
[Route("[controller]")]
public class JokesController : ControllerBase