ASP.NET Core 中的筛选器Filters in ASP.NET Core

本文内容

作者:Kirk LarkinRick AndersonTom DykstraSteve Smith

通过使用 ASP.NET Core 中的筛选器,可在请求处理管道中的特定阶段之前或之后运行代码。

内置筛选器处理任务,例如:

  • 授权(防止用户访问未获授权的资源)。
  • 响应缓存(对请求管道进行短路出路,以便返回缓存的响应)。

可以创建自定义筛选器,用于处理横切关注点。横切关注点的示例包括错误处理、缓存、配置、授权和日志记录。筛选器可以避免复制代码。例如,错误处理异常筛选器可以合并错误处理。

本文档适用于 Razor Pages、API 控制器和具有视图的控制器。筛选器无法直接与 Razor 组件一起使用。筛选器只能在以下情况下间接影响组件:

  • 该组件嵌入在页面或视图中。
  • 页面或控制器/视图使用此筛选器。

查看或下载示例如何下载)。

筛选器的工作原理How filters work

筛选器在 ASP.NET Core 操作调用管道(有时称为筛选器管道)内运行。筛选器管道在 ASP.NET Core 选择了要执行的操作之后运行。

请求通过其他中间件、路由中间件、操作选择和操作调用管道进行处理。

筛选器类型Filter types

每种筛选器类型都在筛选器管道中的不同阶段执行:

  • 授权筛选器最先运行,用于确定是否已针对请求为用户授权。如果请求未获授权,授权筛选器可以让管道短路。

  • 资源筛选器

    • 授权后运行。
    • OnResourceExecuting 在筛选器管道的其余阶段之前运行代码。例如,OnResourceExecuting 在模型绑定之前运行代码。
    • OnResourceExecuted 在管道的其余阶段完成之后运行代码。
  • 操作筛选器

    • 在调用操作方法之前和之后立即运行代码。
    • 可以更改传递到操作中的参数。
    • 可以更改从操作返回的结果。
    • 不可在 Razor Pages 中使用。
  • 异常筛选器在向响应正文写入任何内容之前,对未经处理的异常应用全局策略。

  • 结果筛选器在执行操作结果之前和之后立即运行代码。仅当操作方法成功执行时,它们才会运行。对于必须围绕视图或格式化程序的执行的逻辑,它们很有用。

下图展示了筛选器类型在筛选器管道中的交互方式。

请求通过授权过滤器、资源过滤器、模型绑定、操作过滤器、操作执行和操作结果转换、异常过滤器、结果过滤器和结果执行进行处理。

实现Implementation

通过不同的接口定义,筛选器同时支持同步和异步实现。

同步筛选器在其管道阶段之前和之后运行代码。例如,OnActionExecuting 在调用操作方法之前调用。OnActionExecuted 在操作方法返回之后调用。

  1. public class MySampleActionFilter : IActionFilter
  2. {
  3. public void OnActionExecuting(ActionExecutingContext context)
  4. {
  5. // Do something before the action executes.
  6. MyDebug.Write(MethodBase.GetCurrentMethod(), context.HttpContext.Request.Path);
  7. }
  8. public void OnActionExecuted(ActionExecutedContext context)
  9. {
  10. // Do something after the action executes.
  11. MyDebug.Write(MethodBase.GetCurrentMethod(), context.HttpContext.Request.Path);
  12. }
  13. }

异步筛选器定义 On-Stage-ExecutionAsync 方法。例如,OnActionExecutionAsync

  1. public class SampleAsyncActionFilter : IAsyncActionFilter
  2. {
  3. public async Task OnActionExecutionAsync(
  4. ActionExecutingContext context,
  5. ActionExecutionDelegate next)
  6. {
  7. // Do something before the action executes.
  8. // next() calls the action method.
  9. var resultContext = await next();
  10. // resultContext.Result is set.
  11. // Do something after the action executes.
  12. }
  13. }

在前面的代码中,SampleAsyncActionFilter 具有执行操作方法的 ActionExecutionDelegate (next)。

多个筛选器阶段Multiple filter stages

可以在单个类中实现多个筛选器阶段的接口。例如,ActionFilterAttribute 类可实现:

筛选器接口的同步和异步版本任意实现一个,而不是同时实现。运行时会先查看筛选器是否实现了异步接口,如果是,则调用该接口。如果不是,则调用同步接口的方法。如果在一个类中同时实现异步和同步接口,则仅调用异步方法。使用抽象类(如 ActionFilterAttribute)时,将为每种筛选器类型仅重写同步方法或仅重写异步方法。

内置筛选器属性Built-in filter attributes

ASP.NET Core 包含许多可子类化和自定义的基于属性的内置筛选器。例如,以下结果筛选器会向响应添加标头:

  1. public class AddHeaderAttribute : ResultFilterAttribute
  2. {
  3. private readonly string _name;
  4. private readonly string _value;
  5. public AddHeaderAttribute(string name, string value)
  6. {
  7. _name = name;
  8. _value = value;
  9. }
  10. public override void OnResultExecuting(ResultExecutingContext context)
  11. {
  12. context.HttpContext.Response.Headers.Add( _name, new string[] { _value });
  13. base.OnResultExecuting(context);
  14. }
  15. }

通过使用属性,筛选器可接收参数,如前面的示例所示。AddHeaderAttribute 添加到控制器或操作方法,并指定 HTTP 标头的名称和值:

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

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

以下代码实现了 ActionFilterAttribute

  • 从配置系统读取标题和名称。与前面的示例不同,以下代码不需要将筛选器参数添加到代码中。
  • 将标题和名称添加到响应标头。
  1. public class MyActionFilterAttribute : ActionFilterAttribute
  2. {
  3. private readonly PositionOptions _settings;
  4. public MyActionFilterAttribute(IOptions<PositionOptions> options)
  5. {
  6. _settings = options.Value;
  7. Order = 1;
  8. }
  9. public override void OnResultExecuting(ResultExecutingContext context)
  10. {
  11. context.HttpContext.Response.Headers.Add(_settings.Title,
  12. new string[] { _settings.Name });
  13. base.OnResultExecuting(context);
  14. }
  15. }

使用选项模式配置系统中提供配置选项。例如,从 appsettings.json 文件中:

  1. {
  2. "Position": {
  3. "Title": "编辑器",
  4. "Name": "Joe Smith"
  5. },
  6. "Logging": {
  7. "LogLevel": {
  8. "Default": "Information",
  9. "Microsoft": "Warning",
  10. "Microsoft.Hosting.Lifetime": "Information"
  11. }
  12. },
  13. "AllowedHosts": "*"
  14. }

StartUp.ConfigureServices 中:

  • PositionOptions 类已通过 "Position" 配置区域添加到服务容器。
  • MyActionFilterAttribute 已添加到服务容器。
  1. public void ConfigureServices(IServiceCollection services)
  2. {
  3. services.Configure<PositionOptions>(
  4. Configuration.GetSection("Position"));
  5. services.AddScoped<MyActionFilterAttribute>();
  6. services.AddControllersWithViews();
  7. }

以下代码显示 PositionOptions 类:

  1. public class PositionOptions
  2. {
  3. public string Title { get; set; }
  4. public string Name { get; set; }
  5. }

以下代码将 MyActionFilterAttribute 应用于 Index2 方法:

  1. [AddHeader("Author", "Rick Anderson")]
  2. public class SampleController : Controller
  3. {
  4. public IActionResult Index()
  5. {
  6. return Content("Examine the headers using the F12 developer tools.");
  7. }
  8. [ServiceFilter(typeof(MyActionFilterAttribute))]
  9. public IActionResult Index2()
  10. {
  11. return Content("Header values by configuration.");
  12. }

在响应标头下,author: Rick AndersonEditor: Joe Smith 在调用 Sample/Index2 终结点时显示。

以下代码将 MyActionFilterAttributeAddHeaderAttribute 应用于 Razor 页面:

  1. [AddHeader("Author", "Rick Anderson")]
  2. [ServiceFilter(typeof(MyActionFilterAttribute))]
  3. public class IndexModel : PageModel
  4. {
  5. public void OnGet()
  6. {
  7. }
  8. }

无法将筛选器应用于 Razor 页面处理程序方法。它们可以应用于 Razor 页面模型或全局应用。

多种筛选器接口具有相应属性,这些属性可用作自定义实现的基类。

筛选器属性:

筛选器作用域和执行顺序Filter scopes and order of execution

可以将筛选器添加到管道中的三个作用域之一:

  • 在控制器操作上使用属性。无法将筛选器属性应用于 Razor 页面处理程序方法。
  • 在控制器或 Razor 页面上使用属性。
  • 所有控制器、操作和 Razor 页面的全局筛选器,如下面的代码所示:
  1. public void ConfigureServices(IServiceCollection services)
  2. {
  3. services.AddControllersWithViews(options =>
  4. {
  5. options.Filters.Add(typeof(MySampleActionFilter));
  6. });
  7. }

默认执行顺序Default order of execution

当管道的某个特定阶段有多个筛选器时,作用域可确定筛选器执行的默认顺序。全局筛选器涵盖类筛选器,类筛选器又涵盖方法筛选器。

在筛选器嵌套模式下,筛选器的 after 代码会按照与 before 代码相反的顺序运行。筛选器序列:

  • 全局筛选器的 before 代码。
    • 控制器筛选器和 Razor 页面筛选器的 before 代码。
      • 操作方法筛选器的 before 代码。
      • 操作方法筛选器的 after 代码。
    • 控制器筛选器和 Razor 页面筛选器的 after 代码。
  • 全局筛选器的 after 代码。

下面的示例阐释了为同步操作筛选器调用筛选器方法的顺序。

序列筛选器作用域筛选器方法
1全局OnActionExecuting
2控制器或 Razor 页面OnActionExecuting
3方法OnActionExecuting
4方法OnActionExecuted
5控制器或 Razor 页面OnActionExecuted
6全局OnActionExecuted

控制器级别筛选器Controller level filters

继承自 Controller 基类的每个控制器包括 Controller.OnActionExecutingController.OnActionExecutionAsyncController.OnActionExecutedOnActionExecuted 方法。这些方法:

  • 覆盖为给定操作运行的筛选器。
  • OnActionExecuting 在所有操作筛选器之前调用。
  • OnActionExecuted 在所有操作筛选器之后调用。
  • OnActionExecutionAsync 在所有操作筛选器之前调用。next 之后的筛选器中的代码在操作方法之后运行。

例如,在下载示例中,启动时全局应用 MySampleActionFilter

TestController

  • SampleActionFilterAttribute ([SampleActionFilter]) 应用于 FilterTest2 操作。
  • 重写 OnActionExecutingOnActionExecuted
  1. public class TestController : Controller
  2. {
  3. [SampleActionFilter(Order = int.MinValue)]
  4. public IActionResult FilterTest2()
  5. {
  6. var m = MethodBase.GetCurrentMethod();
  7. MyDebug.Write(m, HttpContext.Request.Path);
  8. return Content(m.ReflectedType.Name + "." + m.Name);
  9. }
  10. public override void OnActionExecuting(ActionExecutingContext context)
  11. {
  12. // Do something before the action executes.
  13. MyDebug.Write(MethodBase.GetCurrentMethod(), HttpContext.Request.Path);
  14. base.OnActionExecuting(context);
  15. }
  16. public override void OnActionExecuted(ActionExecutedContext context)
  17. {
  18. // Do something after the action executes.
  19. MyDebug.Write(MethodBase.GetCurrentMethod(), HttpContext.Request.Path);
  20. base.OnActionExecuted(context);
  21. }
  22. }

导航到 https://localhost:5001/Test2/FilterTest2 运行以下代码:

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

控制器级别筛选器将 Order 属性设置为 int.MinValue控制器级别筛选器无法设置为在将筛选器应用于方法之后运行。在下一节对 Order 进行了介绍。

对于 Razor Pages,请参阅通过重写筛选器方法实现 Razor 页面筛选器

重写默认顺序Overriding the default order

可以通过实现 IOrderedFilter 来重写默认执行序列。IOrderedFilter 公开了 Order 属性来确定执行顺序,该属性优先于作用域。具有较低的 Order 值的筛选器:

  • 在具有较高的 Order 值的筛选器之前运行 before 代码。
  • 在具有较高的 Order 值的筛选器之后运行 after 代码。

使用构造函数参数设置了 Order 属性:

  1. [SampleActionFilter(Order = int.MinValue)]

请考虑以下控制器中的两个操作筛选器:

  1. [MyAction2Filter]
  2. public class Test2Controller : Controller
  3. {
  4. public IActionResult FilterTest2()
  5. {
  6. var m = MethodBase.GetCurrentMethod();
  7. MyDebug.Write(m, HttpContext.Request.Path);
  8. return Content(m.ReflectedType.Name + "." + m.Name);
  9. }
  10. public override void OnActionExecuting(ActionExecutingContext context)
  11. {
  12. // Do something before the action executes.
  13. MyDebug.Write(MethodBase.GetCurrentMethod(), HttpContext.Request.Path);
  14. base.OnActionExecuting(context);
  15. }
  16. public override void OnActionExecuted(ActionExecutedContext context)
  17. {
  18. // Do something after the action executes.
  19. MyDebug.Write(MethodBase.GetCurrentMethod(), HttpContext.Request.Path);
  20. base.OnActionExecuted(context);
  21. }
  22. }

StartUp.ConfigureServices 中添加了全局筛选器:

  1. public void ConfigureServices(IServiceCollection services)
  2. {
  3. services.AddControllersWithViews(options =>
  4. {
  5. options.Filters.Add(typeof(MySampleActionFilter));
  6. });
  7. }

3 个筛选器按下列顺序运行:

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

在确定筛选器的运行顺序时,Order 属性重写作用域。先按顺序对筛选器排序,然后使用作用域消除并列问题。所有内置筛选器实现 IOrderedFilter 并将默认 Order 值设为 0。如前所述,控制器级别筛选器将 Order 属性设置为 int.MinValue。对于内置筛选器,作用域会确定顺序,除非将 Order 设为非零值。

在前面的代码中,MySampleActionFilter 具有全局作用域,因此它在具有控制器作用域的 MyAction2FilterAttribute 之前运行。若要首先运行 MyAction2FilterAttribute,请将顺序设置为 int.MinValue

  1. [MyAction2Filter(int.MinValue)]
  2. public class Test2Controller : Controller
  3. {
  4. public IActionResult FilterTest2()
  5. {
  6. var m = MethodBase.GetCurrentMethod();
  7. MyDebug.Write(m, HttpContext.Request.Path);
  8. return Content(m.ReflectedType.Name + "." + m.Name);
  9. }
  10. public override void OnActionExecuting(ActionExecutingContext context)
  11. {
  12. // Do something before the action executes.
  13. MyDebug.Write(MethodBase.GetCurrentMethod(), HttpContext.Request.Path);
  14. base.OnActionExecuting(context);
  15. }
  16. public override void OnActionExecuted(ActionExecutedContext context)
  17. {
  18. // Do something after the action executes.
  19. MyDebug.Write(MethodBase.GetCurrentMethod(), HttpContext.Request.Path);
  20. base.OnActionExecuted(context);
  21. }
  22. }

若要首先运行全局筛选器 MySampleActionFilter,请将 Order 设置为 int.MinValue

  1. public void ConfigureServices(IServiceCollection services)
  2. {
  3. services.AddControllersWithViews(options =>
  4. {
  5. options.Filters.Add(typeof(MySampleActionFilter),
  6. int.MinValue);
  7. });
  8. }

取消和设置短路Cancellation and short-circuiting

通过设置提供给筛选器方法的 ResourceExecutingContext 参数上的 Result 属性,可以使筛选器管道短路。例如,以下资源筛选器将阻止执行管道的其余阶段:

  1. public class ShortCircuitingResourceFilterAttribute : Attribute, IResourceFilter
  2. {
  3. public void OnResourceExecuting(ResourceExecutingContext context)
  4. {
  5. context.Result = new ContentResult()
  6. {
  7. Content = "Resource unavailable - header not set."
  8. };
  9. }
  10. public void OnResourceExecuted(ResourceExecutedContext context)
  11. {
  12. }
  13. }

在下面的代码中,ShortCircuitingResourceFilterAddHeader 筛选器都以 SomeResource 操作方法为目标。ShortCircuitingResourceFilter

  • 先运行,因为它是资源筛选器且 AddHeader 是操作筛选器。
  • 对管道的其余部分进行短路处理。

这样 AddHeader 筛选器就不会为 SomeResource 操作运行。如果这两个筛选器都应用于操作方法级别,只要 ShortCircuitingResourceFilter 先运行,此行为就不会变。先运行 ShortCircuitingResourceFilter(考虑到它的筛选器类型),或显式使用 Order 属性。

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

依赖关系注入Dependency injection

可按类型或实例添加筛选器。如果添加实例,该实例将用于每个请求。如果添加类型,则将激活该类型。激活类型的筛选器意味着:

  • 将为每个请求创建一个实例。
  • 依赖关系注入 (DI) 将填充所有构造函数依赖项。

如果将筛选器作为属性实现并直接添加到控制器类或操作方法中,则该筛选器不能由依赖关系注入 (DI) 提供构造函数依赖项。无法由 DI 提供构造函数依赖项,因为:

  • 属性在应用时必须提供自己的构造函数参数。
  • 这是属性工作原理上的限制。

以下筛选器支持从 DI 提供的构造函数依赖项:

可以将前面的筛选器应用于控制器或操作方法:

可以从 DI 获取记录器。但是,避免创建和使用筛选器仅用于日志记录。内置框架日志记录通常提供日志记录所需的内容。添加到筛选器的日志记录:

  • 应重点关注业务域问题或特定于筛选器的行为。
  • 不应记录操作或其他框架事件。内置筛选器记录操作和框架事件。

ServiceFilterAttributeServiceFilterAttribute

ConfigureServices 中注册服务筛选器实现类型。ServiceFilterAttribute 可从 DI 检索筛选器实例。

以下代码显示 AddHeaderResultServiceFilter

  1. public class AddHeaderResultServiceFilter : IResultFilter
  2. {
  3. private ILogger _logger;
  4. public AddHeaderResultServiceFilter(ILoggerFactory loggerFactory)
  5. {
  6. _logger = loggerFactory.CreateLogger<AddHeaderResultServiceFilter>();
  7. }
  8. public void OnResultExecuting(ResultExecutingContext context)
  9. {
  10. var headerName = "OnResultExecuting";
  11. context.HttpContext.Response.Headers.Add(
  12. headerName, new string[] { "ResultExecutingSuccessfully" });
  13. _logger.LogInformation("Header added: {HeaderName}", headerName);
  14. }
  15. public void OnResultExecuted(ResultExecutedContext context)
  16. {
  17. // Can't add to headers here because response has started.
  18. _logger.LogInformation("AddHeaderResultServiceFilter.OnResultExecuted");
  19. }
  20. }

在以下代码中,AddHeaderResultServiceFilter 将添加到 DI 容器中:

  1. public void ConfigureServices(IServiceCollection services)
  2. {
  3. // Add service filters.
  4. services.AddScoped<AddHeaderResultServiceFilter>();
  5. services.AddScoped<SampleActionFilterAttribute>();
  6. services.AddControllersWithViews(options =>
  7. {
  8. options.Filters.Add(new AddHeaderAttribute("GlobalAddHeader",
  9. "Result filter added to MvcOptions.Filters")); // An instance
  10. options.Filters.Add(typeof(MySampleActionFilter)); // By type
  11. options.Filters.Add(new SampleGlobalActionFilter()); // An instance
  12. });
  13. }

在以下代码中,ServiceFilter 属性将从 DI 中检索 AddHeaderResultServiceFilter 筛选器的实例:

  1. [ServiceFilter(typeof(AddHeaderResultServiceFilter))]
  2. public IActionResult Index()
  3. {
  4. return View();
  5. }

使用 ServiceFilterAttribute 时,ServiceFilterAttribute.IsReusable 设置:

  • 提供以下提示:筛选器实例可能在其创建的请求范围之外被重用。ASP.NET Core 运行时不保证:

    • 将创建筛选器的单一实例。
    • 稍后不会从 DI 容器重新请求筛选器。
  • 不应与依赖于生命周期不同于单一实例的服务的筛选器一起使用。

ServiceFilterAttribute 可实现 IFilterFactoryIFilterFactory 公开用于创建 IFilterMetadata 实例的 CreateInstance 方法。CreateInstance 从 DI 中加载指定的类型。

TypeFilterAttributeTypeFilterAttribute

TypeFilterAttributeServiceFilterAttribute 类似,但不会直接从 DI 容器解析其类型。它使用 Microsoft.Extensions.DependencyInjection.ObjectFactory 对类型进行实例化。

因为不会直接从 DI 容器解析 TypeFilterAttribute 类型:

  • 使用 TypeFilterAttribute 引用的类型不需要注册在 DI 容器中。它们具备由 DI 容器实现的依赖项。
  • TypeFilterAttribute 可以选择为类型接受构造函数参数。

使用 TypeFilterAttribute 时,TypeFilterAttribute.IsReusable 设置:

  • 提供提示:筛选器实例可能在其创建的请求范围之外被重用。ASP.NET Core 运行时不保证将创建筛选器的单一实例。

  • 不应与依赖于生命周期不同于单一实例的服务的筛选器一起使用。

下面的示例演示如何使用 TypeFilterAttribute 将参数传递到类型:

  1. [TypeFilter(typeof(LogConstantFilter),
  2. Arguments = new object[] { "Method 'Hi' called" })]
  3. public IActionResult Hi(string name)
  4. {
  5. return Content($"Hi {name}");
  6. }

授权筛选器Authorization filters

授权筛选器:

  • 是筛选器管道中运行的第一个筛选器。
  • 控制对操作方法的访问。
  • 具有在它之前的执行的方法,但没有之后执行的方法。

自定义授权筛选器需要自定义授权框架。建议配置授权策略或编写自定义授权策略,而不是编写自定义筛选器。内置授权筛选器:

  • 调用授权系统。
  • 不授权请求。

不会在授权筛选器中引发异常:

  • 不会处理异常。
  • 异常筛选器不会处理异常。

在授权筛选器出现异常时请小心应对。

详细了解授权

资源筛选器Resource filters

资源筛选器:

如果要使大部分管道短路,资源筛选器会很有用。例如,如果缓存命中,则缓存筛选器可以绕开管道的其余阶段。

资源筛选器示例:

操作筛选器Action filters

操作筛选器不应用于 Razor Pages。Razor Pages 支持 IPageFilterIAsyncPageFilter有关详细信息,请参阅 Razor 页面的筛选方法

操作筛选器:

以下代码显示示例操作筛选器:

  1. public class MySampleActionFilter : IActionFilter
  2. {
  3. public void OnActionExecuting(ActionExecutingContext context)
  4. {
  5. // Do something before the action executes.
  6. MyDebug.Write(MethodBase.GetCurrentMethod(), context.HttpContext.Request.Path);
  7. }
  8. public void OnActionExecuted(ActionExecutedContext context)
  9. {
  10. // Do something after the action executes.
  11. MyDebug.Write(MethodBase.GetCurrentMethod(), context.HttpContext.Request.Path);
  12. }
  13. }

ActionExecutingContext 提供以下属性:

  • ActionArguments - 用于读取操作方法的输入。
  • Controller - 用于处理控制器实例。
  • Result - 设置 Result 会使操作方法和后续操作筛选器的执行短路。

在操作方法中引发异常:

  • 防止运行后续筛选器。
  • 与设置 Result 不同,结果被视为失败而不是成功。

ActionExecutedContext 提供 ControllerResult 以及以下属性:

  • Canceled - 如果操作执行已被另一个筛选器设置短路,则为 true。

  • Exception - 如果操作或之前运行的操作筛选器引发了异常,则为非 NULL 值。将此属性设置为 null:

    • 有效地处理异常。
    • 执行 Result,从操作方法中将它返回。

对于 IAsyncActionFilter,一个向 ActionExecutionDelegate 的调用可以达到以下目的:

  • 执行所有后续操作筛选器和操作方法。
  • 返回 ActionExecutedContext

若要设置短路,可将 Microsoft.AspNetCore.Mvc.Filters.ActionExecutingContext.Result 分配到某个结果实例,并且不调用 next (ActionExecutionDelegate)。

该框架提供一个可子类化的抽象 ActionFilterAttribute

OnActionExecuting 操作筛选器可用于:

  • 验证模型状态。
  • 如果状态无效,则返回错误。
  1. public class ValidateModelAttribute : ActionFilterAttribute
  2. {
  3. public override void OnActionExecuting(ActionExecutingContext
  4. context)
  5. {
  6. if (!context.ModelState.IsValid)
  7. {
  8. context.Result = new BadRequestObjectResult(
  9. context.ModelState);
  10. }
  11. }

OnActionExecuted 方法在操作方法之后运行:

  • 可通过 Result 属性查看和处理操作结果。

  • 如果操作执行已被另一个筛选器设置短路,则 Canceled 设置为 true。

  • 如果操作或后续操作筛选器引发了异常,则 Exception 设置为非 NULL 值。将 Exception 设置为 null:

    • 有效地处理异常。
    • 执行 ActionExecutedContext.Result,从操作方法中将它正常返回。
  1. public class ValidateModelAttribute : ActionFilterAttribute
  2. {
  3. public override void OnActionExecuting(ActionExecutingContext
  4. context)
  5. {
  6. if (!context.ModelState.IsValid)
  7. {
  8. context.Result = new BadRequestObjectResult(
  9. context.ModelState);
  10. }
  11. }
  12. public override void OnActionExecuted(ActionExecutedContext
  13. context)
  14. {
  15. var result = context.Result;
  16. // Do something with Result.
  17. if (context.Canceled == true)
  18. {
  19. // Action execution was short-circuited by another filter.
  20. }
  21. if(context.Exception != null)
  22. {
  23. // Exception thrown by action or action filter.
  24. // Set to null to handle the exception.
  25. context.Exception = null;
  26. }
  27. base.OnActionExecuted(context);
  28. }
  29. }

异常筛选器Exception filters

异常筛选器:

下面的异常筛选器示例使用自定义错误视图,显示在开发应用时发生的异常的相关详细信息:

  1. public class CustomExceptionFilter : IExceptionFilter
  2. {
  3. private readonly IWebHostEnvironment _hostingEnvironment;
  4. private readonly IModelMetadataProvider _modelMetadataProvider;
  5. public CustomExceptionFilter(
  6. IWebHostEnvironment hostingEnvironment,
  7. IModelMetadataProvider modelMetadataProvider)
  8. {
  9. _hostingEnvironment = hostingEnvironment;
  10. _modelMetadataProvider = modelMetadataProvider;
  11. }
  12. public void OnException(ExceptionContext context)
  13. {
  14. if (!_hostingEnvironment.IsDevelopment())
  15. {
  16. return;
  17. }
  18. var result = new ViewResult {ViewName = "CustomError"};
  19. result.ViewData = new ViewDataDictionary(_modelMetadataProvider,
  20. context.ModelState);
  21. result.ViewData.Add("Exception", context.Exception);
  22. // TODO: Pass additional detailed data via ViewData
  23. context.Result = result;
  24. }
  25. }

以下代码测试异常筛选器:

  1. [TypeFilter(typeof(CustomExceptionFilter))]
  2. public class FailingController : Controller
  3. {
  4. [AddHeader("Failing Controller",
  5. "Won't appear when exception is handled")]
  6. public IActionResult Index()
  7. {
  8. throw new Exception("Testing custom exception filter.");
  9. }
  10. }

异常筛选器:

  • 没有之前和之后的事件。
  • 实现 OnExceptionOnExceptionAsync
  • 处理 Razor 页面或控制器创建、模型绑定、操作筛选器或操作方法中发生的未经处理的异常。
  • 请不要捕获资源筛选器、结果筛选器或 MVC 结果执行中发生的异常。

若要处理异常,请将 ExceptionHandled 属性设置为 true,或编写响应。这将停止传播异常。异常筛选器无法将异常转变为“成功”。只有操作筛选器才能执行该转变。

异常筛选器:

  • 非常适合捕获发生在操作中的异常。
  • 并不像错误处理中间件那么灵活。

建议使用中间件处理异常。基于所调用的操作方法,仅当错误处理不同时,才使用异常筛选器。例如,应用可能具有用于 API 终结点和视图/HTML 的操作方法。API 终结点可能返回 JSON 形式的错误信息,而基于视图的操作可能返回 HTML 形式的错误页。

结果筛选器Result filters

结果筛选器:

IResultFilter 和 IAsyncResultFilterIResultFilter and IAsyncResultFilter

以下代码显示一个添加 HTTP 标头的结果筛选器:

  1. public class AddHeaderResultServiceFilter : IResultFilter
  2. {
  3. private ILogger _logger;
  4. public AddHeaderResultServiceFilter(ILoggerFactory loggerFactory)
  5. {
  6. _logger = loggerFactory.CreateLogger<AddHeaderResultServiceFilter>();
  7. }
  8. public void OnResultExecuting(ResultExecutingContext context)
  9. {
  10. var headerName = "OnResultExecuting";
  11. context.HttpContext.Response.Headers.Add(
  12. headerName, new string[] { "ResultExecutingSuccessfully" });
  13. _logger.LogInformation("Header added: {HeaderName}", headerName);
  14. }
  15. public void OnResultExecuted(ResultExecutedContext context)
  16. {
  17. // Can't add to headers here because response has started.
  18. _logger.LogInformation("AddHeaderResultServiceFilter.OnResultExecuted");
  19. }
  20. }

要执行的结果类型取决于所执行的操作。返回视图的操作会将所有 Razor 处理作为要执行的 ViewResult 的一部分。API 方法可能会将某些序列化操作作为结果执行的一部分。详细了解操作结果

仅当操作或操作筛选器生成操作结果时,才会执行结果筛选器。不会在以下情况下执行结果筛选器:

  • 授权筛选器或资源筛选器使管道短路。
  • 异常筛选器通过生成操作结果来处理异常。

Microsoft.AspNetCore.Mvc.Filters.IResultFilter.OnResultExecuting 方法可以将 Microsoft.AspNetCore.Mvc.Filters.ResultExecutingContext.Cancel 设置为 true,使操作结果和后续结果筛选器的执行短路。设置短路时写入响应对象,以免生成空响应。如果在 IResultFilter.OnResultExecuting 中引发异常,则会导致:

  • 阻止操作结果和后续筛选器的执行。
  • 结果被视为失败而不是成功。

Microsoft.AspNetCore.Mvc.Filters.IResultFilter.OnResultExecuted 方法运行时,响应可能已发送到客户端。如果响应已发送到客户端,则无法更改。

如果操作结果执行已被另一个筛选器设置短路,则 ResultExecutedContext.Canceled 设置为 true

如果操作结果或后续结果筛选器引发了异常,则 ResultExecutedContext.Exception 设置为非 NULL 值。Exception 设置为 NULL 可有效地处理异常,并防止在管道的后续阶段引发该异常。处理结果筛选器中出现的异常时,没有可靠的方法来将数据写入响应。如果在操作结果引发异常时标头已刷新到客户端,则没有任何可靠的机制可用于发送失败代码。

对于 IAsyncResultFilter,通过调用 ResultExecutionDelegate 上的 await next 可执行所有后续结果筛选器和操作结果。若要设置短路,请将 ResultExecutingContext.Cancel 设置为 true,并且不调用 ResultExecutionDelegate

  1. public class MyAsyncResponseFilter : IAsyncResultFilter
  2. {
  3. public async Task OnResultExecutionAsync(ResultExecutingContext context,
  4. ResultExecutionDelegate next)
  5. {
  6. if (!(context.Result is EmptyResult))
  7. {
  8. await next();
  9. }
  10. else
  11. {
  12. context.Cancel = true;
  13. }
  14. }
  15. }

该框架提供一个可子类化的抽象 ResultFilterAttribute前面所示的 AddHeaderAttribute 类是一种结果筛选器属性。

IAlwaysRunResultFilter 和 IAsyncAlwaysRunResultFilterIAlwaysRunResultFilter and IAsyncAlwaysRunResultFilter

IAlwaysRunResultFilterIAsyncAlwaysRunResultFilter 接口声明了一个针对所有操作结果运行的 IResultFilter 实现。这包括由以下对象生成的操作结果:

  • 设置短路的授权筛选器和资源筛选器。
  • 异常筛选器。

例如,以下筛选器始终运行并在内容协商失败时设置具有“422 无法处理的实体”状态代码的操作结果 (ObjectResult):

  1. public class UnprocessableResultFilter : Attribute, IAlwaysRunResultFilter
  2. {
  3. public void OnResultExecuting(ResultExecutingContext context)
  4. {
  5. if (context.Result is StatusCodeResult statusCodeResult &&
  6. statusCodeResult.StatusCode == 415)
  7. {
  8. context.Result = new ObjectResult("Can't process this!")
  9. {
  10. StatusCode = 422,
  11. };
  12. }
  13. }
  14. public void OnResultExecuted(ResultExecutedContext context)
  15. {
  16. }
  17. }

IFilterFactoryIFilterFactory

IFilterFactory 可实现 IFilterMetadata因此,IFilterFactory 实例可在筛选器管道中的任意位置用作 IFilterMetadata 实例。当运行时准备调用筛选器时,它会尝试将其转换为 IFilterFactory如果转换成功,则调用 CreateInstance 方法来创建将调用的 IFilterMetadata 实例。这提供了一种很灵活的设计,因为无需在应用启动时显式设置精确的筛选器管道。

可以使用自定义属性实现来实现 IFilterFactory 作为另一种创建筛选器的方法:

  1. public class AddHeaderWithFactoryAttribute : Attribute, IFilterFactory
  2. {
  3. // Implement IFilterFactory
  4. public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
  5. {
  6. return new InternalAddHeaderFilter();
  7. }
  8. private class InternalAddHeaderFilter : IResultFilter
  9. {
  10. public void OnResultExecuting(ResultExecutingContext context)
  11. {
  12. context.HttpContext.Response.Headers.Add(
  13. "Internal", new string[] { "My header" });
  14. }
  15. public void OnResultExecuted(ResultExecutedContext context)
  16. {
  17. }
  18. }
  19. public bool IsReusable
  20. {
  21. get
  22. {
  23. return false;
  24. }
  25. }
  26. }

在以下代码中应用了筛选器:

  1. [AddHeader("Author", "Rick Anderson")]
  2. public class SampleController : Controller
  3. {
  4. public IActionResult Index()
  5. {
  6. return Content("Examine the headers using the F12 developer tools.");
  7. }
  8. [ServiceFilter(typeof(MyActionFilterAttribute))]
  9. public IActionResult Index2()
  10. {
  11. return Content("Header values by configuration.");
  12. }
  13. [ShortCircuitingResourceFilter]
  14. public IActionResult SomeResource()
  15. {
  16. return Content("Successful access to resource - header is set.");
  17. }
  18. [AddHeaderWithFactory]
  19. public IActionResult HeaderWithFactory()
  20. {
  21. return Content("Examine the headers using the F12 developer tools.");
  22. }
  23. }

通过运行下载示例来测试前面的代码:

F12 开发人员工具显示示例代码添加的以下响应标头:

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

前面的代码创建 internal: My header 响应标头。

在属性上实现 IFilterFactoryIFilterFactory implemented on an attribute

实现 IFilterFactory 的筛选器可用于以下筛选器:

  • 不需要传递参数。
  • 具备需要由 DI 填充的构造函数依赖项。

TypeFilterAttribute 可实现 IFilterFactoryIFilterFactory 公开用于创建 IFilterMetadata 实例的 CreateInstance 方法。CreateInstance 从服务容器 (DI) 中加载指定的类型。

  1. public class SampleActionFilterAttribute : TypeFilterAttribute
  2. {
  3. public SampleActionFilterAttribute()
  4. :base(typeof(SampleActionFilterImpl))
  5. {
  6. }
  7. private class SampleActionFilterImpl : IActionFilter
  8. {
  9. private readonly ILogger _logger;
  10. public SampleActionFilterImpl(ILoggerFactory loggerFactory)
  11. {
  12. _logger = loggerFactory.CreateLogger<SampleActionFilterAttribute>();
  13. }
  14. public void OnActionExecuting(ActionExecutingContext context)
  15. {
  16. _logger.LogInformation("SampleActionFilterAttribute.OnActionExecuting");
  17. }
  18. public void OnActionExecuted(ActionExecutedContext context)
  19. {
  20. _logger.LogInformation("SampleActionFilterAttribute.OnActionExecuted");
  21. }
  22. }
  23. }

以下代码显示应用 [SampleActionFilter] 的三种方法:

  1. [SampleActionFilter]
  2. public IActionResult FilterTest()
  3. {
  4. return Content("From FilterTest");
  5. }
  6. [TypeFilter(typeof(SampleActionFilterAttribute))]
  7. public IActionResult TypeFilterTest()
  8. {
  9. return Content("From TypeFilterTest");
  10. }
  11. // ServiceFilter must be registered in ConfigureServices or
  12. // System.InvalidOperationException: No service for type '<filter>'
  13. // has been registered. Is thrown.
  14. [ServiceFilter(typeof(SampleActionFilterAttribute))]
  15. public IActionResult ServiceFilterTest()
  16. {
  17. return Content("From ServiceFilterTest");
  18. }

在前面的代码中,使用 [SampleActionFilter] 修饰方法是应用 SampleActionFilter 的首选方法。

在筛选器管道中使用中间件Using middleware in the filter pipeline

资源筛选器的工作方式与中间件类似,即涵盖管道中的所有后续执行。但筛选器又不同于中间件,它们是运行时的一部分,这意味着它们有权访问上下文和构造。

若要将中间件用作筛选器,可创建一个具有 Configure 方法的类型,该方法可指定要注入到筛选器管道的中间件。下面的示例使用本地化中间件为请求建立当前区域性:

  1. public class LocalizationPipeline
  2. {
  3. public void Configure(IApplicationBuilder applicationBuilder)
  4. {
  5. var supportedCultures = new[]
  6. {
  7. new CultureInfo("en-US"),
  8. new CultureInfo("fr")
  9. };
  10. var options = new RequestLocalizationOptions
  11. {
  12. DefaultRequestCulture = new RequestCulture(
  13. culture: "en-US",
  14. uiCulture: "en-US"),
  15. SupportedCultures = supportedCultures,
  16. SupportedUICultures = supportedCultures
  17. };
  18. options.RequestCultureProviders = new[]
  19. { new RouteDataRequestCultureProvider() {
  20. Options = options } };
  21. applicationBuilder.UseRequestLocalization(options);
  22. }
  23. }

使用 MiddlewareFilterAttribute 运行中间件:

  1. [Route("{culture}/[controller]/[action]")]
  2. [MiddlewareFilter(typeof(LocalizationPipeline))]
  3. public IActionResult CultureFromRouteData()
  4. {
  5. return Content(
  6. $"CurrentCulture:{CultureInfo.CurrentCulture.Name},"
  7. + $"CurrentUICulture:{CultureInfo.CurrentUICulture.Name}");
  8. }

中间件筛选器与资源筛选器在筛选器管道的相同阶段运行,即,在模型绑定之前以及管道的其余阶段之后。

后续操作Next actions

作者:Kirk LarkinRick AndersonTom DykstraSteve Smith

通过使用 ASP.NET Core 中的筛选器,可在请求处理管道中的特定阶段之前或之后运行代码。

内置筛选器处理任务,例如:

  • 授权(防止用户访问未获授权的资源)。
  • 响应缓存(对请求管道进行短路出路,以便返回缓存的响应)。

可以创建自定义筛选器,用于处理横切关注点。横切关注点的示例包括错误处理、缓存、配置、授权和日志记录。筛选器可以避免复制代码。例如,错误处理异常筛选器可以合并错误处理。

本文档适用于 Razor Pages、API 控制器和具有视图的控制器。

查看或下载示例如何下载)。

筛选器的工作原理How filters work

筛选器在 ASP.NET Core 操作调用管道(有时称为筛选器管道)内运行。筛选器管道在 ASP.NET Core 选择了要执行的操作之后运行。

请求通过其他中间件、路由中间件、操作选择和 ASP.NET Core 操作调用管道进行处理。

筛选器类型Filter types

每种筛选器类型都在筛选器管道中的不同阶段执行:

  • 授权筛选器最先运行,用于确定是否已针对请求为用户授权。如果请求未获授权,授权筛选器可以让管道短路。

  • 资源筛选器

    • 授权后运行。
    • OnResourceExecuting 可以在筛选器管道的其余阶段之前运行代码。例如,OnResourceExecuting 可以在模型绑定之前运行代码。
    • OnResourceExecuted 可以在管道的其余阶段完成之后运行代码。
  • 操作筛选器可以在调用单个操作方法之前和之后立即运行代码。它们可用于处理传入某个操作的参数以及从该操作返回的结果。不可在 Razor Pages 中使用操作筛选器。

  • 异常筛选器用于在向响应正文写入任何内容之前,对未经处理的异常应用全局策略。

  • 结果筛选器可以在执行单个操作结果之前和之后立即运行代码。仅当操作方法成功执行时,它们才会运行。对于必须围绕视图或格式化程序的执行的逻辑,它们很有用。

下图展示了筛选器类型在筛选器管道中的交互方式。

请求通过授权过滤器、资源过滤器、模型绑定、操作过滤器、操作执行和操作结果转换、异常过滤器、结果过滤器和结果执行进行处理。

实现Implementation

通过不同的接口定义,筛选器同时支持同步和异步实现。

同步筛选器可以在其管道阶段之前 (On-Stage-Executing) 和之后 (On-Stage-Executed) 运行代码。例如,OnActionExecuting 在调用操作方法之前调用。OnActionExecuted 在操作方法返回之后调用。

  1. public class MySampleActionFilter : IActionFilter
  2. {
  3. public void OnActionExecuting(ActionExecutingContext context)
  4. {
  5. // Do something before the action executes.
  6. }
  7. public void OnActionExecuted(ActionExecutedContext context)
  8. {
  9. // Do something after the action executes.
  10. }
  11. }

异步筛选器定义 On-Stage-ExecutionAsync 方法:

  1. public class SampleAsyncActionFilter : IAsyncActionFilter
  2. {
  3. public async Task OnActionExecutionAsync(
  4. ActionExecutingContext context,
  5. ActionExecutionDelegate next)
  6. {
  7. // Do something before the action executes.
  8. // next() calls the action method.
  9. var resultContext = await next();
  10. // resultContext.Result is set.
  11. // Do something after the action executes.
  12. }
  13. }

在前面的代码中,SampleAsyncActionFilter 具有执行操作方法的 ActionExecutionDelegate (next)。每个 On-Stage-ExecutionAsync 方法采用执行筛选器的管道阶段的 FilterType-ExecutionDelegate

多个筛选器阶段Multiple filter stages

可以在单个类中实现多个筛选器阶段的接口。例如,ActionFilterAttribute 类实现 IActionFilterIResultFilter 及其异步等效接口。

筛选器接口的同步和异步版本任意实现一个,而不是同时实现。运行时会先查看筛选器是否实现了异步接口,如果是,则调用该接口。如果不是,则调用同步接口的方法。如果在一个类中同时实现异步和同步接口,则仅调用异步方法。使用抽象类时(如 ActionFilterAttribute),将为每种筛选器类型仅重写同步方法或仅重写异步方法。

内置筛选器属性Built-in filter attributes

ASP.NET Core 包含许多可子类化和自定义的基于属性的内置筛选器。例如,以下结果筛选器会向响应添加标头:

  1. public class AddHeaderAttribute : ResultFilterAttribute
  2. {
  3. private readonly string _name;
  4. private readonly string _value;
  5. public AddHeaderAttribute(string name, string value)
  6. {
  7. _name = name;
  8. _value = value;
  9. }
  10. public override void OnResultExecuting(ResultExecutingContext context)
  11. {
  12. context.HttpContext.Response.Headers.Add( _name, new string[] { _value });
  13. base.OnResultExecuting(context);
  14. }
  15. }

通过使用属性,筛选器可接收参数,如前面的示例所示。AddHeaderAttribute 添加到控制器或操作方法,并指定 HTTP 标头的名称和值:

  1. [AddHeader("Author", "Joe Smith")]
  2. public class SampleController : Controller
  3. {
  4. public IActionResult Index()
  5. {
  6. return Content("Examine the headers using the F12 developer tools.");
  7. }
  8. [ShortCircuitingResourceFilter]
  9. public IActionResult SomeResource()
  10. {
  11. return Content("Successful access to resource - header is set.");
  12. }

多种筛选器接口具有相应属性,这些属性可用作自定义实现的基类。

筛选器属性:

筛选器作用域和执行顺序Filter scopes and order of execution

可以将筛选器添加到管道中的三个作用域之一:

  • 在操作上使用属性。
  • 在控制器上使用属性。
  • 所有控制器和操作的全局筛选器,如下面的代码所示:
  1. public void ConfigureServices(IServiceCollection services)
  2. {
  3. services.AddMvc(options =>
  4. {
  5. options.Filters.Add(new AddHeaderAttribute("GlobalAddHeader",
  6. "Result filter added to MvcOptions.Filters")); // An instance
  7. options.Filters.Add(typeof(MySampleActionFilter)); // By type
  8. options.Filters.Add(new SampleGlobalActionFilter()); // An instance
  9. }).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
  10. }

前面的代码使用 MvcOptions.Filters 集合全局添加三个筛选器。

默认执行顺序Default order of execution

当有同一类型的多个筛选器时,作用域可确定筛选器执行的默认顺序。全局筛选器涵盖类筛选器。类筛选器涵盖方法筛选器。

在筛选器嵌套模式下,筛选器的 after 代码会按照与 before 代码相反的顺序运行。筛选器序列:

  • 全局筛选器的 before 代码。
    • 控制器筛选器的 before 代码。
      • 操作方法筛选器的 before 代码。
      • 操作方法筛选器的 after 代码。
    • 控制器筛选器的 after 代码。
  • 全局筛选器的 after 代码。

下面的示例阐释了为同步操作筛选器调用筛选器方法的顺序。

序列筛选器作用域筛选器方法
1全局OnActionExecuting
2控制器OnActionExecuting
3方法OnActionExecuting
4方法OnActionExecuted
5控制器OnActionExecuted
6全局OnActionExecuted

此序列显示:

  • 方法筛选器已嵌套在控制器筛选器中。
  • 控制器筛选器已嵌套在全局筛选器中。

控制器和 Razor 页面级筛选器Controller and Razor Page level filters

继承自 Controller 基类的每个控制器包括 Controller.OnActionExecutingController.OnActionExecutionAsyncController.OnActionExecutedOnActionExecuted 方法。这些方法:

  • 覆盖为给定操作运行的筛选器。
  • OnActionExecuting 在所有操作筛选器之前调用。
  • OnActionExecuted 在所有操作筛选器之后调用。
  • OnActionExecutionAsync 在所有操作筛选器之前调用。next 之后的筛选器中的代码在操作方法之后运行。

例如,在下载示例中,启动时全局应用 MySampleActionFilter

TestController

  • SampleActionFilterAttribute ([SampleActionFilter]) 应用于 FilterTest2 操作。
  • 重写 OnActionExecutingOnActionExecuted
  1. public class TestController : Controller
  2. {
  3. [SampleActionFilter]
  4. public IActionResult FilterTest2()
  5. {
  6. return Content($"From FilterTest2");
  7. }
  8. public override void OnActionExecuting(ActionExecutingContext context)
  9. {
  10. // Do something before the action executes.
  11. base.OnActionExecuting(context);
  12. }
  13. public override void OnActionExecuted(ActionExecutedContext context)
  14. {
  15. // Do something after the action executes.
  16. base.OnActionExecuted(context);
  17. }
  18. }

导航到 https://localhost:5001/Test/FilterTest2 运行以下代码:

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

对于 Razor Pages,请参阅通过重写筛选器方法实现 Razor 页面筛选器

重写默认顺序Overriding the default order

可以通过实现 IOrderedFilter 来重写默认执行序列。IOrderedFilter 公开了 Order 属性来确定执行顺序,该属性优先于作用域。具有较低的 Order 值的筛选器:

  • 在具有较高的 Order 值的筛选器之前运行 before 代码。
  • 在具有较高的 Order 值的筛选器之后运行 after 代码。

可以使用构造函数参数设置 Order 属性:

  1. [MyFilter(Name = "Controller Level Attribute", Order=1)]

请考虑前面示例中所示的 3 个相同操作筛选器。如果控制器和全局筛选器的 Order 属性分别设置为 1 和 2,则会反转执行顺序。

序列筛选器作用域Order 属性筛选器方法
1方法0OnActionExecuting
2控制器1OnActionExecuting
3全局2OnActionExecuting
4全局2OnActionExecuted
5控制器1OnActionExecuted
6方法0OnActionExecuted

在确定筛选器的运行顺序时,Order 属性重写作用域。先按顺序对筛选器排序,然后使用作用域消除并列问题。所有内置筛选器实现 IOrderedFilter 并将默认 Order 值设为 0。对于内置筛选器,作用域会确定顺序,除非将 Order 设为非零值。

取消和设置短路Cancellation and short-circuiting

通过设置提供给筛选器方法的 ResourceExecutingContext 参数上的 Result 属性,可以使筛选器管道短路。例如,以下资源筛选器将阻止执行管道的其余阶段:

  1. public class ShortCircuitingResourceFilterAttribute : Attribute, IResourceFilter
  2. {
  3. public void OnResourceExecuting(ResourceExecutingContext context)
  4. {
  5. context.Result = new ContentResult()
  6. {
  7. Content = "Resource unavailable - header not set."
  8. };
  9. }
  10. public void OnResourceExecuted(ResourceExecutedContext context)
  11. {
  12. }
  13. }

在下面的代码中,ShortCircuitingResourceFilterAddHeader 筛选器都以 SomeResource 操作方法为目标。ShortCircuitingResourceFilter

  • 先运行,因为它是资源筛选器且 AddHeader 是操作筛选器。
  • 对管道的其余部分进行短路处理。

这样 AddHeader 筛选器就不会为 SomeResource 操作运行。如果这两个筛选器都应用于操作方法级别,只要 ShortCircuitingResourceFilter 先运行,此行为就不会变。先运行 ShortCircuitingResourceFilter(考虑到它的筛选器类型),或显式使用 Order 属性。

  1. [AddHeader("Author", "Joe Smith")]
  2. public class SampleController : Controller
  3. {
  4. public IActionResult Index()
  5. {
  6. return Content("Examine the headers using the F12 developer tools.");
  7. }
  8. [ShortCircuitingResourceFilter]
  9. public IActionResult SomeResource()
  10. {
  11. return Content("Successful access to resource - header is set.");
  12. }

依赖关系注入Dependency injection

可按类型或实例添加筛选器。如果添加实例,该实例将用于每个请求。如果添加类型,则将激活该类型。激活类型的筛选器意味着:

  • 将为每个请求创建一个实例。
  • 依赖关系注入 (DI) 将填充所有构造函数依赖项。

如果将筛选器作为属性实现并直接添加到控制器类或操作方法中,则该筛选器不能由依赖关系注入 (DI) 提供构造函数依赖项。无法由 DI 提供构造函数依赖项,因为:

  • 属性在应用时必须提供自己的构造函数参数。
  • 这是属性工作原理上的限制。

以下筛选器支持从 DI 提供的构造函数依赖项:

可以将前面的筛选器应用于控制器或操作方法:

可以从 DI 获取记录器。但是,避免创建和使用筛选器仅用于日志记录。内置框架日志记录通常提供日志记录所需的内容。添加到筛选器的日志记录:

  • 应重点关注业务域问题或特定于筛选器的行为。
  • 不应记录操作或其他框架事件。内置筛选器记录操作和框架事件。

ServiceFilterAttributeServiceFilterAttribute

ConfigureServices 中注册服务筛选器实现类型。ServiceFilterAttribute 可从 DI 检索筛选器实例。

以下代码显示 AddHeaderResultServiceFilter

  1. public class AddHeaderResultServiceFilter : IResultFilter
  2. {
  3. private ILogger _logger;
  4. public AddHeaderResultServiceFilter(ILoggerFactory loggerFactory)
  5. {
  6. _logger = loggerFactory.CreateLogger<AddHeaderResultServiceFilter>();
  7. }
  8. public void OnResultExecuting(ResultExecutingContext context)
  9. {
  10. var headerName = "OnResultExecuting";
  11. context.HttpContext.Response.Headers.Add(
  12. headerName, new string[] { "ResultExecutingSuccessfully" });
  13. _logger.LogInformation("Header added: {HeaderName}", headerName);
  14. }
  15. public void OnResultExecuted(ResultExecutedContext context)
  16. {
  17. // Can't add to headers here because response has started.
  18. }
  19. }

在以下代码中,AddHeaderResultServiceFilter 将添加到 DI 容器中:

  1. public void ConfigureServices(IServiceCollection services)
  2. {
  3. // Add service filters.
  4. services.AddScoped<AddHeaderResultServiceFilter>();
  5. services.AddScoped<SampleActionFilterAttribute>();
  6. services.AddMvc(options =>
  7. {
  8. options.Filters.Add(new AddHeaderAttribute("GlobalAddHeader",
  9. "Result filter added to MvcOptions.Filters")); // An instance
  10. options.Filters.Add(typeof(MySampleActionFilter)); // By type
  11. options.Filters.Add(new SampleGlobalActionFilter()); // An instance
  12. }).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
  13. }

在以下代码中,ServiceFilter 属性将从 DI 中检索 AddHeaderResultServiceFilter 筛选器的实例:

  1. [ServiceFilter(typeof(AddHeaderResultServiceFilter))]
  2. public IActionResult Index()
  3. {
  4. return View();
  5. }

使用 ServiceFilterAttribute 时,ServiceFilterAttribute.IsReusable 设置:

  • 提供以下提示:筛选器实例可能在其创建的请求范围之外被重用。ASP.NET Core 运行时不保证:

    • 将创建筛选器的单一实例。
    • 稍后不会从 DI 容器重新请求筛选器。
  • 不应与依赖于生命周期不同于单一实例的服务的筛选器一起使用。

ServiceFilterAttribute 可实现 IFilterFactoryIFilterFactory 公开用于创建 IFilterMetadata 实例的 CreateInstance 方法。CreateInstance 从 DI 中加载指定的类型。

TypeFilterAttributeTypeFilterAttribute

TypeFilterAttributeServiceFilterAttribute 类似,但不会直接从 DI 容器解析其类型。它使用 Microsoft.Extensions.DependencyInjection.ObjectFactory 对类型进行实例化。

因为不会直接从 DI 容器解析 TypeFilterAttribute 类型:

  • 使用 TypeFilterAttribute 引用的类型不需要注册在 DI 容器中。它们具备由 DI 容器实现的依赖项。
  • TypeFilterAttribute 可以选择为类型接受构造函数参数。

使用 TypeFilterAttribute 时,TypeFilterAttribute.IsReusable 设置:

  • 提供提示:筛选器实例可能在其创建的请求范围之外被重用。ASP.NET Core 运行时不保证将创建筛选器的单一实例。

  • 不应与依赖于生命周期不同于单一实例的服务的筛选器一起使用。

下面的示例演示如何使用 TypeFilterAttribute 将参数传递到类型:

  1. [TypeFilter(typeof(LogConstantFilter),
  2. Arguments = new object[] { "Method 'Hi' called" })]
  3. public IActionResult Hi(string name)
  4. {
  5. return Content($"Hi {name}");
  6. }
  1. public class LogConstantFilter : IActionFilter
  2. {
  3. private readonly string _value;
  4. private readonly ILogger<LogConstantFilter> _logger;
  5. public LogConstantFilter(string value, ILogger<LogConstantFilter> logger)
  6. {
  7. _logger = logger;
  8. _value = value;
  9. }
  10. public void OnActionExecuting(ActionExecutingContext context)
  11. {
  12. _logger.LogInformation(_value);
  13. }
  14. public void OnActionExecuted(ActionExecutedContext context)
  15. { }
  16. }

授权筛选器Authorization filters

授权筛选器:

  • 是筛选器管道中运行的第一个筛选器。
  • 控制对操作方法的访问。
  • 具有在它之前的执行的方法,但没有之后执行的方法。

自定义授权筛选器需要自定义授权框架。建议配置授权策略或编写自定义授权策略,而不是编写自定义筛选器。内置授权筛选器:

  • 调用授权系统。
  • 不授权请求。

不会在授权筛选器中引发异常:

  • 不会处理异常。
  • 异常筛选器不会处理异常。

在授权筛选器出现异常时请小心应对。

详细了解授权

资源筛选器Resource filters

资源筛选器:

如果要使大部分管道短路,资源筛选器会很有用。例如,如果缓存命中,则缓存筛选器可以绕开管道的其余阶段。

资源筛选器示例:

操作筛选器Action filters

重要

操作筛选器不应用于 Razor Pages。Razor Pages 支持 IPageFilterIAsyncPageFilter有关详细信息,请参阅 Razor 页面的筛选方法

操作筛选器:

以下代码显示示例操作筛选器:

  1. public class MySampleActionFilter : IActionFilter
  2. {
  3. public void OnActionExecuting(ActionExecutingContext context)
  4. {
  5. // Do something before the action executes.
  6. }
  7. public void OnActionExecuted(ActionExecutedContext context)
  8. {
  9. // Do something after the action executes.
  10. }
  11. }

ActionExecutingContext 提供以下属性:

  • ActionArguments - 用于读取操作方法的输入。
  • Controller - 用于处理控制器实例。
  • Result - 设置 Result 会使操作方法和后续操作筛选器的执行短路。

在操作方法中引发异常:

  • 防止运行后续筛选器。
  • 与设置 Result 不同,结果被视为失败而不是成功。

ActionExecutedContext 提供 ControllerResult 以及以下属性:

  • Canceled - 如果操作执行已被另一个筛选器设置短路,则为 true。

  • Exception - 如果操作或之前运行的操作筛选器引发了异常,则为非 NULL 值。将此属性设置为 null:

    • 有效地处理异常。
    • 执行 Result,从操作方法中将它返回。

对于 IAsyncActionFilter,一个向 ActionExecutionDelegate 的调用可以达到以下目的:

  • 执行所有后续操作筛选器和操作方法。
  • 返回 ActionExecutedContext

若要设置短路,可将 Microsoft.AspNetCore.Mvc.Filters.ActionExecutingContext.Result 分配到某个结果实例,并且不调用 next (ActionExecutionDelegate)。

该框架提供一个可子类化的抽象 ActionFilterAttribute

OnActionExecuting 操作筛选器可用于:

  • 验证模型状态。
  • 如果状态无效,则返回错误。
  1. public class ValidateModelAttribute : ActionFilterAttribute
  2. {
  3. public override void OnActionExecuting(ActionExecutingContext context)
  4. {
  5. if (!context.ModelState.IsValid)
  6. {
  7. context.Result = new BadRequestObjectResult(context.ModelState);
  8. }
  9. }

OnActionExecuted 方法在操作方法之后运行:

  • 可通过 Result 属性查看和处理操作结果。

  • 如果操作执行已被另一个筛选器设置短路,则 Canceled 设置为 true。

  • 如果操作或后续操作筛选器引发了异常,则 Exception 设置为非 NULL 值。将 Exception 设置为 null:

    • 有效地处理异常。
    • 执行 ActionExecutedContext.Result,从操作方法中将它正常返回。
  1. public class ValidateModelAttribute : ActionFilterAttribute
  2. {
  3. public override void OnActionExecuting(ActionExecutingContext context)
  4. {
  5. if (!context.ModelState.IsValid)
  6. {
  7. context.Result = new BadRequestObjectResult(context.ModelState);
  8. }
  9. }
  10. public override void OnActionExecuted(ActionExecutedContext context)
  11. {
  12. var result = context.Result;
  13. // Do something with Result.
  14. if (context.Canceled == true)
  15. {
  16. // Action execution was short-circuited by another filter.
  17. }
  18. if(context.Exception != null)
  19. {
  20. // Exception thrown by action or action filter.
  21. // Set to null to handle the exception.
  22. context.Exception = null;
  23. }
  24. base.OnActionExecuted(context);
  25. }
  26. }

异常筛选器Exception filters

异常筛选器:

下面的异常筛选器示例使用自定义错误视图,显示在开发应用时发生的异常的相关详细信息:

  1. public class CustomExceptionFilter : IExceptionFilter
  2. {
  3. private readonly IHostingEnvironment _hostingEnvironment;
  4. private readonly IModelMetadataProvider _modelMetadataProvider;
  5. public CustomExceptionFilter(
  6. IHostingEnvironment hostingEnvironment,
  7. IModelMetadataProvider modelMetadataProvider)
  8. {
  9. _hostingEnvironment = hostingEnvironment;
  10. _modelMetadataProvider = modelMetadataProvider;
  11. }
  12. public void OnException(ExceptionContext context)
  13. {
  14. if (!_hostingEnvironment.IsDevelopment())
  15. {
  16. return;
  17. }
  18. var result = new ViewResult {ViewName = "CustomError"};
  19. result.ViewData = new ViewDataDictionary(_modelMetadataProvider,
  20. context.ModelState);
  21. result.ViewData.Add("Exception", context.Exception);
  22. // TODO: Pass additional detailed data via ViewData
  23. context.Result = result;
  24. }
  25. }

异常筛选器:

  • 没有之前和之后的事件。
  • 实现 OnExceptionOnExceptionAsync
  • 处理 Razor 页面或控制器创建、模型绑定、操作筛选器或操作方法中发生的未经处理的异常。
  • 请不要捕获资源筛选器、结果筛选器或 MVC 结果执行中发生的异常。

若要处理异常,请将 ExceptionHandled 属性设置为 true,或编写响应。这将停止传播异常。异常筛选器无法将异常转变为“成功”。只有操作筛选器才能执行该转变。

异常筛选器:

  • 非常适合捕获发生在操作中的异常。
  • 并不像错误处理中间件那么灵活。

建议使用中间件处理异常。基于所调用的操作方法,仅当错误处理不同时,才使用异常筛选器。例如,应用可能具有用于 API 终结点和视图/HTML 的操作方法。API 终结点可能返回 JSON 形式的错误信息,而基于视图的操作可能返回 HTML 形式的错误页。

结果筛选器Result filters

结果筛选器:

IResultFilter 和 IAsyncResultFilterIResultFilter and IAsyncResultFilter

以下代码显示一个添加 HTTP 标头的结果筛选器:

  1. public class AddHeaderResultServiceFilter : IResultFilter
  2. {
  3. private ILogger _logger;
  4. public AddHeaderResultServiceFilter(ILoggerFactory loggerFactory)
  5. {
  6. _logger = loggerFactory.CreateLogger<AddHeaderResultServiceFilter>();
  7. }
  8. public void OnResultExecuting(ResultExecutingContext context)
  9. {
  10. var headerName = "OnResultExecuting";
  11. context.HttpContext.Response.Headers.Add(
  12. headerName, new string[] { "ResultExecutingSuccessfully" });
  13. _logger.LogInformation("Header added: {HeaderName}", headerName);
  14. }
  15. public void OnResultExecuted(ResultExecutedContext context)
  16. {
  17. // Can't add to headers here because response has started.
  18. }
  19. }

要执行的结果类型取决于所执行的操作。返回视图的操作会将所有 Razor 处理作为要执行的 ViewResult 的一部分。API 方法可能会将某些序列化操作作为结果执行的一部分。详细了解操作结果

仅当操作或操作筛选器生成操作结果时,才会执行结果筛选器。不会在以下情况下执行结果筛选器:

  • 授权筛选器或资源筛选器使管道短路。
  • 异常筛选器通过生成操作结果来处理异常。

Microsoft.AspNetCore.Mvc.Filters.IResultFilter.OnResultExecuting 方法可以将 Microsoft.AspNetCore.Mvc.Filters.ResultExecutingContext.Cancel 设置为 true,使操作结果和后续结果筛选器的执行短路。设置短路时写入响应对象,以免生成空响应。如果在 IResultFilter.OnResultExecuting 中引发异常,则会导致:

  • 阻止操作结果和后续筛选器的执行。
  • 结果被视为失败而不是成功。

Microsoft.AspNetCore.Mvc.Filters.IResultFilter.OnResultExecuted 方法运行时,响应可能已发送到客户端。如果响应已发送到客户端,则无法再更改。

如果操作结果执行已被另一个筛选器设置短路,则 ResultExecutedContext.Canceled 设置为 true

如果操作结果或后续结果筛选器引发了异常,则 ResultExecutedContext.Exception 设置为非 NULL 值。Exception 设置为 NULL 可有效地处理异常,并防止 ASP.NET Core 在管道的后续阶段重新引发该异常。处理结果筛选器中出现的异常时,没有可靠的方法来将数据写入响应。如果在操作结果引发异常时标头已刷新到客户端,则没有任何可靠的机制可用于发送失败代码。

对于 IAsyncResultFilter,通过调用 ResultExecutionDelegate 上的 await next 可执行所有后续结果筛选器和操作结果。若要设置短路,请将 ResultExecutingContext.Cancel 设置为 true,并且不调用 ResultExecutionDelegate

  1. public class MyAsyncResponseFilter : IAsyncResultFilter
  2. {
  3. public async Task OnResultExecutionAsync(ResultExecutingContext context,
  4. ResultExecutionDelegate next)
  5. {
  6. if (!(context.Result is EmptyResult))
  7. {
  8. await next();
  9. }
  10. else
  11. {
  12. context.Cancel = true;
  13. }
  14. }
  15. }

该框架提供一个可子类化的抽象 ResultFilterAttribute前面所示的 AddHeaderAttribute 类是一种结果筛选器属性。

IAlwaysRunResultFilter 和 IAsyncAlwaysRunResultFilterIAlwaysRunResultFilter and IAsyncAlwaysRunResultFilter

IAlwaysRunResultFilterIAsyncAlwaysRunResultFilter 接口声明了一个针对所有操作结果运行的 IResultFilter 实现。这包括由以下对象生成的操作结果:

  • 设置短路的授权筛选器和资源筛选器。
  • 异常筛选器。

例如,以下筛选器始终运行并在内容协商失败时设置具有“422 无法处理的实体”状态代码的操作结果 (ObjectResult):

  1. public class UnprocessableResultFilter : Attribute, IAlwaysRunResultFilter
  2. {
  3. public void OnResultExecuting(ResultExecutingContext context)
  4. {
  5. if (context.Result is StatusCodeResult statusCodeResult &&
  6. statusCodeResult.StatusCode == 415)
  7. {
  8. context.Result = new ObjectResult("Can't process this!")
  9. {
  10. StatusCode = 422,
  11. };
  12. }
  13. }
  14. public void OnResultExecuted(ResultExecutedContext context)
  15. {
  16. }
  17. }

IFilterFactoryIFilterFactory

IFilterFactory 可实现 IFilterMetadata因此,IFilterFactory 实例可在筛选器管道中的任意位置用作 IFilterMetadata 实例。当运行时准备调用筛选器时,它会尝试将其转换为 IFilterFactory如果转换成功,则调用 CreateInstance 方法来创建将调用的 IFilterMetadata 实例。这提供了一种很灵活的设计,因为无需在应用启动时显式设置精确的筛选器管道。

可以使用自定义属性实现来实现 IFilterFactory 作为另一种创建筛选器的方法:

  1. public class AddHeaderWithFactoryAttribute : Attribute, IFilterFactory
  2. {
  3. // Implement IFilterFactory
  4. public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
  5. {
  6. return new InternalAddHeaderFilter();
  7. }
  8. private class InternalAddHeaderFilter : IResultFilter
  9. {
  10. public void OnResultExecuting(ResultExecutingContext context)
  11. {
  12. context.HttpContext.Response.Headers.Add(
  13. "Internal", new string[] { "My header" });
  14. }
  15. public void OnResultExecuted(ResultExecutedContext context)
  16. {
  17. }
  18. }
  19. public bool IsReusable
  20. {
  21. get
  22. {
  23. return false;
  24. }
  25. }
  26. }

可以通过运行下载示例来测试前面的代码:

F12 开发人员工具显示示例代码添加的以下响应标头:

  • author: Joe Smith
  • globaladdheader: Result filter added to MvcOptions.Filters
  • internal: My header

前面的代码创建 internal: My header 响应标头。

在属性上实现 IFilterFactoryIFilterFactory implemented on an attribute

实现 IFilterFactory 的筛选器可用于以下筛选器:

  • 不需要传递参数。
  • 具备需要由 DI 填充的构造函数依赖项。

TypeFilterAttribute 可实现 IFilterFactoryIFilterFactory 公开用于创建 IFilterMetadata 实例的 CreateInstance 方法。CreateInstance 从服务容器 (DI) 中加载指定的类型。

  1. public class SampleActionFilterAttribute : TypeFilterAttribute
  2. {
  3. public SampleActionFilterAttribute():base(typeof(SampleActionFilterImpl))
  4. {
  5. }
  6. private class SampleActionFilterImpl : IActionFilter
  7. {
  8. private readonly ILogger _logger;
  9. public SampleActionFilterImpl(ILoggerFactory loggerFactory)
  10. {
  11. _logger = loggerFactory.CreateLogger<SampleActionFilterAttribute>();
  12. }
  13. public void OnActionExecuting(ActionExecutingContext context)
  14. {
  15. _logger.LogInformation("Business action starting...");
  16. // perform some business logic work
  17. }
  18. public void OnActionExecuted(ActionExecutedContext context)
  19. {
  20. // perform some business logic work
  21. _logger.LogInformation("Business action completed.");
  22. }
  23. }
  24. }

以下代码显示应用 [SampleActionFilter] 的三种方法:

  1. [SampleActionFilter]
  2. public IActionResult FilterTest()
  3. {
  4. return Content($"From FilterTest");
  5. }
  6. [TypeFilter(typeof(SampleActionFilterAttribute))]
  7. public IActionResult TypeFilterTest()
  8. {
  9. return Content($"From ServiceFilterTest");
  10. }
  11. // ServiceFilter must be registered in ConfigureServices or
  12. // System.InvalidOperationException: No service for type '<filter>' has been registered.
  13. // Is thrown.
  14. [ServiceFilter(typeof(SampleActionFilterAttribute))]
  15. public IActionResult ServiceFilterTest()
  16. {
  17. return Content($"From ServiceFilterTest");
  18. }

在前面的代码中,使用 [SampleActionFilter] 修饰方法是应用 SampleActionFilter 的首选方法。

在筛选器管道中使用中间件Using middleware in the filter pipeline

资源筛选器的工作方式与中间件类似,即涵盖管道中的所有后续执行。但筛选器又不同于中间件,它们是 ASP.NET Core 运行时的一部分,这意味着它们有权访问 ASP.NET Core 上下文和构造。

若要将中间件用作筛选器,可创建一个具有 Configure 方法的类型,该方法可指定要注入到筛选器管道的中间件。下面的示例使用本地化中间件为请求建立当前区域性:

  1. public class LocalizationPipeline
  2. {
  3. public void Configure(IApplicationBuilder applicationBuilder)
  4. {
  5. var supportedCultures = new[]
  6. {
  7. new CultureInfo("en-US"),
  8. new CultureInfo("fr")
  9. };
  10. var options = new RequestLocalizationOptions
  11. {
  12. DefaultRequestCulture = new RequestCulture(culture: "en-US",
  13. uiCulture: "en-US"),
  14. SupportedCultures = supportedCultures,
  15. SupportedUICultures = supportedCultures
  16. };
  17. options.RequestCultureProviders = new[]
  18. { new RouteDataRequestCultureProvider() { Options = options } };
  19. applicationBuilder.UseRequestLocalization(options);
  20. }
  21. }

使用 MiddlewareFilterAttribute 运行中间件:

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

中间件筛选器与资源筛选器在筛选器管道的相同阶段运行,即,在模型绑定之前以及管道的其余阶段之后。

后续操作Next actions