ASP.NET Core 中的 Razor Pages 的筛选方法

作者:Rick Anderson

Razor 页面筛选器 IPageFilterIAsyncPageFilter 允许 Razor Pages 在运行 Razor 页面处理程序前后运行代码。 Razor 页面筛选器与 ASP.NET Core MVC 操作筛选器类似,但它们不能应用于单个页面处理程序方法。

Razor 页面筛选器:

  • 在选择处理程序方法后但在模型绑定发生前运行代码。
  • 在模型绑定完成后,执行处理程序方法前运行代码。
  • 在执行处理程序方法后运行代码。
  • 可在页面或全局范围内实现。
  • 无法应用于特定的页面处理程序方法。
  • 可以用依赖项注入 (DI) 填充构造函数依赖项。 有关详细信息,请参阅 ServiceFilterAttributeTypeFilterAttribute

虽然页构造函数和中间件允许在处理程序方法执行之前执行自定义代码,但只有 Razor 页面筛选器允许访问 HttpContext 和页面。 中间件可以访问 HttpContext,但不能访问“页面上下文”。 筛选器具有 FilterContext 派生参数,该参数提供对 HttpContext 的访问权限。 下面是页面筛选器的示例:实现筛选器属性,该属性将标头添加到响应中,而使用构造函数或中间件则无法做到这点。 对页面上下文的访问(包括对页面实例及其模型的访问)仅在执行筛选器、处理程序或 Razor 页面的正文时适用。

查看或下载示例代码如何下载

Razor 页面筛选器提供的以下方法可在全局或页面级应用:

筛选器接口的同步和异步版本任意实现一个,而不是同时实现 。 该框架会先查看筛选器是否实现了异步接口,如果是,则调用该接口。 如果不是,则调用同步接口的方法。 如果两个接口都已实现,则只会调用异步方法。 对页面中的替代应用相同的规则,同步替代或异步替代只能任选其一实现,不可二者皆选。

全局实现 Razor 页面筛选器

以下代码实现了 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();
    }
}

在前面的代码中,ProcessUserAgent.Write 是用户提供的与用户代理字符串一起使用的代码。

以下代码启用 Startup 类中的 SampleAsyncPageFilter

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

以下代码调用 AddFolderApplicationModelConvention,将 SampleAsyncPageFilter 仅应用于 /Movies 中的页面:

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

以下代码实现同步的 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.");
    }
}

以下代码启用 SamplePageFilter

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

通过重写筛选器方法实现 Razor 页面筛选器

以下代码替代异步 Razor 页面筛选器:

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

实现筛选器属性

基于属性的内置筛选器 OnResultExecutionAsync 可以进行子类化。 以下筛选器向响应添加标头:

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

以下代码应用 AddHeader 属性:

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

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

        }
    }
}

使用浏览器开发人员工具等工具来检查标头。 在响应标头下,将显示 author: Rick

有关重写顺序的说明,请参阅重写默认顺序

有关将筛选器管道与筛选器短路的说明,请参阅取消和设置短路

授权筛选器属性

Authorize 属性可应用于 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();
    }
}

作者:Rick Anderson

Razor 页面筛选器 IPageFilterIAsyncPageFilter 允许 Razor Pages 在运行 Razor 页面处理程序前后运行代码。 Razor 页面筛选器与 ASP.NET Core MVC 操作筛选器类似,但它们不能应用于单个页面处理程序方法。

Razor 页面筛选器:

  • 在选择处理程序方法后但在模型绑定发生前运行代码。
  • 在模型绑定完成后,执行处理程序方法前运行代码。
  • 在执行处理程序方法后运行代码。
  • 可在页面或全局范围内实现。
  • 无法应用于特定的页面处理程序方法。

代码可在使用页面构造函数或中间件执行处理程序方法前运行,但仅 Razor 页面筛选器可访问 HttpContext。 筛选器具有 FilterContext 派生参数,该参数提供对 HttpContext 的访问权限。 例如,实现筛选器属性示例将标头添加到响应中,而使用构造函数或中间件则无法做到这点。

查看或下载示例代码如何下载

Razor 页面筛选器提供的以下方法可在全局或页面级应用:

注意

筛选器接口的同步和异步版本任意实现一个,而不是同时实现。 该框架会先查看筛选器是否实现了异步接口,如果是,则调用该接口。 如果不是,则调用同步接口的方法。 如果两个接口都已实现,则只会调用异步方法。 对页面中的替代应用相同的规则,同步替代或异步替代只能任选其一实现,不可二者皆选。

全局实现 Razor 页面筛选器

以下代码实现了 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();
        }
    }
}

在前面的代码中,ILogger 不是必需的。 它在示例中用于提供应用程序的跟踪信息。

以下代码启用 Startup 类中的 SampleAsyncPageFilter

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

以下代码显示完整的 Startup 类:

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

以下代码调用 AddFolderApplicationModelConventionSampleAsyncPageFilter 仅应用于 /subFolder 中的页面:

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

以下代码实现同步的 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.");
        }
    }
}

以下代码启用 SamplePageFilter

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

通过重写筛选器方法实现 Razor 页面筛选器

以下代码替代同步 Razor 页面筛选器:

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

实现筛选器属性

基于属性的内置筛选器 OnResultExecutionAsync 可以进行子类化。 以下筛选器向响应添加标头:

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

以下代码应用 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;
    }
}

有关重写顺序的说明,请参阅重写默认顺序

有关将筛选器管道与筛选器短路的说明,请参阅取消和设置短路

授权筛选器属性

Authorize 属性可应用于 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();
    }
}