Métodos de filtrado de Razor Pages de ASP.NET Core

Por Rick Anderson

Los filtros de Razor Page IPageFilter e IAsyncPageFilter permiten queRazor Pages ejecute código antes y después de que se haya ejecutado el controlador de una página de Razor. Los filtros de las páginas de Razor son similares a los filtros de acciones de ASP.NET Core MVC, salvo por el hecho de que no se pueden aplicar a métodos de control de páginas individuales.

Los filtros de páginas de Razor:

  • Ejecutan código después de que se haya seleccionado un método de controlador, pero antes de que el enlace de modelos tenga lugar.
  • Ejecutan código antes de que se ejecute el método de controlador, después de que el enlace de modelos se haya completado.
  • Ejecutan código después de que se haya ejecutado el método de controlador.
  • Se pueden implementar en una página o globalmente.
  • No se pueden usar con métodos de controlador de páginas específicas.
  • Pueden tener dependencias de constructor rellenadas mediante la Inserción de dependencias (DI). Para obtener más información, vea ServiceFilterAttribute y TypeFilterAttribute.

Mientras que los constructores de páginas y el middleware permiten la ejecución de código personalizado antes de que se ejecute un método de control, solo los filtros de páginas de Razor permiten el acceso a HttpContext y a la página. El middleware tiene acceso a HttpContext, pero no al "contexto de la página". Los filtros tienen un parámetro derivado FilterContext que proporciona acceso a HttpContext. A continuación, se muestra un ejemplo de un filtro de página: Implemente un atributo de filtro que agregue un encabezado a la respuesta, lo cual que no es posible con constructores o con middleware. El acceso al contexto de la página, que incluye el acceso a las instancias de la página y a su modelo, solo está disponible cuando se ejecutan filtros, controladores o el cuerpo de una página de Razor.

Vea o descargue el código de ejemplo (cómo descargarlo)

Los filtros de páginas de Razor proporcionan los siguientes métodos, que se pueden aplicar globalmente o bien en el nivel de página:

  • Métodos sincrónicos:

    • OnPageHandlerSelected: se llama a este método después de que se haya seleccionado un método de controlador, pero antes de que se produzca el enlace de modelos.
    • OnPageHandlerExecuting: se llama a este método antes de que se ejecute el método de controlador, pero después de que el enlace de modelos se haya completado.
    • OnPageHandlerExecuted: se llama a este método después de que se ejecute el método de controlador, pero antes de obtener el resultado de la acción.
  • Métodos asincrónicos:

    • OnPageHandlerSelectionAsync: se llama a este método de forma asincrónica después de que se haya seleccionado un método de controlador, pero antes de que se produzca el enlace de modelos.
    • OnPageHandlerExecutionAsync: se llama a este método de forma asincrónica antes de que se invoque el método de controlador, pero después de que el enlace de modelos se haya completado.

Implemente la versión sincrónica o la versión asincrónica de una interfaz de filtro, pero no ambas. El marco comprueba primero si el filtro implementa la interfaz asincrónica y, si es así, es a la interfaz que llama. De lo contrario, llamará a métodos de interfaz sincrónicos. Si se implementan ambas interfaces, solo se llamará a los métodos asincrónicos. La misma regla se cumple con las invalidaciones en páginas: implemente la versión sincrónica o asincrónica de la invalidación, pero no ambas.

Implementación de filtros de páginas de Razor globalmente

El siguiente código 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();
    }
}

En el código anterior, ProcessUserAgent.Write es código proporcionado por el usuario que funciona con la cadena del agente de usuario.

El siguiente código habilita SampleAsyncPageFilter en la clase Startup:

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

El código siguiente llama a AddFolderApplicationModelConvention para aplicar SampleAsyncPageFilter solo a las páginas en /Movies:

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

El siguiente código implementa el método sincrónico IPageFilter:

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

El siguiente código habilita SamplePageFilter:

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

Implementación de filtros de páginas de Razor mediante la invalidación de métodos de filtro

El código siguiente invalida los filtros de páginas de Razor asincrónicos:

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 un atributo de filtro

Se pueden crear subclases del filtro OnResultExecutionAsync basado en atributos integrados. El siguiente filtro agrega un encabezado a la respuesta:

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

El siguiente código se aplica al atributo AddHeader:

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

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

        }
    }
}

Para examinar los encabezados, use una herramienta como, por ejemplo, las herramientas de desarrollo del explorador. En Encabezados de respuesta, se muestra author: Rick.

Vea Invalidación del orden predeterminado para obtener instrucciones sobre cómo invalidar el orden.

Vea Cancelación y cortocircuito para obtener instrucciones sobre cómo cortocircuitar la canalización de filtro de un filtro.

Atributo de filtro Authorize

El atributo Authorize se puede aplicar a un 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();
    }
}

Por Rick Anderson

Los filtros de Razor Page IPageFilter e IAsyncPageFilter permiten queRazor Pages ejecute código antes y después de que se haya ejecutado el controlador de una página de Razor. Los filtros de las páginas de Razor son similares a los filtros de acciones de ASP.NET Core MVC, salvo por el hecho de que no se pueden aplicar a métodos de control de páginas individuales.

Los filtros de páginas de Razor:

  • Ejecutan código después de que se haya seleccionado un método de controlador, pero antes de que el enlace de modelos tenga lugar.
  • Ejecutan código antes de que se ejecute el método de controlador, después de que el enlace de modelos se haya completado.
  • Ejecutan código después de que se haya ejecutado el método de controlador.
  • Se pueden implementar en una página o globalmente.
  • No se pueden usar con métodos de controlador de páginas específicas.

Se puede ejecutar código antes de que un método de control se ejecute por medio del constructor de página o el middleware, pero solo los filtros de páginas de Razor tienen acceso a HttpContext. Los filtros tienen un parámetro derivado FilterContext que proporciona acceso a HttpContext. Por ejemplo, en el ejemplo Implementar un atributo de filtro se agrega un encabezado a la respuesta, cosa que no es posible con constructores o con middleware.

Vea o descargue el código de ejemplo (cómo descargarlo)

Los filtros de páginas de Razor proporcionan los siguientes métodos, que se pueden aplicar globalmente o bien en el nivel de página:

  • Métodos sincrónicos:

    • OnPageHandlerSelected: se llama a este método después de que se haya seleccionado un método de controlador, pero antes de que se produzca el enlace de modelos.
    • OnPageHandlerExecuting: se llama a este método antes de que se ejecute el método de controlador, pero después de que el enlace de modelos se haya completado.
    • OnPageHandlerExecuted: se llama a este método después de que se ejecute el método de controlador, pero antes de obtener el resultado de la acción.
  • Métodos asincrónicos:

    • OnPageHandlerSelectionAsync: se llama a este método de forma asincrónica después de que se haya seleccionado un método de controlador, pero antes de que se produzca el enlace de modelos.
    • OnPageHandlerExecutionAsync: se llama a este método de forma asincrónica antes de que se invoque el método de controlador, después de que el enlace de modelos se haya completado.

Nota

Implemente la versión sincrónica o la versión asincrónica de una interfaz de filtro, pero no ambas. El marco comprueba primero si el filtro implementa la interfaz asincrónica y, si es así, es a la interfaz que llama. De lo contrario, llamará a métodos de interfaz sincrónicos. Si se implementan ambas interfaces, solo se llamará a los métodos asincrónicos. La misma regla se cumple con las invalidaciones en páginas: implemente la versión sincrónica o asincrónica de la invalidación, pero no ambas.

Implementación de filtros de páginas de Razor globalmente

El siguiente código 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();
        }
    }
}

En el código anterior, ILogger no es necesario; se usa para proporcionar información de seguimiento relativa a la aplicación.

El siguiente código habilita SampleAsyncPageFilter en la clase Startup:

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

El siguiente código muestra la clase 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();
        }
    }
}

El siguiente código llama a AddFolderApplicationModelConvention para aplicar SampleAsyncPageFilter solo a las páginas en /subFolder:

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

El siguiente código implementa el método sincrónico IPageFilter:

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

El siguiente código habilita SamplePageFilter:

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

Implementación de filtros de páginas de Razor mediante la invalidación de métodos de filtro

El código siguiente invalida los filtros de páginas de Razor sincrónicos:

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 un atributo de filtro

Se pueden crear subclases del filtro OnResultExecutionAsync basado en atributos integrados. El siguiente filtro agrega un encabezado a la respuesta:

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

El siguiente código se aplica al 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;
    }
}

Vea Invalidación del orden predeterminado para obtener instrucciones sobre cómo invalidar el orden.

Vea Cancelación y cortocircuito para obtener instrucciones sobre cómo cortocircuitar la canalización de filtro de un filtro.

Atributo de filtro Authorize

El atributo Authorize se puede aplicar a un 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();
    }
}