ASP .NET Core - Apresentando Filters - I

 Neste artigo eu vou apresentar os filtros da ASP .NET Core MVC mostrando como funcionam e como usá-los.

Os filtros são atributos anexados às classes ou métodos dos controladores que injetam lógica extra ao processamento da requisição e permitem a implementação de funcionalidades relacionadas a autorização, exception, log e cache de forma simples e elegante.

Os filtros permitem executar um código personalizado antes ou depois de executar um método Action. Assim, eles fornecem maneiras de realizar tarefas repetitivas comuns nos métodos Action e são chamados em determinados estágios no pipeline de processamento de solicitações.
 
Há muitos filtros internos disponíveis com o ASP.NET Core MVC e também podemos criar filtros personalizados.

Como os filtros atuam ?

Os filtros são executados dentro do pipeline de invocação de ações do MVC, às vezes chamado de pipeline de filtros.

O pipeline de filtros é executado após o MVC selecionar a ação a ser executada.

fonte: https://docs.microsoft.com/pt-br/aspnet/core/mvc/controllers/filters

Tipos de Filtros na ASP .NET Core MVC

Cada tipo de filtro é executado em um estágio diferente no pipeline de filtro. Temos os seguintes tipos de filtro.

Tipo de Filtro Descrição
Authorization (Autorização) Os filtros de autorização são executados primeiro. Esse filtro nos ajuda a determinar se o usuário está autorizado para o request atual. Pode causar curto-circuito em um pipeline se um usuário não for autorizado para o request atual. Também podemos criar um filtro de autorização personalizado.
Resource (Recursos)  Os filtros de recursos tratam do request após a autorização. Podem executar código antes e depois do resto do filtro ser executado. Isso executa antes do model binding ocorrer. Pode ser usado para implementar o armazenamento em cache.
Action (Ação)  Os filtros de ação executam o código imediatamente antes e depois do método Action do controlador ser chamado. Pode ser usado para executar qualquer ação antes ou depois da execução do método Action do controlador. Também podemos manipular os argumentos passados ​​para uma Action.
Exception (Exceção)  Os filtros de exceção são usados ​​para manipular exceções ocorridas antes de qualquer coisa ser escrita no corpo da resposta.
Result (Resultados)  Os filtros de Resultado são usados ​​para executar o código antes ou depois da execução dos resultados de Actions individuais do controlador. Eles são executados somente se o método Action do controlador tiver sido executado com sucesso.

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

fonte: https://docs.microsoft.com/pt-br/aspnet/core/mvc/controllers/filters

Implementação do filtros

Os filtros suportam dois tipos de implementação: síncrona e assíncrona.

Ambas as implementações usam diferentes definições de interface, e, você deve escolher a implementação, dependendo do tipo de tarefa que você precisa executar.

Os
filtros síncronos que executam código antes e depois do estágio do pipeline definem os métodos OnStageExecuting e OnStageExecuted.

Por exemplo, no
ActionFilter. O método OnActionExecuting é chamado antes do método Action ser invocado e o método OnActionExecuted é chamado após o método Action retornar.

Exemplo de filtro Síncrono

    using Microsoft.AspNetCore.Mvc.Filters; 
    namespace Filters 
    { 
        public class CustomActionFilter : IActionFilter 
        { 
            public void OnActionExecuting(ActionExecutingContext context) 
            { 
            
   //Código :  antes que a action executa
            }       

            public void OnActionExecuted(ActionExecutedContext context) 
            { 
             
  //Codigo  : depois que a action executa
            } 
        } 
    } 

Os filtros assíncronos são definidos apenas com um único método: OnStageExecutionAsync, que usa um FilterTypeExecutingContext e usa o delegate FilterTypeExecutionDelegate para executar o estágio do pipeline do filtro.

Por exemplo, em um
ActionFilter, o ActionExecutionDelegate chama o método Action e podemos escrever o código antes e depois de chamarmos o método Action.

Exemplo de filtro Síncrono

    using System.Threading.Tasks; 
    using Microsoft.AspNetCore.Mvc.Filters;       

    namespace Filters 
    { 
        public class
CustomAsyncActionFilter : IAsyncActionFilter 
        { 
            public async Task OnActionExecutionAsync(ActionExecutingContext context,
                                                                            ActionExecutionDelegate
next) 
            {
             
  //Código :  antes que a action executa
              
 await next(); 
              
//Codigo  : depois que a action executa
            } 
        } 
    } 

Podemos implementar interfaces para vários tipos de filtro (estágio) em uma única classe.

Podemos implementar a versão síncrona ou a assíncrona de uma interface de filtro, não ambas.

O .NET Framework verifica primeiro a interface do filtro assíncrono, se a encontrar, ela é chamada. Se não for encontrada, chama o(s) método(s) da interface síncrona. Se implementarmos ambos, a interface síncrona nunca será chamada.

Escopo do filtro e ordem de execução

Um filtro pode ser adicionado ao pipeline em um dos três escopos:

1- Pelo método
Action;
2- Pela classe do
controlador;
3-
Globalmente (é aplicado a todos os controladores e actions). Para isso precisamos adicionar o filtro na coleção MvcOptions.Filters no método ConfigureServices;

Exemplo de filtro global

    public void ConfigureServices(IServiceCollection services) 
    { 
        // Adiciona serviços do framework
        services.AddMvc(options=> { 
      
 //adicionado por instância
        options.Filters.Add(
new CustomActionFilter()); 
     
  //adicionado por tipo 
        options.Filters.Add(
typeof(CustomActionFilter)); 
        }); 
    } 

Quando vários filtros são aplicados ao estágio específico do pipeline, o escopo do filtro define a ordem padrão da execução do filtro.

A ordem padrão de execução é a seguinte:

  1. O filtro global é aplicado primeiro,
  2. Depois o filtro de nível de classe é aplicado
  3. Finalmente, o filtro de nível de método é aplicado.

A figura abaixo mostra a ordem de execução padrão dos filtros:

Alterando a ordem de execução padrão

Podemos substituir a seqüência padrão de execução do filtro implementando a interface IOrderedFilter.

Essa interface expõe uma propriedade Order que tem precedência sobre o escopo para determinar a ordem de execução.

Um filtro com um valor mais baixo de Order terá seu código anterior executado antes que um filtro com um valor mais alto de Order. Um filtro com um valor mais baixo de Order terá seu código posterior executado depois que um filtro com um valor mais alto de Order.

Podemos definir a propriedade Order usando um parâmetro de construtor. Ex:  [MeuFiltro(Name = "Controller Level Attribute", Order=1)]

Exemplo de alteração da ordem padrão de execução do filtro

1- Filtro MeuFiltro

    using System; 
    using Microsoft.AspNetCore.Mvc.Filters; 
    namespace Filters 
    { 
        public class MeuFiltroAttribute : Attribute, IActionFilter, IOrderedFilter 
        { 
            public int Order { get; set; }               

            public void OnActionExecuting(ActionExecutingContext context) 
            { 
             
 //Código :  antes que a action executa
            }       

            public void OnActionExecuted(ActionExecutedContext context) 
            { 
             
  //Codigo  : depois que a action executa
            } 
        } 
    }

2- Controlador

    using System; 
    using Microsoft.AspNetCore.Mvc; 
    using Filters;       

    namespace Filters.Controllers 
    { 
        [MeuFiltro(Order = 1)] 
        public class HomeController : Controller 
        { 
            public IActionResult Index() 
            { 
                return View(); 
            } 
        } 
    } 

Quando os filtros são executados no pipeline, os filtros são classificados primeiro por ordem e, em seguida, por escopo.

Todos os filtros internos são implementados pelo IOrderFilter e definem a ordem de filtro padrão como zero (0).

Cancelamento e curto-circuito

Você pode fazer um curto-circuito no pipeline de filtros a qualquer momento, definindo a propriedade Result no parâmetro context fornecido ao método do filtro. Por exemplo, o filtro de recurso a seguir impede que o resto do pipeline seja executado.

Exemplo de curto-circuito

using System;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;

namespace FiltersSample.Filters
{
    public class ShortCircuitingResourceFilterAttribute : Attribute, IResourceFilter
    {
        public void OnResourceExecuting(ResourceExecutingContext context)
        {
            context.Result = new ContentResult()
            {
                Content = "Recurso indisponível - header não definido"
            };

        }

        public void OnResourceExecuted(ResourceExecutedContext context)
        {
        }
    }
}

Os filtros e a injeção de dependência (DI)

Os filtros podem ser adicionados por tipo ou por instância. Se você adicionar por instância, ela será usada para cada request. Se você adicionar por tipo, ele será ativado pelo tipo, o que significa que uma instância será criada para cada request e as dependências de construtor serão populadas pela DI (injeção de dependência).

Adicionar um filtro por tipo é equivalente a usar o código: filters.Add(new TypeFilterAttribute(typeof(MyFilter))).

Os filtros que são implementados como atributos e adicionados diretamente a classes de controlador ou métodos Action não podem ter dependências de construtor fornecidas pela DI (injeção de dependência). Isso ocorre porque os atributos precisam ter os parâmetros de construtor fornecidos quando eles são aplicados. Essa é uma limitação do funcionamento dos atributos.

Se seus filtros tiverem dependências que você precisa acessar a partir da injeção de dependência, existem várias abordagens que você pode usar para contornar essa limitação. É possível aplicar o filtro a um método Action ou classe usando uma das opções a seguir:

1 - ServiceFilterAttribute;

Um ServiceFilter recupera uma instância do filtro da DI. Adicione o filtro ao contêiner em ConfigureServices e faça uma referência a ele em um atributo ServiceFilter.

2 - TypeFilterAttribute;

Um TypeFilterAttribute é muito semelhante a ServiceFilterAttribute (e também implementa IFilterFactory), mas seu tipo não é resolvido diretamente pelo contêiner de DI. Em vez disso, ele cria uma instância do tipo usando Microsoft.Extensions.DependencyInjection.ObjectFactory.

Devido a essa diferença, os tipos que são referenciados usando o TypeFilterAttribute não precisam ser registrados com o contâiner primeiro (mas eles ainda terão suas dependências atendidas pelo contêiner). Além disso, TypeFilterAttribute também pode aceitar argumentos de construtor para o tipo em questão.

3 - IFilterFactory implementado em seu atributo;

E assim, apresentei os conceitos básicos sobre filtros, mostrando os filtros internos, seu escopo, ordem de execução e cancelamento.

No próximo artigo veremos como implementar filtros.

"Deus nunca foi visto por alguém. O Filho unigênito (Jesus), que está no seio do Pai, esse o revelou."
João 1:18

Referências:


José Carlos Macoratti