Métodos de filtro para Páginas Razor no ASP.NET Core

De Rick Anderson

Razor Os filtros de página IPageFilter e IAsyncPageFilter permitem que as páginas Razor executem o código antes e depois que um manipulador de página Razor é executado. Os filtros de página Razor são semelhantes aos filtros de ação de MVC do ASP.NET Core, exceto que eles não podem ser aplicados aos métodos do manipulador de cada página individual.

Filtros de página Razor:

  • Executam o código depois que um método do manipulador é selecionado, mas antes que o model binding ocorra.
  • Executam o código antes que o método do manipulador seja executado, após a conclusão do model binding.
  • Executam o código após a execução do método do manipulador.
  • Podem ser implementados em uma única página ou globalmente.
  • Não podem ser aplicados a métodos do manipulador de uma página específica.
  • A dependência de construtor pode ser preenchida pela Injeção de dependência (DI). Para obter mais informações, consulte ServiceFilterAttribute e TypeFilterAttribute.

Embora os construtores de página e o middleware habilitem a execução de código personalizado antes da execução de um método de manipulador, somente os filtros de página Razor habilitam o acesso a HttpContext e a página. O middleware tem acesso ao HttpContext, mas não ao "contexto de página". Os filtros têm um parâmetro derivado FilterContext, que fornece acesso a HttpContext. Aqui está um exemplo de um filtro de página: Implementar um atributo de filtro que adiciona um cabeçalho à resposta, algo que não pode ser feito com construtores nem middlewares. O acesso ao contexto da página, que inclui o acesso às instâncias da página e seu modelo, só está disponível ao executar filtros, manipuladores ou o corpo de uma Página Razor.

Exibir ou baixar código de exemplo (como baixar)

Os filtros de página Razor fornecem os métodos a seguir, que podem ser aplicados globalmente ou no nível da página:

  • Métodos síncronos:

    • OnPageHandlerSelected: chamado depois que um método do manipulador é selecionado, mas antes que o model binding ocorra.
    • OnPageHandlerExecuting: chamado antes que o método do manipulador seja executado, após a conclusão do model binding.
    • OnPageHandlerExecuted: chamado depois que o método do manipulador é executado, antes do resultado da ação.
  • Métodos assíncronos:

    • OnPageHandlerSelectionAsync: chamado de forma assíncrona depois que o método do manipulador é selecionado, mas antes que o model binding ocorra.
    • OnPageHandlerExecutionAsync: chamado de forma assíncrona, antes que o método do manipulador seja invocado, após a conclusão do model binding.

Implemente ou a versão assíncrona ou a versão síncrona de uma interface de filtro, não ambas. Primeiro, a estrutura 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 ambas as interfaces forem implementadas, somente os métodos assíncronos serão chamados. A mesma regra aplica-se para substituições em páginas. Implemente a versão síncrona ou a assíncrona da substituição, não ambas.

Implementar filtros de página Razor globalmente

O código a seguir implementa IAsyncPageFilter:

public class SampleAsyncPageFilter : IAsyncPageFilter
{
    private readonly IConfiguration _config;

    public SampleAsyncPageFilter(IConfiguration config)
    {
        _config = config;
    }

    public Task OnPageHandlerSelectionAsync(PageHandlerSelectedContext context)
    {
        var key = _config["UserAgentID"];
        context.HttpContext.Request.Headers.TryGetValue("user-agent",
                                                        out StringValues value);
        ProcessUserAgent.Write(context.ActionDescriptor.DisplayName,
                               "SampleAsyncPageFilter.OnPageHandlerSelectionAsync",
                               value, key.ToString());

        return Task.CompletedTask;
    }

    public async Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context,
                                                  PageHandlerExecutionDelegate next)
    {
        // Do post work.
        await next.Invoke();
    }
}

No código anterior, ProcessUserAgent.Write é o código fornecido pelo usuário que funciona com a cadeia de caracteres do agente do usuário.

O código a seguir habilita o SampleAsyncPageFilter na classe Startup:

public void ConfigureServices(IServiceCollection services)
{
    services.AddRazorPages()
        .AddMvcOptions(options =>
        {
            options.Filters.Add(new SampleAsyncPageFilter(Configuration));
        });
}

O código a seguir chama AddFolderApplicationModelConvention para aplicar o SampleAsyncPageFilter somente às páginas em /Movies:

public void ConfigureServices(IServiceCollection services)
{
    services.AddRazorPages(options =>
    {
        options.Conventions.AddFolderApplicationModelConvention(
            "/Movies",
            model => model.Filters.Add(new SampleAsyncPageFilter(Configuration)));
    });
}

O código a seguir implementa o IPageFilter síncrono:

public class SamplePageFilter : IPageFilter
{
    private readonly IConfiguration _config;

    public SamplePageFilter(IConfiguration config)
    {
        _config = config;
    }

    public void OnPageHandlerSelected(PageHandlerSelectedContext context)
    {
        var key = _config["UserAgentID"];
        context.HttpContext.Request.Headers.TryGetValue("user-agent", out StringValues value);
        ProcessUserAgent.Write(context.ActionDescriptor.DisplayName,
                               "SamplePageFilter.OnPageHandlerSelected",
                               value, key.ToString());
    }

    public void OnPageHandlerExecuting(PageHandlerExecutingContext context)
    {
        Debug.WriteLine("Global sync OnPageHandlerExecuting called.");
    }

    public void OnPageHandlerExecuted(PageHandlerExecutedContext context)
    {
        Debug.WriteLine("Global sync OnPageHandlerExecuted called.");
    }
}

O código a seguir habilita o SamplePageFilter:

public void ConfigureServices(IServiceCollection services)
{
    services.AddRazorPages()
        .AddMvcOptions(options =>
        {
            options.Filters.Add(new SamplePageFilter(Configuration));
        });
}

Implementar filtros de página Razor substituindo os métodos de filtro

O código a seguir substitui os filtros de página Razor assíncronos:

public class IndexModel : PageModel
{
    private readonly IConfiguration _config;

    public IndexModel(IConfiguration config)
    {
        _config = config;
    }

    public override Task OnPageHandlerSelectionAsync(PageHandlerSelectedContext context)
    {
        Debug.WriteLine("/IndexModel OnPageHandlerSelectionAsync");
        return Task.CompletedTask;
    }

    public async override Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context, 
                                                           PageHandlerExecutionDelegate next)
    {
        var key = _config["UserAgentID"];
        context.HttpContext.Request.Headers.TryGetValue("user-agent", out StringValues value);
        ProcessUserAgent.Write(context.ActionDescriptor.DisplayName,
                               "/IndexModel-OnPageHandlerExecutionAsync",
                                value, key.ToString());

        await next.Invoke();
    }
}

Implementar um atributo de filtro

O filtro baseado em atributo interno OnResultExecutionAsync pode tornar-se uma subclasse. O filtro a seguir adiciona um cabeçalho à resposta:

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

namespace PageFilter.Filters
{
    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 });
        }
    }
}

O código a seguir se aplica ao atributo AddHeader:

using Microsoft.AspNetCore.Mvc.RazorPages;
using PageFilter.Filters;

namespace PageFilter.Movies
{
    [AddHeader("Author", "Rick")]
    public class TestModel : PageModel
    {
        public void OnGet()
        {

        }
    }
}

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

Confira Substituindo a ordem padrão para obter instruções sobre a substituição da ordem.

Confira Cancelamento e curto-circuito para obter instruções para causar um curto-circuito no pipeline do filtro por meio de um filtro.

Autorizar o atributo de filtro

O atributo Authorize pode ser aplicado a um PageModel:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace PageFilter.Pages
{
    [Authorize]
    public class ModelWithAuthFilterModel : PageModel
    {
        public IActionResult OnGet() => Page();
    }
}

De Rick Anderson

Razor Os filtros de página IPageFilter e IAsyncPageFilter permitem que as páginas Razor executem o código antes e depois que um manipulador de página Razor é executado. Os filtros de página Razor são semelhantes aos filtros de ação de MVC do ASP.NET Core, exceto que eles não podem ser aplicados aos métodos do manipulador de cada página individual.

Filtros de página Razor:

  • Executam o código depois que um método do manipulador é selecionado, mas antes que o model binding ocorra.
  • Executam o código antes que o método do manipulador seja executado, após a conclusão do model binding.
  • Executam o código após a execução do método do manipulador.
  • Podem ser implementados em uma única página ou globalmente.
  • Não podem ser aplicados a métodos do manipulador de uma página específica.

O código pode ser executado antes que um método do manipulador seja executado usando o construtor de página ou o middleware, mas somente os filtros de página Razor têm acesso a HttpContext. Os filtros têm um parâmetro derivado FilterContext, que fornece acesso a HttpContext. Por exemplo, a amostra Implementar um atributo de filtro adiciona um cabeçalho à resposta, algo que não pode ser feito com construtores nem middlewares.

Exibir ou baixar código de exemplo (como baixar)

Os filtros de página Razor fornecem os métodos a seguir, que podem ser aplicados globalmente ou no nível da página:

  • Métodos síncronos:

    • OnPageHandlerSelected: chamado depois que um método do manipulador é selecionado, mas antes que o model binding ocorra.
    • OnPageHandlerExecuting: chamado antes que o método do manipulador seja executado, após a conclusão do model binding.
    • OnPageHandlerExecuted: chamado depois que o método do manipulador é executado, antes do resultado da ação.
  • Métodos assíncronos:

    • OnPageHandlerSelectionAsync: chamado de forma assíncrona depois que o método do manipulador é selecionado, mas antes que o model binding ocorra.
    • OnPageHandlerExecutionAsync: chamado de forma assíncrona, antes que o método do manipulador seja invocado, após a conclusão do model binding.

Observação

Implemente ou a versão assíncrona ou a versão síncrona de uma interface de filtro, não ambas. Primeiro, a estrutura 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 ambas as interfaces forem implementadas, somente os métodos assíncronos serão chamados. A mesma regra aplica-se para substituições em páginas. Implemente a versão síncrona ou a assíncrona da substituição, não ambas.

Implementar filtros de página Razor globalmente

O código a seguir implementa IAsyncPageFilter:

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

namespace PageFilter.Filters
{
    public class SampleAsyncPageFilter : IAsyncPageFilter
    {
        private readonly ILogger _logger;

        public SampleAsyncPageFilter(ILogger logger)
        {
            _logger = logger;
        }

        public async Task OnPageHandlerSelectionAsync(
                                            PageHandlerSelectedContext context)
        {
            _logger.LogDebug("Global OnPageHandlerSelectionAsync called.");
            await Task.CompletedTask;
        }

        public async Task OnPageHandlerExecutionAsync(
                                            PageHandlerExecutingContext context,
                                            PageHandlerExecutionDelegate next)
        {
            _logger.LogDebug("Global OnPageHandlerExecutionAsync called.");
            await next.Invoke();
        }
    }
}

No código anterior, ILogger não é necessário. Ele é usado na amostra para fornecer informações de rastreamento do aplicativo.

O código a seguir habilita o SampleAsyncPageFilter na classe Startup:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc(options =>
    {
        options.Filters.Add(new SampleAsyncPageFilter(_logger));
    });
}

O código a seguir mostra a classe Startup completa:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using PageFilter.Filters;

namespace PageFilter
{
    public class Startup
    {
        ILogger _logger;
        public Startup(ILoggerFactory loggerFactory, IConfiguration configuration)
        {
            _logger = loggerFactory.CreateLogger<GlobalFiltersLogger>();
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc(options =>
            {
                options.Filters.Add(new SampleAsyncPageFilter(_logger));
            });
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseStaticFiles();
            app.UseCookiePolicy();

            app.UseMvc();
        }
    }
}

O código a seguir chama AddFolderApplicationModelConvention para aplicar o SampleAsyncPageFilter somente às páginas em /subFolder:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc()
       .AddRazorPagesOptions(options =>
       {
           options.Conventions.AddFolderApplicationModelConvention(
               "/subFolder",
               model => model.Filters.Add(new SampleAsyncPageFilter(_logger)));
       });
}

O código a seguir implementa o IPageFilter síncrono:

using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging;

namespace PageFilter.Filters
{
    public class SamplePageFilter : IPageFilter
    {
        private readonly ILogger _logger;

        public SamplePageFilter(ILogger logger)
        {
            _logger = logger;
        }

        public void OnPageHandlerSelected(PageHandlerSelectedContext context)
        {
            _logger.LogDebug("Global sync OnPageHandlerSelected called.");
        }

        public void OnPageHandlerExecuting(PageHandlerExecutingContext context)
        {
            _logger.LogDebug("Global sync PageHandlerExecutingContext called.");
        }

        public void OnPageHandlerExecuted(PageHandlerExecutedContext context)
        {
            _logger.LogDebug("Global sync OnPageHandlerExecuted called.");
        }
    }
}

O código a seguir habilita o SamplePageFilter:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc(options =>
    {
        options.Filters.Add(new SamplePageFilter(_logger));
    });
}

Implementar filtros de página Razor substituindo os métodos de filtro

O código a seguir substitui os filtros de página Razor síncronos:

using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;

namespace PageFilter.Pages
{
    public class IndexModel : PageModel
    {
        private readonly ILogger _logger;

        public IndexModel(ILogger<IndexModel> logger)
        {
            _logger = logger;
        }
        public string Message { get; set; }

        public void OnGet()
        {
            _logger.LogDebug("IndexModel/OnGet");
        }
        
        public override void OnPageHandlerSelected(
                                    PageHandlerSelectedContext context)
        {
            _logger.LogDebug("IndexModel/OnPageHandlerSelected");          
        }

        public override void OnPageHandlerExecuting(
                                    PageHandlerExecutingContext context)
        {
            Message = "Message set in handler executing";
            _logger.LogDebug("IndexModel/OnPageHandlerExecuting");
        }


        public override void OnPageHandlerExecuted(
                                    PageHandlerExecutedContext context)
        {
            _logger.LogDebug("IndexModel/OnPageHandlerExecuted");
        }
    }
}

Implementar um atributo de filtro

O filtro baseado em atributo interno OnResultExecutionAsync pode tornar-se uma subclasse. O filtro a seguir adiciona um cabeçalho à resposta:

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

namespace PageFilter.Filters
{
    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 });
        }
    }
}

O código a seguir se aplica ao atributo AddHeader:

[AddHeader("Author", "Rick")]
public class ContactModel : PageModel
{
    private readonly ILogger _logger;

    public ContactModel(ILogger<ContactModel> logger)
    {
        _logger = logger;
    }
    public string Message { get; set; }

    public async Task OnGetAsync()
    {
        Message = "Your contact page.";
        _logger.LogDebug("Contact/OnGet");
        await Task.CompletedTask;
    }
}

Confira Substituindo a ordem padrão para obter instruções sobre a substituição da ordem.

Confira Cancelamento e curto-circuito para obter instruções para causar um curto-circuito no pipeline do filtro por meio de um filtro.

Autorizar o atributo de filtro

O atributo Authorize pode ser aplicado a um PageModel:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace PageFilter.Pages
{
    [Authorize]
    public class ModelWithAuthFilterModel : PageModel
    {
        public IActionResult OnGet() => Page();
    }
}