Filtros no ASP.NET Core

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

Os filtros no ASP.NET Core permitem a execução de código antes ou depois de determinados estágios do pipeline de processamento de solicitações.

O filtros internos lidam com tarefas como:

  • Autorização, impedindo o acesso a recursos aos quais o usuário não está autorizado.
  • Cache de resposta, causando um curto-circuito no pipeline de solicitação para retornar uma resposta armazenada em cache.

É possível criar filtros personalizados para lidar com interesses paralelos. Entre os exemplos de interesses paralelos estão o tratamento de erros, cache, configuração, autorização e registro em log. Filtros evitam a duplicação do código. Por exemplo, um filtro de exceção de tratamento de erro poderia consolidar o tratamento de erro.

Este documento se aplica aos Razor Pages, a controladores de API e controladores com exibição. Os filtros não funcionam diretamente com componentes do Razor. Um filtro só pode afetar indiretamente um componente quando:

  • O componente é inserido em uma página ou exibição.
  • A página ou o controlador e a exibição usam o filtro.

Como os filtros funcionam

Os filtros são executados dentro do pipeline de invocação de ações do ASP.NET Core, às vezes chamado de pipeline de filtros. O pipeline de filtros é executado após o ASP.NET Core selecionar a ação a ser executada:

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 é executado em um estágio diferente no pipeline de filtros:

  • Filtros de autorização:

    • São executados primeiro.
    • Determinam se o usuário está autorizado para a solicitação.
    • Fazem um curto-circuito no pipeline se a solicitação não estiver autorizada.
  • Filtros de recurso:

    • Execute após a autorização.
    • OnResourceExecuting executa o código antes do restante do pipeline de filtros. Por exemplo, OnResourceExecuting executa o código antes do model binding.
    • OnResourceExecuted executa o código após a conclusão do restante do pipeline.
  • Filtros de ação:

    • São executados imediatamente antes e depois que o método de ação é chamado.
    • Podem alterar os argumentos passados para uma ação.
    • Podem alterar o resultado retornado da ação.
    • Não há suporte nos Razor Pages.
  • Filtros de ponto de extremidade:

    • São executados imediatamente antes e depois que o método de ação é chamado.
    • Podem alterar os argumentos passados para uma ação.
    • Podem alterar o resultado retornado da ação.
    • Não há suporte nos Razor Pages.
    • Podem ser invocados em ações e pontos de extremidade baseados no manipulador de rotas.
  • Filtros de exceção aplicam políticas globais para exceções sem tratamento que ocorrem antes que o corpo da resposta tenha sido gravado.

  • Filtros de resultado:

    • São executados antes e depois da execução dos resultados da ação.
    • São executados somente quando o método de ação for executado com êxito.
    • São úteis para a lógica que precisa envolver a execução da exibição ou do formatador.

O diagrama a seguir mostra como os tipos de filtro interagem no pipeline de filtros:

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.

Os Razor Pages também dão suporte aos filtros do Razor Page, que são executados antes e depois de um manipulador do Razor Page.

Implementação

Os filtros dão suporte a implementações síncronas e assíncronas por meio de diferentes definições de interface.

Os filtros síncronos são executados antes e depois do estágio de pipeline. Por exemplo, OnActionExecuting é chamado antes que o método de ação seja chamado. OnActionExecuted é chamado após o método de ação retornar:

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.
    }
}

Os filtros assíncronos definem um método On-Stage-ExecutionAsync. Por exemplo 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.
    }
}

No código anterior, o SampleAsyncActionFilter tem um ActionExecutionDelegate, next, que executa o método de ação.

Vários estágios do filtro

É possível implementar interfaces para vários estágios do filtro em uma única classe. Por exemplo, a classe ActionFilterAttribute implementa:

Implemente ou a versão assíncrona ou a versão síncrona de uma interface de filtro, não ambas. Primeiro, o runtime verifica se o filtro implementa a interface assíncrona e, se for esse o caso, a chama. Caso contrário, ela chama os métodos da interface síncrona. Se as interfaces síncrona e assíncrona forem implementadas em uma classe, somente o método assíncrono será chamado. Ao usar classes abstratas como ActionFilterAttribute, substitua apenas os métodos síncronos ou os método assíncronos para cada tipo de filtro.

Atributos de filtro internos

O ASP.NET Core inclui filtros internos baseados em atributos que podem ser organizados em subclasses e personalizados. Por exemplo, o filtro de resultado a seguir adiciona um cabeçalho à resposta:

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);
    }
}

Os atributos permitem que os filtros aceitem argumentos, conforme mostrado no exemplo acima. Aplique o ResponseHeaderAttribute a um controlador ou método de ação e especifique o nome e o valor do cabeçalho HTTP:

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

    // ...

Use uma ferramenta como as ferramentas de desenvolvedor de navegador para examinar os cabeçalhos. Em Cabeçalhos de Resposta, filter-header: Filter Value é exibido.

O código a seguir aplica ResponseHeaderAttribute a um controlador e a uma ação:

[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.");
}

As respostas da ação Multiple incluem os seguintes cabeçalhos:

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

Várias interfaces de filtro têm atributos correspondentes que podem ser usados como classes base para implementações personalizadas.

Atributos de filtro:

Não é possível aplicar filtros aos métodos de manipulador do Razor Page. Eles podem ser aplicados ao modelo de Razor Page ou globalmente.

Escopos e ordem de execução dos filtros

Um filtro pode ser adicionado ao pipeline com um de três escopos:

  • Usando um atributo em um controlador ou o Razor Page.
  • Usando um atributo em uma ação do controlador. Os atributos de filtro não podem ser aplicados aos métodos do manipulador dos Razor Pages.
  • Globalmente para todos os controladores, ações e Razor Pages, como mostra o código a seguir:
    var builder = WebApplication.CreateBuilder(args);
    
    // Add services to the container.
    builder.Services.AddControllersWithViews(options =>
    {
        options.Filters.Add<GlobalSampleActionFilter>();
    });
    

Ordem padrão de execução

Quando há vários filtros para um determinado estágio do pipeline, o escopo determina a ordem padrão de execução dos filtros. Filtros globais circundam filtros de classe, que, por sua vez, circundam filtros de método.

Como resultado do aninhamento de filtro, o código posterior dos filtros é executado na ordem inversa do código anterior. A sequência de filtro:

  • O código anterior dos filtros globais.
    • O código anterior dos filtros de controlador.
      • O código anterior dos filtros de método de ação.
      • O código posterior dos filtros de método de ação.
    • O código posterior dos filtros de controlador.
  • O código posterior dos filtros globais.

O exemplo a seguir ilustra a ordem na qual os métodos de filtro são executados para filtros de ação síncrona:

Sequência Escopo do filtro Método do filtro
1 Global OnActionExecuting
2 Controller OnActionExecuting
3 Ação OnActionExecuting
4 Ação OnActionExecuted
5 Controller OnActionExecuted
6 Global OnActionExecuted

Filtros no nível do controlador

Cada controlador que herda do Controller inclui os métodos OnActionExecuting, OnActionExecutionAsync e OnActionExecuted. Esses métodos encapsulam os filtros executados para uma determinada ação:

  • OnActionExecuting é executado antes de qualquer um dos filtros da ação.
  • OnActionExecuted é executado após todos os filtros da ação.
  • OnActionExecutionAsync é executado antes de qualquer um dos filtros da ação. Um código após uma chamada para next é executado após os filtros da ação.

A seguinte classe ControllerFiltersController:

  • Aplica o SampleActionFilterAttribute ([SampleActionFilter]) ao controlador.
  • Substitui OnActionExecuting e 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.");
    }
}

Navegar até https://localhost:<port>/ControllerFilters executa o seguinte código:

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

Os filtros no nível do controlador definem a propriedade Order como int.MinValue. Os filtros no nível do controlador não podem ser definidos para serem executados após os filtros aplicados aos métodos. Ordem é explicada na próxima seção.

Para os Razor Pages, consulte Implementar filtros dos Razor Pages substituindo os métodos de filtro.

Substituir a ordem padrão

É possível substituir a sequência padrão de execução implementando IOrderedFilter. IOrderedFilter expõe a propriedade Order que tem precedência sobre o escopo para determinar a ordem da execução. Um filtro com um valor de Order menor:

  • Executa o código anterior antes de um filtro com um valor mais alto de Order.
  • Executa o código posterior após um filtro com um valor mais alto de Order.

No exemplo de filtros de nível do controlador, GlobalSampleActionFilter tem escopo global, portanto, ele é executado antes de SampleActionFilterAttribute , que tem escopo do controlador. Para executar SampleActionFilterAttribute primeiro, defina sua ordem como int.MinValue:

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

Para que o filtro global GlobalSampleActionFilter seja executado primeiro, defina sua Order como int.MinValue:

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

Cancelamento e curto-circuito

O pipeline de filtro pode sofrer um curto-circuito por meio da configuração da propriedade Result no parâmetro ResourceExecutingContext fornecido ao método do filtro. Por exemplo, o filtro de Recurso a seguir impede que o resto do pipeline seja executado:

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

    public void OnResourceExecuted(ResourceExecutedContext context) { }
}

No código a seguir, os filtros [ShortCircuitingResourceFilter] e [ResponseHeader] têm como destino o método de ação Index. O filtro ShortCircuitingResourceFilterAttribute:

  • É executado primeiro, porque ele é um filtro de recurso e ResponseHeaderAttribute é um filtro de ação.
  • Causa um curto-circuito no restante do pipeline.

Portanto, o filtro ResponseHeaderAttribute nunca é executado para a ação Index. Esse comportamento seria o mesmo se os dois filtros fossem aplicados no nível do método de ação, desde que ShortCircuitingResourceFilterAttribute fosse executado primeiro. O ShortCircuitingResourceFilterAttribute é executado primeiro devido ao seu tipo de filtro:

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

Injeção de dependência

Filtros podem ser adicionados por tipo ou por instância. Se você adicionar uma instância, ela será usada para cada solicitação. Se um tipo for adicionado, ele será ativado pelo tipo. Um filtro ativado por tipo significa:

  • Uma instância é criada para cada solicitação.
  • Qualquer dependência de construtor é preenchida pela injeção de dependência (DI).

Filtros que são implementados como atributos e adicionados diretamente a classes de controlador ou métodos de ação não podem ter dependências de construtor fornecidas pela DI (injeção de dependência). As dependências do construtor não podem ser fornecidas pela injeção de dependência (DI) porque os atributos devem ter seus parâmetros de construtor fornecidos onde são aplicados.

Os filtros a seguir são compatíveis com dependências de construtor fornecidas pela injeção de dependência:

Os filtros anteriores podem ser aplicados a um controlador ou a uma ação.

Os agentes estão disponíveis na DI. No entanto, evite criar e usar filtros apenas para fins de registro em log. O registro em log da estrutura interna normalmente fornece o que é necessário para o registro em log. Registro em log adicionado aos filtros:

  • Deve se concentrar em questões de domínio de negócios ou comportamento específico ao filtro.
  • Não deve registrar ações ou outros eventos da estrutura. Os filtros internos já registram ações de log e eventos da estrutura.

ServiceFilterAttribute

Os tipos de implementação do filtro de serviço são registrados em Program.cs. Um ServiceFilterAttribute recupera uma instância do filtro da DI.

O código a seguir mostra a classe LoggingResponseHeaderFilterService, que usa DI:

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)}");
    }
}

No código a seguir, LoggingResponseHeaderFilterService é adicionado ao contêiner de DI:

builder.Services.AddScoped<LoggingResponseHeaderFilterService>();

No código a seguir, o atributo ServiceFilter recupera uma instância do filtro LoggingResponseHeaderFilterService da DI:

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

Ao usar ServiceFilterAttribute, a configuração ServiceFilterAttribute.IsReusable:

  • Fornece uma dica de que a instância de filtro pode ser reutilizada fora do escopo de solicitação em que foi criada. O runtime do ASP.NET Core não garante:
    • Que uma única instância do filtro será criada.
    • Que o filtro não será solicitado novamente no contêiner de DI em algum momento posterior.
  • Não deve ser usada com um filtro que dependa de serviços com um tempo de vida de um singleton.

ServiceFilterAttribute implementa IFilterFactory. IFilterFactory expõe o método CreateInstance para criar uma instância de IFilterMetadata. CreateInstance carrega o tipo especificado na DI.

TypeFilterAttribute

O TypeFilterAttribute é semelhante ao ServiceFilterAttribute, mas seu tipo não é resolvido diretamente por meio do contêiner de DI. Ele cria uma instância do tipo usando Microsoft.Extensions.DependencyInjection.ObjectFactory.

Como os tipos TypeFilterAttribute não são resolvidos diretamente do contêiner de DI:

  • Os tipos referenciados usando o TypeFilterAttribute não precisam ser registrados no contêiner de DI. Eles têm suas dependências atendidas pelo contêiner de DI.
  • Opcionalmente, o TypeFilterAttribute pode aceitar argumentos de construtor para o tipo.

Ao usar TypeFilterAttribute, a configuração TypeFilterAttribute.IsReusable:

  • Fornece uma dica de que a instância de filtro pode ser reutilizada fora do escopo de solicitação em que foi criada. O runtime do ASP.NET Core não fornece garantias de que uma única instância do filtro será criada.

  • Não deve ser usado com um filtro que dependa dos serviços com um tempo de vida diferente de singleton.

O exemplo a seguir mostra como passar argumentos para um tipo usando TypeFilterAttribute:

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

Filtros de autorização

Filtros de autorização:

  • São os primeiros filtros executados no pipeline de filtro.
  • Controlam o acesso aos métodos de ação.
  • Têm um método anterior, mas não têm um método posterior.

Filtros de autorização personalizados exigem uma estrutura de autorização personalizada. Prefira configurar as políticas de autorização ou escrever uma política de autorização personalizada em vez de escrever um filtro personalizado. O filtro de autorização interno:

  • Chama o sistema de autorização.
  • Não autoriza solicitações.

Não gera exceções dentro de filtros de autorização:

  • A exceção não será tratada.
  • Os filtros de exceção não tratarão a exceção.

Considere a possibilidade de emitir um desafio quando ocorrer uma exceção em um filtro de autorização.

Saiba mais sobre Autorização.

Filtros de recurso

Filtros de recurso:

Os filtros de recursos são úteis para causar um curto-circuito na maior parte do pipeline. Por exemplo, um filtro de cache pode evitar o restante do pipeline em uma ocorrência no cache.

Exemplos de filtros de recurso:

Filtros de ação

Os filtros de ação não se aplicam aos Razor Pages. Os Razor Pages não são dão suporte a IPageFilter e IAsyncPageFilter. Para obter mais informações, confira Métodos de filtragem para Páginas do Razor.

Filtros de ação:

O código a seguir mostra um exemplo de filtro de ação:

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.
    }
}

A classe ActionExecutingContext fornece as seguintes propriedades:

  • ActionArguments - permite a leitura das entradas para um método de ação.
  • Controller – permite a manipulação da instância do controlador.
  • Result – configuração de Result execução de curtos-circuitos do método de ação e dos filtros de ação posteriores.

Gerar uma exceção em um método de ação:

  • Impede a execução de filtros subsequentes.
  • Ao contrário da configuração Result, é tratada como uma falha e não como um resultado bem-sucedido.

A classe ActionExecutedContext fornece Controller e Result, além das seguintes propriedades:

  • Canceled – verdadeiro se a execução da ação tiver sofrido curto-circuito por outro filtro.
  • Exception – não será nulo se a ação ou um filtro de ação executado antes tiver apresentado uma exceção. Definir essa propriedade como nulo:
    • Trata efetivamente a exceção.
    • Result é executado como se tivesse retornado do método de ação.

Para um IAsyncActionFilter, uma chamada para o ActionExecutionDelegate:

  • Executa todos os próximos filtros de ação e o método de ação.
  • Retorna ActionExecutedContext.

Para fazer um curto-circuito, atribua Microsoft.AspNetCore.Mvc.Filters.ActionExecutingContext.Result a uma instância de resultado e não chame o next (ActionExecutionDelegate).

A estrutura fornece um ActionFilterAttribute abstrato que você pode colocar em uma subclasse.

O filtro de ação OnActionExecuting pode ser usado para:

  • Validar o estado do modelo.
  • Retornar um erro se o estado for inválido.
public class ValidateModelAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        if (!context.ModelState.IsValid)
        {
            context.Result = new BadRequestObjectResult(context.ModelState);
        }
    }
}

Observação

Os controladores anotados com o atributo [ApiController] validam automaticamente o estado do modelo e retornam uma resposta 400. Para obter mais informações, veja Respostas automáticas HTTP 400.

O método OnActionExecuted é executado após o método de ação:

  • E pode ver e manipular os resultados da ação por meio da propriedade Result.
  • Canceled é definido como verdadeiro se a execução da ação tiver sofrido curto-circuito por outro filtro.
  • Exception é definido como um valor não nulo se a ação ou um filtro de ação posterior tiver apresentado uma exceção. Definindo Exception como nulo:
    • Trata efetivamente uma exceção.
    • ActionExecutedContext.Result é executado como se fosse retornado normalmente do método de ação.

Filtros de exceção

Filtros de exceção:

O exemplo de filtro de exceção a seguir exibe detalhes sobre as exceções que ocorrem quando o aplicativo está em desenvolvimento:

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()
        };
    }
}

O código a seguir testa o filtro de exceção:

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

Filtros de exceção:

  • Não têm eventos anteriores nem posteriores.
  • Implementam OnException ou OnExceptionAsync.
  • Tratam as exceções sem tratamento que ocorrem no Razor Page ou na criação do controlador, no model binding, nos filtros de ação ou nos métodos de ação.
  • Não capturam as exceções que ocorrem em filtros de recurso, em filtros de resultado ou na execução do resultado de MVC.

Para tratar uma exceção, defina a propriedade ExceptionHandled como true ou atribua a propriedade Result. Isso interrompe a propagação da exceção. Um filtro de exceção não pode transformar uma exceção em "êxito". Somente um filtro de ação pode fazer isso.

Filtros de exceção:

  • São bons para interceptar as exceções que ocorrem nas ações.
  • Não são tão flexíveis quanto o middleware de tratamento de erro.

Prefira o middleware para tratamento de exceção. Use filtros de exceção apenas quando o tratamento de erros for diferente com base no método de ação chamado. Por exemplo, um aplicativo pode ter métodos de ação para os pontos de extremidade da API e para modos de exibição/HTML. Os pontos de extremidade da API podem retornar informações de erro como JSON, enquanto as ações baseadas em modo de exibição podem retornar uma página de erro como HTML.

Filtros de resultado

Filtros de resultado:

IResultFilter e IAsyncResultFilter

O código a seguir mostra um exemplo de filtro de ação:

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.
    }
}

O tipo de resultado que está sendo executado depende da ação. Uma ação que retorna um modo de exibição inclui todo o processamento do razor como parte do ViewResult em execução. Um método de API pode executar alguma serialização como parte da execução do resultado. Saiba mais sobre resultados de ação.

Os filtros de resultado somente são executados quando uma ação ou filtro de ação produz um resultado de ação. Os filtros de resultado não são executados quando:

  • Um filtro de autorização ou filtro de recurso causa um curto-circuito no pipeline.
  • Um filtro de exceção trata uma exceção produzindo um resultado de ação.

O método Microsoft.AspNetCore.Mvc.Filters.IResultFilter.OnResultExecuting pode fazer o curto-circuito da execução do resultado da ação e dos filtros de resultados posteriores definindo Microsoft.AspNetCore.Mvc.Filters.ResultExecutingContext.Cancel como true. Grave no objeto de resposta ao fazer um curto-circuito para evitar gerar uma resposta vazia. Gerar uma exceção em IResultFilter.OnResultExecuting:

  • Impede a execução do resultado da ação e dos próximos filtros.
  • É tratado como uma falha e não como um resultado com êxito.

Quando o método Microsoft.AspNetCore.Mvc.Filters.IResultFilter.OnResultExecuted é executado, a resposta provavelmente já foi enviada ao cliente. Se a resposta já tiver sido enviada ao cliente, ela não poderá ser alterada.

ResultExecutedContext.Canceled será definido como true se a execução do resultado da ação tiver sofrido curto-circuito por outro filtro.

ResultExecutedContext.Exception será definido como um valor não nulo se o resultado da ação ou um filtro de resultado posterior tiver gerado uma exceção. Definir Exception como nula trata uma exceção com eficiência e impede que a exceção seja apresentada novamente no pipeline. Não há nenhuma maneira confiável para gravar dados em uma resposta ao manipular uma exceção em um filtro de resultado. Se os cabeçalhos tiverem sido liberados para o cliente quando o resultado de uma ação gerar uma exceção, não haverá mecanismo confiável para enviar um código de falha.

Para um IAsyncResultFilter, uma chamada para await next no ResultExecutionDelegate executa qualquer filtro de resultado posterior e o resultado da ação. Para fazer um curto-circuito, defina ResultExecutingContext.Cancel para true e não chame o 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;
        }
    }
}

A estrutura fornece um ResultFilterAttribute abstrato que você pode colocar em uma subclasse. A classe AddHeaderAttribute mostrada anteriormente é um exemplo de atributo de filtro de resultado.

IAlwaysRunResultFilter e IAsyncAlwaysRunResultFilter

As interfaces IAlwaysRunResultFilter e IAsyncAlwaysRunResultFilter declaram uma implementação IResultFilter que é executada para todos os resultados da ação. Isso inclui os resultados da ação produzidos por:

  • Filtros de autorização e filtros de recursos que fazem curto-circuito.
  • Filtros de exceção.

Por exemplo, o filtro a seguir sempre é executado e define o resultado de uma ação (ObjectResult) com um código de status 422 Entidade Não Processável quando ocorre falha na negociação de conteúdo:

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. Portanto, uma instância IFilterFactory pode ser usada como uma instância IFilterMetadata em qualquer parte do pipeline de filtro. Quando o runtime se prepara para invocar o filtro, tenta convertê-lo em um IFilterFactory. Se essa conversão for bem-sucedida, o método CreateInstance será chamado para criar a instância IFilterMetadata invocada. Isso fornece um design flexível, porque o pipeline de filtro preciso não precisa ser definido explicitamente quando o aplicativo é iniciado.

IFilterFactory.IsReusable:

  • É uma indicação de que a instância de filtro criada pelo alocador pode ser reutilizada fora do escopo da solicitação em que foi criada.
  • Não deve ser usada com um filtro que dependa de serviços com um tempo de vida de um singleton.

O runtime do ASP.NET Core não garante:

  • Que uma única instância do filtro será criada.
  • Que o filtro não será solicitado novamente no contêiner de DI em algum momento posterior.

Aviso

Configure IFilterFactory.IsReusable apenas para retornar true se a origem dos filtros for inequívoca, os filtros estiverem sem estado e os filtros forem seguros para usar em várias solicitações HTTP. Por exemplo, não retorne filtros de DI registrados como com escopo ou transitórios se IFilterFactory.IsReusable retornar true.

Implemente IFilterFactory usando implementações personalizadas de atributo como outra abordagem à criação de 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) { }
    }

O filtro é aplicado no seguinte código:

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

IFilterFactory implementado em um atributo

Filtros que implementam IFilterFactory são úteis para filtros que:

  • Não exigem a passagem de parâmetros.
  • Tenha dependências de construtor que precisem ser atendidas pela DI.

TypeFilterAttribute implementa IFilterFactory. IFilterFactory expõe o método CreateInstance para criar uma instância de IFilterMetadata. CreateInstance carrega o tipo especificado do contêiner de serviços (DI).

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)}");
        }
    }
}

O código a seguir mostra três abordagens para aplicar o 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)}");

No código anterior, a primeira abordagem para aplicar o filtro é preferencial.

Usando middleware no pipeline de filtros

Filtros de recursos funcionam como middleware, no sentido em que envolvem a execução de tudo o que vem depois no pipeline. No entanto, os filtros diferem do middleware porque fazem parte do runtime, o que significa que eles têm acesso ao contexto e aos constructos.

Para usar o middleware como um filtro, crie um tipo com um método Configure que especifica o middleware para injeção no pipeline de filtros. O exemplo a seguir usa middleware para definir um cabeçalho de resposta:

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

            await next();
        });
    }
}

Use MiddlewareFilterAttribute para executar o middleware:

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

Filtros de middleware são executados no mesmo estágio do pipeline de filtros que filtros de recurso, antes do model binding e depois do restante do pipeline.

Acesso thread-safe

Ao passar uma instância de um filtro para Add, em vez de para seu Type, o filtro é um singleton e não é thread-safe.

Recursos adicionais

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

Os filtros no ASP.NET Core permitem a execução de código antes ou depois de determinados estágios do pipeline de processamento de solicitações.

O filtros internos lidam com tarefas como:

  • Autorização, impedindo o acesso a recursos aos quais o usuário não está autorizado.
  • Cache de resposta, causando um curto-circuito no pipeline de solicitação para retornar uma resposta armazenada em cache.

É possível criar filtros personalizados para lidar com interesses paralelos. Entre os exemplos de interesses paralelos estão o tratamento de erros, cache, configuração, autorização e registro em log. Filtros evitam a duplicação do código. Por exemplo, um filtro de exceção de tratamento de erro poderia consolidar o tratamento de erro.

Este documento se aplica aos Razor Pages, a controladores de API e controladores com exibição. Os filtros não funcionam diretamente com componentes do Razor. Um filtro só pode afetar indiretamente um componente quando:

  • O componente é inserido em uma página ou exibição.
  • A página ou o controlador e a exibição usam o filtro.

Como os filtros funcionam

Os filtros são executados dentro do pipeline de invocação de ações do ASP.NET Core, às vezes chamado de pipeline de filtros. O pipeline de filtros é executado após o ASP.NET Core selecionar a ação a ser executada:

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 é executado em um estágio diferente no pipeline de filtros:

  • Filtros de autorização:

    • São executados primeiro.
    • Determinam se o usuário está autorizado para a solicitação.
    • Fazem um curto-circuito no pipeline se a solicitação não estiver autorizada.
  • Filtros de recurso:

    • Execute após a autorização.
    • OnResourceExecuting executa o código antes do restante do pipeline de filtros. Por exemplo, OnResourceExecuting executa o código antes do model binding.
    • OnResourceExecuted executa o código após a conclusão do restante do pipeline.
  • Filtros de ação:

    • São executados imediatamente antes e depois que o método de ação é chamado.
    • Podem alterar os argumentos passados para uma ação.
    • Podem alterar o resultado retornado da ação.
    • Não há suporte nos Razor Pages.
  • Filtros de ponto de extremidade:

    • São executados imediatamente antes e depois que o método de ação é chamado.
    • Podem alterar os argumentos passados para uma ação.
    • Podem alterar o resultado retornado da ação.
    • Não há suporte nos Razor Pages.
    • Podem ser invocados em ações e pontos de extremidade baseados no manipulador de rotas.
  • Filtros de exceção aplicam políticas globais para exceções sem tratamento que ocorrem antes que o corpo da resposta tenha sido gravado.

  • Filtros de resultado:

    • São executados antes e depois da execução dos resultados da ação.
    • São executados somente quando o método de ação for executado com êxito.
    • São úteis para a lógica que precisa envolver a execução da exibição ou do formatador.

O diagrama a seguir mostra como os tipos de filtro interagem no pipeline de filtros:

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.

Os Razor Pages também dão suporte aos filtros do Razor Page, que são executados antes e depois de um manipulador do Razor Page.

Implementação

Os filtros dão suporte a implementações síncronas e assíncronas por meio de diferentes definições de interface.

Os filtros síncronos são executados antes e depois do estágio de pipeline. Por exemplo, OnActionExecuting é chamado antes que o método de ação seja chamado. OnActionExecuted é chamado após o método de ação retornar:

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.
    }
}

Os filtros assíncronos definem um método On-Stage-ExecutionAsync. Por exemplo 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.
    }
}

No código anterior, o SampleAsyncActionFilter tem um ActionExecutionDelegate, next, que executa o método de ação.

Vários estágios do filtro

É possível implementar interfaces para vários estágios do filtro em uma única classe. Por exemplo, a classe ActionFilterAttribute implementa:

Implemente ou a versão assíncrona ou a versão síncrona de uma interface de filtro, não ambas. Primeiro, o runtime verifica se o filtro implementa a interface assíncrona e, se for esse o caso, a chama. Caso contrário, ela chama os métodos da interface síncrona. Se as interfaces síncrona e assíncrona forem implementadas em uma classe, somente o método assíncrono será chamado. Ao usar classes abstratas como ActionFilterAttribute, substitua apenas os métodos síncronos ou os método assíncronos para cada tipo de filtro.

Atributos de filtro internos

O ASP.NET Core inclui filtros internos baseados em atributos que podem ser organizados em subclasses e personalizados. Por exemplo, o filtro de resultado a seguir adiciona um cabeçalho à resposta:

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);
    }
}

Os atributos permitem que os filtros aceitem argumentos, conforme mostrado no exemplo acima. Aplique o ResponseHeaderAttribute a um controlador ou método de ação e especifique o nome e o valor do cabeçalho HTTP:

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

    // ...

Use uma ferramenta como as ferramentas de desenvolvedor de navegador para examinar os cabeçalhos. Em Cabeçalhos de Resposta, filter-header: Filter Value é exibido.

O código a seguir aplica ResponseHeaderAttribute a um controlador e a uma ação:

[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.");
}

As respostas da ação Multiple incluem os seguintes cabeçalhos:

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

Várias interfaces de filtro têm atributos correspondentes que podem ser usados como classes base para implementações personalizadas.

Atributos de filtro:

Não é possível aplicar filtros aos métodos de manipulador do Razor Page. Eles podem ser aplicados ao modelo de Razor Page ou globalmente.

Escopos e ordem de execução dos filtros

Um filtro pode ser adicionado ao pipeline com um de três escopos:

  • Usando um atributo em um controlador ou o Razor Page.
  • Usando um atributo em uma ação do controlador. Os atributos de filtro não podem ser aplicados aos métodos do manipulador dos Razor Pages.
  • Globalmente para todos os controladores, ações e Razor Pages, como mostra o código a seguir:
    var builder = WebApplication.CreateBuilder(args);
    
    // Add services to the container.
    builder.Services.AddControllersWithViews(options =>
    {
        options.Filters.Add<GlobalSampleActionFilter>();
    });
    

Ordem padrão de execução

Quando há vários filtros para um determinado estágio do pipeline, o escopo determina a ordem padrão de execução dos filtros. Filtros globais circundam filtros de classe, que, por sua vez, circundam filtros de método.

Como resultado do aninhamento de filtro, o código posterior dos filtros é executado na ordem inversa do código anterior. A sequência de filtro:

  • O código anterior dos filtros globais.
    • O código anterior dos filtros de controlador.
      • O código anterior dos filtros de método de ação.
      • O código posterior dos filtros de método de ação.
    • O código posterior dos filtros de controlador.
  • O código posterior dos filtros globais.

O exemplo a seguir ilustra a ordem na qual os métodos de filtro são executados para filtros de ação síncrona:

Sequência Escopo do filtro Método do filtro
1 Global OnActionExecuting
2 Controller OnActionExecuting
3 Ação OnActionExecuting
4 Ação OnActionExecuted
5 Controller OnActionExecuted
6 Global OnActionExecuted

Filtros no nível do controlador

Cada controlador que herda do Controller inclui os métodos OnActionExecuting, OnActionExecutionAsync e OnActionExecuted. Esses métodos encapsulam os filtros executados para uma determinada ação:

  • OnActionExecuting é executado antes de qualquer um dos filtros da ação.
  • OnActionExecuted é executado após todos os filtros da ação.
  • OnActionExecutionAsync é executado antes de qualquer um dos filtros da ação. Um código após uma chamada para next é executado após os filtros da ação.

A seguinte classe ControllerFiltersController:

  • Aplica o SampleActionFilterAttribute ([SampleActionFilter]) ao controlador.
  • Substitui OnActionExecuting e 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.");
    }
}

Navegar até https://localhost:<port>/ControllerFilters executa o seguinte código:

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

Os filtros no nível do controlador definem a propriedade Order como int.MinValue. Os filtros no nível do controlador não podem ser definidos para serem executados após os filtros aplicados aos métodos. Ordem é explicada na próxima seção.

Para os Razor Pages, consulte Implementar filtros dos Razor Pages substituindo os métodos de filtro.

Substituir a ordem padrão

É possível substituir a sequência padrão de execução implementando IOrderedFilter. IOrderedFilter expõe a propriedade Order que tem precedência sobre o escopo para determinar a ordem da execução. Um filtro com um valor de Order menor:

  • Executa o código anterior antes de um filtro com um valor mais alto de Order.
  • Executa o código posterior após um filtro com um valor mais alto de Order.

No exemplo de filtros de nível do controlador, GlobalSampleActionFilter tem escopo global, portanto, ele é executado antes de SampleActionFilterAttribute , que tem escopo do controlador. Para executar SampleActionFilterAttribute primeiro, defina sua ordem como int.MinValue:

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

Para que o filtro global GlobalSampleActionFilter seja executado primeiro, defina sua Order como int.MinValue:

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

Cancelamento e curto-circuito

O pipeline de filtro pode sofrer um curto-circuito por meio da configuração da propriedade Result no parâmetro ResourceExecutingContext fornecido ao método do filtro. Por exemplo, o filtro de Recurso a seguir impede que o resto do pipeline seja executado:

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

    public void OnResourceExecuted(ResourceExecutedContext context) { }
}

No código a seguir, os filtros [ShortCircuitingResourceFilter] e [ResponseHeader] têm como destino o método de ação Index. O filtro ShortCircuitingResourceFilterAttribute:

  • É executado primeiro, porque ele é um filtro de recurso e ResponseHeaderAttribute é um filtro de ação.
  • Causa um curto-circuito no restante do pipeline.

Portanto, o filtro ResponseHeaderAttribute nunca é executado para a ação Index. Esse comportamento seria o mesmo se os dois filtros fossem aplicados no nível do método de ação, desde que ShortCircuitingResourceFilterAttribute fosse executado primeiro. O ShortCircuitingResourceFilterAttribute é executado primeiro devido ao seu tipo de filtro:

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

Injeção de dependência

Filtros podem ser adicionados por tipo ou por instância. Se você adicionar uma instância, ela será usada para cada solicitação. Se um tipo for adicionado, ele será ativado pelo tipo. Um filtro ativado por tipo significa:

  • Uma instância é criada para cada solicitação.
  • Qualquer dependência de construtor é preenchida pela injeção de dependência (DI).

Filtros que são implementados como atributos e adicionados diretamente a classes de controlador ou métodos de ação não podem ter dependências de construtor fornecidas pela DI (injeção de dependência). As dependências do construtor não podem ser fornecidas pela injeção de dependência (DI) porque os atributos devem ter seus parâmetros de construtor fornecidos onde são aplicados.

Os filtros a seguir são compatíveis com dependências de construtor fornecidas pela injeção de dependência:

Os filtros anteriores podem ser aplicados a um controlador ou a uma ação.

Os agentes estão disponíveis na DI. No entanto, evite criar e usar filtros apenas para fins de registro em log. O registro em log da estrutura interna normalmente fornece o que é necessário para o registro em log. Registro em log adicionado aos filtros:

  • Deve se concentrar em questões de domínio de negócios ou comportamento específico ao filtro.
  • Não deve registrar ações ou outros eventos da estrutura. Os filtros internos já registram ações de log e eventos da estrutura.

ServiceFilterAttribute

Os tipos de implementação do filtro de serviço são registrados em Program.cs. Um ServiceFilterAttribute recupera uma instância do filtro da DI.

O código a seguir mostra a classe LoggingResponseHeaderFilterService, que usa DI:

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)}");
    }
}

No código a seguir, LoggingResponseHeaderFilterService é adicionado ao contêiner de DI:

builder.Services.AddScoped<LoggingResponseHeaderFilterService>();

No código a seguir, o atributo ServiceFilter recupera uma instância do filtro LoggingResponseHeaderFilterService da DI:

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

Ao usar ServiceFilterAttribute, a configuração ServiceFilterAttribute.IsReusable:

  • Fornece uma dica de que a instância de filtro pode ser reutilizada fora do escopo de solicitação em que foi criada. O runtime do ASP.NET Core não garante:
    • Que uma única instância do filtro será criada.
    • Que o filtro não será solicitado novamente no contêiner de DI em algum momento posterior.
  • Não deve ser usada com um filtro que dependa de serviços com um tempo de vida de um singleton.

ServiceFilterAttribute implementa IFilterFactory. IFilterFactory expõe o método CreateInstance para criar uma instância de IFilterMetadata. CreateInstance carrega o tipo especificado na DI.

TypeFilterAttribute

O TypeFilterAttribute é semelhante ao ServiceFilterAttribute, mas seu tipo não é resolvido diretamente por meio do contêiner de DI. Ele cria uma instância do tipo usando Microsoft.Extensions.DependencyInjection.ObjectFactory.

Como os tipos TypeFilterAttribute não são resolvidos diretamente do contêiner de DI:

  • Os tipos referenciados usando o TypeFilterAttribute não precisam ser registrados no contêiner de DI. Eles têm suas dependências atendidas pelo contêiner de DI.
  • Opcionalmente, o TypeFilterAttribute pode aceitar argumentos de construtor para o tipo.

Ao usar TypeFilterAttribute, a configuração TypeFilterAttribute.IsReusable:

  • Fornece uma dica de que a instância de filtro pode ser reutilizada fora do escopo de solicitação em que foi criada. O runtime do ASP.NET Core não fornece garantias de que uma única instância do filtro será criada.

  • Não deve ser usado com um filtro que dependa dos serviços com um tempo de vida diferente de singleton.

O exemplo a seguir mostra como passar argumentos para um tipo usando TypeFilterAttribute:

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

Filtros de autorização

Filtros de autorização:

  • São os primeiros filtros executados no pipeline de filtro.
  • Controlam o acesso aos métodos de ação.
  • Têm um método anterior, mas não têm um método posterior.

Filtros de autorização personalizados exigem uma estrutura de autorização personalizada. Prefira configurar as políticas de autorização ou escrever uma política de autorização personalizada em vez de escrever um filtro personalizado. O filtro de autorização interno:

  • Chama o sistema de autorização.
  • Não autoriza solicitações.

Não gera exceções dentro de filtros de autorização:

  • A exceção não será tratada.
  • Os filtros de exceção não tratarão a exceção.

Considere a possibilidade de emitir um desafio quando ocorrer uma exceção em um filtro de autorização.

Saiba mais sobre Autorização.

Filtros de recurso

Filtros de recurso:

Os filtros de recursos são úteis para causar um curto-circuito na maior parte do pipeline. Por exemplo, um filtro de cache pode evitar o restante do pipeline em uma ocorrência no cache.

Exemplos de filtros de recurso:

Filtros de ação

Os filtros de ação não se aplicam aos Razor Pages. Os Razor Pages não são dão suporte a IPageFilter e IAsyncPageFilter. Para obter mais informações, confira Métodos de filtragem para Páginas do Razor.

Filtros de ação:

O código a seguir mostra um exemplo de filtro de ação:

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.
    }
}

A classe ActionExecutingContext fornece as seguintes propriedades:

  • ActionArguments - permite a leitura das entradas para um método de ação.
  • Controller – permite a manipulação da instância do controlador.
  • Result – configuração de Result execução de curtos-circuitos do método de ação e dos filtros de ação posteriores.

Gerar uma exceção em um método de ação:

  • Impede a execução de filtros subsequentes.
  • Ao contrário da configuração Result, é tratada como uma falha e não como um resultado bem-sucedido.

A classe ActionExecutedContext fornece Controller e Result, além das seguintes propriedades:

  • Canceled – verdadeiro se a execução da ação tiver sofrido curto-circuito por outro filtro.
  • Exception – não será nulo se a ação ou um filtro de ação executado antes tiver apresentado uma exceção. Definir essa propriedade como nulo:
    • Trata efetivamente a exceção.
    • Result é executado como se tivesse retornado do método de ação.

Para um IAsyncActionFilter, uma chamada para o ActionExecutionDelegate:

  • Executa todos os próximos filtros de ação e o método de ação.
  • Retorna ActionExecutedContext.

Para fazer um curto-circuito, atribua Microsoft.AspNetCore.Mvc.Filters.ActionExecutingContext.Result a uma instância de resultado e não chame o next (ActionExecutionDelegate).

A estrutura fornece um ActionFilterAttribute abstrato que você pode colocar em uma subclasse.

O filtro de ação OnActionExecuting pode ser usado para:

  • Validar o estado do modelo.
  • Retornar um erro se o estado for inválido.
public class ValidateModelAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        if (!context.ModelState.IsValid)
        {
            context.Result = new BadRequestObjectResult(context.ModelState);
        }
    }
}

Observação

Os controladores anotados com o atributo [ApiController] validam automaticamente o estado do modelo e retornam uma resposta 400. Para obter mais informações, veja Respostas automáticas HTTP 400.

O método OnActionExecuted é executado após o método de ação:

  • E pode ver e manipular os resultados da ação por meio da propriedade Result.
  • Canceled é definido como verdadeiro se a execução da ação tiver sofrido curto-circuito por outro filtro.
  • Exception é definido como um valor não nulo se a ação ou um filtro de ação posterior tiver apresentado uma exceção. Definindo Exception como nulo:
    • Trata efetivamente uma exceção.
    • ActionExecutedContext.Result é executado como se fosse retornado normalmente do método de ação.

Filtros de exceção

Filtros de exceção:

O exemplo de filtro de exceção a seguir exibe detalhes sobre as exceções que ocorrem quando o aplicativo está em desenvolvimento:

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()
        };
    }
}

O código a seguir testa o filtro de exceção:

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

Filtros de exceção:

  • Não têm eventos anteriores nem posteriores.
  • Implementam OnException ou OnExceptionAsync.
  • Tratam as exceções sem tratamento que ocorrem no Razor Page ou na criação do controlador, no model binding, nos filtros de ação ou nos métodos de ação.
  • Não capturam as exceções que ocorrem em filtros de recurso, em filtros de resultado ou na execução do resultado de MVC.

Para tratar uma exceção, defina a propriedade ExceptionHandled como true ou atribua a propriedade Result. Isso interrompe a propagação da exceção. Um filtro de exceção não pode transformar uma exceção em "êxito". Somente um filtro de ação pode fazer isso.

Filtros de exceção:

  • São bons para interceptar as exceções que ocorrem nas ações.
  • Não são tão flexíveis quanto o middleware de tratamento de erro.

Prefira o middleware para tratamento de exceção. Use filtros de exceção apenas quando o tratamento de erros for diferente com base no método de ação chamado. Por exemplo, um aplicativo pode ter métodos de ação para os pontos de extremidade da API e para modos de exibição/HTML. Os pontos de extremidade da API podem retornar informações de erro como JSON, enquanto as ações baseadas em modo de exibição podem retornar uma página de erro como HTML.

Filtros de resultado

Filtros de resultado:

IResultFilter e IAsyncResultFilter

O código a seguir mostra um exemplo de filtro de ação:

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.
    }
}

O tipo de resultado que está sendo executado depende da ação. Uma ação que retorna um modo de exibição inclui todo o processamento do razor como parte do ViewResult em execução. Um método de API pode executar alguma serialização como parte da execução do resultado. Saiba mais sobre resultados de ação.

Os filtros de resultado somente são executados quando uma ação ou filtro de ação produz um resultado de ação. Os filtros de resultado não são executados quando:

  • Um filtro de autorização ou filtro de recurso causa um curto-circuito no pipeline.
  • Um filtro de exceção trata uma exceção produzindo um resultado de ação.

O método Microsoft.AspNetCore.Mvc.Filters.IResultFilter.OnResultExecuting pode fazer o curto-circuito da execução do resultado da ação e dos filtros de resultados posteriores definindo Microsoft.AspNetCore.Mvc.Filters.ResultExecutingContext.Cancel como true. Grave no objeto de resposta ao fazer um curto-circuito para evitar gerar uma resposta vazia. Gerar uma exceção em IResultFilter.OnResultExecuting:

  • Impede a execução do resultado da ação e dos próximos filtros.
  • É tratado como uma falha e não como um resultado com êxito.

Quando o método Microsoft.AspNetCore.Mvc.Filters.IResultFilter.OnResultExecuted é executado, a resposta provavelmente já foi enviada ao cliente. Se a resposta já tiver sido enviada ao cliente, ela não poderá ser alterada.

ResultExecutedContext.Canceled será definido como true se a execução do resultado da ação tiver sofrido curto-circuito por outro filtro.

ResultExecutedContext.Exception será definido como um valor não nulo se o resultado da ação ou um filtro de resultado posterior tiver gerado uma exceção. Definir Exception como nula trata uma exceção com eficiência e impede que a exceção seja apresentada novamente no pipeline. Não há nenhuma maneira confiável para gravar dados em uma resposta ao manipular uma exceção em um filtro de resultado. Se os cabeçalhos tiverem sido liberados para o cliente quando o resultado de uma ação gerar uma exceção, não haverá mecanismo confiável para enviar um código de falha.

Para um IAsyncResultFilter, uma chamada para await next no ResultExecutionDelegate executa qualquer filtro de resultado posterior e o resultado da ação. Para fazer um curto-circuito, defina ResultExecutingContext.Cancel para true e não chame o 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;
        }
    }
}

A estrutura fornece um ResultFilterAttribute abstrato que você pode colocar em uma subclasse. A classe AddHeaderAttribute mostrada anteriormente é um exemplo de atributo de filtro de resultado.

IAlwaysRunResultFilter e IAsyncAlwaysRunResultFilter

As interfaces IAlwaysRunResultFilter e IAsyncAlwaysRunResultFilter declaram uma implementação IResultFilter que é executada para todos os resultados da ação. Isso inclui os resultados da ação produzidos por:

  • Filtros de autorização e filtros de recursos que fazem curto-circuito.
  • Filtros de exceção.

Por exemplo, o filtro a seguir sempre é executado e define o resultado de uma ação (ObjectResult) com um código de status 422 Entidade Não Processável quando ocorre falha na negociação de conteúdo:

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. Portanto, uma instância IFilterFactory pode ser usada como uma instância IFilterMetadata em qualquer parte do pipeline de filtro. Quando o runtime se prepara para invocar o filtro, tenta convertê-lo em um IFilterFactory. Se essa conversão for bem-sucedida, o método CreateInstance será chamado para criar a instância IFilterMetadata invocada. Isso fornece um design flexível, porque o pipeline de filtro preciso não precisa ser definido explicitamente quando o aplicativo é iniciado.

IFilterFactory.IsReusable:

  • É uma indicação de que a instância de filtro criada pelo alocador pode ser reutilizada fora do escopo da solicitação em que foi criada.
  • Não deve ser usada com um filtro que dependa de serviços com um tempo de vida de um singleton.

O runtime do ASP.NET Core não garante:

  • Que uma única instância do filtro será criada.
  • Que o filtro não será solicitado novamente no contêiner de DI em algum momento posterior.

Aviso

Configure IFilterFactory.IsReusable apenas para retornar true se a origem dos filtros for inequívoca, os filtros estiverem sem estado e os filtros forem seguros para usar em várias solicitações HTTP. Por exemplo, não retorne filtros de DI registrados como com escopo ou transitórios se IFilterFactory.IsReusable retornar true.

Implemente IFilterFactory usando implementações personalizadas de atributo como outra abordagem à criação de 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) { }
    }

O filtro é aplicado no seguinte código:

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

IFilterFactory implementado em um atributo

Filtros que implementam IFilterFactory são úteis para filtros que:

  • Não exigem a passagem de parâmetros.
  • Tenha dependências de construtor que precisem ser atendidas pela DI.

TypeFilterAttribute implementa IFilterFactory. IFilterFactory expõe o método CreateInstance para criar uma instância de IFilterMetadata. CreateInstance carrega o tipo especificado do contêiner de serviços (DI).

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)}");
        }
    }
}

O código a seguir mostra três abordagens para aplicar o 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)}");

No código anterior, a primeira abordagem para aplicar o filtro é preferencial.

Usando middleware no pipeline de filtros

Filtros de recursos funcionam como middleware, no sentido em que envolvem a execução de tudo o que vem depois no pipeline. No entanto, os filtros diferem do middleware porque fazem parte do runtime, o que significa que eles têm acesso ao contexto e aos constructos.

Para usar o middleware como um filtro, crie um tipo com um método Configure que especifica o middleware para injeção no pipeline de filtros. O exemplo a seguir usa middleware para definir um cabeçalho de resposta:

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

            await next();
        });
    }
}

Use MiddlewareFilterAttribute para executar o middleware:

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

Filtros de middleware são executados no mesmo estágio do pipeline de filtros que filtros de recurso, antes do model binding e depois do restante do pipeline.

Acesso thread-safe

Ao passar uma instância de um filtro para Add, em vez de para seu Type, o filtro é um singleton e não é thread-safe.

Recursos adicionais

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

Os Filtros no ASP.NET Core permitem a execução de código antes ou depois de determinados estágios do pipeline de processamento de solicitações.

O filtros internos lidam com tarefas como:

  • Autorização, impedindo o acesso a recursos aos quais o usuário não está autorizado.
  • Cache de resposta, causando um curto-circuito no pipeline de solicitação para retornar uma resposta armazenada em cache.

É possível criar filtros personalizados para lidar com interesses paralelos. Entre os exemplos de interesses paralelos estão o tratamento de erros, cache, configuração, autorização e registro em log. Filtros evitam a duplicação do código. Por exemplo, um filtro de exceção de tratamento de erro poderia consolidar o tratamento de erro.

Este documento se aplica aos Razor Pages, a controladores de API e controladores com exibição. Os filtros não funcionam diretamente com componentes do Razor. Um filtro só pode afetar indiretamente um componente quando:

  • O componente é inserido em uma página ou exibição.
  • A página ou o controlador e a exibição usam o filtro.

Exibir ou baixar exemplo (como baixar).

Como os filtros funcionam

Os filtros são executados dentro do pipeline de invocação de ações do ASP.NET Core, às vezes chamado de pipeline de filtros. O pipeline de filtros é executado após o ASP.NET Core selecionar a ação a ser executada.

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 é executado em um estágio diferente no pipeline de filtros:

  • Filtros de autorização são executados primeiro e são usados para determinar se o usuário tem autorização para a solicitação. Os filtros de autorização podem causar um curto-circuito no pipeline se uma solicitação não for autorizada.

  • Filtros de recurso:

    • Execute após a autorização.
    • OnResourceExecuting executa o código antes do restante do pipeline de filtros. Por exemplo, OnResourceExecuting executa o código antes do model binding.
    • OnResourceExecuted executa o código após a conclusão do restante do pipeline.
  • Filtros de ação:

    • São executados imediatamente antes e depois que o método de ação é chamado.
    • Podem alterar os argumentos passados para uma ação.
    • Podem alterar o resultado retornado da ação.
    • Não há suporte nos Razor Pages.
  • Filtros de exceção aplicam políticas globais para as exceções sem tratamento que ocorrem antes que o corpo da resposta tenha sido gravado.

  • Filtros de resposta executam código imediatamente antes e depois da execução de resultados de ações. Eles são executados somente quando o método de ação é executado com êxito. Eles são úteis para a lógica que precisa envolver a execução da exibição ou do formatador.

O diagrama a seguir mostra como os tipos de filtro interagem no pipeline de filtros.

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.

Implementação

Os filtros dão suporte a implementações síncronas e assíncronas por meio de diferentes definições de interface.

Os filtros síncronos executam o código antes e depois do estágio de pipeline. Por exemplo, OnActionExecuting é chamado antes que o método de ação seja chamado. OnActionExecuted é chamado após o método de ação retornar.

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);
    }
}

No código anterior, MyDebug é uma função de utilitário no download de exemplo.

Os filtros assíncronos definem um método On-Stage-ExecutionAsync. Por exemplo 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.
    }
}

No código anterior, o SampleAsyncActionFilter tem um ActionExecutionDelegate (next) que executa o método de ação.

Vários estágios do filtro

É possível implementar interfaces para vários estágios do filtro em uma única classe. Por exemplo, a classe ActionFilterAttribute implementa:

Implemente ou a versão assíncrona ou a versão síncrona de uma interface de filtro, não ambas. Primeiro, o runtime verifica se o filtro implementa a interface assíncrona e, se for esse o caso, a chama. Caso contrário, ela chama os métodos da interface síncrona. Se as interfaces síncrona e assíncrona forem implementadas em uma classe, somente o método assíncrono será chamado. Ao usar classes abstratas como ActionFilterAttribute, substitua apenas os métodos síncronos ou os método assíncronos para cada tipo de filtro.

Atributos de filtro internos

O ASP.NET Core inclui filtros internos baseados em atributos que podem ser organizados em subclasses e personalizados. Por exemplo, o filtro de resultado a seguir adiciona um cabeçalho à resposta:

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);
    }
}

Os atributos permitem que os filtros aceitem argumentos, conforme mostrado no exemplo acima. Aplique o AddHeaderAttribute a um controlador ou método de ação e especifique o nome e o valor do cabeçalho HTTP:

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

Use uma ferramenta como as ferramentas de desenvolvedor de navegador para examinar os cabeçalhos. Em Cabeçalhos de Resposta, author: Rick Anderson é exibido.

O código a seguir implementa um ActionFilterAttribute que:

  • Lê o título e o nome do sistema de configuração. Ao contrário do exemplo anterior, o código a seguir não exige que parâmetros de filtro sejam adicionados ao código.
  • Adiciona o título e o nome ao cabeçalho de resposta.
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);
    }
}

As opções de configuração são fornecidas do sistema de configuração usando o padrão de opções. Por exemplo, do arquivo appsettings.json:

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

No StartUp.ConfigureServices:

  • A classe PositionOptions é adicionada ao contêiner de serviço com a área de configuração "Position".
  • O MyActionFilterAttribute é adicionado ao contêiner de serviço.
public void ConfigureServices(IServiceCollection services)
{
    services.Configure<PositionOptions>(
             Configuration.GetSection("Position"));
    services.AddScoped<MyActionFilterAttribute>();

    services.AddControllersWithViews();
}

O código a seguir mostra a classe PositionOptions:

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

O código a seguir aplica MyActionFilterAttribute ao 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.");
    }

Em Cabeçalhos de Resposta, author: Rick Anderson e Editor: Joe Smith é exibido quando o ponto de extremidade Sample/Index2 é chamado.

O código a seguir aplica o MyActionFilterAttribute e o AddHeaderAttribute ao Razor Page:

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

Não é possível aplicar filtros aos métodos de manipulador do Razor Page. Eles podem ser aplicados ao modelo de Razor Page ou globalmente.

Várias interfaces de filtro têm atributos correspondentes que podem ser usados como classes base para implementações personalizadas.

Atributos de filtro:

Escopos e ordem de execução dos filtros

Um filtro pode ser adicionado ao pipeline com um de três escopos:

  • Usando um atributo em uma ação do controlador. Os atributos de filtro não podem ser aplicados aos métodos do manipulador dos Razor Pages.
  • Usando um atributo em um controlador ou o Razor Page.
  • Globalmente para todos os controladores, ações e Razor Pages, como mostra o código a seguir:
public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews(options =>
   {
        options.Filters.Add(typeof(MySampleActionFilter));
    });
}

Ordem padrão de execução

Quando há vários filtros para um determinado estágio do pipeline, o escopo determina a ordem padrão de execução dos filtros. Filtros globais circundam filtros de classe, que, por sua vez, circundam filtros de método.

Como resultado do aninhamento de filtro, o código posterior dos filtros é executado na ordem inversa do código anterior. A sequência de filtro:

  • O código anterior dos filtros globais.
    • O código anterior dos filtros de controlador e filtros do Razor Page.
      • O código anterior dos filtros de método de ação.
      • O código posterior dos filtros de método de ação.
    • O código posterior dos filtros de controlador e filtros do Razor Page.
  • O código posterior dos filtros globais.

O exemplo a seguir ilustra a ordem na qual os métodos de filtro são chamados para filtros de ação síncrona.

Sequência Escopo do filtro Método do filtro
1 Global OnActionExecuting
2 Controlador ou Razor Page OnActionExecuting
3 Método OnActionExecuting
4 Método OnActionExecuted
5 Controlador ou Razor Page OnActionExecuted
6 Global OnActionExecuted

Filtros no nível do controlador

Cada controlador que herda da classe base Controller inclui os métodos Controller.OnActionExecuting, Controller.OnActionExecutionAsync e métodos Controller.OnActionExecutedOnActionExecuted. Estes métodos:

  • Encapsulam os filtros executados para uma determinada ação.
  • OnActionExecuting é chamado antes de qualquer um dos filtros da ação.
  • OnActionExecuted é chamado após todos os filtros da ação.
  • OnActionExecutionAsync é chamado antes de qualquer um dos filtros da ação. O código no filtro após next é executado após o método de ação.

Por exemplo, no download de exemplo, MySampleActionFilter é aplicado globalmente na inicialização.

TestController:

  • Aplica o SampleActionFilterAttribute ([SampleActionFilter]) à ação FilterTest2.
  • Substitui OnActionExecuting e 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);
    }
}

MyDisplayRouteInfo é fornecido pelo pacote NuGet Rick.Docs.Samples.RouteInfo e exibe as informações de rota.

Navegar até https://localhost:5001/Test/FilterTest2 executa o seguinte código:

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

Os filtros no nível do controlador definem a propriedade Order como int.MinValue. Os filtros no nível do controlador não podem ser definidos para serem executados após os filtros aplicados aos métodos. Ordem é explicada na próxima seção.

Para os Razor Pages, consulte Implementar filtros dos Razor Pages substituindo os métodos de filtro.

Substituindo a ordem padrão

É possível substituir a sequência padrão de execução implementando IOrderedFilter. IOrderedFilter expõe a propriedade Order que tem precedência sobre o escopo para determinar a ordem da execução. Um filtro com um valor de Order menor:

  • Executa o código anterior antes de um filtro com um valor mais alto de Order.
  • Executa o código posterior após um filtro com um valor mais alto de Order.

A propriedade Order pode ser definida com um parâmetro de construtor:

[SampleActionFilter(Order = int.MinValue)]

Considere os dois filtros de ação no seguinte 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);
    }
}

Um filtro global é adicionado em StartUp.ConfigureServices:

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

Os três filtros são executados na seguinte ordem:

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

A propriedade Order substitui o escopo ao determinar a ordem na qual os filtros serão executados. Os filtros são classificados primeiro pela ordem e o escopo é usado para desempatar. Todos os filtros internos implementam IOrderedFilter e definem o valor de Order padrão como 0. Conforme mencionado anteriormente, os filtros de nível do controlador definem a propriedade Order como int.MinValue. Para filtros internos, o escopo determina a ordem, a menos que Order seja definido como um valor diferente de zero.

No código anterior, MySampleActionFilter tem escopo global, portanto, ele é executado antes de MyAction2FilterAttribute, que tem escopo do controlador. Para executar MyAction2FilterAttribute primeiro, defina sua ordem como 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 o filtro global MySampleActionFilter seja executado primeiro, defina Order como int.MinValue:

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

Cancelamento e curto-circuito

O pipeline de filtro pode sofrer um curto-circuito por meio da configuração da propriedade Result no parâmetro ResourceExecutingContext fornecido ao método do filtro. Por exemplo, o filtro de Recurso a seguir impede que o resto do pipeline seja executado:

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)
    {
    }
}

No código a seguir, os filtros ShortCircuitingResourceFilter e AddHeader têm como destino o método de ação SomeResource. ShortCircuitingResourceFilter:

  • É executado primeiro, porque ele é um filtro de recurso e AddHeader é um filtro de ação.
  • Causa um curto-circuito no restante do pipeline.

Portanto, o filtro AddHeader nunca é executado para a ação SomeResource. Esse comportamento seria o mesmo se os dois filtros fossem aplicados no nível do método de ação, desde que ShortCircuitingResourceFilter fosse executado primeiro. O ShortCircuitingResourceFilter é executado primeiro, devido ao seu tipo de filtro ou pelo uso explícito da propriedade 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.");
    }
}

Injeção de dependência

Filtros podem ser adicionados por tipo ou por instância. Se você adicionar uma instância, ela será usada para cada solicitação. Se um tipo for adicionado, ele será ativado pelo tipo. Um filtro ativado por tipo significa:

  • Uma instância é criada para cada solicitação.
  • Qualquer dependência de construtor é preenchida pela injeção de dependência (DI).

Filtros que são implementados como atributos e adicionados diretamente a classes de controlador ou métodos de ação não podem ter dependências de construtor fornecidas pela DI (injeção de dependência). Dependências de construtor não podem ser fornecidas pela DI porque:

  • Os atributos precisam ter os parâmetros de construtor fornecidos quando são aplicados.
  • Essa é uma limitação do funcionamento dos atributos.

Os filtros a seguir são compatíveis com dependências de construtor fornecidas pela injeção de dependência:

Os filtros anteriores podem ser aplicados a um método de ação ou controlador:

Os agentes estão disponíveis na DI. No entanto, evite criar e usar filtros apenas para fins de registro em log. O registro em log da estrutura interna normalmente fornece o que é necessário para o registro em log. Registro em log adicionado aos filtros:

  • Deve se concentrar em questões de domínio de negócios ou comportamento específico ao filtro.
  • Não deve registrar ações ou outros eventos da estrutura. Os filtros internos registram ações de log e eventos da estrutura.

ServiceFilterAttribute

Os tipos de implementação do filtro de serviço são registrados em ConfigureServices. Um ServiceFilterAttribute recupera uma instância do filtro da DI.

O código a seguir mostra 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");
    }
}

No código a seguir, AddHeaderResultServiceFilter é adicionado ao contêiner de DI:

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
    });
}

No código a seguir, o atributo ServiceFilter recupera uma instância do filtro AddHeaderResultServiceFilter da DI:

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

Ao usar ServiceFilterAttribute, a configuração ServiceFilterAttribute.IsReusable:

  • Fornece uma dica de que a instância de filtro pode ser reutilizada fora do escopo de solicitação em que foi criada. O runtime do ASP.NET Core não garante:

    • Que uma única instância do filtro será criada.
    • Que o filtro não será solicitado novamente no contêiner de DI em algum momento posterior.
  • Não deve ser usado com um filtro que dependa dos serviços com um tempo de vida diferente de singleton.

ServiceFilterAttribute implementa IFilterFactory. IFilterFactory expõe o método CreateInstance para criar uma instância de IFilterMetadata. CreateInstance carrega o tipo especificado na DI.

TypeFilterAttribute

O TypeFilterAttribute é semelhante ao ServiceFilterAttribute, mas seu tipo não é resolvido diretamente por meio do contêiner de DI. Ele cria uma instância do tipo usando Microsoft.Extensions.DependencyInjection.ObjectFactory.

Como os tipos TypeFilterAttribute não são resolvidos diretamente do contêiner de DI:

  • Os tipos referenciados usando o TypeFilterAttribute não precisam ser registrados no contêiner de DI. Eles têm suas dependências atendidas pelo contêiner de DI.
  • Opcionalmente, o TypeFilterAttribute pode aceitar argumentos de construtor para o tipo.

Ao usar TypeFilterAttribute, a configuração TypeFilterAttribute.IsReusable:

  • Fornece uma dica de que a instância de filtro pode ser reutilizada fora do escopo de solicitação em que foi criada. O runtime do ASP.NET Core não fornece garantias de que uma única instância do filtro será criada.

  • Não deve ser usado com um filtro que dependa dos serviços com um tempo de vida diferente de singleton.

O exemplo a seguir mostra como passar argumentos para um tipo usando TypeFilterAttribute:

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

Filtros de autorização

Filtros de autorização:

  • São os primeiros filtros executados no pipeline de filtro.
  • Controlam o acesso aos métodos de ação.
  • Têm um método anterior, mas não têm um método posterior.

Filtros de autorização personalizados exigem uma estrutura de autorização personalizada. Prefira configurar as políticas de autorização ou escrever uma política de autorização personalizada em vez de escrever um filtro personalizado. O filtro de autorização interno:

  • Chama o sistema de autorização.
  • Não autoriza solicitações.

Não gera exceções dentro de filtros de autorização:

  • A exceção não será tratada.
  • Os filtros de exceção não tratarão a exceção.

Considere a possibilidade de emitir um desafio quando ocorrer uma exceção em um filtro de autorização.

Saiba mais sobre Autorização.

Filtros de recurso

Filtros de recurso:

Os filtros de recursos são úteis para causar um curto-circuito na maior parte do pipeline. Por exemplo, um filtro de cache pode evitar o restante do pipeline em uma ocorrência no cache.

Exemplos de filtros de recurso:

Filtros de ação

Os filtros de ação não se aplicam aos Razor Pages. Os Razor Pages não são dão suporte a IPageFilter e IAsyncPageFilter. Para obter mais informações, confira Métodos de filtragem para Páginas do Razor.

Filtros de ação:

O código a seguir mostra um exemplo de filtro de ação:

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);
    }
}

A classe ActionExecutingContext fornece as seguintes propriedades:

  • ActionArguments - permite a leitura das entradas para um método de ação.
  • Controller – permite a manipulação da instância do controlador.
  • Result – configuração de Result execução de curtos-circuitos do método de ação e dos filtros de ação posteriores.

Gerar uma exceção em um método de ação:

  • Impede a execução de filtros subsequentes.
  • Ao contrário da configuração Result, é tratada como uma falha e não como um resultado bem-sucedido.

A classe ActionExecutedContext fornece Controller e Result, além das seguintes propriedades:

  • Canceled – verdadeiro se a execução da ação tiver sofrido curto-circuito por outro filtro.

  • Exception – não será nulo se a ação ou um filtro de ação executado antes tiver apresentado uma exceção. Definir essa propriedade como nulo:

    • Trata efetivamente a exceção.
    • Result é executado como se tivesse retornado do método de ação.

Para um IAsyncActionFilter, uma chamada para o ActionExecutionDelegate:

  • Executa todos os próximos filtros de ação e o método de ação.
  • Retorna ActionExecutedContext.

Para fazer um curto-circuito, atribua Microsoft.AspNetCore.Mvc.Filters.ActionExecutingContext.Result a uma instância de resultado e não chame o next (ActionExecutionDelegate).

A estrutura fornece um ActionFilterAttribute abstrato que você pode colocar em uma subclasse.

O filtro de ação OnActionExecuting pode ser usado para:

  • Validar o estado do modelo.
  • Retornar um erro se o estado for inválido.
public class ValidateModelAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext 
                                           context)
    {
        if (!context.ModelState.IsValid)
        {
            context.Result = new BadRequestObjectResult(
                                                context.ModelState);
        }
    }

Observação

Os controladores anotados com o atributo [ApiController] validam automaticamente o estado do modelo e retornam uma resposta 400. Para obter mais informações, veja Respostas automáticas HTTP 400. O método OnActionExecuted é executado após o método de ação:

  • E pode ver e manipular os resultados da ação por meio da propriedade Result.

  • Canceled é definido como verdadeiro se a execução da ação tiver sofrido curto-circuito por outro filtro.

  • Exception é definido como um valor não nulo se a ação ou um filtro de ação posterior tiver apresentado uma exceção. Definindo Exception como nulo:

    • Trata efetivamente uma exceção.
    • ActionExecutedContext.Result é executado como se fosse retornado normalmente do método de ação.
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 exceção

Filtros de exceção:

O exemplo de filtro de exceção a seguir usa uma exibição de erro personalizada para mostrar detalhes sobre exceções que ocorrem quando o aplicativo está em desenvolvimento:

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;
    }
}

O código a seguir testa o filtro de exceção:

[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.");
    }
}

Filtros de exceção:

  • Não têm eventos anteriores nem posteriores.
  • Implementam OnException ou OnExceptionAsync.
  • Tratam as exceções sem tratamento que ocorrem no Razor Page ou na criação do controlador, no model binding, nos filtros de ação ou nos métodos de ação.
  • Não capturam as exceções que ocorrem em filtros de recurso, em filtros de resultado ou na execução do resultado de MVC.

Para tratar uma exceção, defina a propriedade ExceptionHandled como true ou atribua a propriedade Result. Isso interrompe a propagação da exceção. Um filtro de exceção não pode transformar uma exceção em "êxito". Somente um filtro de ação pode fazer isso.

Filtros de exceção:

  • São bons para interceptar as exceções que ocorrem nas ações.
  • Não são tão flexíveis quanto o middleware de tratamento de erro.

Prefira o middleware para tratamento de exceção. Use filtros de exceção apenas quando o tratamento de erros for diferente com base no método de ação chamado. Por exemplo, um aplicativo pode ter métodos de ação para os pontos de extremidade da API e para modos de exibição/HTML. Os pontos de extremidade da API podem retornar informações de erro como JSON, enquanto as ações baseadas em modo de exibição podem retornar uma página de erro como HTML.

Filtros de resultado

Filtros de resultado:

IResultFilter e IAsyncResultFilter

O código a seguir mostra um filtro de resultado que adiciona um cabeçalho 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");
    }
}

O tipo de resultado que está sendo executado depende da ação. Uma ação que retorna um modo de exibição inclui todo o processamento do razor como parte do ViewResult em execução. Um método de API pode executar alguma serialização como parte da execução do resultado. Saiba mais sobre resultados de ação.

Os filtros de resultado somente são executados quando uma ação ou filtro de ação produz um resultado de ação. Os filtros de resultado não são executados quando:

  • Um filtro de autorização ou filtro de recurso causa um curto-circuito no pipeline.
  • Um filtro de exceção trata uma exceção produzindo um resultado de ação.

O método Microsoft.AspNetCore.Mvc.Filters.IResultFilter.OnResultExecuting pode fazer o curto-circuito da execução do resultado da ação e dos filtros de resultados posteriores definindo Microsoft.AspNetCore.Mvc.Filters.ResultExecutingContext.Cancel como true. Grave no objeto de resposta ao fazer um curto-circuito para evitar gerar uma resposta vazia. Gerar uma exceção em IResultFilter.OnResultExecuting:

  • Impede a execução do resultado da ação e dos próximos filtros.
  • É tratado como uma falha e não como um resultado com êxito.

Quando o método Microsoft.AspNetCore.Mvc.Filters.IResultFilter.OnResultExecuted é executado, a resposta provavelmente já foi enviada ao cliente. Se a resposta já tiver sido enviada ao cliente, ela não poderá ser alterada.

ResultExecutedContext.Canceled será definido como true se a execução do resultado da ação tiver sofrido curto-circuito por outro filtro.

ResultExecutedContext.Exception será definido como um valor não nulo se o resultado da ação ou um filtro de resultado posterior tiver gerado uma exceção. Definir Exception como nula trata uma exceção com eficiência e impede que a exceção seja apresentada novamente no pipeline. Não há nenhuma maneira confiável para gravar dados em uma resposta ao manipular uma exceção em um filtro de resultado. Se os cabeçalhos tiverem sido liberados para o cliente quando o resultado de uma ação gerar uma exceção, não haverá mecanismo confiável para enviar um código de falha.

Para um IAsyncResultFilter, uma chamada para await next no ResultExecutionDelegate executa qualquer filtro de resultado posterior e o resultado da ação. Para fazer um curto-circuito, defina ResultExecutingContext.Cancel para true e não chame o ResultExecutionDelegate :

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

    }
}

A estrutura fornece um ResultFilterAttribute abstrato que você pode colocar em uma subclasse. A classe AddHeaderAttribute mostrada anteriormente é um exemplo de atributo de filtro de resultado.

IAlwaysRunResultFilter e IAsyncAlwaysRunResultFilter

As interfaces IAlwaysRunResultFilter e IAsyncAlwaysRunResultFilter declaram uma implementação IResultFilter que é executada para todos os resultados da ação. Isso inclui os resultados da ação produzidos por:

  • Filtros de autorização e filtros de recursos que fazem curto-circuito.
  • Filtros de exceção.

Por exemplo, o filtro a seguir sempre é executado e define o resultado de uma ação (ObjectResult) com um código de status 422 Entidade Não Processável quando ocorre falha na negociação de conteúdo:

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. Portanto, uma instância IFilterFactory pode ser usada como uma instância IFilterMetadata em qualquer parte do pipeline de filtro. Quando o runtime se prepara para invocar o filtro, tenta convertê-lo em um IFilterFactory. Se essa conversão for bem-sucedida, o método CreateInstance será chamado para criar a instância IFilterMetadata invocada. Isso fornece um design flexível, porque o pipeline de filtro preciso não precisa ser definido explicitamente quando o aplicativo é iniciado.

IFilterFactory.IsReusable:

  • É uma indicação de que a instância de filtro criada pelo alocador pode ser reutilizada fora do escopo da solicitação em que foi criada.
  • Não deve ser usada com um filtro que dependa de serviços com um tempo de vida de um singleton.

O runtime do ASP.NET Core não garante:

  • Que uma única instância do filtro será criada.
  • Que o filtro não será solicitado novamente no contêiner de DI em algum momento posterior.

Aviso

Configure IFilterFactory.IsReusable apenas para retornar true se a origem dos filtros for inequívoca, os filtros estiverem sem estado e os filtros forem seguros para usar em várias solicitações HTTP. Por exemplo, não retorne filtros de DI registrados como com escopo ou transitórios se IFilterFactory.IsReusable retornar true. Implemente IFilterFactory usando implementações personalizadas de atributo como outra abordagem à criação de 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;
        }
    }
}

O filtro é aplicado no seguinte código:

[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.");
    }
}

O código anterior pode ser testado executando o download de exemplo:

  • Invocar as ferramentas de desenvolvedor F12.
  • Navegue até https://localhost:5001/Sample/HeaderWithFactory.

As ferramentas de desenvolvedor F12 exibem os seguintes cabeçalhos de resposta adicionados pelo código de exemplo:

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

O código anterior cria o cabeçalho de resposta interno:My header.

IFilterFactory implementado em um atributo

Filtros que implementam IFilterFactory são úteis para filtros que:

  • Não exigem a passagem de parâmetros.
  • Tenha dependências de construtor que precisem ser atendidas pela DI.

TypeFilterAttribute implementa IFilterFactory. IFilterFactory expõe o método CreateInstance para criar uma instância de IFilterMetadata. CreateInstance carrega o tipo especificado do contêiner de serviços (DI).

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");
        }
    }
}

O código a seguir mostra três abordagens para aplicar o [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");
}

No código anterior, decorar o método com [SampleActionFilter] é a abordagem preferida para aplicar o SampleActionFilter.

Usando middleware no pipeline de filtros

Filtros de recursos funcionam como middleware, no sentido em que envolvem a execução de tudo o que vem depois no pipeline. No entanto, os filtros diferem do middleware porque fazem parte do runtime, o que significa que eles têm acesso ao contexto e aos constructos.

Para usar o middleware como um filtro, crie um tipo com um método Configure que especifica o middleware para injeção no pipeline de filtros. O exemplo a seguir usa o middleware de localização para estabelecer a cultura atual para uma solicitação:

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 executar o middleware:

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

Filtros de middleware são executados no mesmo estágio do pipeline de filtros que filtros de recurso, antes do model binding e depois do restante do pipeline.

Acesso thread-safe

Ao passar uma instância de um filtro para Add, em vez de para seu Type, o filtro é um singleton e não é thread-safe.

Próximas ações