Filtros en ASP.NET Core

Por Kirk Larkin, Rick Anderson, Tom Dykstra y Steve Smith

Los filtros en ASP.NET Core permiten que se ejecute el código antes o después de determinadas fases de la canalización del procesamiento de la solicitud.

Los filtros integrados se encargan de tareas como las siguientes:

  • Autorización, que impide el acceso a recursos para los que un usuario no está autorizado.
  • Almacenamiento en caché de la respuesta, que permite cortocircuitar la canalización de la solicitud para devolver una respuesta almacenada en caché.

Se pueden crear filtros personalizados que se encarguen de cuestiones transversales. Entre los ejemplos de cuestiones transversales se incluyen el control de errores, el almacenamiento en caché, la configuración, la autorización y el registro. Los filtros evitan la duplicación de código. Así, por ejemplo, un filtro de excepción de control de errores puede consolidar el control de errores.

Este documento se aplica a Razor Pages, a los controladores de API y a los controladores con vistas. Los filtros no funcionan directamente con componentes de Razor. Un filtro solo puede afectar indirectamente a un componente cuando:

  • El componente está insertado en una página o vista.
  • La página o controlador y la vista usan el filtro.

Funcionamiento de los filtros

Los filtros se ejecutan dentro de la canalización de invocación de acciones de ASP.NET Core, a veces denominada canalización de filtro. La canalización de filtro se ejecuta después de que ASP.NET Core seleccione la acción que se va a ejecutar:

The request is processed through Other Middleware, Routing Middleware, Action Selection, and the Action Invocation Pipeline. The request processing continues back through Action Selection, Routing Middleware, and various Other Middleware before becoming a response sent to the client.

Tipos de filtro

Cada tipo de filtro se ejecuta en una fase diferente dentro de la canalización de filtro:

  • Filtros de autorización:

    • Se ejecutan primero.
    • Determinan si el usuario está autorizado para la solicitud.
    • Cortocircuitan la canalización si una solicitud no está autorizada.
  • Filtros de recursos:

    • Se ejecutan después de la autorización.
    • OnResourceExecuting ejecuta código antes que el resto de la canalización del filtro. Por ejemplo, OnResourceExecuting ejecuta código antes que el enlace de modelos.
    • OnResourceExecuted ejecuta el código una vez que el resto de la canalización se haya completado.
  • Los filtros de acciones:

    • Se ejecutan inmediatamente antes y después de llamar a un método de acción.
    • Pueden cambiar los argumentos pasados a una acción.
    • Pueden cambiar el resultado devuelto de la acción.
    • No se admiten en Razor Pages.
  • Filtros de punto de conexión:

    • Se ejecutan inmediatamente antes y después de llamar a un método de acción.
    • Pueden cambiar los argumentos pasados a una acción.
    • Pueden cambiar el resultado devuelto de la acción.
    • No se admiten en Razor Pages.
    • Se pueden invocar tanto en acciones como en puntos de conexión basados en controladores de ruta.
  • Los filtros de excepciones aplican directivas globales a las excepciones no controladas que se producen antes de que se escriba el cuerpo de respuesta.

  • Filtros de resultados:

    • Se ejecutan inmediatamente antes y después de la ejecución de resultados de acción.
    • Solo se ejecutan cuando el método de acción se ejecuta correctamente.
    • Son útiles para la lógica que debe regir la ejecución de la vista o el formateador.

En el siguiente diagrama se muestra cómo interactúan los tipos de filtro en la canalización de filtro:

The request is processed through Authorization Filters, Resource Filters, Model Binding, Action Filters, Action Execution and Action Result Conversion, Exception Filters, Result Filters, and Result Execution. On the way out, the request is only processed by Result Filters and Resource Filters before becoming a response sent to the client.

Razor Pages también admite filtros de Razor Page, que se ejecutan antes y después de un controlador de Razor Page.

Implementación

Los filtros admiten implementaciones tanto sincrónicas como asincrónicas a través de diferentes definiciones de interfaz.

Los filtros sincrónicos se ejecutan antes y después de la fase de canalización. Por ejemplo, OnActionExecuting se llama antes de llamar al método de acción. OnActionExecuted se llama después de devolver el método de acción:

public class SampleActionFilter : IActionFilter
{
    public void OnActionExecuting(ActionExecutingContext context)
    {
        // Do something before the action executes.
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
        // Do something after the action executes.
    }
}

Los filtros asincrónicos definen un método On-Stage-ExecutionAsync. Por ejemplo, OnActionExecutionAsync:

public class SampleAsyncActionFilter : IAsyncActionFilter
{
    public async Task OnActionExecutionAsync(
        ActionExecutingContext context, ActionExecutionDelegate next)
    {
        // Do something before the action executes.
        await next();
        // Do something after the action executes.
    }
}

En el código anterior, SampleAsyncActionFilter tiene un ActionExecutionDelegate, next, que ejecuta el método de acción.

Varias fases de filtro

Se pueden implementar interfaces para varias fases de filtro en una sola clase. Por ejemplo, la clase ActionFilterAttribute implementa:

Implemente la versión sincrónica o la versión asincrónica de una interfaz de filtro, pero no ambas. El entorno de ejecución comprueba primero si el filtro implementa la interfaz asincrónica y, si es así, llama a la interfaz. De lo contrario, llamará a métodos de interfaz sincrónicos. Si se implementan las interfaces asincrónicas y sincrónicas en una clase, solo se llama al método asincrónico. Cuando se usan clases abstractas como ActionFilterAttribute, se invalidan solo los métodos sincrónicos o los métodos asincrónicos de cada tipo de filtro.

Atributos de filtros integrados

ASP.NET Core incluye filtros integrados basados en atributos que se pueden personalizar y a partir de los cuales crear subclases. Por ejemplo, el siguiente filtro de resultados agrega un encabezado a la respuesta:

public class ResponseHeaderAttribute : ActionFilterAttribute
{
    private readonly string _name;
    private readonly string _value;

    public ResponseHeaderAttribute(string name, string value) =>
        (_name, _value) = (name, value);

    public override void OnResultExecuting(ResultExecutingContext context)
    {
        context.HttpContext.Response.Headers.Add(_name, _value);

        base.OnResultExecuting(context);
    }
}

Los atributos permiten a los filtros aceptar argumentos, como se muestra en el ejemplo anterior. Aplique el ResponseHeaderAttribute a un método de acción o controlador y especifique el nombre y el valor del encabezado HTTP:

[ResponseHeader("Filter-Header", "Filter Value")]
public class ResponseHeaderController : ControllerBase
{
    public IActionResult Index() =>
        Content("Examine the response headers using the F12 developer tools.");

    // ...

Use una herramienta como las herramientas de desarrollo del explorador para examinar los encabezados. En Encabezados de respuesta, se muestra filter-header: Filter Value.

El código siguiente aplica ResponseHeaderAttribute tanto a un controlador como a una acción:

[ResponseHeader("Filter-Header", "Filter Value")]
public class ResponseHeaderController : ControllerBase
{
    public IActionResult Index() =>
        Content("Examine the response headers using the F12 developer tools.");

    // ...

    [ResponseHeader("Another-Filter-Header", "Another Filter Value")]
    public IActionResult Multiple() =>
        Content("Examine the response headers using the F12 developer tools.");
}

Las respuestas de la acción Multiple incluyen los siguientes encabezados:

  • filter-header: Filter Value
  • another-filter-header: Another Filter Value

Algunas de las interfaces de filtro tienen atributos correspondientes que se pueden usar como clases base en las implementaciones personalizadas.

Atributos de filtro:

Los filtros no se pueden aplicar a métodos de controlador de Razor Page. Se pueden usar con el modelo de página de Razor Page o globalmente.

Ámbitos del filtro y orden de ejecución

Un filtro se puede agregar a la canalización en uno de tres ámbitos posibles:

  • Mediante un atributo en un controlador o una página de Razor Page.
  • Mediante un atributo en una acción del controlador. Los atributos de filtro no se pueden usar con métodos de controlador de páginas de Razor Pages.
  • Globalmente para todos los controladores, las acciones y las páginas de Razor Pages tal como se muestra en el siguiente código:
    var builder = WebApplication.CreateBuilder(args);
    
    // Add services to the container.
    builder.Services.AddControllersWithViews(options =>
    {
        options.Filters.Add<GlobalSampleActionFilter>();
    });
    

Orden de ejecución predeterminado

Cuando hay varios filtros en una determinada fase de la canalización, el ámbito determina el orden predeterminado en el que esos filtros se van a ejecutar. Los filtros globales abarcan a los filtros de clase, que a su vez engloban a los filtros de método.

Como resultado de este anidamiento de filtros, el código de filtros posterior se ejecuta en el orden inverso al código anterior. La secuencia de filtro:

  • El código anterior de los filtros globales.
    • El código anterior de los filtros de controlador.
      • El código anterior de los filtros de métodos de acción.
      • El código posterior de los filtros de métodos de acción.
    • El código posterior de los filtros de controlador.
  • El código posterior de los filtros globales.

El siguiente ejemplo ilustra el orden de ejecución de los métodos de filtrado para los filtros de acción sincrónicos:

Secuencia Ámbito del filtro Método de filtro
1 Global OnActionExecuting
2 Controlador OnActionExecuting
3 Acción OnActionExecuting
4 Acción OnActionExecuted
5 Controlador OnActionExecuted
6 Global OnActionExecuted

Filtros de nivel de controlador

Cada controlador que hereda de Controller incluye los métodos OnActionExecuting, OnActionExecutionAsync y OnActionExecuted. Estos métodos encapsulan los filtros que se ejecutan para una acción determinada:

  • OnActionExecuting se ejecuta antes de cualquiera de los filtros de la acción.
  • OnActionExecuted se ejecuta después de todos los filtros de la acción.
  • OnActionExecutionAsync se ejecuta antes de cualquiera de los filtros de la acción. El código después de una llamada a next se ejecuta después de los filtros de la acción.

La siguiente clase ControllerFiltersController:

  • Aplica el SampleActionFilterAttribute ([SampleActionFilter]) al controlador.
  • Invalida OnActionExecuting y OnActionExecuted.
[SampleActionFilter]
public class ControllerFiltersController : Controller
{
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        Console.WriteLine(
            $"- {nameof(ControllerFiltersController)}.{nameof(OnActionExecuting)}");

        base.OnActionExecuting(context);
    }

    public override void OnActionExecuted(ActionExecutedContext context)
    {
        Console.WriteLine(
            $"- {nameof(ControllerFiltersController)}.{nameof(OnActionExecuted)}");

        base.OnActionExecuted(context);
    }

    public IActionResult Index()
    {
        Console.WriteLine(
            $"- {nameof(ControllerFiltersController)}.{nameof(Index)}");

        return Content("Check the Console.");
    }
}

Si se dirige a https://localhost:<port>/ControllerFilters, se ejecuta el código siguiente:

  • ControllerFiltersController.OnActionExecuting
    • GlobalSampleActionFilter.OnActionExecuting
      • SampleActionFilterAttribute.OnActionExecuting
        • ControllerFiltersController.Index
      • SampleActionFilterAttribute.OnActionExecuted
    • GlobalSampleActionFilter.OnActionExecuted
  • ControllerFiltersController.OnActionExecuted

Los filtros de nivel de controlador establecen la propiedad Order en int.MinValue. Los filtros de nivel de controlador no pueden establecerse para ejecutarse tras aplicarse los filtros a los métodos. Order se explica en la sección siguiente.

Para Razor Pages, consulte Implementar filtros de Razor Page invalidando métodos de filtro.

Invalidación del orden predeterminado

La secuencia de ejecución predeterminada se puede invalidar con la implementación de IOrderedFilter. Order expone la propiedad IOrderedFilter que tiene prioridad sobre el ámbito a la hora de determinar el orden de ejecución. Un filtro con un valor Order menor:

  • Ejecuta el código anterior antes que el de un filtro con un valor mayor de Order.
  • Ejecuta el código posterior después que el de un filtro con un valor mayor de Order.

En el ejemplo Filtros de nivel de controlador, GlobalSampleActionFilter tiene ámbito global por lo que se ejecuta antes que SampleActionFilterAttribute, que tiene ámbito de controlador. Para que SampleActionFilterAttribute se ejecute en primer lugar, establezca el orden en int.MinValue:

[SampleActionFilter(Order = int.MinValue)]
public class ControllerFiltersController : Controller
{
    // ...
}

Para que el filtro global GlobalSampleActionFilter se ejecute en primer lugar, establezca su Order en int.MinValue:

builder.Services.AddControllersWithViews(options =>
{
    options.Filters.Add<GlobalSampleActionFilter>(int.MinValue);
});

Cancelación y cortocircuito

La canalización de filtro se puede cortocircuitar en cualquier momento mediante el establecimiento de la propiedad Result en el parámetro ResourceExecutingContext que se ha proporcionado al método de filtro. Por ejemplo, el siguiente filtro de recursos impide que el resto de la canalización se ejecute:

public class ShortCircuitingResourceFilterAttribute : Attribute, IResourceFilter
{
    public void OnResourceExecuting(ResourceExecutingContext context)
    {
        context.Result = new ContentResult
        {
            Content = nameof(ShortCircuitingResourceFilterAttribute)
        };
    }

    public void OnResourceExecuted(ResourceExecutedContext context) { }
}

En el siguiente código, tanto el filtro [ShortCircuitingResourceFilter] como el filtro [ResponseHeader] tienen como destino el método de acción Index. El filtro ShortCircuitingResourceFilterAttribute:

  • Se ejecuta en primer lugar, porque es un filtro de recursos y ResponseHeaderAttribute es un filtro de acciones.
  • Cortocircuita el resto de la canalización.

Por tanto, el filtro ResponseHeaderAttribute nunca se ejecuta en relación con la acción Index. Este comportamiento sería el mismo si ambos filtros se aplicaran en el nivel de método de acción, siempre y cuando ShortCircuitingResourceFilterAttribute se haya ejecutado primero. El ShortCircuitingResourceFilterAttribute se ejecuta primero debido a su tipo de filtro:

[ResponseHeader("Filter-Header", "Filter Value")]
public class ShortCircuitingController : Controller
{
    [ShortCircuitingResourceFilter]
    public IActionResult Index() =>
        Content($"- {nameof(ShortCircuitingController)}.{nameof(Index)}");
}

Inserción de dependencias

Los filtros se pueden agregar por tipo o por instancia. Si se agrega una instancia, esta se utiliza para todas las solicitudes. Si se agrega un tipo, se activa por tipo. Un filtro activado por tipo significa:

Los filtros que se implementan como atributos y se agregan directamente a las clases de controlador o a los métodos de acción no pueden tener dependencias de constructor proporcionadas por la inserción de dependencias. Las dependencias del constructor no se pueden proporcionar mediante la inserción de dependencias porque los atributos deben tener sus parámetros de constructor proporcionados donde se aplican.

Los filtros siguientes admiten dependencias de constructor proporcionadas en la inserción de dependencias:

Los filtros anteriores pueden aplicarse a un controlador o a una acción.

Los registradores están disponibles en la inserción de dependencias. Sin embargo, evite crear y utilizar filtros únicamente con fines de registro. El registro del marco integrado proporciona normalmente lo que se necesita para el registro. Registro agregado a los filtros:

  • Debe centrarse en cuestiones de dominio empresarial o en el comportamiento específico del filtro.
  • No debe registrar acciones u otros eventos del marco. Los filtros integrados ya registran acciones y eventos del marco.

ServiceFilterAttribute

Los tipos de implementación de filtro de servicio se registran en Program.cs. ServiceFilterAttribute recupera una instancia del filtro de la inserción de dependencias.

En el código siguiente se muestra la clase LoggingResponseHeaderFilterService, que usa la inserción de dependencias:

public class LoggingResponseHeaderFilterService : IResultFilter
{
    private readonly ILogger _logger;

    public LoggingResponseHeaderFilterService(
            ILogger<LoggingResponseHeaderFilterService> logger) =>
        _logger = logger;

    public void OnResultExecuting(ResultExecutingContext context)
    {
        _logger.LogInformation(
            $"- {nameof(LoggingResponseHeaderFilterService)}.{nameof(OnResultExecuting)}");

        context.HttpContext.Response.Headers.Add(
            nameof(OnResultExecuting), nameof(LoggingResponseHeaderFilterService));
    }

    public void OnResultExecuted(ResultExecutedContext context)
    {
        _logger.LogInformation(
            $"- {nameof(LoggingResponseHeaderFilterService)}.{nameof(OnResultExecuted)}");
    }
}

En el código siguiente, LoggingResponseHeaderFilterService se agrega al contenedor de inserción de dependencias:

builder.Services.AddScoped<LoggingResponseHeaderFilterService>();

En el código siguiente, el atributo ServiceFilter recupera una instancia del filtro LoggingResponseHeaderFilterService desde la inserción de dependencias:

[ServiceFilter<LoggingResponseHeaderFilterService>]
public IActionResult WithServiceFilter() =>
    Content($"- {nameof(FilterDependenciesController)}.{nameof(WithServiceFilter)}");

Al usar ServiceFilterAttribute, estableciendo ServiceFilterAttribute.IsReusable:

  • Proporciona una sugerencia que la instancia de filtro podría reutilizarse fuera del ámbito de la solicitud en la que se creó. El entorno de ejecución de ASP.NET Core no garantiza:
    • Que se creará una única instancia del filtro.
    • El filtro no volverá a solicitarse desde el contenedor de inserción de dependencias en algún momento posterior.
  • No debería usarse con un filtro que dependa de servicios con una duración distinta a singleton.

ServiceFilterAttribute implementa IFilterFactory. IFilterFactory expone el método CreateInstance para crear una instancia de IFilterMetadata. CreateInstance carga el tipo especificado desde la inserción de dependencias.

TypeFilterAttribute

TypeFilterAttribute es similar a ServiceFilterAttribute, pero su tipo no se resuelve directamente desde el contenedor de inserción de dependencias, sino que crea una instancia del tipo usando el elemento Microsoft.Extensions.DependencyInjection.ObjectFactory.

Dado que los tipos TypeFilterAttribute no se resuelven directamente desde el contenedor de inserción de dependencias:

  • Los tipos a los que se hace referencia con TypeFilterAttribute no tienen que estar registrados con el contenedor de inserción de dependencias. Sus dependencias se completan a través del contenedor de inserción de dependencias.
  • TypeFilterAttribute puede aceptar opcionalmente argumentos de constructor del tipo en cuestión.

Al usar TypeFilterAttribute, estableciendo TypeFilterAttribute.IsReusable:

  • Proporciona una sugerencia que indica que la instancia de filtro podría reutilizarse fuera del ámbito de la solicitud en la que se creó. El entorno de ejecución de ASP.NET Core no ofrece ninguna garantía de que se vaya a crear una única instancia del filtro.

  • No debe usarse con un filtro que depende de servicios con una duración distinta de singleton.

En el siguiente ejemplo se muestra cómo pasar argumentos a un tipo mediante TypeFilterAttribute:

[TypeFilter(typeof(LoggingResponseHeaderFilter),
    Arguments = new object[] { "Filter-Header", "Filter Value" })]
public IActionResult WithTypeFilter() =>
    Content($"- {nameof(FilterDependenciesController)}.{nameof(WithTypeFilter)}");

Filtros de autorización

Filtros de autorización:

  • Son los primeros filtros que se ejecutan en la canalización del filtro.
  • Controlan el acceso a los métodos de acción.
  • Tienen un método anterior, pero no uno posterior.

Los filtros de autorización personalizados requieren un marco de autorización personalizado. Es preferible configurar directivas de autorización o escribir una directiva de autorización personalizada a escribir un filtro personalizado. El filtro de autorización integrado:

  • Llama a la autorización del sistema.
  • No autoriza las solicitudes.

No inicie excepciones dentro de los filtros de autorización:

  • La excepción no se controlará.
  • Los filtros de excepciones no controlarán la excepción.

Considere la posibilidad de emitir un desafío cuando se produzca una excepción en un filtro de autorizaciones.

Aquí encontrará más información sobre la autorización.

Filtros de recursos

Filtros de recursos:

Los filtros de recursos son útiles para cortocircuitar la mayor parte de la canalización. Por ejemplo, un filtro de almacenamiento en caché puede evitar que se ejecute el resto de la canalización en un acierto de caché.

Ejemplos de filtros de recursos:

Filtros de acciones

Los filtros de acción no se aplican a Razor Pages. Razor Pages admite IPageFilter y IAsyncPageFilter. Para obtener más información, vea Métodos de filtrado de Razor Pages.

Filtros de acciones:

El código siguiente muestra un ejemplo de filtro de acciones:

public class SampleActionFilter : IActionFilter
{
    public void OnActionExecuting(ActionExecutingContext context)
    {
        // Do something before the action executes.
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
        // Do something after the action executes.
    }
}

ActionExecutingContext ofrece las siguientes propiedades:

  • ActionArguments: permite leer las entradas de un método de acción.
  • Controller: permite manipular la instancia del controlador.
  • Result: si se establece Result, se cortocircuita la ejecución del método de acción y de los filtros de acciones posteriores.

Inicio de una excepción en un método de acción:

  • Impide la ejecución de los filtros subsiguientes.
  • A diferencia del establecimiento de Result, se trata como un error en lugar de como un resultado correcto.

ActionExecutedContext proporciona Controller y Result, además de las siguientes propiedades:

  • Canceled: es true si otro filtro ha cortocircuitado la ejecución de la acción.
  • Exception: es un valor distinto de NULL si la acción o un filtro de acción de ejecución anterior han producido una excepción. Si se establece esta propiedad en un valor NULL:
    • Controla la excepción eficazmente.
    • Result se ejecuta como si se devolviera desde el método de acción.

En un IAsyncActionFilter, una llamada a ActionExecutionDelegate:

  • Ejecuta cualquier filtro de acciones posterior y el método de acción.
  • Devuelve ActionExecutedContext.

Para cortocircuitar esto, asigne Microsoft.AspNetCore.Mvc.Filters.ActionExecutingContext.Result a una instancia de resultado y no llame a next (la clase ActionExecutionDelegate).

El marco proporciona una clase ActionFilterAttribute abstracta de la que se pueden crear subclases.

Se puede usar el filtro de acción OnActionExecuting para:

  • Validar el estado del modelo.
  • Devolver un error si el estado no es válido.
public class ValidateModelAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        if (!context.ModelState.IsValid)
        {
            context.Result = new BadRequestObjectResult(context.ModelState);
        }
    }
}

Nota:

Los controladores anotados con el atributo [ApiController] validan automáticamente el estado del modelo y devuelven una respuesta 400. Para obtener más información, consulte Respuestas HTTP 400 automáticas.

El método OnActionExecuted se ejecuta después del método de acción:

  • Y puede ver y manipular los resultados de la acción a través de la propiedad Result.
  • Canceled se establece en true si otro filtro ha cortocircuitado la ejecución de la acción.
  • Exception se establece en un valor distinto de NULL si la acción o un filtro de acción posterior han producido una excepción. Si Exception se establece como nulo:
    • Controla una excepción eficazmente.
    • ActionExecutedContext.Result se ejecuta como si se devolviera con normalidad desde el método de acción.

Filtros de excepciones

Los filtros de excepciones:

En el siguiente filtro de excepciones de ejemplo se muestran los detalles sobre las excepciones que se producen cuando la aplicación está en fase de desarrollo:

public class SampleExceptionFilter : IExceptionFilter
{
    private readonly IHostEnvironment _hostEnvironment;

    public SampleExceptionFilter(IHostEnvironment hostEnvironment) =>
        _hostEnvironment = hostEnvironment;

    public void OnException(ExceptionContext context)
    {
        if (!_hostEnvironment.IsDevelopment())
        {
            // Don't display exception details unless running in Development.
            return;
        }

        context.Result = new ContentResult
        {
            Content = context.Exception.ToString()
        };
    }
}

El código siguiente prueba el filtro de excepciones:

[TypeFilter<SampleExceptionFilter>]
public class ExceptionController : Controller
{
    public IActionResult Index() =>
        Content($"- {nameof(ExceptionController)}.{nameof(Index)}");
}

Los filtros de excepciones:

  • No tienen eventos anteriores ni posteriores.
  • Implementan OnException o OnExceptionAsync.
  • Controlan las excepciones sin controlar que se producen en Razor Pages, al crear controladores, en el enlace de modelos, en los filtros de acciones o en los métodos de acción.
  • No detectan aquellas excepciones que se produzcan en los filtros de recursos, en los filtros de resultados o en la ejecución de resultados de MVC.

Para controlar una excepción, establezca la propiedad ExceptionHandled en true o asigne la propiedad Result. Esto detiene la propagación de la excepción. Un filtro de excepciones no tiene capacidad para convertir una excepción en un proceso "correcto". Eso solo lo pueden hacer los filtros de acciones.

Los filtros de excepciones:

  • Son adecuados para interceptar las excepciones que se producen en las acciones.
  • No son tan flexibles como el middleware de control de errores.

Es preferible usar middleware de control de excepciones. Utilice los filtros de excepciones solo cuando el control de errores es diferente en función del método de acción que se llama. Por ejemplo, puede que una aplicación tenga métodos de acción tanto para los puntos de conexión de API como para las vistas/HTML. Los puntos de conexión de API podrían devolver información sobre errores como JSON, mientras que las acciones basadas en vistas podrían devolver una página de error como HTML.

Filtros de resultados

Filtros de resultados:

IResultFilter e IAsyncResultFilter

El código siguiente muestra un ejemplo de filtro de resultados:

public class SampleResultFilter : IResultFilter
{
    public void OnResultExecuting(ResultExecutingContext context)
    {
        // Do something before the result executes.
    }

    public void OnResultExecuted(ResultExecutedContext context)
    {
        // Do something after the result executes.
    }
}

El tipo de resultado que se ejecute dependerá de la acción. Una acción que devuelve una vista incluye todo el procesamiento de Razor como parte del elemento ViewResult que se está ejecutando. Un método API puede llevar a cabo algunas funciones de serialización como parte de la ejecución del resultado. Aquí encontrará más información sobre los resultados de acciones.

Los filtros de resultados solo se ejecutan cuando una acción o un filtro de acción genera un resultado de acción. Los filtros de resultados no se ejecutan cuando:

  • Un filtro de autorización o un filtro de recursos genera un cortocircuito en la canalización.
  • Un filtro de excepciones controla una excepción al producir un resultado de acción.

El método Microsoft.AspNetCore.Mvc.Filters.IResultFilter.OnResultExecuting puede cortocircuitar la ejecución del resultado de la acción y de los filtros de resultados posteriores mediante el establecimiento de Microsoft.AspNetCore.Mvc.Filters.ResultExecutingContext.Cancel en true. Escriba en el objeto de respuesta cuando el proceso se cortocircuite, ya que así evitará que se genere una respuesta vacía. Generación de una excepción en IResultFilter.OnResultExecuting:

  • Impide la ejecución del resultado de la acción y de los filtros subsiguientes.
  • Se trata como un error en lugar de como un resultado correcto.

Cuando se ejecuta el método Microsoft.AspNetCore.Mvc.Filters.IResultFilter.OnResultExecuted, es probable que la respuesta ya se haya enviado al cliente. En este caso, no se puede cambiar.

ResultExecutedContext.Canceled se establece en true si otro filtro ha cortocircuitado la ejecución del resultado de la acción.

ResultExecutedContext.Exception se establece en un valor distinto de NULL si el resultado de la acción o un filtro de resultado posterior ha producido una excepción. Establecer Exception en un valor null hace que una excepción se controle de forma eficaz y evita que se vuelva a producir dicha excepción más adelante en la canalización. No hay una forma confiable de escribir datos en una respuesta cuando se controla una excepción en un filtro de resultados. Si los encabezados ya se han vaciado en el cliente si el resultado de una acción inicia una excepción, no hay ningún mecanismo confiable que permita enviar un código de error.

En un elemento IAsyncResultFilter, una llamada a await next en ResultExecutionDelegate ejecuta cualquier filtro de resultados posterior y el resultado de la acción. Para cortocircuitar esto, establezca ResultExecutingContext.Cancel en true y no llame a ResultExecutionDelegate:

public class SampleAsyncResultFilter : IAsyncResultFilter
{
    public async Task OnResultExecutionAsync(
        ResultExecutingContext context, ResultExecutionDelegate next)
    {
        if (context.Result is not EmptyResult)
        {
            await next();
        }
        else
        {
            context.Cancel = true;
        }
    }
}

El marco proporciona una clase ResultFilterAttribute abstracta de la que se pueden crear subclases. La clase ResponseHeaderAttribute mostrada anteriormente es un ejemplo de un atributo de filtro de resultados.

IAlwaysRunResultFilter e IAsyncAlwaysRunResultFilter

Las interfaces IAlwaysRunResultFilter e IAsyncAlwaysRunResultFilter declaran una implementación IResultFilter que se ejecuta para obtener todos los resultados de la acción. Esto incluye los resultados de la acción generados por:

  • Filtros de autorización y filtros de recursos que generan un cortocircuito.
  • Filtros de excepción.

Por ejemplo, el siguiente filtro siempre ejecuta y establece un resultado de la acción (ObjectResult) con un código de estado 422 - Entidad no procesable cuando se produce un error en la negociación de contenido:

public class UnprocessableResultFilter : IAlwaysRunResultFilter
{
    public void OnResultExecuting(ResultExecutingContext context)
    {
        if (context.Result is StatusCodeResult statusCodeResult
            && statusCodeResult.StatusCode == StatusCodes.Status415UnsupportedMediaType)
        {
            context.Result = new ObjectResult("Unprocessable")
            {
                StatusCode = StatusCodes.Status422UnprocessableEntity
            };
        }
    }

    public void OnResultExecuted(ResultExecutedContext context) { }
}

IFilterFactory

IFilterFactory implementa IFilterMetadata. Por tanto, una instancia de IFilterFactory se puede usar como una instancia de IFilterMetadata en cualquier parte de la canalización de filtro. Cuando el entorno de ejecución se prepara para invocar el filtro, intenta convertirlo a un IFilterFactory. Si esa conversión se realiza correctamente, se llama al método CreateInstance para crear la instancia IFilterMetadata que se va a invocar. Esto proporciona un diseño flexible, dado que no hay que establecer la canalización de filtro exacta de forma explícita cuando la aplicación se inicia.

IFilterFactory.IsReusable:

  • Es una indicación por parte de la fábrica de que la instancia de filtro creada por la fábrica puede ser reutilizada fuera del ámbito de solicitud en el que fue creada.
  • No debería usarse con un filtro que dependa de servicios con una duración distinta de singleton.

El entorno de ejecución de ASP.NET Core no garantiza:

  • Que se creará una única instancia del filtro.
  • El filtro no volverá a solicitarse desde el contenedor de inserción de dependencias en algún momento posterior.

Advertencia

Solo configure IFilterFactory.IsReusable para que devuelva true si el origen de los filtros no es ambiguo, los filtros no tienen estado y los filtros son seguros de usar en múltiples peticiones HTTP. Por ejemplo, no devuelva filtros de DI que estén registrados como de ámbito o transitorios si IFilterFactory.IsReusable devuelve true.

Puede implementar IFilterFactory con las implementaciones de atributos personalizados como método alternativo para crear filtros:

public class ResponseHeaderFilterFactory : Attribute, IFilterFactory
{
    public bool IsReusable => false;

    public IFilterMetadata CreateInstance(IServiceProvider serviceProvider) =>
        new InternalResponseHeaderFilter();

    private class InternalResponseHeaderFilter : IActionFilter
    {
        public void OnActionExecuting(ActionExecutingContext context) =>
            context.HttpContext.Response.Headers.Add(
                nameof(OnActionExecuting), nameof(InternalResponseHeaderFilter));

        public void OnActionExecuted(ActionExecutedContext context) { }
    }

El filtro se aplica en el código siguiente:

[ResponseHeaderFilterFactory]
public IActionResult Index() =>
    Content($"- {nameof(FilterFactoryController)}.{nameof(Index)}");

IFilterFactory implementado en un atributo

Los filtros que implementan IFilterFactory son útiles para los filtros que:

  • No requieren pasar parámetros.
  • Tienen dependencias de constructor que deben completarse por medio de la inserción de dependencias.

TypeFilterAttribute implementa IFilterFactory. IFilterFactory expone el método CreateInstance para crear una instancia de IFilterMetadata. CreateInstance carga el tipo especificado desde el contenedor de servicios (inserción de dependencias).

public class SampleActionTypeFilterAttribute : TypeFilterAttribute
{
    public SampleActionTypeFilterAttribute()
         : base(typeof(InternalSampleActionFilter)) { }

    private class InternalSampleActionFilter : IActionFilter
    {
        private readonly ILogger<InternalSampleActionFilter> _logger;

        public InternalSampleActionFilter(ILogger<InternalSampleActionFilter> logger) =>
            _logger = logger;

        public void OnActionExecuting(ActionExecutingContext context)
        {
            _logger.LogInformation(
                $"- {nameof(InternalSampleActionFilter)}.{nameof(OnActionExecuting)}");
        }

        public void OnActionExecuted(ActionExecutedContext context)
        {
            _logger.LogInformation(
                $"- {nameof(InternalSampleActionFilter)}.{nameof(OnActionExecuted)}");
        }
    }
}

El siguiente código muestra tres métodos para aplicar el filtro:

[SampleActionTypeFilter]
public IActionResult WithDirectAttribute() =>
    Content($"- {nameof(FilterFactoryController)}.{nameof(WithDirectAttribute)}");

[TypeFilter<SampleActionTypeFilterAttribute>]
public IActionResult WithTypeFilterAttribute() =>
    Content($"- {nameof(FilterFactoryController)}.{nameof(WithTypeFilterAttribute)}");

[ServiceFilter<SampleActionTypeFilterAttribute>]
public IActionResult WithServiceFilterAttribute() =>
    Content($"- {nameof(FilterFactoryController)}.{nameof(WithServiceFilterAttribute)}");

En el código anterior, se prefiere el primer método para aplicar el filtro.

Uso de middleware en la canalización de filtro

Los filtros de recursos funcionan como el middleware, en el sentido de que se encargan de la ejecución de todo lo que viene después en la canalización. Pero los filtros se diferencian del middleware en que forman parte del runtime, lo que significa que tienen acceso al contexto y las construcciones.

Para usar middleware como un filtro, cree un tipo con un método Configure en el que se especifique el middleware para insertar en la canalización de filtro. En el ejemplo siguiente se usa middleware para establecer un encabezado de respuesta:

public class FilterMiddlewarePipeline
{
    public void Configure(IApplicationBuilder app)
    {
        app.Use(async (context, next) =>
        {
            context.Response.Headers.Add("Pipeline", "Middleware");

            await next();
        });
    }
}

Use MiddlewareFilterAttribute para ejecutar el middleware:

[MiddlewareFilter<FilterMiddlewarePipeline>]
public class FilterMiddlewareController : Controller
{
    public IActionResult Index() =>
        Content($"- {nameof(FilterMiddlewareController)}.{nameof(Index)}");
}

Los filtros de middleware se ejecutan en la misma fase de la canalización de filtro que los filtros de recursos, antes del enlace de modelos y después del resto de la canalización.

Seguridad para subprocesos

Al pasar una instancia de un filtro a Add, en lugar de a su Type, el filtro es un singleton y no es seguro para subprocesos.

Recursos adicionales

Por Kirk Larkin, Rick Anderson, Tom Dykstra y Steve Smith

Los filtros en ASP.NET Core permiten que se ejecute el código antes o después de determinadas fases de la canalización del procesamiento de la solicitud.

Los filtros integrados se encargan de tareas como las siguientes:

  • Autorización, que impide el acceso a recursos para los que un usuario no está autorizado.
  • Almacenamiento en caché de la respuesta, que permite cortocircuitar la canalización de la solicitud para devolver una respuesta almacenada en caché.

Se pueden crear filtros personalizados que se encarguen de cuestiones transversales. Entre los ejemplos de cuestiones transversales se incluyen el control de errores, el almacenamiento en caché, la configuración, la autorización y el registro. Los filtros evitan la duplicación de código. Así, por ejemplo, un filtro de excepción de control de errores puede consolidar el control de errores.

Este documento se aplica a Razor Pages, a los controladores de API y a los controladores con vistas. Los filtros no funcionan directamente con componentes de Razor. Un filtro solo puede afectar indirectamente a un componente cuando:

  • El componente está insertado en una página o vista.
  • La página o controlador y la vista usan el filtro.

Funcionamiento de los filtros

Los filtros se ejecutan dentro de la canalización de invocación de acciones de ASP.NET Core, a veces denominada canalización de filtro. La canalización de filtro se ejecuta después de que ASP.NET Core seleccione la acción que se va a ejecutar:

The request is processed through Other Middleware, Routing Middleware, Action Selection, and the Action Invocation Pipeline. The request processing continues back through Action Selection, Routing Middleware, and various Other Middleware before becoming a response sent to the client.

Tipos de filtro

Cada tipo de filtro se ejecuta en una fase diferente dentro de la canalización de filtro:

  • Filtros de autorización:

    • Se ejecutan primero.
    • Determinan si el usuario está autorizado para la solicitud.
    • Cortocircuitan la canalización si una solicitud no está autorizada.
  • Filtros de recursos:

    • Se ejecutan después de la autorización.
    • OnResourceExecuting ejecuta código antes que el resto de la canalización del filtro. Por ejemplo, OnResourceExecuting ejecuta código antes que el enlace de modelos.
    • OnResourceExecuted ejecuta el código una vez que el resto de la canalización se haya completado.
  • Los filtros de acciones:

    • Se ejecutan inmediatamente antes y después de llamar a un método de acción.
    • Pueden cambiar los argumentos pasados a una acción.
    • Pueden cambiar el resultado devuelto de la acción.
    • No se admiten en Razor Pages.
  • Filtros de punto de conexión:

    • Se ejecutan inmediatamente antes y después de llamar a un método de acción.
    • Pueden cambiar los argumentos pasados a una acción.
    • Pueden cambiar el resultado devuelto de la acción.
    • No se admiten en Razor Pages.
    • Se pueden invocar tanto en acciones como en puntos de conexión basados en controladores de ruta.
  • Los filtros de excepciones aplican directivas globales a las excepciones no controladas que se producen antes de que se escriba el cuerpo de respuesta.

  • Filtros de resultados:

    • Se ejecutan inmediatamente antes y después de la ejecución de resultados de acción.
    • Solo se ejecutan cuando el método de acción se ejecuta correctamente.
    • Son útiles para la lógica que debe regir la ejecución de la vista o el formateador.

En el siguiente diagrama se muestra cómo interactúan los tipos de filtro en la canalización de filtro:

The request is processed through Authorization Filters, Resource Filters, Model Binding, Action Filters, Action Execution and Action Result Conversion, Exception Filters, Result Filters, and Result Execution. On the way out, the request is only processed by Result Filters and Resource Filters before becoming a response sent to the client.

Razor Pages también admite filtros de Razor Page, que se ejecutan antes y después de un controlador de Razor Page.

Implementación

Los filtros admiten implementaciones tanto sincrónicas como asincrónicas a través de diferentes definiciones de interfaz.

Los filtros sincrónicos se ejecutan antes y después de la fase de canalización. Por ejemplo, OnActionExecuting se llama antes de llamar al método de acción. OnActionExecuted se llama después de devolver el método de acción:

public class SampleActionFilter : IActionFilter
{
    public void OnActionExecuting(ActionExecutingContext context)
    {
        // Do something before the action executes.
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
        // Do something after the action executes.
    }
}

Los filtros asincrónicos definen un método On-Stage-ExecutionAsync. Por ejemplo, OnActionExecutionAsync:

public class SampleAsyncActionFilter : IAsyncActionFilter
{
    public async Task OnActionExecutionAsync(
        ActionExecutingContext context, ActionExecutionDelegate next)
    {
        // Do something before the action executes.
        await next();
        // Do something after the action executes.
    }
}

En el código anterior, SampleAsyncActionFilter tiene un ActionExecutionDelegate, next, que ejecuta el método de acción.

Varias fases de filtro

Se pueden implementar interfaces para varias fases de filtro en una sola clase. Por ejemplo, la clase ActionFilterAttribute implementa:

Implemente la versión sincrónica o la versión asincrónica de una interfaz de filtro, pero no ambas. El entorno de ejecución comprueba primero si el filtro implementa la interfaz asincrónica y, si es así, llama a la interfaz. De lo contrario, llamará a métodos de interfaz sincrónicos. Si se implementan las interfaces asincrónicas y sincrónicas en una clase, solo se llama al método asincrónico. Cuando se usan clases abstractas como ActionFilterAttribute, se invalidan solo los métodos sincrónicos o los métodos asincrónicos de cada tipo de filtro.

Atributos de filtros integrados

ASP.NET Core incluye filtros integrados basados en atributos que se pueden personalizar y a partir de los cuales crear subclases. Por ejemplo, el siguiente filtro de resultados agrega un encabezado a la respuesta:

public class ResponseHeaderAttribute : ActionFilterAttribute
{
    private readonly string _name;
    private readonly string _value;

    public ResponseHeaderAttribute(string name, string value) =>
        (_name, _value) = (name, value);

    public override void OnResultExecuting(ResultExecutingContext context)
    {
        context.HttpContext.Response.Headers.Add(_name, _value);

        base.OnResultExecuting(context);
    }
}

Los atributos permiten a los filtros aceptar argumentos, como se muestra en el ejemplo anterior. Aplique el ResponseHeaderAttribute a un método de acción o controlador y especifique el nombre y el valor del encabezado HTTP:

[ResponseHeader("Filter-Header", "Filter Value")]
public class ResponseHeaderController : ControllerBase
{
    public IActionResult Index() =>
        Content("Examine the response headers using the F12 developer tools.");

    // ...

Use una herramienta como las herramientas de desarrollo del explorador para examinar los encabezados. En Encabezados de respuesta, se muestra filter-header: Filter Value.

El código siguiente aplica ResponseHeaderAttribute tanto a un controlador como a una acción:

[ResponseHeader("Filter-Header", "Filter Value")]
public class ResponseHeaderController : ControllerBase
{
    public IActionResult Index() =>
        Content("Examine the response headers using the F12 developer tools.");

    // ...

    [ResponseHeader("Another-Filter-Header", "Another Filter Value")]
    public IActionResult Multiple() =>
        Content("Examine the response headers using the F12 developer tools.");
}

Las respuestas de la acción Multiple incluyen los siguientes encabezados:

  • filter-header: Filter Value
  • another-filter-header: Another Filter Value

Algunas de las interfaces de filtro tienen atributos correspondientes que se pueden usar como clases base en las implementaciones personalizadas.

Atributos de filtro:

Los filtros no se pueden aplicar a métodos de controlador de Razor Page. Se pueden usar con el modelo de página de Razor Page o globalmente.

Ámbitos del filtro y orden de ejecución

Un filtro se puede agregar a la canalización en uno de tres ámbitos posibles:

  • Mediante un atributo en un controlador o una página de Razor Page.
  • Mediante un atributo en una acción del controlador. Los atributos de filtro no se pueden usar con métodos de controlador de páginas de Razor Pages.
  • Globalmente para todos los controladores, las acciones y las páginas de Razor Pages tal como se muestra en el siguiente código:
    var builder = WebApplication.CreateBuilder(args);
    
    // Add services to the container.
    builder.Services.AddControllersWithViews(options =>
    {
        options.Filters.Add<GlobalSampleActionFilter>();
    });
    

Orden de ejecución predeterminado

Cuando hay varios filtros en una determinada fase de la canalización, el ámbito determina el orden predeterminado en el que esos filtros se van a ejecutar. Los filtros globales abarcan a los filtros de clase, que a su vez engloban a los filtros de método.

Como resultado de este anidamiento de filtros, el código de filtros posterior se ejecuta en el orden inverso al código anterior. La secuencia de filtro:

  • El código anterior de los filtros globales.
    • El código anterior de los filtros de controlador.
      • El código anterior de los filtros de métodos de acción.
      • El código posterior de los filtros de métodos de acción.
    • El código posterior de los filtros de controlador.
  • El código posterior de los filtros globales.

El siguiente ejemplo ilustra el orden de ejecución de los métodos de filtrado para los filtros de acción sincrónicos:

Secuencia Ámbito del filtro Método de filtro
1 Global OnActionExecuting
2 Controlador OnActionExecuting
3 Acción OnActionExecuting
4 Acción OnActionExecuted
5 Controlador OnActionExecuted
6 Global OnActionExecuted

Filtros de nivel de controlador

Cada controlador que hereda de Controller incluye los métodos OnActionExecuting, OnActionExecutionAsync y OnActionExecuted. Estos métodos encapsulan los filtros que se ejecutan para una acción determinada:

  • OnActionExecuting se ejecuta antes de cualquiera de los filtros de la acción.
  • OnActionExecuted se ejecuta después de todos los filtros de la acción.
  • OnActionExecutionAsync se ejecuta antes de cualquiera de los filtros de la acción. El código después de una llamada a next se ejecuta después de los filtros de la acción.

La siguiente clase ControllerFiltersController:

  • Aplica el SampleActionFilterAttribute ([SampleActionFilter]) al controlador.
  • Invalida OnActionExecuting y OnActionExecuted.
[SampleActionFilter]
public class ControllerFiltersController : Controller
{
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        Console.WriteLine(
            $"- {nameof(ControllerFiltersController)}.{nameof(OnActionExecuting)}");

        base.OnActionExecuting(context);
    }

    public override void OnActionExecuted(ActionExecutedContext context)
    {
        Console.WriteLine(
            $"- {nameof(ControllerFiltersController)}.{nameof(OnActionExecuted)}");

        base.OnActionExecuted(context);
    }

    public IActionResult Index()
    {
        Console.WriteLine(
            $"- {nameof(ControllerFiltersController)}.{nameof(Index)}");

        return Content("Check the Console.");
    }
}

Si se dirige a https://localhost:<port>/ControllerFilters, se ejecuta el código siguiente:

  • ControllerFiltersController.OnActionExecuting
    • GlobalSampleActionFilter.OnActionExecuting
      • SampleActionFilterAttribute.OnActionExecuting
        • ControllerFiltersController.Index
      • SampleActionFilterAttribute.OnActionExecuted
    • GlobalSampleActionFilter.OnActionExecuted
  • ControllerFiltersController.OnActionExecuted

Los filtros de nivel de controlador establecen la propiedad Order en int.MinValue. Los filtros de nivel de controlador no pueden establecerse para ejecutarse tras aplicarse los filtros a los métodos. Order se explica en la sección siguiente.

Para Razor Pages, consulte Implementar filtros de Razor Page invalidando métodos de filtro.

Invalidación del orden predeterminado

La secuencia de ejecución predeterminada se puede invalidar con la implementación de IOrderedFilter. Order expone la propiedad IOrderedFilter que tiene prioridad sobre el ámbito a la hora de determinar el orden de ejecución. Un filtro con un valor Order menor:

  • Ejecuta el código anterior antes que el de un filtro con un valor mayor de Order.
  • Ejecuta el código posterior después que el de un filtro con un valor mayor de Order.

En el ejemplo Filtros de nivel de controlador, GlobalSampleActionFilter tiene ámbito global por lo que se ejecuta antes que SampleActionFilterAttribute, que tiene ámbito de controlador. Para que SampleActionFilterAttribute se ejecute en primer lugar, establezca el orden en int.MinValue:

[SampleActionFilter(Order = int.MinValue)]
public class ControllerFiltersController : Controller
{
    // ...
}

Para que el filtro global GlobalSampleActionFilter se ejecute en primer lugar, establezca su Order en int.MinValue:

builder.Services.AddControllersWithViews(options =>
{
    options.Filters.Add<GlobalSampleActionFilter>(int.MinValue);
});

Cancelación y cortocircuito

La canalización de filtro se puede cortocircuitar en cualquier momento mediante el establecimiento de la propiedad Result en el parámetro ResourceExecutingContext que se ha proporcionado al método de filtro. Por ejemplo, el siguiente filtro de recursos impide que el resto de la canalización se ejecute:

public class ShortCircuitingResourceFilterAttribute : Attribute, IResourceFilter
{
    public void OnResourceExecuting(ResourceExecutingContext context)
    {
        context.Result = new ContentResult
        {
            Content = nameof(ShortCircuitingResourceFilterAttribute)
        };
    }

    public void OnResourceExecuted(ResourceExecutedContext context) { }
}

En el siguiente código, tanto el filtro [ShortCircuitingResourceFilter] como el filtro [ResponseHeader] tienen como destino el método de acción Index. El filtro ShortCircuitingResourceFilterAttribute:

  • Se ejecuta en primer lugar, porque es un filtro de recursos y ResponseHeaderAttribute es un filtro de acciones.
  • Cortocircuita el resto de la canalización.

Por tanto, el filtro ResponseHeaderAttribute nunca se ejecuta en relación con la acción Index. Este comportamiento sería el mismo si ambos filtros se aplicaran en el nivel de método de acción, siempre y cuando ShortCircuitingResourceFilterAttribute se haya ejecutado primero. El ShortCircuitingResourceFilterAttribute se ejecuta primero debido a su tipo de filtro:

[ResponseHeader("Filter-Header", "Filter Value")]
public class ShortCircuitingController : Controller
{
    [ShortCircuitingResourceFilter]
    public IActionResult Index() =>
        Content($"- {nameof(ShortCircuitingController)}.{nameof(Index)}");
}

Inserción de dependencias

Los filtros se pueden agregar por tipo o por instancia. Si se agrega una instancia, esta se utiliza para todas las solicitudes. Si se agrega un tipo, se activa por tipo. Un filtro activado por tipo significa:

Los filtros que se implementan como atributos y se agregan directamente a las clases de controlador o a los métodos de acción no pueden tener dependencias de constructor proporcionadas por la inserción de dependencias. Las dependencias del constructor no se pueden proporcionar mediante la inserción de dependencias porque los atributos deben tener sus parámetros de constructor proporcionados donde se aplican.

Los filtros siguientes admiten dependencias de constructor proporcionadas en la inserción de dependencias:

Los filtros anteriores pueden aplicarse a un controlador o a una acción.

Los registradores están disponibles en la inserción de dependencias. Sin embargo, evite crear y utilizar filtros únicamente con fines de registro. El registro del marco integrado proporciona normalmente lo que se necesita para el registro. Registro agregado a los filtros:

  • Debe centrarse en cuestiones de dominio empresarial o en el comportamiento específico del filtro.
  • No debe registrar acciones u otros eventos del marco. Los filtros integrados ya registran acciones y eventos del marco.

ServiceFilterAttribute

Los tipos de implementación de filtro de servicio se registran en Program.cs. ServiceFilterAttribute recupera una instancia del filtro de la inserción de dependencias.

En el código siguiente se muestra la clase LoggingResponseHeaderFilterService, que usa la inserción de dependencias:

public class LoggingResponseHeaderFilterService : IResultFilter
{
    private readonly ILogger _logger;

    public LoggingResponseHeaderFilterService(
            ILogger<LoggingResponseHeaderFilterService> logger) =>
        _logger = logger;

    public void OnResultExecuting(ResultExecutingContext context)
    {
        _logger.LogInformation(
            $"- {nameof(LoggingResponseHeaderFilterService)}.{nameof(OnResultExecuting)}");

        context.HttpContext.Response.Headers.Add(
            nameof(OnResultExecuting), nameof(LoggingResponseHeaderFilterService));
    }

    public void OnResultExecuted(ResultExecutedContext context)
    {
        _logger.LogInformation(
            $"- {nameof(LoggingResponseHeaderFilterService)}.{nameof(OnResultExecuted)}");
    }
}

En el código siguiente, LoggingResponseHeaderFilterService se agrega al contenedor de inserción de dependencias:

builder.Services.AddScoped<LoggingResponseHeaderFilterService>();

En el código siguiente, el atributo ServiceFilter recupera una instancia del filtro LoggingResponseHeaderFilterService desde la inserción de dependencias:

[ServiceFilter(typeof(LoggingResponseHeaderFilterService))]
public IActionResult WithServiceFilter() =>
    Content($"- {nameof(FilterDependenciesController)}.{nameof(WithServiceFilter)}");

Al usar ServiceFilterAttribute, estableciendo ServiceFilterAttribute.IsReusable:

  • Proporciona una sugerencia que la instancia de filtro podría reutilizarse fuera del ámbito de la solicitud en la que se creó. El entorno de ejecución de ASP.NET Core no garantiza:
    • Que se creará una única instancia del filtro.
    • El filtro no volverá a solicitarse desde el contenedor de inserción de dependencias en algún momento posterior.
  • No debería usarse con un filtro que dependa de servicios con una duración distinta a singleton.

ServiceFilterAttribute implementa IFilterFactory. IFilterFactory expone el método CreateInstance para crear una instancia de IFilterMetadata. CreateInstance carga el tipo especificado desde la inserción de dependencias.

TypeFilterAttribute

TypeFilterAttribute es similar a ServiceFilterAttribute, pero su tipo no se resuelve directamente desde el contenedor de inserción de dependencias, sino que crea una instancia del tipo usando el elemento Microsoft.Extensions.DependencyInjection.ObjectFactory.

Dado que los tipos TypeFilterAttribute no se resuelven directamente desde el contenedor de inserción de dependencias:

  • Los tipos a los que se hace referencia con TypeFilterAttribute no tienen que estar registrados con el contenedor de inserción de dependencias. Sus dependencias se completan a través del contenedor de inserción de dependencias.
  • TypeFilterAttribute puede aceptar opcionalmente argumentos de constructor del tipo en cuestión.

Al usar TypeFilterAttribute, estableciendo TypeFilterAttribute.IsReusable:

  • Proporciona una sugerencia que indica que la instancia de filtro podría reutilizarse fuera del ámbito de la solicitud en la que se creó. El entorno de ejecución de ASP.NET Core no ofrece ninguna garantía de que se vaya a crear una única instancia del filtro.

  • No debe usarse con un filtro que depende de servicios con una duración distinta de singleton.

En el siguiente ejemplo se muestra cómo pasar argumentos a un tipo mediante TypeFilterAttribute:

[TypeFilter(typeof(LoggingResponseHeaderFilter),
    Arguments = new object[] { "Filter-Header", "Filter Value" })]
public IActionResult WithTypeFilter() =>
    Content($"- {nameof(FilterDependenciesController)}.{nameof(WithTypeFilter)}");

Filtros de autorización

Filtros de autorización:

  • Son los primeros filtros que se ejecutan en la canalización del filtro.
  • Controlan el acceso a los métodos de acción.
  • Tienen un método anterior, pero no uno posterior.

Los filtros de autorización personalizados requieren un marco de autorización personalizado. Es preferible configurar directivas de autorización o escribir una directiva de autorización personalizada a escribir un filtro personalizado. El filtro de autorización integrado:

  • Llama a la autorización del sistema.
  • No autoriza las solicitudes.

No inicie excepciones dentro de los filtros de autorización:

  • La excepción no se controlará.
  • Los filtros de excepciones no controlarán la excepción.

Considere la posibilidad de emitir un desafío cuando se produzca una excepción en un filtro de autorizaciones.

Aquí encontrará más información sobre la autorización.

Filtros de recursos

Filtros de recursos:

Los filtros de recursos son útiles para cortocircuitar la mayor parte de la canalización. Por ejemplo, un filtro de almacenamiento en caché puede evitar que se ejecute el resto de la canalización en un acierto de caché.

Ejemplos de filtros de recursos:

Filtros de acciones

Los filtros de acción no se aplican a Razor Pages. Razor Pages admite IPageFilter y IAsyncPageFilter. Para obtener más información, vea Métodos de filtrado de Razor Pages.

Filtros de acciones:

El código siguiente muestra un ejemplo de filtro de acciones:

public class SampleActionFilter : IActionFilter
{
    public void OnActionExecuting(ActionExecutingContext context)
    {
        // Do something before the action executes.
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
        // Do something after the action executes.
    }
}

ActionExecutingContext ofrece las siguientes propiedades:

  • ActionArguments: permite leer las entradas de un método de acción.
  • Controller: permite manipular la instancia del controlador.
  • Result: si se establece Result, se cortocircuita la ejecución del método de acción y de los filtros de acciones posteriores.

Inicio de una excepción en un método de acción:

  • Impide la ejecución de los filtros subsiguientes.
  • A diferencia del establecimiento de Result, se trata como un error en lugar de como un resultado correcto.

ActionExecutedContext proporciona Controller y Result, además de las siguientes propiedades:

  • Canceled: es true si otro filtro ha cortocircuitado la ejecución de la acción.
  • Exception: es un valor distinto de NULL si la acción o un filtro de acción de ejecución anterior han producido una excepción. Si se establece esta propiedad en un valor NULL:
    • Controla la excepción eficazmente.
    • Result se ejecuta como si se devolviera desde el método de acción.

En un IAsyncActionFilter, una llamada a ActionExecutionDelegate:

  • Ejecuta cualquier filtro de acciones posterior y el método de acción.
  • Devuelve ActionExecutedContext.

Para cortocircuitar esto, asigne Microsoft.AspNetCore.Mvc.Filters.ActionExecutingContext.Result a una instancia de resultado y no llame a next (la clase ActionExecutionDelegate).

El marco proporciona una clase ActionFilterAttribute abstracta de la que se pueden crear subclases.

Se puede usar el filtro de acción OnActionExecuting para:

  • Validar el estado del modelo.
  • Devolver un error si el estado no es válido.
public class ValidateModelAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        if (!context.ModelState.IsValid)
        {
            context.Result = new BadRequestObjectResult(context.ModelState);
        }
    }
}

Nota:

Los controladores anotados con el atributo [ApiController] validan automáticamente el estado del modelo y devuelven una respuesta 400. Para obtener más información, consulte Respuestas HTTP 400 automáticas.

El método OnActionExecuted se ejecuta después del método de acción:

  • Y puede ver y manipular los resultados de la acción a través de la propiedad Result.
  • Canceled se establece en true si otro filtro ha cortocircuitado la ejecución de la acción.
  • Exception se establece en un valor distinto de NULL si la acción o un filtro de acción posterior han producido una excepción. Si Exception se establece como nulo:
    • Controla una excepción eficazmente.
    • ActionExecutedContext.Result se ejecuta como si se devolviera con normalidad desde el método de acción.

Filtros de excepciones

Los filtros de excepciones:

En el siguiente filtro de excepciones de ejemplo se muestran los detalles sobre las excepciones que se producen cuando la aplicación está en fase de desarrollo:

public class SampleExceptionFilter : IExceptionFilter
{
    private readonly IHostEnvironment _hostEnvironment;

    public SampleExceptionFilter(IHostEnvironment hostEnvironment) =>
        _hostEnvironment = hostEnvironment;

    public void OnException(ExceptionContext context)
    {
        if (!_hostEnvironment.IsDevelopment())
        {
            // Don't display exception details unless running in Development.
            return;
        }

        context.Result = new ContentResult
        {
            Content = context.Exception.ToString()
        };
    }
}

El código siguiente prueba el filtro de excepciones:

[TypeFilter(typeof(SampleExceptionFilter))]
public class ExceptionController : Controller
{
    public IActionResult Index() =>
        Content($"- {nameof(ExceptionController)}.{nameof(Index)}");
}

Los filtros de excepciones:

  • No tienen eventos anteriores ni posteriores.
  • Implementan OnException o OnExceptionAsync.
  • Controlan las excepciones sin controlar que se producen en Razor Pages, al crear controladores, en el enlace de modelos, en los filtros de acciones o en los métodos de acción.
  • No detectan aquellas excepciones que se produzcan en los filtros de recursos, en los filtros de resultados o en la ejecución de resultados de MVC.

Para controlar una excepción, establezca la propiedad ExceptionHandled en true o asigne la propiedad Result. Esto detiene la propagación de la excepción. Un filtro de excepciones no tiene capacidad para convertir una excepción en un proceso "correcto". Eso solo lo pueden hacer los filtros de acciones.

Los filtros de excepciones:

  • Son adecuados para interceptar las excepciones que se producen en las acciones.
  • No son tan flexibles como el middleware de control de errores.

Es preferible usar middleware de control de excepciones. Utilice los filtros de excepciones solo cuando el control de errores es diferente en función del método de acción que se llama. Por ejemplo, puede que una aplicación tenga métodos de acción tanto para los puntos de conexión de API como para las vistas/HTML. Los puntos de conexión de API podrían devolver información sobre errores como JSON, mientras que las acciones basadas en vistas podrían devolver una página de error como HTML.

Filtros de resultados

Filtros de resultados:

IResultFilter e IAsyncResultFilter

El código siguiente muestra un ejemplo de filtro de resultados:

public class SampleResultFilter : IResultFilter
{
    public void OnResultExecuting(ResultExecutingContext context)
    {
        // Do something before the result executes.
    }

    public void OnResultExecuted(ResultExecutedContext context)
    {
        // Do something after the result executes.
    }
}

El tipo de resultado que se ejecute dependerá de la acción. Una acción que devuelve una vista incluye todo el procesamiento de Razor como parte del elemento ViewResult que se está ejecutando. Un método API puede llevar a cabo algunas funciones de serialización como parte de la ejecución del resultado. Aquí encontrará más información sobre los resultados de acciones.

Los filtros de resultados solo se ejecutan cuando una acción o un filtro de acción genera un resultado de acción. Los filtros de resultados no se ejecutan cuando:

  • Un filtro de autorización o un filtro de recursos genera un cortocircuito en la canalización.
  • Un filtro de excepciones controla una excepción al producir un resultado de acción.

El método Microsoft.AspNetCore.Mvc.Filters.IResultFilter.OnResultExecuting puede cortocircuitar la ejecución del resultado de la acción y de los filtros de resultados posteriores mediante el establecimiento de Microsoft.AspNetCore.Mvc.Filters.ResultExecutingContext.Cancel en true. Escriba en el objeto de respuesta cuando el proceso se cortocircuite, ya que así evitará que se genere una respuesta vacía. Generación de una excepción en IResultFilter.OnResultExecuting:

  • Impide la ejecución del resultado de la acción y de los filtros subsiguientes.
  • Se trata como un error en lugar de como un resultado correcto.

Cuando se ejecuta el método Microsoft.AspNetCore.Mvc.Filters.IResultFilter.OnResultExecuted, es probable que la respuesta ya se haya enviado al cliente. En este caso, no se puede cambiar.

ResultExecutedContext.Canceled se establece en true si otro filtro ha cortocircuitado la ejecución del resultado de la acción.

ResultExecutedContext.Exception se establece en un valor distinto de NULL si el resultado de la acción o un filtro de resultado posterior ha producido una excepción. Establecer Exception en un valor null hace que una excepción se controle de forma eficaz y evita que se vuelva a producir dicha excepción más adelante en la canalización. No hay una forma confiable de escribir datos en una respuesta cuando se controla una excepción en un filtro de resultados. Si los encabezados ya se han vaciado en el cliente si el resultado de una acción inicia una excepción, no hay ningún mecanismo confiable que permita enviar un código de error.

En un elemento IAsyncResultFilter, una llamada a await next en ResultExecutionDelegate ejecuta cualquier filtro de resultados posterior y el resultado de la acción. Para cortocircuitar esto, establezca ResultExecutingContext.Cancel en true y no llame a ResultExecutionDelegate:

public class SampleAsyncResultFilter : IAsyncResultFilter
{
    public async Task OnResultExecutionAsync(
        ResultExecutingContext context, ResultExecutionDelegate next)
    {
        if (context.Result is not EmptyResult)
        {
            await next();
        }
        else
        {
            context.Cancel = true;
        }
    }
}

El marco proporciona una clase ResultFilterAttribute abstracta de la que se pueden crear subclases. La clase ResponseHeaderAttribute mostrada anteriormente es un ejemplo de un atributo de filtro de resultados.

IAlwaysRunResultFilter e IAsyncAlwaysRunResultFilter

Las interfaces IAlwaysRunResultFilter e IAsyncAlwaysRunResultFilter declaran una implementación IResultFilter que se ejecuta para obtener todos los resultados de la acción. Esto incluye los resultados de la acción generados por:

  • Filtros de autorización y filtros de recursos que generan un cortocircuito.
  • Filtros de excepción.

Por ejemplo, el siguiente filtro siempre ejecuta y establece un resultado de la acción (ObjectResult) con un código de estado 422 - Entidad no procesable cuando se produce un error en la negociación de contenido:

public class UnprocessableResultFilter : IAlwaysRunResultFilter
{
    public void OnResultExecuting(ResultExecutingContext context)
    {
        if (context.Result is StatusCodeResult statusCodeResult
            && statusCodeResult.StatusCode == StatusCodes.Status415UnsupportedMediaType)
        {
            context.Result = new ObjectResult("Unprocessable")
            {
                StatusCode = StatusCodes.Status422UnprocessableEntity
            };
        }
    }

    public void OnResultExecuted(ResultExecutedContext context) { }
}

IFilterFactory

IFilterFactory implementa IFilterMetadata. Por tanto, una instancia de IFilterFactory se puede usar como una instancia de IFilterMetadata en cualquier parte de la canalización de filtro. Cuando el entorno de ejecución se prepara para invocar el filtro, intenta convertirlo a un IFilterFactory. Si esa conversión se realiza correctamente, se llama al método CreateInstance para crear la instancia IFilterMetadata que se va a invocar. Esto proporciona un diseño flexible, dado que no hay que establecer la canalización de filtro exacta de forma explícita cuando la aplicación se inicia.

IFilterFactory.IsReusable:

  • Es una indicación por parte de la fábrica de que la instancia de filtro creada por la fábrica puede ser reutilizada fuera del ámbito de solicitud en el que fue creada.
  • No debería usarse con un filtro que dependa de servicios con una duración distinta de singleton.

El entorno de ejecución de ASP.NET Core no garantiza:

  • Que se creará una única instancia del filtro.
  • El filtro no volverá a solicitarse desde el contenedor de inserción de dependencias en algún momento posterior.

Advertencia

Solo configure IFilterFactory.IsReusable para que devuelva true si el origen de los filtros no es ambiguo, los filtros no tienen estado y los filtros son seguros de usar en múltiples peticiones HTTP. Por ejemplo, no devuelva filtros de DI que estén registrados como de ámbito o transitorios si IFilterFactory.IsReusable devuelve true.

Puede implementar IFilterFactory con las implementaciones de atributos personalizados como método alternativo para crear filtros:

public class ResponseHeaderFilterFactory : Attribute, IFilterFactory
{
    public bool IsReusable => false;

    public IFilterMetadata CreateInstance(IServiceProvider serviceProvider) =>
        new InternalResponseHeaderFilter();

    private class InternalResponseHeaderFilter : IActionFilter
    {
        public void OnActionExecuting(ActionExecutingContext context) =>
            context.HttpContext.Response.Headers.Add(
                nameof(OnActionExecuting), nameof(InternalResponseHeaderFilter));

        public void OnActionExecuted(ActionExecutedContext context) { }
    }

El filtro se aplica en el código siguiente:

[ResponseHeaderFilterFactory]
public IActionResult Index() =>
    Content($"- {nameof(FilterFactoryController)}.{nameof(Index)}");

IFilterFactory implementado en un atributo

Los filtros que implementan IFilterFactory son útiles para los filtros que:

  • No requieren pasar parámetros.
  • Tienen dependencias de constructor que deben completarse por medio de la inserción de dependencias.

TypeFilterAttribute implementa IFilterFactory. IFilterFactory expone el método CreateInstance para crear una instancia de IFilterMetadata. CreateInstance carga el tipo especificado desde el contenedor de servicios (inserción de dependencias).

public class SampleActionTypeFilterAttribute : TypeFilterAttribute
{
    public SampleActionTypeFilterAttribute()
         : base(typeof(InternalSampleActionFilter)) { }

    private class InternalSampleActionFilter : IActionFilter
    {
        private readonly ILogger<InternalSampleActionFilter> _logger;

        public InternalSampleActionFilter(ILogger<InternalSampleActionFilter> logger) =>
            _logger = logger;

        public void OnActionExecuting(ActionExecutingContext context)
        {
            _logger.LogInformation(
                $"- {nameof(InternalSampleActionFilter)}.{nameof(OnActionExecuting)}");
        }

        public void OnActionExecuted(ActionExecutedContext context)
        {
            _logger.LogInformation(
                $"- {nameof(InternalSampleActionFilter)}.{nameof(OnActionExecuted)}");
        }
    }
}

El siguiente código muestra tres métodos para aplicar el filtro:

[SampleActionTypeFilter]
public IActionResult WithDirectAttribute() =>
    Content($"- {nameof(FilterFactoryController)}.{nameof(WithDirectAttribute)}");

[TypeFilter(typeof(SampleActionTypeFilterAttribute))]
public IActionResult WithTypeFilterAttribute() =>
    Content($"- {nameof(FilterFactoryController)}.{nameof(WithTypeFilterAttribute)}");

[ServiceFilter(typeof(SampleActionTypeFilterAttribute))]
public IActionResult WithServiceFilterAttribute() =>
    Content($"- {nameof(FilterFactoryController)}.{nameof(WithServiceFilterAttribute)}");

En el código anterior, se prefiere el primer método para aplicar el filtro.

Uso de middleware en la canalización de filtro

Los filtros de recursos funcionan como el middleware, en el sentido de que se encargan de la ejecución de todo lo que viene después en la canalización. Pero los filtros se diferencian del middleware en que forman parte del runtime, lo que significa que tienen acceso al contexto y las construcciones.

Para usar middleware como un filtro, cree un tipo con un método Configure en el que se especifique el middleware para insertar en la canalización de filtro. En el ejemplo siguiente se usa middleware para establecer un encabezado de respuesta:

public class FilterMiddlewarePipeline
{
    public void Configure(IApplicationBuilder app)
    {
        app.Use(async (context, next) =>
        {
            context.Response.Headers.Add("Pipeline", "Middleware");

            await next();
        });
    }
}

Use MiddlewareFilterAttribute para ejecutar el middleware:

[MiddlewareFilter(typeof(FilterMiddlewarePipeline))]
public class FilterMiddlewareController : Controller
{
    public IActionResult Index() =>
        Content($"- {nameof(FilterMiddlewareController)}.{nameof(Index)}");
}

Los filtros de middleware se ejecutan en la misma fase de la canalización de filtro que los filtros de recursos, antes del enlace de modelos y después del resto de la canalización.

Seguridad para subprocesos

Al pasar una instancia de un filtro a Add, en lugar de a su Type, el filtro es un singleton y no es seguro para subprocesos.

Recursos adicionales

Por Kirk Larkin, Rick Anderson, Tom Dykstra y Steve Smith

Los filtros en ASP.NET Core permiten que se ejecute el código antes o después de determinadas fases de la canalización del procesamiento de la solicitud.

Los filtros integrados se encargan de tareas como las siguientes:

  • Autorización, que impide el acceso a recursos para los que un usuario no está autorizado.
  • Almacenamiento en caché de la respuesta, que permite cortocircuitar la canalización de la solicitud para devolver una respuesta almacenada en caché.

Se pueden crear filtros personalizados que se encarguen de cuestiones transversales. Entre los ejemplos de cuestiones transversales se incluyen el control de errores, el almacenamiento en caché, la configuración, la autorización y el registro. Los filtros evitan la duplicación de código. Así, por ejemplo, un filtro de excepción de control de errores puede consolidar el control de errores.

Este documento se aplica a Razor Pages, a los controladores de API y a los controladores con vistas. Los filtros no funcionan directamente con componentes de Razor. Un filtro solo puede afectar indirectamente a un componente cuando:

  • El componente está insertado en una página o vista.
  • La página o controlador y la vista usan el filtro.

Vea o descargue el ejemplo (cómo descargarlo).

Funcionamiento de los filtros

Los filtros se ejecutan dentro de la canalización de invocación de acciones de ASP.NET Core, a veces denominada canalización de filtro. La canalización de filtro se ejecuta después de que ASP.NET Core seleccione la acción que se va a ejecutar.

The request is processed through Other Middleware, Routing Middleware, Action Selection, and the Action Invocation Pipeline. The request processing continues back through Action Selection, Routing Middleware, and various Other Middleware before becoming a response sent to the client.

Tipos de filtro

Cada tipo de filtro se ejecuta en una fase diferente dentro de la canalización de filtro:

  • Los filtros de autorización se ejecutan en primer lugar y sirven para averiguar si el usuario está autorizado para realizar la solicitud. Los filtros de autorización pueden cortocircuitar la canalización si una solicitud no está autorizada.

  • Filtros de recursos:

    • Se ejecutan después de la autorización.
    • OnResourceExecuting ejecuta código antes que el resto de la canalización del filtro. Por ejemplo, OnResourceExecuting ejecuta código antes que el enlace de modelos.
    • OnResourceExecuted ejecuta el código una vez que el resto de la canalización se haya completado.
  • Los filtros de acciones:

    • Ejecutan código inmediatamente antes y después de llamar a un método de acción.
    • Pueden cambiar los argumentos pasados a una acción.
    • Pueden cambiar el resultado devuelto de la acción.
    • No se admiten en Razor Pages.
  • Los filtros de excepciones aplican directivas globales a las excepciones no controladas que se producen antes de que se escriba el cuerpo de respuesta.

  • Los filtros de resultados ejecutan código inmediatamente antes y después de la ejecución de resultados de acción. Se ejecutan solo cuando el método de acción se ha ejecutado correctamente. Son útiles para la lógica que debe regir la ejecución de la vista o el formateador.

En el siguiente diagrama se muestra cómo interactúan los tipos de filtro en la canalización de filtro.

The request is processed through Authorization Filters, Resource Filters, Model Binding, Action Filters, Action Execution and Action Result Conversion, Exception Filters, Result Filters, and Result Execution. On the way out, the request is only processed by Result Filters and Resource Filters before becoming a response sent to the client.

Implementación

Los filtros admiten implementaciones tanto sincrónicas como asincrónicas a través de diferentes definiciones de interfaz.

Los filtros sincrónicos ejecutan código antes y después de la fase de canalización. Por ejemplo, OnActionExecuting se llama antes de llamar al método de acción. OnActionExecuted se llama después de devolver el método de acción.

public class MySampleActionFilter : IActionFilter 
{
    public void OnActionExecuting(ActionExecutingContext context)
    {
        // Do something before the action executes.
        MyDebug.Write(MethodBase.GetCurrentMethod(), context.HttpContext.Request.Path);
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
        // Do something after the action executes.
        MyDebug.Write(MethodBase.GetCurrentMethod(), context.HttpContext.Request.Path);
    }
}

En el código anterior, MyDebug es una función de utilidad en la descarga de ejemplo.

Los filtros asincrónicos definen un método On-Stage-ExecutionAsync. Por ejemplo, OnActionExecutionAsync:

public class SampleAsyncActionFilter : IAsyncActionFilter
{
    public async Task OnActionExecutionAsync(
        ActionExecutingContext context,
        ActionExecutionDelegate next)
    {
        // Do something before the action executes.

        // next() calls the action method.
        var resultContext = await next();
        // resultContext.Result is set.
        // Do something after the action executes.
    }
}

En el código anterior, SampleAsyncActionFilter tiene un delegado ActionExecutionDelegate (next) que ejecuta el método de acción.

Varias fases de filtro

Se pueden implementar interfaces para varias fases de filtro en una sola clase. Por ejemplo, la clase ActionFilterAttribute implementa:

Implemente la versión sincrónica o la versión asincrónica de una interfaz de filtro, pero no ambas. El entorno de ejecución comprueba primero si el filtro implementa la interfaz asincrónica y, si es así, llama a la interfaz. De lo contrario, llamará a métodos de interfaz sincrónicos. Si se implementan las interfaces asincrónicas y sincrónicas en una clase, solo se llama al método asincrónico. Cuando se usan clases abstractas como ActionFilterAttribute, se invalidan solo los métodos sincrónicos o los métodos asincrónicos de cada tipo de filtro.

Atributos de filtros integrados

ASP.NET Core incluye filtros integrados basados en atributos que se pueden personalizar y a partir de los cuales crear subclases. Por ejemplo, el siguiente filtro de resultados agrega un encabezado a la respuesta:

public class AddHeaderAttribute : ResultFilterAttribute
{
    private readonly string _name;
    private readonly string _value;

    public AddHeaderAttribute(string name, string value)
    {
        _name = name;
        _value = value;
    }

    public override void OnResultExecuting(ResultExecutingContext context)
    {
        context.HttpContext.Response.Headers.Add( _name, new string[] { _value });
        base.OnResultExecuting(context);
    }
}

Los atributos permiten a los filtros aceptar argumentos, como se muestra en el ejemplo anterior. Aplique el AddHeaderAttribute a un método de acción o controlador y especifique el nombre y el valor del encabezado HTTP:

[AddHeader("Author", "Rick Anderson")]
public class SampleController : Controller
{
    public IActionResult Index()
    {
        return Content("Examine the headers using the F12 developer tools.");
    }

Use una herramienta como las herramientas de desarrollo del explorador para examinar los encabezados. En Encabezados de respuesta, se muestra author: Rick Anderson.

El código siguiente implementa un atributo ActionFilterAttribute que:

  • Lee el título y el nombre del sistema de configuración. A diferencia del ejemplo anterior, el siguiente código no requiere que se agreguen parámetros de filtro al código.
  • Agrega el título y el nombre al encabezado de respuesta.
public class MyActionFilterAttribute : ActionFilterAttribute
{
    private readonly PositionOptions _settings;

    public MyActionFilterAttribute(IOptions<PositionOptions> options)
    {
        _settings = options.Value;
        Order = 1;
    }

    public override void OnResultExecuting(ResultExecutingContext context)
    {
        context.HttpContext.Response.Headers.Add(_settings.Title, 
                                                 new string[] { _settings.Name });
        base.OnResultExecuting(context);
    }
}

Las opciones de configuración las proporciona el sistema de configuración mediante el patrón de opciones. Por ejemplo, desde el archivo appsettings.json:

{
  "Position": {
    "Title": "Editor",
    "Name": "Joe Smith"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
}

En StartUp.ConfigureServices:

  • La clase PositionOptions se agrega al contenedor de servicios con el área de configuración "Position".
  • MyActionFilterAttribute se agrega al contenedor de servicios.
public void ConfigureServices(IServiceCollection services)
{
    services.Configure<PositionOptions>(
             Configuration.GetSection("Position"));
    services.AddScoped<MyActionFilterAttribute>();

    services.AddControllersWithViews();
}

En el código siguiente se muestra la clase PositionOptions :

public class PositionOptions
{
    public string Title { get; set; }
    public string Name { get; set; }
}

El siguiente código aplica el atributo MyActionFilterAttribute al método Index2:

[AddHeader("Author", "Rick Anderson")]
public class SampleController : Controller
{
    public IActionResult Index()
    {
        return Content("Examine the headers using the F12 developer tools.");
    }

    [ServiceFilter(typeof(MyActionFilterAttribute))]
    public IActionResult Index2()
    {
        return Content("Header values by configuration.");
    }

En Encabezados de respuesta, author: Rick Anderson y Editor: Joe Smith se muestran cuando se llama al punto de conexión Sample/Index2.

El siguiente código aplica los atributos MyActionFilterAttribute y AddHeaderAttribute a la página de Razor Pages:

[AddHeader("Author", "Rick Anderson")]
[ServiceFilter(typeof(MyActionFilterAttribute))]
public class IndexModel : PageModel
{
    public void OnGet()
    {
    }
}

Los filtros no se pueden aplicar a métodos de controlador de Razor Page. Se pueden usar con el modelo de página de Razor Page o globalmente.

Algunas de las interfaces de filtro tienen atributos correspondientes que se pueden usar como clases base en las implementaciones personalizadas.

Atributos de filtro:

Ámbitos del filtro y orden de ejecución

Un filtro se puede agregar a la canalización en uno de tres ámbitos posibles:

  • Mediante un atributo en una acción del controlador. Los atributos de filtro no se pueden usar con métodos de controlador de páginas de Razor Pages.
  • Mediante un atributo en un controlador o una página de Razor Page.
  • Globalmente para todos los controladores, las acciones y las páginas de Razor Pages tal como se muestra en el siguiente código:
public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews(options =>
   {
        options.Filters.Add(typeof(MySampleActionFilter));
    });
}

Orden de ejecución predeterminado

Cuando hay varios filtros en una determinada fase de la canalización, el ámbito determina el orden predeterminado en el que esos filtros se van a ejecutar. Los filtros globales abarcan a los filtros de clase, que a su vez engloban a los filtros de método.

Como resultado de este anidamiento de filtros, el código de filtros posterior se ejecuta en el orden inverso al código anterior. La secuencia de filtro:

  • El código anterior de los filtros globales.
    • El código anterior de los filtros de controlador y página de Razor Pages.
      • El código anterior de los filtros de métodos de acción.
      • El código posterior de los filtros de métodos de acción.
    • El código posterior de los filtros de controlador y página de Razor Pages.
  • El código posterior de los filtros globales.

El ejemplo siguiente que ilustra el orden en el que se llama a los métodos de filtro relativos a filtros de acciones sincrónicos.

Secuencia Ámbito del filtro Método de filtro
1 Global OnActionExecuting
2 Controlador o Razor Page OnActionExecuting
3 Método OnActionExecuting
4 Método OnActionExecuted
5 Controlador o Razor Page OnActionExecuted
6 Global OnActionExecuted

Filtros de nivel de controlador

Todos los controladores que heredan de la clase base Controller incluyen los métodos Controller.OnActionExecuting, Controller.OnActionExecutionAsync y Controller.OnActionExecutedOnActionExecuted. Estos métodos:

  • Encapsulan los filtros que se ejecutan para una acción determinada.
  • OnActionExecuting se llama antes de cualquiera de los filtros de acciones.
  • OnActionExecuted se llama después de todos los filtros de acciones.
  • OnActionExecutionAsync se llama antes de cualquiera de los filtros de acciones. El código del filtro después de next se ejecuta después del método de acción.

Por ejemplo, en el ejemplo de descarga, se aplica MySampleActionFilter globalmente al inicio.

El TestController:

  • Aplica SampleActionFilterAttribute ([SampleActionFilter]) a la acción FilterTest2.
  • Invalida OnActionExecuting y OnActionExecuted.
public class TestController : Controller
{
    [SampleActionFilter(Order = int.MinValue)]
    public IActionResult FilterTest2()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    public override void OnActionExecuting(ActionExecutingContext context)
    {
        // Do something before the action executes.
        MyDebug.Write(MethodBase.GetCurrentMethod(), HttpContext.Request.Path);
        base.OnActionExecuting(context);
    }

    public override void OnActionExecuted(ActionExecutedContext context)
    {
        // Do something after the action executes.
        MyDebug.Write(MethodBase.GetCurrentMethod(), HttpContext.Request.Path);
        base.OnActionExecuted(context);
    }
}

El paquete de NuGet Rick.Docs.Samples.RouteInfo proporciona MyDisplayRouteInfo y se muestra la información de ruta.

Si se dirige a https://localhost:5001/Test/FilterTest2, se ejecuta el código siguiente:

  • TestController.OnActionExecuting
    • MySampleActionFilter.OnActionExecuting
      • SampleActionFilterAttribute.OnActionExecuting
        • TestController.FilterTest2
      • SampleActionFilterAttribute.OnActionExecuted
    • MySampleActionFilter.OnActionExecuted
  • TestController.OnActionExecuted

Los filtros de nivel de controlador establecen la propiedad Order en int.MinValue. Los filtros de nivel de controlador no pueden establecerse para ejecutarse tras aplicarse los filtros a los métodos. Order se explica en la sección siguiente.

Para Razor Pages, consulte Implementar filtros de Razor Page invalidando métodos de filtro.

Invalidación del orden predeterminado

La secuencia de ejecución predeterminada se puede invalidar con la implementación de IOrderedFilter. Order expone la propiedad IOrderedFilter que tiene prioridad sobre el ámbito a la hora de determinar el orden de ejecución. Un filtro con un valor Order menor:

  • Ejecuta el código anterior antes que el de un filtro con un valor mayor de Order.
  • Ejecuta el código posterior después que el de un filtro con un valor mayor de Order.

La propiedad Order se establece con un parámetro de constructor:

[SampleActionFilter(Order = int.MinValue)]

Tenga en cuenta los dos filtros de acción en el siguiente controlador:

[MyAction2Filter]
public class Test2Controller : Controller
{
    public IActionResult FilterTest2()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    public override void OnActionExecuting(ActionExecutingContext context)
    {
        // Do something before the action executes.
        MyDebug.Write(MethodBase.GetCurrentMethod(), HttpContext.Request.Path);
        base.OnActionExecuting(context);
    }

    public override void OnActionExecuted(ActionExecutedContext context)
    {
        // Do something after the action executes.
        MyDebug.Write(MethodBase.GetCurrentMethod(), HttpContext.Request.Path);
        base.OnActionExecuted(context);
    }
}

Un filtro global se agrega en StartUp.ConfigureServices:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews(options =>
   {
        options.Filters.Add(typeof(MySampleActionFilter));
    });
}

Los tres filtros se ejecutan en el siguiente orden:

  • Test2Controller.OnActionExecuting
    • MySampleActionFilter.OnActionExecuting
      • MyAction2FilterAttribute.OnActionExecuting
        • Test2Controller.FilterTest2
      • MyAction2FilterAttribute.OnResultExecuting
    • MySampleActionFilter.OnActionExecuted
  • Test2Controller.OnActionExecuted

La propiedad Order invalida el ámbito al determinar el orden en el que se ejecutarán los filtros. Los filtros se clasifican por orden en primer lugar y, después, se usa el ámbito para priorizar en caso de igualdad. Todos los filtros integrados implementan IOrderedFilter y establecen el valor predeterminado de Order en 0. Como se mencionó anteriormente, los filtros de nivel de controlador establecen la propiedad Order en int.MinValue. En el caso de los filtros integrados, el ámbito determina el orden a menos que se establezca Order en un valor distinto de cero.

En el código anterior, MySampleActionFilter tiene ámbito global, por lo que se ejecuta antes de MyAction2FilterAttribute, que tiene ámbito de controlador. Para que MyAction2FilterAttribute se ejecute en primer lugar, establezca el orden en int.MinValue:

[MyAction2Filter(int.MinValue)]
public class Test2Controller : Controller
{
    public IActionResult FilterTest2()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    public override void OnActionExecuting(ActionExecutingContext context)
    {
        // Do something before the action executes.
        MyDebug.Write(MethodBase.GetCurrentMethod(), HttpContext.Request.Path);
        base.OnActionExecuting(context);
    }

    public override void OnActionExecuted(ActionExecutedContext context)
    {
        // Do something after the action executes.
        MyDebug.Write(MethodBase.GetCurrentMethod(), HttpContext.Request.Path);
        base.OnActionExecuted(context);
    }
}

Para que el filtro global MySampleActionFilter se ejecute en primer lugar, establezca Order en int.MinValue:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews(options =>
   {
        options.Filters.Add(typeof(MySampleActionFilter),
                            int.MinValue);
    });
}

Cancelación y cortocircuito

La canalización de filtro se puede cortocircuitar en cualquier momento mediante el establecimiento de la propiedad Result en el parámetro ResourceExecutingContext que se ha proporcionado al método de filtro. Por ejemplo, el siguiente filtro de recursos impide que el resto de la canalización se ejecute:

public class ShortCircuitingResourceFilterAttribute : Attribute, IResourceFilter
{
    public void OnResourceExecuting(ResourceExecutingContext context)
    {
        context.Result = new ContentResult()
        {
            Content = "Resource unavailable - header not set."
        };
    }

    public void OnResourceExecuted(ResourceExecutedContext context)
    {
    }
}

En el siguiente código, tanto el filtro ShortCircuitingResourceFilter como el filtro AddHeader tienen como destino el método de acción SomeResource. El ShortCircuitingResourceFilter:

  • Se ejecuta en primer lugar, porque es un filtro de recursos y AddHeader es un filtro de acciones.
  • Cortocircuita el resto de la canalización.

Por tanto, el filtro AddHeader nunca se ejecuta en relación con la acción SomeResource. Este comportamiento sería el mismo si ambos filtros se aplicaran en el nivel de método de acción, siempre y cuando ShortCircuitingResourceFilter se haya ejecutado primero. ShortCircuitingResourceFilter se ejecuta primero debido a su tipo de filtro o al uso explícito de la propiedad Order.

[AddHeader("Author", "Rick Anderson")]
public class SampleController : Controller
{
    public IActionResult Index()
    {
        return Content("Examine the headers using the F12 developer tools.");
    }

    [ServiceFilter(typeof(MyActionFilterAttribute))]
    public IActionResult Index2()
    {
        return Content("Header values by configuration.");
    }

    [ShortCircuitingResourceFilter]
    public IActionResult SomeResource()
    {
        return Content("Successful access to resource - header is set.");
    }

    [AddHeaderWithFactory]
    public IActionResult HeaderWithFactory()
    {
        return Content("Examine the headers using the F12 developer tools.");
    }
}

Inserción de dependencias

Los filtros se pueden agregar por tipo o por instancia. Si se agrega una instancia, esta se utiliza para todas las solicitudes. Si se agrega un tipo, se activa por tipo. Un filtro activado por tipo significa:

Los filtros que se implementan como atributos y se agregan directamente a las clases de controlador o a los métodos de acción no pueden tener dependencias de constructor proporcionadas por la inserción de dependencias. Las dependencias de constructor no pueden proporcionarse mediante la inserción de dependencias porque:

  • Los atributos deben tener los parámetros de constructor proporcionados allá donde se apliquen.
  • Se trata de una limitación de cómo funcionan los atributos.

Los filtros siguientes admiten dependencias de constructor proporcionadas en la inserción de dependencias:

Los filtros anteriores se pueden aplicar a un método de controlador o de acción:

Los registradores están disponibles en la inserción de dependencias. Sin embargo, evite crear y utilizar filtros únicamente con fines de registro. El registro del marco integrado proporciona normalmente lo que se necesita para el registro. Registro agregado a los filtros:

  • Debe centrarse en cuestiones de dominio empresarial o en el comportamiento específico del filtro.
  • No debe registrar acciones u otros eventos del marco. Los filtros integrados registran acciones y eventos del marco.

ServiceFilterAttribute

Los tipos de implementación de filtro de servicio se registran en ConfigureServices. ServiceFilterAttribute recupera una instancia del filtro de la inserción de dependencias.

El código siguiente muestra AddHeaderResultServiceFilter:

public class AddHeaderResultServiceFilter : IResultFilter
{
    private ILogger _logger;
    public AddHeaderResultServiceFilter(ILoggerFactory loggerFactory)
    {
        _logger = loggerFactory.CreateLogger<AddHeaderResultServiceFilter>();
    }

    public void OnResultExecuting(ResultExecutingContext context)
    {
        var headerName = "OnResultExecuting";
        context.HttpContext.Response.Headers.Add(
            headerName, new string[] { "ResultExecutingSuccessfully" });
        _logger.LogInformation("Header added: {HeaderName}", headerName);
    }

    public void OnResultExecuted(ResultExecutedContext context)
    {
        // Can't add to headers here because response has started.
        _logger.LogInformation("AddHeaderResultServiceFilter.OnResultExecuted");
    }
}

En el código siguiente, AddHeaderResultServiceFilter se agrega al contenedor de inserción de dependencias:

public void ConfigureServices(IServiceCollection services)
{
    // Add service filters.
    services.AddScoped<AddHeaderResultServiceFilter>();
    services.AddScoped<SampleActionFilterAttribute>();

    services.AddControllersWithViews(options =>
   {
       options.Filters.Add(new AddHeaderAttribute("GlobalAddHeader",
           "Result filter added to MvcOptions.Filters"));         // An instance
        options.Filters.Add(typeof(MySampleActionFilter));         // By type
        options.Filters.Add(new SampleGlobalActionFilter());       // An instance
    });
}

En el código siguiente, el atributo ServiceFilter recupera una instancia del filtro AddHeaderResultServiceFilter desde la inserción de dependencias:

[ServiceFilter(typeof(AddHeaderResultServiceFilter))]
public IActionResult Index()
{
    return View();
}

Al usar ServiceFilterAttribute, estableciendo ServiceFilterAttribute.IsReusable:

  • Proporciona una sugerencia que la instancia de filtro podría reutilizarse fuera del ámbito de la solicitud en la que se creó. El entorno de ejecución de ASP.NET Core no garantiza:

    • Que se creará una única instancia del filtro.
    • El filtro no volverá a solicitarse desde el contenedor de inserción de dependencias en algún momento posterior.
  • No debe usarse con un filtro que depende de servicios con una duración distinta de singleton.

ServiceFilterAttribute implementa IFilterFactory. IFilterFactory expone el método CreateInstance para crear una instancia de IFilterMetadata. CreateInstance carga el tipo especificado desde la inserción de dependencias.

TypeFilterAttribute

TypeFilterAttribute es similar a ServiceFilterAttribute, pero su tipo no se resuelve directamente desde el contenedor de inserción de dependencias, sino que crea una instancia del tipo usando el elemento Microsoft.Extensions.DependencyInjection.ObjectFactory.

Dado que los tipos TypeFilterAttribute no se resuelven directamente desde el contenedor de inserción de dependencias:

  • Los tipos a los que se hace referencia con TypeFilterAttribute no tienen que estar registrados con el contenedor de inserción de dependencias. Sus dependencias se completan a través del contenedor de inserción de dependencias.
  • TypeFilterAttribute puede aceptar opcionalmente argumentos de constructor del tipo en cuestión.

Al usar TypeFilterAttribute, estableciendo TypeFilterAttribute.IsReusable:

  • Proporciona una sugerencia que indica que la instancia de filtro podría reutilizarse fuera del ámbito de la solicitud en la que se creó. El entorno de ejecución de ASP.NET Core no ofrece ninguna garantía de que se vaya a crear una única instancia del filtro.

  • No debe usarse con un filtro que depende de servicios con una duración distinta de singleton.

En el siguiente ejemplo se muestra cómo pasar argumentos a un tipo mediante TypeFilterAttribute:

[TypeFilter(typeof(LogConstantFilter),
    Arguments = new object[] { "Method 'Hi' called" })]
public IActionResult Hi(string name)
{
    return Content($"Hi {name}");
}

Filtros de autorización

Filtros de autorización:

  • Son los primeros filtros que se ejecutan en la canalización del filtro.
  • Controlan el acceso a los métodos de acción.
  • Tienen un método anterior, pero no uno posterior.

Los filtros de autorización personalizados requieren un marco de autorización personalizado. Es preferible configurar directivas de autorización o escribir una directiva de autorización personalizada a escribir un filtro personalizado. El filtro de autorización integrado:

  • Llama a la autorización del sistema.
  • No autoriza las solicitudes.

No inicie excepciones dentro de los filtros de autorización:

  • La excepción no se controlará.
  • Los filtros de excepciones no controlarán la excepción.

Considere la posibilidad de emitir un desafío cuando se produzca una excepción en un filtro de autorizaciones.

Aquí encontrará más información sobre la autorización.

Filtros de recursos

Filtros de recursos:

Los filtros de recursos son útiles para cortocircuitar la mayor parte de la canalización. Por ejemplo, un filtro de almacenamiento en caché puede evitar que se ejecute el resto de la canalización en un acierto de caché.

Ejemplos de filtros de recursos:

Filtros de acciones

Los filtros de acción no se aplican a Razor Pages. Razor Pages admite IPageFilter y IAsyncPageFilter. Para obtener más información, vea Métodos de filtrado de Razor Pages.

Filtros de acciones:

El código siguiente muestra un ejemplo de filtro de acciones:

public class MySampleActionFilter : IActionFilter 
{
    public void OnActionExecuting(ActionExecutingContext context)
    {
        // Do something before the action executes.
        MyDebug.Write(MethodBase.GetCurrentMethod(), context.HttpContext.Request.Path);
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
        // Do something after the action executes.
        MyDebug.Write(MethodBase.GetCurrentMethod(), context.HttpContext.Request.Path);
    }
}

ActionExecutingContext ofrece las siguientes propiedades:

  • ActionArguments: permite leer las entradas de un método de acción.
  • Controller: permite manipular la instancia del controlador.
  • Result: si se establece Result, se cortocircuita la ejecución del método de acción y de los filtros de acciones posteriores.

Inicio de una excepción en un método de acción:

  • Impide la ejecución de los filtros subsiguientes.
  • A diferencia del establecimiento de Result, se trata como un error en lugar de como un resultado correcto.

ActionExecutedContext proporciona Controller y Result, además de las siguientes propiedades:

  • Canceled: es true si otro filtro ha cortocircuitado la ejecución de la acción.

  • Exception: es un valor distinto de NULL si la acción o un filtro de acción de ejecución anterior han producido una excepción. Si se establece esta propiedad en un valor NULL:

    • Controla la excepción eficazmente.
    • Result se ejecuta como si se devolviera desde el método de acción.

En un IAsyncActionFilter, una llamada a ActionExecutionDelegate:

  • Ejecuta cualquier filtro de acciones posterior y el método de acción.
  • Devuelve ActionExecutedContext.

Para cortocircuitar esto, asigne Microsoft.AspNetCore.Mvc.Filters.ActionExecutingContext.Result a una instancia de resultado y no llame a next (la clase ActionExecutionDelegate).

El marco proporciona una clase ActionFilterAttribute abstracta de la que se pueden crear subclases.

Se puede usar el filtro de acción OnActionExecuting para:

  • Validar el estado del modelo.
  • Devolver un error si el estado no es válido.
public class ValidateModelAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext 
                                           context)
    {
        if (!context.ModelState.IsValid)
        {
            context.Result = new BadRequestObjectResult(
                                                context.ModelState);
        }
    }

Nota:

Los controladores anotados con el atributo [ApiController] validan automáticamente el estado del modelo y devuelven una respuesta 400. Para obtener más información, consulte Respuestas HTTP 400 automáticas. El método OnActionExecuted se ejecuta después del método de acción:

  • Y puede ver y manipular los resultados de la acción a través de la propiedad Result.

  • Canceled se establece en true si otro filtro ha cortocircuitado la ejecución de la acción.

  • Exception se establece en un valor distinto de NULL si la acción o un filtro de acción posterior han producido una excepción. Si Exception se establece como nulo:

    • Controla una excepción eficazmente.
    • ActionExecutedContext.Result se ejecuta como si se devolviera con normalidad desde el método de acción.
public class ValidateModelAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext 
                                           context)
    {
        if (!context.ModelState.IsValid)
        {
            context.Result = new BadRequestObjectResult(
                                                context.ModelState);
        }
    }


    public override void OnActionExecuted(ActionExecutedContext 
                                          context)
    {
        var result = context.Result;
        // Do something with Result.
        if (context.Canceled == true)
        {
            // Action execution was short-circuited by another filter.
        }

        if(context.Exception != null)
        {
            // Exception thrown by action or action filter.
            // Set to null to handle the exception.
            context.Exception = null;
        }
        base.OnActionExecuted(context);
    }
}

Filtros de excepciones

Los filtros de excepciones:

En el siguiente filtro de excepciones de ejemplo se usa una vista de error personalizada para mostrar los detalles sobre las excepciones que se producen cuando la aplicación está en fase de desarrollo:

public class CustomExceptionFilter : IExceptionFilter
{
    private readonly IWebHostEnvironment _hostingEnvironment;
    private readonly IModelMetadataProvider _modelMetadataProvider;

    public CustomExceptionFilter(
        IWebHostEnvironment hostingEnvironment,
        IModelMetadataProvider modelMetadataProvider)
    {
        _hostingEnvironment = hostingEnvironment;
        _modelMetadataProvider = modelMetadataProvider;
    }

    public void OnException(ExceptionContext context)
    {
        if (!_hostingEnvironment.IsDevelopment())
        {
            return;
        }
        var result = new ViewResult {ViewName = "CustomError"};
        result.ViewData = new ViewDataDictionary(_modelMetadataProvider,
                                                    context.ModelState);
        result.ViewData.Add("Exception", context.Exception);
        // TODO: Pass additional detailed data via ViewData
        context.Result = result;
    }
}

El código siguiente prueba el filtro de excepciones:

[TypeFilter(typeof(CustomExceptionFilter))]
public class FailingController : Controller
{
    [AddHeader("Failing Controller", 
               "Won't appear when exception is handled")]
    public IActionResult Index()
    {
        throw new Exception("Testing custom exception filter.");
    }
}

Los filtros de excepciones:

  • No tienen eventos anteriores ni posteriores.
  • Implementan OnException o OnExceptionAsync.
  • Controlan las excepciones sin controlar que se producen en Razor Pages, al crear controladores, en el enlace de modelos, en los filtros de acciones o en los métodos de acción.
  • No detectan aquellas excepciones que se produzcan en los filtros de recursos, en los filtros de resultados o en la ejecución de resultados de MVC.

Para controlar una excepción, establezca la propiedad ExceptionHandled en true o asigne la propiedad Result. Esto detiene la propagación de la excepción. Un filtro de excepciones no tiene capacidad para convertir una excepción en un proceso "correcto". Eso solo lo pueden hacer los filtros de acciones.

Los filtros de excepciones:

  • Son adecuados para interceptar las excepciones que se producen en las acciones.
  • No son tan flexibles como el middleware de control de errores.

Es preferible usar middleware de control de excepciones. Utilice los filtros de excepciones solo cuando el control de errores es diferente en función del método de acción que se llama. Por ejemplo, puede que una aplicación tenga métodos de acción tanto para los puntos de conexión de API como para las vistas/HTML. Los puntos de conexión de API podrían devolver información sobre errores como JSON, mientras que las acciones basadas en vistas podrían devolver una página de error como HTML.

Filtros de resultados

Filtros de resultados:

IResultFilter e IAsyncResultFilter

El código siguiente muestra un filtro de resultados que agrega un encabezado HTTP:

public class AddHeaderResultServiceFilter : IResultFilter
{
    private ILogger _logger;
    public AddHeaderResultServiceFilter(ILoggerFactory loggerFactory)
    {
        _logger = loggerFactory.CreateLogger<AddHeaderResultServiceFilter>();
    }

    public void OnResultExecuting(ResultExecutingContext context)
    {
        var headerName = "OnResultExecuting";
        context.HttpContext.Response.Headers.Add(
            headerName, new string[] { "ResultExecutingSuccessfully" });
        _logger.LogInformation("Header added: {HeaderName}", headerName);
    }

    public void OnResultExecuted(ResultExecutedContext context)
    {
        // Can't add to headers here because response has started.
        _logger.LogInformation("AddHeaderResultServiceFilter.OnResultExecuted");
    }
}

El tipo de resultado que se ejecute dependerá de la acción. Una acción que devuelve una vista incluye todo el procesamiento de Razor como parte del elemento ViewResult que se está ejecutando. Un método API puede llevar a cabo algunas funciones de serialización como parte de la ejecución del resultado. Aquí encontrará más información sobre los resultados de acciones.

Los filtros de resultados solo se ejecutan cuando una acción o un filtro de acción genera un resultado de acción. Los filtros de resultados no se ejecutan cuando:

  • Un filtro de autorización o un filtro de recursos genera un cortocircuito en la canalización.
  • Un filtro de excepciones controla una excepción al producir un resultado de acción.

El método Microsoft.AspNetCore.Mvc.Filters.IResultFilter.OnResultExecuting puede cortocircuitar la ejecución del resultado de la acción y de los filtros de resultados posteriores mediante el establecimiento de Microsoft.AspNetCore.Mvc.Filters.ResultExecutingContext.Cancel en true. Escriba en el objeto de respuesta cuando el proceso se cortocircuite, ya que así evitará que se genere una respuesta vacía. Generación de una excepción en IResultFilter.OnResultExecuting:

  • Impide la ejecución del resultado de la acción y de los filtros subsiguientes.
  • Se trata como un error en lugar de como un resultado correcto.

Cuando se ejecuta el método Microsoft.AspNetCore.Mvc.Filters.IResultFilter.OnResultExecuted, es probable que la respuesta ya se haya enviado al cliente. En este caso, no se puede cambiar.

ResultExecutedContext.Canceled se establece en true si otro filtro ha cortocircuitado la ejecución del resultado de la acción.

ResultExecutedContext.Exception se establece en un valor distinto de NULL si el resultado de la acción o un filtro de resultado posterior ha producido una excepción. Establecer Exception en un valor null hace que una excepción se controle de forma eficaz y evita que se vuelva a producir dicha excepción más adelante en la canalización. No hay una forma confiable de escribir datos en una respuesta cuando se controla una excepción en un filtro de resultados. Si los encabezados ya se han vaciado en el cliente si el resultado de una acción inicia una excepción, no hay ningún mecanismo confiable que permita enviar un código de error.

En un elemento IAsyncResultFilter, una llamada a await next en ResultExecutionDelegate ejecuta cualquier filtro de resultados posterior y el resultado de la acción. Para cortocircuitar esto, establezca ResultExecutingContext.Cancel en true y no llame a ResultExecutionDelegate:

public class MyAsyncResponseFilter : IAsyncResultFilter
{
    public async Task OnResultExecutionAsync(ResultExecutingContext context,
                                             ResultExecutionDelegate next)
    {
        if (!(context.Result is EmptyResult))
        {
            await next();
        }
        else
        {
            context.Cancel = true;
        }

    }
}

El marco proporciona una clase ResultFilterAttribute abstracta de la que se pueden crear subclases. La clase AddHeaderAttribute mostrada anteriormente es un ejemplo de un atributo de filtro de resultados.

IAlwaysRunResultFilter e IAsyncAlwaysRunResultFilter

Las interfaces IAlwaysRunResultFilter e IAsyncAlwaysRunResultFilter declaran una implementación IResultFilter que se ejecuta para obtener todos los resultados de la acción. Esto incluye los resultados de la acción generados por:

  • Filtros de autorización y filtros de recursos que generan un cortocircuito.
  • Filtros de excepción.

Por ejemplo, el siguiente filtro siempre ejecuta y establece un resultado de la acción (ObjectResult) con un código de estado 422 - Entidad no procesable cuando se produce un error en la negociación de contenido:

public class UnprocessableResultFilter : Attribute, IAlwaysRunResultFilter
{
    public void OnResultExecuting(ResultExecutingContext context)
    {
        if (context.Result is StatusCodeResult statusCodeResult &&
            statusCodeResult.StatusCode == (int) HttpStatusCode.UnsupportedMediaType)
        {
            context.Result = new ObjectResult("Can't process this!")
            {
                StatusCode = (int) HttpStatusCode.UnsupportedMediaType,
            };
        }
    }

    public void OnResultExecuted(ResultExecutedContext context)
    {
    }
}

IFilterFactory

IFilterFactory implementa IFilterMetadata. Por tanto, una instancia de IFilterFactory se puede usar como una instancia de IFilterMetadata en cualquier parte de la canalización de filtro. Cuando el entorno de ejecución se prepara para invocar el filtro, intenta convertirlo a un IFilterFactory. Si esa conversión se realiza correctamente, se llama al método CreateInstance para crear la instancia IFilterMetadata que se va a invocar. Esto proporciona un diseño flexible, dado que no hay que establecer la canalización de filtro exacta de forma explícita cuando la aplicación se inicia.

IFilterFactory.IsReusable:

  • Es una indicación por parte de la fábrica de que la instancia de filtro creada por la fábrica puede ser reutilizada fuera del ámbito de solicitud en el que fue creada.
  • No debería usarse con un filtro que dependa de servicios con una duración distinta de singleton.

El entorno de ejecución de ASP.NET Core no garantiza:

  • Que se creará una única instancia del filtro.
  • El filtro no volverá a solicitarse desde el contenedor de inserción de dependencias en algún momento posterior.

Advertencia

Solo configure IFilterFactory.IsReusable para que devuelva true si el origen de los filtros no es ambiguo, los filtros no tienen estado y los filtros son seguros de usar en múltiples peticiones HTTP. Por ejemplo, no devuelva filtros de DI que estén registrados como de ámbito o transitorios si IFilterFactory.IsReusable devuelve true. Puede implementar IFilterFactory con las implementaciones de atributos personalizados como método alternativo para crear filtros:

public class AddHeaderWithFactoryAttribute : Attribute, IFilterFactory
{
    // Implement IFilterFactory
    public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
    {
        return new InternalAddHeaderFilter();
    }

    private class InternalAddHeaderFilter : IResultFilter
    {
        public void OnResultExecuting(ResultExecutingContext context)
        {
            context.HttpContext.Response.Headers.Add(
                "Internal", new string[] { "My header" });
        }

        public void OnResultExecuted(ResultExecutedContext context)
        {
        }
    }

    public bool IsReusable
    {
        get
        {
            return false;
        }
    }
}

El filtro se aplica en el código siguiente:

[AddHeader("Author", "Rick Anderson")]
public class SampleController : Controller
{
    public IActionResult Index()
    {
        return Content("Examine the headers using the F12 developer tools.");
    }

    [ServiceFilter(typeof(MyActionFilterAttribute))]
    public IActionResult Index2()
    {
        return Content("Header values by configuration.");
    }

    [ShortCircuitingResourceFilter]
    public IActionResult SomeResource()
    {
        return Content("Successful access to resource - header is set.");
    }

    [AddHeaderWithFactory]
    public IActionResult HeaderWithFactory()
    {
        return Content("Examine the headers using the F12 developer tools.");
    }
}

Pruebe el código anterior mediante la ejecución del ejemplo de descarga:

  • Invoque las herramientas de desarrollador de F12.
  • Vaya a https://localhost:5001/Sample/HeaderWithFactory.

Las herramientas de desarrollador F12 muestran los siguientes encabezados de respuesta agregados por el código de ejemplo:

  • author:Rick Anderson
  • globaladdheader:Result filter added to MvcOptions.Filters
  • internal:My header

El código anterior crea el encabezado de respuesta internal:My header.

IFilterFactory implementado en un atributo

Los filtros que implementan IFilterFactory son útiles para los filtros que:

  • No requieren pasar parámetros.
  • Tienen dependencias de constructor que deben completarse por medio de la inserción de dependencias.

TypeFilterAttribute implementa IFilterFactory. IFilterFactory expone el método CreateInstance para crear una instancia de IFilterMetadata. CreateInstance carga el tipo especificado desde el contenedor de servicios (inserción de dependencias).

public class SampleActionFilterAttribute : TypeFilterAttribute
{
    public SampleActionFilterAttribute()
                         :base(typeof(SampleActionFilterImpl))
    { 
    }

    private class SampleActionFilterImpl : IActionFilter
    {
        private readonly ILogger _logger;
        public SampleActionFilterImpl(ILoggerFactory loggerFactory)
        {
            _logger = loggerFactory.CreateLogger<SampleActionFilterAttribute>();
        }

        public void OnActionExecuting(ActionExecutingContext context)
        {
           _logger.LogInformation("SampleActionFilterAttribute.OnActionExecuting");
        }

        public void OnActionExecuted(ActionExecutedContext context)
        {
            _logger.LogInformation("SampleActionFilterAttribute.OnActionExecuted");
        }
    }
}

El código siguiente muestra tres métodos para aplicar [SampleActionFilter]:

[SampleActionFilter]
public IActionResult FilterTest()
{
    return Content("From FilterTest");
}

[TypeFilter(typeof(SampleActionFilterAttribute))]
public IActionResult TypeFilterTest()
{
    return Content("From TypeFilterTest");
}

// ServiceFilter must be registered in ConfigureServices or
// System.InvalidOperationException: No service for type '<filter>'
// has been registered. Is thrown.
[ServiceFilter(typeof(SampleActionFilterAttribute))]
public IActionResult ServiceFilterTest()
{
    return Content("From ServiceFilterTest");
}

En el código anterior, decorar el método con [SampleActionFilter] es el enfoque preferido para aplicar SampleActionFilter.

Uso de middleware en la canalización de filtro

Los filtros de recursos funcionan como el middleware, en el sentido de que se encargan de la ejecución de todo lo que viene después en la canalización. Pero los filtros se diferencian del middleware en que forman parte del runtime, lo que significa que tienen acceso al contexto y las construcciones.

Para usar middleware como un filtro, cree un tipo con un método Configure en el que se especifique el middleware para insertar en la canalización de filtro. El ejemplo siguiente usa el middleware de localización para establecer la referencia cultural actual de una solicitud:

public class LocalizationPipeline
{
    public void Configure(IApplicationBuilder applicationBuilder)
    {
        var supportedCultures = new[]
        {
            new CultureInfo("en-US"),
            new CultureInfo("fr")
        };

        var options = new RequestLocalizationOptions
        {
            DefaultRequestCulture = new RequestCulture(
                                       culture: "en-US", 
                                       uiCulture: "en-US"),
            SupportedCultures = supportedCultures,
            SupportedUICultures = supportedCultures
        };
        options.RequestCultureProviders = new[] 
            { new RouteDataRequestCultureProvider() {
                Options = options } };

        applicationBuilder.UseRequestLocalization(options);
    }
}

Use MiddlewareFilterAttribute para ejecutar el middleware:

[Route("{culture}/[controller]/[action]")]
[MiddlewareFilter(typeof(LocalizationPipeline))]
public IActionResult CultureFromRouteData()
{
    return Content(
          $"CurrentCulture:{CultureInfo.CurrentCulture.Name},"
        + $"CurrentUICulture:{CultureInfo.CurrentUICulture.Name}");
}

Los filtros de middleware se ejecutan en la misma fase de la canalización de filtro que los filtros de recursos, antes del enlace de modelos y después del resto de la canalización.

Seguridad para subprocesos

Al pasar una instancia de un filtro a Add, en lugar de a su Type, el filtro es un singleton y no es seguro para subprocesos.

Siguientes acciones