ASP.NET Core 中的筛选器
作者:Kirk Larkin、Rick Anderson、Tom Dykstra 和 Steve Smith
通过使用 ASP.NET Core 中的筛选器,可在请求处理管道中的特定阶段之前或之后运行代码。
内置筛选器处理任务,例如:
- 授权(防止用户访问未获授权的资源)。
- 响应缓存(对请求管道进行短路出路,以便返回缓存的响应)。
可以创建自定义筛选器,用于处理横切关注点。 横切关注点的示例包括错误处理、缓存、配置、授权和日志记录。 筛选器可以避免复制代码。 例如,错误处理异常筛选器可以合并错误处理。
本文档适用于 Razor Pages、API 控制器和具有视图的控制器。 筛选器无法直接与 Razor 组件一起使用。 筛选器只能在以下情况下间接影响组件:
- 该组件嵌入在页面或视图中。
- 页面或控制器和视图使用此筛选器。
筛选器的工作原理
筛选器在 ASP.NET Core 操作调用管道(有时称为筛选器管道)内运行。 筛选器管道在 ASP.NET Core 选择了要执行的操作之后运行:
筛选器类型
每种筛选器类型都在筛选器管道中的不同阶段执行:
-
- 首先运行。
- 确定用户是否获得请求授权。
- 如果请求未获授权,可以让管道短路。
-
- 授权后运行。
- OnResourceExecuting 在筛选器管道的 rest 之前运行代码。 例如,
OnResourceExecuting
在模型绑定之前运行代码。 - OnResourceExecuted 在管道的 rest 完成之后运行代码。
-
- 在调用操作方法之前和之后立即运行。
- 可以更改传递到操作中的参数。
- 可以更改从操作返回的结果。
- 不可在 Razor Pages 中使用。
-
- 在调用操作方法之前和之后立即运行。
- 可以更改传递到操作中的参数。
- 可以更改从操作返回的结果。
- 不可在 Razor Pages 中使用。
- 可以在操作和基于路由处理程序的终结点上调用。
异常筛选器在向响应正文写入任何内容之前,对未经处理的异常应用全局策略。
-
- 在执行操作结果之前和之后立即运行。
- 仅当操作方法成功执行时才会运行。
- 对于必须围绕视图或格式化程序的执行的逻辑,会很有用。
下图展示了筛选器类型在筛选器管道中的交互方式:
Razor Pages 还支持在 Razor 页面处理程序之前和之后运行的 Razor 页面筛选器。
实现
筛选器通过不同的接口定义支持同步和异步实现。
同步筛选器在其管道阶段之前和之后运行。 例如,OnActionExecuting 在调用操作方法之前调用。 OnActionExecuted 在操作方法返回之后调用:
public class SampleActionFilter : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
// Do something before the action executes.
}
public void OnActionExecuted(ActionExecutedContext context)
{
// Do something after the action executes.
}
}
异步筛选器定义 On-Stage-ExecutionAsync
方法。 例如,OnActionExecutionAsync:
public class SampleAsyncActionFilter : IAsyncActionFilter
{
public async Task OnActionExecutionAsync(
ActionExecutingContext context, ActionExecutionDelegate next)
{
// Do something before the action executes.
await next();
// Do something after the action executes.
}
}
在前面的代码中,SampleAsyncActionFilter
具有执行操作方法的 ActionExecutionDelegate (next
)。
多个筛选器阶段
可以在单个类中实现多个筛选器阶段的接口。 例如,ActionFilterAttribute 类可实现:
筛选器接口的同步和异步版本任意实现一个,而不是同时实现 。 运行时会先查看筛选器是否实现了异步接口,如果是,则调用该接口。 如果不是,则调用同步接口的方法。 如果在一个类中同时实现异步和同步接口,则仅调用异步方法。 使用抽象类(如 ActionFilterAttribute)时,将为每种筛选器类型仅重写同步方法或仅重写异步方法。
内置筛选器属性
ASP.NET Core 包含许多可子类化和自定义的基于属性的内置筛选器。 例如,以下结果筛选器会向响应添加标头:
public class ResponseHeaderAttribute : ActionFilterAttribute
{
private readonly string _name;
private readonly string _value;
public ResponseHeaderAttribute(string name, string value) =>
(_name, _value) = (name, value);
public override void OnResultExecuting(ResultExecutingContext context)
{
context.HttpContext.Response.Headers.Add(_name, _value);
base.OnResultExecuting(context);
}
}
通过使用属性,筛选器可接收参数,如前面的示例所示。 将 ResponseHeaderAttribute
添加到控制器或操作方法,并指定 HTTP 标头的名称和值:
[ResponseHeader("Filter-Header", "Filter Value")]
public class ResponseHeaderController : ControllerBase
{
public IActionResult Index() =>
Content("Examine the response headers using the F12 developer tools.");
// ...
使用浏览器开发人员工具等工具来检查标头。 在响应标头下,将显示 filter-header: Filter Value
。
以下代码将 ResponseHeaderAttribute
应用于控制器和操作:
[ResponseHeader("Filter-Header", "Filter Value")]
public class ResponseHeaderController : ControllerBase
{
public IActionResult Index() =>
Content("Examine the response headers using the F12 developer tools.");
// ...
[ResponseHeader("Another-Filter-Header", "Another Filter Value")]
public IActionResult Multiple() =>
Content("Examine the response headers using the F12 developer tools.");
}
Multiple
操作的响应包括以下标头:
filter-header: Filter Value
another-filter-header: Another Filter Value
多种筛选器接口具有相应属性,这些属性可用作自定义实现的基类。
筛选器属性:
- ActionFilterAttribute
- ExceptionFilterAttribute
- ResultFilterAttribute
- FormatFilterAttribute
- ServiceFilterAttribute
- TypeFilterAttribute
无法将筛选器应用于 Razor 页面处理程序方法。 它们可以应用于 Razor 页面模型或全局应用。
筛选器作用域和执行顺序
可以将筛选器添加到管道中的三个作用域之一:
- 在控制器或 Razor 页面上使用属性。
- 在控制器操作上使用属性。 无法将筛选器属性应用于 Razor 页面处理程序方法。
- 所有控制器、操作和 Razor 页面的全局筛选器,如下面的代码所示:
var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddControllersWithViews(options => { options.Filters.Add<GlobalSampleActionFilter>(); });
默认执行顺序
当管道的某个特定阶段有多个筛选器时,作用域可确定筛选器执行的默认顺序。 全局筛选器涵盖类筛选器,类筛选器又涵盖方法筛选器。
在筛选器嵌套模式下,筛选器的 after 代码会按照与 before 代码相反的顺序运行。 筛选器序列:
- 全局筛选器的 before 代码。
- 控制器筛选器的 before 代码。
- 操作方法筛选器的 before 代码。
- 操作方法筛选器的 after 代码。
- 控制器筛选器的 after 代码。
- 控制器筛选器的 before 代码。
- 全局筛选器的 after 代码。
下面的示例阐释了为同步操作筛选器运行筛选器方法的顺序:
序列 | 筛选器作用域 | 筛选器方法 |
---|---|---|
1 | 全球 | OnActionExecuting |
2 | 控制器 | OnActionExecuting |
3 | 操作 | OnActionExecuting |
4 | 操作 | OnActionExecuted |
5 | 控制器 | OnActionExecuted |
6 | 全球 | OnActionExecuted |
控制器级别筛选器
继承自 Controller 的每个控制器都包括 OnActionExecuting、OnActionExecutionAsync 和 OnActionExecuted 方法。 这些方法可覆盖为给定操作运行的筛选器:
OnActionExecuting
在所有操作筛选器之前运行。OnActionExecuted
在所有操作筛选器之后运行。OnActionExecutionAsync
在所有操作筛选器之前运行。 调用next
后的代码在操作筛选器之后运行。
以下 ControllerFiltersController
类:
- 将
SampleActionFilterAttribute
([SampleActionFilter]
) 应用于控制器。 - 重写
OnActionExecuting
和OnActionExecuted
。
[SampleActionFilter]
public class ControllerFiltersController : Controller
{
public override void OnActionExecuting(ActionExecutingContext context)
{
Console.WriteLine(
$"- {nameof(ControllerFiltersController)}.{nameof(OnActionExecuting)}");
base.OnActionExecuting(context);
}
public override void OnActionExecuted(ActionExecutedContext context)
{
Console.WriteLine(
$"- {nameof(ControllerFiltersController)}.{nameof(OnActionExecuted)}");
base.OnActionExecuted(context);
}
public IActionResult Index()
{
Console.WriteLine(
$"- {nameof(ControllerFiltersController)}.{nameof(Index)}");
return Content("Check the Console.");
}
}
导航到 https://localhost:<port>/ControllerFilters
运行以下代码:
ControllerFiltersController.OnActionExecuting
GlobalSampleActionFilter.OnActionExecuting
SampleActionFilterAttribute.OnActionExecuting
ControllerFiltersController.Index
SampleActionFilterAttribute.OnActionExecuted
GlobalSampleActionFilter.OnActionExecuted
ControllerFiltersController.OnActionExecuted
控制器级别筛选器将 Order 属性设置为 int.MinValue
。 控制器级别筛选器无法设置为在将筛选器应用于方法之后运行。 在下一节对 Order 进行了介绍。
对于 Razor Pages,请参阅通过重写筛选器方法实现 Razor 页面筛选器。
替代默认顺序
可以通过实现 IOrderedFilter 来重写默认执行序列。 IOrderedFilter
公开了 Order 属性来确定执行顺序,该属性优先于作用域。 具有较低的 Order
值的筛选器:
- 在具有较高的
Order
值的筛选器之前运行 before 代码。 - 在具有较高的
Order
值的筛选器之后运行 after 代码。
在控制器级别筛选器示例中,GlobalSampleActionFilter
具有全局作用域,因此它在具有控制器作用域的 SampleActionFilterAttribute
之前运行。 若要首先运行 SampleActionFilterAttribute
,请将其顺序设置为 int.MinValue
:
[SampleActionFilter(Order = int.MinValue)]
public class ControllerFiltersController : Controller
{
// ...
}
若要首先运行全局筛选器 GlobalSampleActionFilter
,请将其 Order
设置为 int.MinValue
:
builder.Services.AddControllersWithViews(options =>
{
options.Filters.Add<GlobalSampleActionFilter>(int.MinValue);
});
取消和设置短路
通过设置提供给筛选器方法的 ResourceExecutingContext 参数上的 Result 属性,可以使筛选器管道短路。 例如,以下资源筛选器将阻止执行管道的 rest:
public class ShortCircuitingResourceFilterAttribute : Attribute, IResourceFilter
{
public void OnResourceExecuting(ResourceExecutingContext context)
{
context.Result = new ContentResult
{
Content = nameof(ShortCircuitingResourceFilterAttribute)
};
}
public void OnResourceExecuted(ResourceExecutedContext context) { }
}
在下面的代码中,[ShortCircuitingResourceFilter]
和 [ResponseHeader]
筛选器都以 Index
操作方法为目标。 ShortCircuitingResourceFilterAttribute
筛选器:
- 先运行,因为它是资源筛选器且
ResponseHeaderAttribute
是操作筛选器。 - 对管道的 rest 进行短路处理。
这样 ResponseHeaderAttribute
筛选器就不会为 Index
操作运行。 如果这两个筛选器都应用于操作方法级别,只要 ShortCircuitingResourceFilterAttribute
先运行,此行为就不会变。 ShortCircuitingResourceFilterAttribute
因其筛选器类型而首先运行:
[ResponseHeader("Filter-Header", "Filter Value")]
public class ShortCircuitingController : Controller
{
[ShortCircuitingResourceFilter]
public IActionResult Index() =>
Content($"- {nameof(ShortCircuitingController)}.{nameof(Index)}");
}
依赖关系注入
可按类型或实例添加筛选器。 如果添加实例,该实例将用于每个请求。 如果添加类型,则将激活该类型。 激活类型的筛选器意味着:
- 将为每个请求创建一个实例。
- 依赖关系注入 (DI) 将填充所有构造函数依赖项。
如果将筛选器作为属性实现并直接添加到控制器类或操作方法中,则该筛选器不能由依赖关系注入 (DI) 提供构造函数依赖项。 构造函数依赖项不能由 DI 提供,因为属性在应用时必须提供自己的构造函数参数。
以下筛选器支持从 DI 提供的构造函数依赖项:
可以将前面的筛选器应用于控制器或操作。
可以从 DI 获取记录器。 但是,避免创建和使用筛选器仅用于日志记录。 内置框架日志记录通常提供日志记录所需的内容。 添加到筛选器的日志记录:
- 应重点关注业务域问题或特定于筛选器的行为。
- 不应记录操作或其他框架事件。 内置筛选器已记录操作和框架事件。
ServiceFilterAttribute
在 Program.cs
中注册服务筛选器实现类型。 ServiceFilterAttribute 可从 DI 检索筛选器实例。
以下代码显示了使用 DI 的 LoggingResponseHeaderFilterService
类:
public class LoggingResponseHeaderFilterService : IResultFilter
{
private readonly ILogger _logger;
public LoggingResponseHeaderFilterService(
ILogger<LoggingResponseHeaderFilterService> logger) =>
_logger = logger;
public void OnResultExecuting(ResultExecutingContext context)
{
_logger.LogInformation(
$"- {nameof(LoggingResponseHeaderFilterService)}.{nameof(OnResultExecuting)}");
context.HttpContext.Response.Headers.Add(
nameof(OnResultExecuting), nameof(LoggingResponseHeaderFilterService));
}
public void OnResultExecuted(ResultExecutedContext context)
{
_logger.LogInformation(
$"- {nameof(LoggingResponseHeaderFilterService)}.{nameof(OnResultExecuted)}");
}
}
在以下代码中,LoggingResponseHeaderFilterService
将添加到 DI 容器中:
builder.Services.AddScoped<LoggingResponseHeaderFilterService>();
在以下代码中,ServiceFilter
属性将从 DI 中检索 LoggingResponseHeaderFilterService
筛选器的实例:
[ServiceFilter<LoggingResponseHeaderFilterService>]
public IActionResult WithServiceFilter() =>
Content($"- {nameof(FilterDependenciesController)}.{nameof(WithServiceFilter)}");
使用 ServiceFilterAttribute
时,设置 ServiceFilterAttribute.IsReusable:
- 提供以下提示:筛选器实例可能在其创建的请求范围之外被重用。 ASP.NET Core 运行时不保证:
- 将创建筛选器的单一实例。
- 稍后不会从 DI 容器重新请求筛选器。
- 不应与依赖于具有除单一实例以外的生命周期的服务的筛选器一起使用。
ServiceFilterAttribute 可实现 IFilterFactory。 IFilterFactory
公开用于创建 IFilterMetadata 实例的 CreateInstance 方法。 CreateInstance
从 DI 中加载指定的类型。
TypeFilterAttribute
TypeFilterAttribute 与 ServiceFilterAttribute 类似,但不会直接从 DI 容器解析其类型。 它使用 Microsoft.Extensions.DependencyInjection.ObjectFactory 对类型进行实例化。
因为不会直接从 DI 容器解析 TypeFilterAttribute
类型:
- 使用
TypeFilterAttribute
引用的类型不需要注册在 DI 容器中。 它们具备由 DI 容器实现的依赖项。 TypeFilterAttribute
可以选择为类型接受构造函数参数。
使用 TypeFilterAttribute
时,设置 TypeFilterAttribute.IsReusable:
提供提示:筛选器实例可能在其创建的请求范围之外被重用。 ASP.NET Core 运行时不保证将创建筛选器的单一实例。
不应与依赖于生命周期不同于单一实例的服务的筛选器一起使用。
下面的示例演示如何使用 TypeFilterAttribute
将参数传递到类型:
[TypeFilter(typeof(LoggingResponseHeaderFilter),
Arguments = new object[] { "Filter-Header", "Filter Value" })]
public IActionResult WithTypeFilter() =>
Content($"- {nameof(FilterDependenciesController)}.{nameof(WithTypeFilter)}");
授权筛选器
授权筛选器:
- 是筛选器管道中运行的第一个筛选器。
- 控制对操作方法的访问。
- 具有在它之前的执行的方法,但没有之后执行的方法。
自定义授权筛选器需要自定义授权框架。 建议配置授权策略或编写自定义授权策略,而不是编写自定义筛选器。 内置授权筛选器:
- 调用授权系统。
- 不授权请求。
不会在授权筛选器中引发异常:
- 不会处理异常。
- 异常筛选器不会处理异常。
在授权筛选器出现异常时请小心应对。
详细了解授权。
资源筛选器
资源筛选器:
- 实现 IResourceFilter 或 IAsyncResourceFilter 接口。
- 执行会覆盖筛选器管道的绝大部分。
- 只有授权筛选器在资源筛选器之前运行。
如果要使大部分管道短路,资源筛选器会很有用。 例如,如果缓存命中,则缓存筛选器可以绕开管道的其余部分。
资源筛选器示例:
之前显示的短路资源筛选器。
DisableFormValueModelBindingAttribute:
- 可以防止模型绑定访问表单数据。
- 用于上传大型文件,以防止表单数据被读入内存。
操作筛选器
操作筛选器不应用于 Razor Pages。 Razor Pages 支持 IPageFilter 和 IAsyncPageFilter。 有关详细信息,请参阅 Razor Pages 的筛选方法。
操作筛选器:
- 实现 IActionFilter 或 IAsyncActionFilter 接口。
- 它们的执行围绕着操作方法的执行。
以下代码显示示例操作筛选器:
public class SampleActionFilter : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
// Do something before the action executes.
}
public void OnActionExecuted(ActionExecutedContext context)
{
// Do something after the action executes.
}
}
ActionExecutingContext 提供以下属性:
- ActionArguments - 用于读取操作方法的输入。
- Controller - 用于处理控制器实例。
- Result - 设置
Result
会使操作方法和后续操作筛选器的执行短路。
在操作方法中引发异常:
- 防止运行后续筛选器。
- 与设置
Result
不同,结果被视为失败而不是成功。
ActionExecutedContext 提供 Controller
和 Result
以及以下属性:
- Canceled - 如果操作执行已被另一个筛选器设置短路,则为 true。
- Exception - 如果操作或之前运行的操作筛选器引发了异常,则为非 NULL 值。 将此属性设置为 null:
- 有效地处理异常。
- 执行
Result
,从操作方法中将它返回。
对于 IAsyncActionFilter
,一个向 ActionExecutionDelegate 的调用可以达到以下目的:
- 执行所有后续操作筛选器和操作方法。
- 返回
ActionExecutedContext
。
若要设置短路,可将 Microsoft.AspNetCore.Mvc.Filters.ActionExecutingContext.Result 分配到某个结果实例,并且不调用 next
(ActionExecutionDelegate
)。
该框架提供一个可子类化的抽象 ActionFilterAttribute。
OnActionExecuting
操作筛选器可用于:
- 验证模型状态。
- 如果状态无效,则返回错误。
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
context.Result = new BadRequestObjectResult(context.ModelState);
}
}
}
注意
使用 [ApiController]
属性注释的控制器会自动验证模型状态并返回 400 响应。 有关详细信息,请参阅自动 HTTP 400 响应。
OnActionExecuted
方法在操作方法之后运行:
- 可通过 Result 属性查看和处理操作结果。
- 如果操作执行已被另一个筛选器设置短路,则 Canceled 设置为 true。
- 如果操作或后续操作筛选器引发了异常,则 Exception 设置为非 NULL 值。 将
Exception
设置为 null:- 有效地处理异常。
- 执行
ActionExecutedContext.Result
,从操作方法中将它正常返回。
异常筛选器
异常筛选器:
- 实现 IExceptionFilter 或 IAsyncExceptionFilter。
- 可用于实现常见的错误处理策略。
下面的异常筛选器示例显示在开发应用时发生的异常的相关详细信息:
public class SampleExceptionFilter : IExceptionFilter
{
private readonly IHostEnvironment _hostEnvironment;
public SampleExceptionFilter(IHostEnvironment hostEnvironment) =>
_hostEnvironment = hostEnvironment;
public void OnException(ExceptionContext context)
{
if (!_hostEnvironment.IsDevelopment())
{
// Don't display exception details unless running in Development.
return;
}
context.Result = new ContentResult
{
Content = context.Exception.ToString()
};
}
}
以下代码测试异常筛选器:
[TypeFilter<SampleExceptionFilter>]
public class ExceptionController : Controller
{
public IActionResult Index() =>
Content($"- {nameof(ExceptionController)}.{nameof(Index)}");
}
异常筛选器:
- 没有之前和之后的事件。
- 实现 OnException 或 OnExceptionAsync。
- 处理 Razor 页面或控制器创建、模型绑定、操作筛选器或操作方法中发生的未经处理的异常。
- 请不要捕获资源筛选器、结果筛选器或 MVC 结果执行中发生的异常。
若要处理异常,请将 ExceptionHandled 属性设置为 true
,或分配 Result 属性。 这将停止传播异常。 异常筛选器无法将异常转变为“成功”。 只有操作筛选器才能执行该转变。
异常筛选器:
- 非常适合捕获发生在操作中的异常。
- 并不像错误处理中间件那么灵活。
建议使用中间件处理异常。 基于所调用的操作方法,仅当错误处理不同时,才使用异常筛选器。 例如,应用可能具有用于 API 终结点和视图/HTML 的操作方法。 API 终结点可能返回 JSON 形式的错误信息,而基于视图的操作可能返回 HTML 形式的错误页。
结果筛选器
结果筛选器:
- 实现接口:
- 它们的执行围绕着操作结果的执行。
IResultFilter 和 IAsyncResultFilter
以下代码显示示例结果筛选器:
public class SampleResultFilter : IResultFilter
{
public void OnResultExecuting(ResultExecutingContext context)
{
// Do something before the result executes.
}
public void OnResultExecuted(ResultExecutedContext context)
{
// Do something after the result executes.
}
}
要执行的结果类型取决于所执行的操作。 返回视图的操作会将所有 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
:
public class SampleAsyncResultFilter : IAsyncResultFilter
{
public async Task OnResultExecutionAsync(
ResultExecutingContext context, ResultExecutionDelegate next)
{
if (context.Result is not EmptyResult)
{
await next();
}
else
{
context.Cancel = true;
}
}
}
该框架提供一个可子类化的抽象 ResultFilterAttribute
。 前面所示的 ResponseHeaderAttribute 类是一种结果筛选器属性。
IAlwaysRunResultFilter 和 IAsyncAlwaysRunResultFilter
IAlwaysRunResultFilter 和 IAsyncAlwaysRunResultFilter 接口声明了一个针对所有操作结果运行的 IResultFilter 实现。 这包括由以下对象生成的操作结果:
- 设置短路的授权筛选器和资源筛选器。
- 异常筛选器。
例如,以下筛选器始终运行并在内容协商失败时设置具有“422 无法处理的实体”状态代码的操作结果 (ObjectResult):
public class UnprocessableResultFilter : IAlwaysRunResultFilter
{
public void OnResultExecuting(ResultExecutingContext context)
{
if (context.Result is StatusCodeResult statusCodeResult
&& statusCodeResult.StatusCode == StatusCodes.Status415UnsupportedMediaType)
{
context.Result = new ObjectResult("Unprocessable")
{
StatusCode = StatusCodes.Status422UnprocessableEntity
};
}
}
public void OnResultExecuted(ResultExecutedContext context) { }
}
IFilterFactory
IFilterFactory 可实现 IFilterMetadata。 因此,IFilterFactory
实例可在筛选器管道中的任意位置用作 IFilterMetadata
实例。 当运行时准备调用筛选器时,它会尝试将其转换为 IFilterFactory
。 如果转换成功,则调用 CreateInstance 方法来创建将调用的 IFilterMetadata
实例。 这提供了一种很灵活的设计,因为无需在应用启动时显式设置精确的筛选器管道。
IFilterFactory.IsReusable
:
- 是工厂的提示,即工厂创建的筛选器实例可以在其创建时的请求范围之外重用。
- 不应与依赖于具有除单一实例以外的生命周期的服务的筛选器一起使用。
ASP.NET Core 运行时不保证:
- 将创建筛选器的单一实例。
- 稍后不会从 DI 容器重新请求筛选器。
警告
仅当筛选器的来源明确、筛选器是无状态的,且筛选器可以安全地跨多个 HTTP 请求使用时,才将 IFilterFactory.IsReusable 配置为返回 true
。 例如,如果 IFilterFactory.IsReusable
返回 true
,则不从注册为作用域或瞬态的 DI 返回筛选器。
可以使用自定义属性实现来实现 IFilterFactory
作为另一种创建筛选器的方法:
public class ResponseHeaderFilterFactory : Attribute, IFilterFactory
{
public bool IsReusable => false;
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider) =>
new InternalResponseHeaderFilter();
private class InternalResponseHeaderFilter : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context) =>
context.HttpContext.Response.Headers.Add(
nameof(OnActionExecuting), nameof(InternalResponseHeaderFilter));
public void OnActionExecuted(ActionExecutedContext context) { }
}
在以下代码中应用了筛选器:
[ResponseHeaderFilterFactory]
public IActionResult Index() =>
Content($"- {nameof(FilterFactoryController)}.{nameof(Index)}");
在属性上实现 IFilterFactory
实现 IFilterFactory
的筛选器可用于以下筛选器:
- 不需要传递参数。
- 具备需要由 DI 填充的构造函数依赖项。
TypeFilterAttribute 可实现 IFilterFactory。 IFilterFactory
公开用于创建 IFilterMetadata 实例的 CreateInstance 方法。 CreateInstance
从服务容器 (DI) 中加载指定的类型。
public class SampleActionTypeFilterAttribute : TypeFilterAttribute
{
public SampleActionTypeFilterAttribute()
: base(typeof(InternalSampleActionFilter)) { }
private class InternalSampleActionFilter : IActionFilter
{
private readonly ILogger<InternalSampleActionFilter> _logger;
public InternalSampleActionFilter(ILogger<InternalSampleActionFilter> logger) =>
_logger = logger;
public void OnActionExecuting(ActionExecutingContext context)
{
_logger.LogInformation(
$"- {nameof(InternalSampleActionFilter)}.{nameof(OnActionExecuting)}");
}
public void OnActionExecuted(ActionExecutedContext context)
{
_logger.LogInformation(
$"- {nameof(InternalSampleActionFilter)}.{nameof(OnActionExecuted)}");
}
}
}
以下代码显示应用筛选器的三种方法:
[SampleActionTypeFilter]
public IActionResult WithDirectAttribute() =>
Content($"- {nameof(FilterFactoryController)}.{nameof(WithDirectAttribute)}");
[TypeFilter<SampleActionTypeFilterAttribute>]
public IActionResult WithTypeFilterAttribute() =>
Content($"- {nameof(FilterFactoryController)}.{nameof(WithTypeFilterAttribute)}");
[ServiceFilter<SampleActionTypeFilterAttribute>]
public IActionResult WithServiceFilterAttribute() =>
Content($"- {nameof(FilterFactoryController)}.{nameof(WithServiceFilterAttribute)}");
在前面的代码中,首选应用筛选器的第一种方法。
在筛选器管道中使用中间件
资源筛选器的工作方式与中间件类似,即涵盖管道中的所有后续执行。 但筛选器又不同于中间件,它们是运行时的一部分,这意味着它们有权访问上下文和构造。
若要将中间件用作筛选器,可创建一个具有 Configure
方法的类型,该方法可指定要注入到筛选器管道的中间件。 以下示例使用中间件设置响应标头:
public class FilterMiddlewarePipeline
{
public void Configure(IApplicationBuilder app)
{
app.Use(async (context, next) =>
{
context.Response.Headers.Add("Pipeline", "Middleware");
await next();
});
}
}
使用 MiddlewareFilterAttribute 运行中间件:
[MiddlewareFilter<FilterMiddlewarePipeline>]
public class FilterMiddlewareController : Controller
{
public IActionResult Index() =>
Content($"- {nameof(FilterMiddlewareController)}.{nameof(Index)}");
}
中间件筛选器与资源筛选器在筛选器管道的相同阶段运行,即,在模型绑定之前以及管道的 rest 之后。
线程安全性
将筛选器的实例传递给 Add
(而不是 Type
)时,筛选器是单一实例且不是线程安全的。
其他资源
作者:Kirk Larkin、Rick Anderson、Tom Dykstra 和 Steve Smith
通过使用 ASP.NET Core 中的筛选器,可在请求处理管道中的特定阶段之前或之后运行代码。
内置筛选器处理任务,例如:
- 授权(防止用户访问未获授权的资源)。
- 响应缓存(对请求管道进行短路出路,以便返回缓存的响应)。
可以创建自定义筛选器,用于处理横切关注点。 横切关注点的示例包括错误处理、缓存、配置、授权和日志记录。 筛选器可以避免复制代码。 例如,错误处理异常筛选器可以合并错误处理。
本文档适用于 Razor Pages、API 控制器和具有视图的控制器。 筛选器无法直接与 Razor 组件一起使用。 筛选器只能在以下情况下间接影响组件:
- 该组件嵌入在页面或视图中。
- 页面或控制器和视图使用此筛选器。
筛选器的工作原理
筛选器在 ASP.NET Core 操作调用管道(有时称为筛选器管道)内运行。 筛选器管道在 ASP.NET Core 选择了要执行的操作之后运行:
筛选器类型
每种筛选器类型都在筛选器管道中的不同阶段执行:
-
- 首先运行。
- 确定用户是否获得请求授权。
- 如果请求未获授权,可以让管道短路。
-
- 授权后运行。
- OnResourceExecuting 在筛选器管道的 rest 之前运行代码。 例如,
OnResourceExecuting
在模型绑定之前运行代码。 - OnResourceExecuted 在管道的 rest 完成之后运行代码。
-
- 在调用操作方法之前和之后立即运行。
- 可以更改传递到操作中的参数。
- 可以更改从操作返回的结果。
- 不可在 Razor Pages 中使用。
-
- 在调用操作方法之前和之后立即运行。
- 可以更改传递到操作中的参数。
- 可以更改从操作返回的结果。
- 不可在 Razor Pages 中使用。
- 可以在操作和基于路由处理程序的终结点上调用。
异常筛选器在向响应正文写入任何内容之前,对未经处理的异常应用全局策略。
-
- 在执行操作结果之前和之后立即运行。
- 仅当操作方法成功执行时才会运行。
- 对于必须围绕视图或格式化程序的执行的逻辑,会很有用。
下图展示了筛选器类型在筛选器管道中的交互方式:
Razor Pages 还支持在 Razor 页面处理程序之前和之后运行的 Razor 页面筛选器。
实现
筛选器通过不同的接口定义支持同步和异步实现。
同步筛选器在其管道阶段之前和之后运行。 例如,OnActionExecuting 在调用操作方法之前调用。 OnActionExecuted 在操作方法返回之后调用:
public class SampleActionFilter : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
// Do something before the action executes.
}
public void OnActionExecuted(ActionExecutedContext context)
{
// Do something after the action executes.
}
}
异步筛选器定义 On-Stage-ExecutionAsync
方法。 例如,OnActionExecutionAsync:
public class SampleAsyncActionFilter : IAsyncActionFilter
{
public async Task OnActionExecutionAsync(
ActionExecutingContext context, ActionExecutionDelegate next)
{
// Do something before the action executes.
await next();
// Do something after the action executes.
}
}
在前面的代码中,SampleAsyncActionFilter
具有执行操作方法的 ActionExecutionDelegate (next
)。
多个筛选器阶段
可以在单个类中实现多个筛选器阶段的接口。 例如,ActionFilterAttribute 类可实现:
筛选器接口的同步和异步版本任意实现一个,而不是同时实现 。 运行时会先查看筛选器是否实现了异步接口,如果是,则调用该接口。 如果不是,则调用同步接口的方法。 如果在一个类中同时实现异步和同步接口,则仅调用异步方法。 使用抽象类(如 ActionFilterAttribute)时,将为每种筛选器类型仅重写同步方法或仅重写异步方法。
内置筛选器属性
ASP.NET Core 包含许多可子类化和自定义的基于属性的内置筛选器。 例如,以下结果筛选器会向响应添加标头:
public class ResponseHeaderAttribute : ActionFilterAttribute
{
private readonly string _name;
private readonly string _value;
public ResponseHeaderAttribute(string name, string value) =>
(_name, _value) = (name, value);
public override void OnResultExecuting(ResultExecutingContext context)
{
context.HttpContext.Response.Headers.Add(_name, _value);
base.OnResultExecuting(context);
}
}
通过使用属性,筛选器可接收参数,如前面的示例所示。 将 ResponseHeaderAttribute
添加到控制器或操作方法,并指定 HTTP 标头的名称和值:
[ResponseHeader("Filter-Header", "Filter Value")]
public class ResponseHeaderController : ControllerBase
{
public IActionResult Index() =>
Content("Examine the response headers using the F12 developer tools.");
// ...
使用浏览器开发人员工具等工具来检查标头。 在响应标头下,将显示 filter-header: Filter Value
。
以下代码将 ResponseHeaderAttribute
应用于控制器和操作:
[ResponseHeader("Filter-Header", "Filter Value")]
public class ResponseHeaderController : ControllerBase
{
public IActionResult Index() =>
Content("Examine the response headers using the F12 developer tools.");
// ...
[ResponseHeader("Another-Filter-Header", "Another Filter Value")]
public IActionResult Multiple() =>
Content("Examine the response headers using the F12 developer tools.");
}
Multiple
操作的响应包括以下标头:
filter-header: Filter Value
another-filter-header: Another Filter Value
多种筛选器接口具有相应属性,这些属性可用作自定义实现的基类。
筛选器属性:
- ActionFilterAttribute
- ExceptionFilterAttribute
- ResultFilterAttribute
- FormatFilterAttribute
- ServiceFilterAttribute
- TypeFilterAttribute
无法将筛选器应用于 Razor 页面处理程序方法。 它们可以应用于 Razor 页面模型或全局应用。
筛选器作用域和执行顺序
可以将筛选器添加到管道中的三个作用域之一:
- 在控制器或 Razor 页面上使用属性。
- 在控制器操作上使用属性。 无法将筛选器属性应用于 Razor 页面处理程序方法。
- 所有控制器、操作和 Razor 页面的全局筛选器,如下面的代码所示:
var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddControllersWithViews(options => { options.Filters.Add<GlobalSampleActionFilter>(); });
默认执行顺序
当管道的某个特定阶段有多个筛选器时,作用域可确定筛选器执行的默认顺序。 全局筛选器涵盖类筛选器,类筛选器又涵盖方法筛选器。
在筛选器嵌套模式下,筛选器的 after 代码会按照与 before 代码相反的顺序运行。 筛选器序列:
- 全局筛选器的 before 代码。
- 控制器筛选器的 before 代码。
- 操作方法筛选器的 before 代码。
- 操作方法筛选器的 after 代码。
- 控制器筛选器的 after 代码。
- 控制器筛选器的 before 代码。
- 全局筛选器的 after 代码。
下面的示例阐释了为同步操作筛选器运行筛选器方法的顺序:
序列 | 筛选器作用域 | 筛选器方法 |
---|---|---|
1 | 全球 | OnActionExecuting |
2 | 控制器 | OnActionExecuting |
3 | 操作 | OnActionExecuting |
4 | 操作 | OnActionExecuted |
5 | 控制器 | OnActionExecuted |
6 | 全球 | OnActionExecuted |
控制器级别筛选器
继承自 Controller 的每个控制器都包括 OnActionExecuting、OnActionExecutionAsync 和 OnActionExecuted 方法。 这些方法可覆盖为给定操作运行的筛选器:
OnActionExecuting
在所有操作筛选器之前运行。OnActionExecuted
在所有操作筛选器之后运行。OnActionExecutionAsync
在所有操作筛选器之前运行。 调用next
后的代码在操作筛选器之后运行。
以下 ControllerFiltersController
类:
- 将
SampleActionFilterAttribute
([SampleActionFilter]
) 应用于控制器。 - 重写
OnActionExecuting
和OnActionExecuted
。
[SampleActionFilter]
public class ControllerFiltersController : Controller
{
public override void OnActionExecuting(ActionExecutingContext context)
{
Console.WriteLine(
$"- {nameof(ControllerFiltersController)}.{nameof(OnActionExecuting)}");
base.OnActionExecuting(context);
}
public override void OnActionExecuted(ActionExecutedContext context)
{
Console.WriteLine(
$"- {nameof(ControllerFiltersController)}.{nameof(OnActionExecuted)}");
base.OnActionExecuted(context);
}
public IActionResult Index()
{
Console.WriteLine(
$"- {nameof(ControllerFiltersController)}.{nameof(Index)}");
return Content("Check the Console.");
}
}
导航到 https://localhost:<port>/ControllerFilters
运行以下代码:
ControllerFiltersController.OnActionExecuting
GlobalSampleActionFilter.OnActionExecuting
SampleActionFilterAttribute.OnActionExecuting
ControllerFiltersController.Index
SampleActionFilterAttribute.OnActionExecuted
GlobalSampleActionFilter.OnActionExecuted
ControllerFiltersController.OnActionExecuted
控制器级别筛选器将 Order 属性设置为 int.MinValue
。 控制器级别筛选器无法设置为在将筛选器应用于方法之后运行。 在下一节对 Order 进行了介绍。
对于 Razor Pages,请参阅通过重写筛选器方法实现 Razor 页面筛选器。
替代默认顺序
可以通过实现 IOrderedFilter 来重写默认执行序列。 IOrderedFilter
公开了 Order 属性来确定执行顺序,该属性优先于作用域。 具有较低的 Order
值的筛选器:
- 在具有较高的
Order
值的筛选器之前运行 before 代码。 - 在具有较高的
Order
值的筛选器之后运行 after 代码。
在控制器级别筛选器示例中,GlobalSampleActionFilter
具有全局作用域,因此它在具有控制器作用域的 SampleActionFilterAttribute
之前运行。 若要首先运行 SampleActionFilterAttribute
,请将其顺序设置为 int.MinValue
:
[SampleActionFilter(Order = int.MinValue)]
public class ControllerFiltersController : Controller
{
// ...
}
若要首先运行全局筛选器 GlobalSampleActionFilter
,请将其 Order
设置为 int.MinValue
:
builder.Services.AddControllersWithViews(options =>
{
options.Filters.Add<GlobalSampleActionFilter>(int.MinValue);
});
取消和设置短路
通过设置提供给筛选器方法的 ResourceExecutingContext 参数上的 Result 属性,可以使筛选器管道短路。 例如,以下资源筛选器将阻止执行管道的 rest:
public class ShortCircuitingResourceFilterAttribute : Attribute, IResourceFilter
{
public void OnResourceExecuting(ResourceExecutingContext context)
{
context.Result = new ContentResult
{
Content = nameof(ShortCircuitingResourceFilterAttribute)
};
}
public void OnResourceExecuted(ResourceExecutedContext context) { }
}
在下面的代码中,[ShortCircuitingResourceFilter]
和 [ResponseHeader]
筛选器都以 Index
操作方法为目标。 ShortCircuitingResourceFilterAttribute
筛选器:
- 先运行,因为它是资源筛选器且
ResponseHeaderAttribute
是操作筛选器。 - 对管道的 rest 进行短路处理。
这样 ResponseHeaderAttribute
筛选器就不会为 Index
操作运行。 如果这两个筛选器都应用于操作方法级别,只要 ShortCircuitingResourceFilterAttribute
先运行,此行为就不会变。 ShortCircuitingResourceFilterAttribute
因其筛选器类型而首先运行:
[ResponseHeader("Filter-Header", "Filter Value")]
public class ShortCircuitingController : Controller
{
[ShortCircuitingResourceFilter]
public IActionResult Index() =>
Content($"- {nameof(ShortCircuitingController)}.{nameof(Index)}");
}
依赖关系注入
可按类型或实例添加筛选器。 如果添加实例,该实例将用于每个请求。 如果添加类型,则将激活该类型。 激活类型的筛选器意味着:
- 将为每个请求创建一个实例。
- 依赖关系注入 (DI) 将填充所有构造函数依赖项。
如果将筛选器作为属性实现并直接添加到控制器类或操作方法中,则该筛选器不能由依赖关系注入 (DI) 提供构造函数依赖项。 构造函数依赖项不能由 DI 提供,因为属性在应用时必须提供自己的构造函数参数。
以下筛选器支持从 DI 提供的构造函数依赖项:
可以将前面的筛选器应用于控制器或操作。
可以从 DI 获取记录器。 但是,避免创建和使用筛选器仅用于日志记录。 内置框架日志记录通常提供日志记录所需的内容。 添加到筛选器的日志记录:
- 应重点关注业务域问题或特定于筛选器的行为。
- 不应记录操作或其他框架事件。 内置筛选器已记录操作和框架事件。
ServiceFilterAttribute
在 Program.cs
中注册服务筛选器实现类型。 ServiceFilterAttribute 可从 DI 检索筛选器实例。
以下代码显示了使用 DI 的 LoggingResponseHeaderFilterService
类:
public class LoggingResponseHeaderFilterService : IResultFilter
{
private readonly ILogger _logger;
public LoggingResponseHeaderFilterService(
ILogger<LoggingResponseHeaderFilterService> logger) =>
_logger = logger;
public void OnResultExecuting(ResultExecutingContext context)
{
_logger.LogInformation(
$"- {nameof(LoggingResponseHeaderFilterService)}.{nameof(OnResultExecuting)}");
context.HttpContext.Response.Headers.Add(
nameof(OnResultExecuting), nameof(LoggingResponseHeaderFilterService));
}
public void OnResultExecuted(ResultExecutedContext context)
{
_logger.LogInformation(
$"- {nameof(LoggingResponseHeaderFilterService)}.{nameof(OnResultExecuted)}");
}
}
在以下代码中,LoggingResponseHeaderFilterService
将添加到 DI 容器中:
builder.Services.AddScoped<LoggingResponseHeaderFilterService>();
在以下代码中,ServiceFilter
属性将从 DI 中检索 LoggingResponseHeaderFilterService
筛选器的实例:
[ServiceFilter(typeof(LoggingResponseHeaderFilterService))]
public IActionResult WithServiceFilter() =>
Content($"- {nameof(FilterDependenciesController)}.{nameof(WithServiceFilter)}");
使用 ServiceFilterAttribute
时,设置 ServiceFilterAttribute.IsReusable:
- 提供以下提示:筛选器实例可能在其创建的请求范围之外被重用。 ASP.NET Core 运行时不保证:
- 将创建筛选器的单一实例。
- 稍后不会从 DI 容器重新请求筛选器。
- 不应与依赖于具有除单一实例以外的生命周期的服务的筛选器一起使用。
ServiceFilterAttribute 可实现 IFilterFactory。 IFilterFactory
公开用于创建 IFilterMetadata 实例的 CreateInstance 方法。 CreateInstance
从 DI 中加载指定的类型。
TypeFilterAttribute
TypeFilterAttribute 与 ServiceFilterAttribute 类似,但不会直接从 DI 容器解析其类型。 它使用 Microsoft.Extensions.DependencyInjection.ObjectFactory 对类型进行实例化。
因为不会直接从 DI 容器解析 TypeFilterAttribute
类型:
- 使用
TypeFilterAttribute
引用的类型不需要注册在 DI 容器中。 它们具备由 DI 容器实现的依赖项。 TypeFilterAttribute
可以选择为类型接受构造函数参数。
使用 TypeFilterAttribute
时,设置 TypeFilterAttribute.IsReusable:
提供提示:筛选器实例可能在其创建的请求范围之外被重用。 ASP.NET Core 运行时不保证将创建筛选器的单一实例。
不应与依赖于生命周期不同于单一实例的服务的筛选器一起使用。
下面的示例演示如何使用 TypeFilterAttribute
将参数传递到类型:
[TypeFilter(typeof(LoggingResponseHeaderFilter),
Arguments = new object[] { "Filter-Header", "Filter Value" })]
public IActionResult WithTypeFilter() =>
Content($"- {nameof(FilterDependenciesController)}.{nameof(WithTypeFilter)}");
授权筛选器
授权筛选器:
- 是筛选器管道中运行的第一个筛选器。
- 控制对操作方法的访问。
- 具有在它之前的执行的方法,但没有之后执行的方法。
自定义授权筛选器需要自定义授权框架。 建议配置授权策略或编写自定义授权策略,而不是编写自定义筛选器。 内置授权筛选器:
- 调用授权系统。
- 不授权请求。
不会在授权筛选器中引发异常:
- 不会处理异常。
- 异常筛选器不会处理异常。
在授权筛选器出现异常时请小心应对。
详细了解授权。
资源筛选器
资源筛选器:
- 实现 IResourceFilter 或 IAsyncResourceFilter 接口。
- 执行会覆盖筛选器管道的绝大部分。
- 只有授权筛选器在资源筛选器之前运行。
如果要使大部分管道短路,资源筛选器会很有用。 例如,如果缓存命中,则缓存筛选器可以绕开管道的其余部分。
资源筛选器示例:
之前显示的短路资源筛选器。
DisableFormValueModelBindingAttribute:
- 可以防止模型绑定访问表单数据。
- 用于上传大型文件,以防止表单数据被读入内存。
操作筛选器
操作筛选器不应用于 Razor Pages。 Razor Pages 支持 IPageFilter 和 IAsyncPageFilter。 有关详细信息,请参阅 Razor Pages 的筛选方法。
操作筛选器:
- 实现 IActionFilter 或 IAsyncActionFilter 接口。
- 它们的执行围绕着操作方法的执行。
以下代码显示示例操作筛选器:
public class SampleActionFilter : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
// Do something before the action executes.
}
public void OnActionExecuted(ActionExecutedContext context)
{
// Do something after the action executes.
}
}
ActionExecutingContext 提供以下属性:
- ActionArguments - 用于读取操作方法的输入。
- Controller - 用于处理控制器实例。
- Result - 设置
Result
会使操作方法和后续操作筛选器的执行短路。
在操作方法中引发异常:
- 防止运行后续筛选器。
- 与设置
Result
不同,结果被视为失败而不是成功。
ActionExecutedContext 提供 Controller
和 Result
以及以下属性:
- Canceled - 如果操作执行已被另一个筛选器设置短路,则为 true。
- Exception - 如果操作或之前运行的操作筛选器引发了异常,则为非 NULL 值。 将此属性设置为 null:
- 有效地处理异常。
- 执行
Result
,从操作方法中将它返回。
对于 IAsyncActionFilter
,一个向 ActionExecutionDelegate 的调用可以达到以下目的:
- 执行所有后续操作筛选器和操作方法。
- 返回
ActionExecutedContext
。
若要设置短路,可将 Microsoft.AspNetCore.Mvc.Filters.ActionExecutingContext.Result 分配到某个结果实例,并且不调用 next
(ActionExecutionDelegate
)。
该框架提供一个可子类化的抽象 ActionFilterAttribute。
OnActionExecuting
操作筛选器可用于:
- 验证模型状态。
- 如果状态无效,则返回错误。
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
context.Result = new BadRequestObjectResult(context.ModelState);
}
}
}
注意
使用 [ApiController]
属性注释的控制器会自动验证模型状态并返回 400 响应。 有关详细信息,请参阅自动 HTTP 400 响应。
OnActionExecuted
方法在操作方法之后运行:
- 可通过 Result 属性查看和处理操作结果。
- 如果操作执行已被另一个筛选器设置短路,则 Canceled 设置为 true。
- 如果操作或后续操作筛选器引发了异常,则 Exception 设置为非 NULL 值。 将
Exception
设置为 null:- 有效地处理异常。
- 执行
ActionExecutedContext.Result
,从操作方法中将它正常返回。
异常筛选器
异常筛选器:
- 实现 IExceptionFilter 或 IAsyncExceptionFilter。
- 可用于实现常见的错误处理策略。
下面的异常筛选器示例显示在开发应用时发生的异常的相关详细信息:
public class SampleExceptionFilter : IExceptionFilter
{
private readonly IHostEnvironment _hostEnvironment;
public SampleExceptionFilter(IHostEnvironment hostEnvironment) =>
_hostEnvironment = hostEnvironment;
public void OnException(ExceptionContext context)
{
if (!_hostEnvironment.IsDevelopment())
{
// Don't display exception details unless running in Development.
return;
}
context.Result = new ContentResult
{
Content = context.Exception.ToString()
};
}
}
以下代码测试异常筛选器:
[TypeFilter(typeof(SampleExceptionFilter))]
public class ExceptionController : Controller
{
public IActionResult Index() =>
Content($"- {nameof(ExceptionController)}.{nameof(Index)}");
}
异常筛选器:
- 没有之前和之后的事件。
- 实现 OnException 或 OnExceptionAsync。
- 处理 Razor 页面或控制器创建、模型绑定、操作筛选器或操作方法中发生的未经处理的异常。
- 请不要捕获资源筛选器、结果筛选器或 MVC 结果执行中发生的异常。
若要处理异常,请将 ExceptionHandled 属性设置为 true
,或分配 Result 属性。 这将停止传播异常。 异常筛选器无法将异常转变为“成功”。 只有操作筛选器才能执行该转变。
异常筛选器:
- 非常适合捕获发生在操作中的异常。
- 并不像错误处理中间件那么灵活。
建议使用中间件处理异常。 基于所调用的操作方法,仅当错误处理不同时,才使用异常筛选器。 例如,应用可能具有用于 API 终结点和视图/HTML 的操作方法。 API 终结点可能返回 JSON 形式的错误信息,而基于视图的操作可能返回 HTML 形式的错误页。
结果筛选器
结果筛选器:
- 实现接口:
- 它们的执行围绕着操作结果的执行。
IResultFilter 和 IAsyncResultFilter
以下代码显示示例结果筛选器:
public class SampleResultFilter : IResultFilter
{
public void OnResultExecuting(ResultExecutingContext context)
{
// Do something before the result executes.
}
public void OnResultExecuted(ResultExecutedContext context)
{
// Do something after the result executes.
}
}
要执行的结果类型取决于所执行的操作。 返回视图的操作会将所有 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
:
public class SampleAsyncResultFilter : IAsyncResultFilter
{
public async Task OnResultExecutionAsync(
ResultExecutingContext context, ResultExecutionDelegate next)
{
if (context.Result is not EmptyResult)
{
await next();
}
else
{
context.Cancel = true;
}
}
}
该框架提供一个可子类化的抽象 ResultFilterAttribute
。 前面所示的 ResponseHeaderAttribute 类是一种结果筛选器属性。
IAlwaysRunResultFilter 和 IAsyncAlwaysRunResultFilter
IAlwaysRunResultFilter 和 IAsyncAlwaysRunResultFilter 接口声明了一个针对所有操作结果运行的 IResultFilter 实现。 这包括由以下对象生成的操作结果:
- 设置短路的授权筛选器和资源筛选器。
- 异常筛选器。
例如,以下筛选器始终运行并在内容协商失败时设置具有“422 无法处理的实体”状态代码的操作结果 (ObjectResult):
public class UnprocessableResultFilter : IAlwaysRunResultFilter
{
public void OnResultExecuting(ResultExecutingContext context)
{
if (context.Result is StatusCodeResult statusCodeResult
&& statusCodeResult.StatusCode == StatusCodes.Status415UnsupportedMediaType)
{
context.Result = new ObjectResult("Unprocessable")
{
StatusCode = StatusCodes.Status422UnprocessableEntity
};
}
}
public void OnResultExecuted(ResultExecutedContext context) { }
}
IFilterFactory
IFilterFactory 可实现 IFilterMetadata。 因此,IFilterFactory
实例可在筛选器管道中的任意位置用作 IFilterMetadata
实例。 当运行时准备调用筛选器时,它会尝试将其转换为 IFilterFactory
。 如果转换成功,则调用 CreateInstance 方法来创建将调用的 IFilterMetadata
实例。 这提供了一种很灵活的设计,因为无需在应用启动时显式设置精确的筛选器管道。
IFilterFactory.IsReusable
:
- 是工厂的提示,即工厂创建的筛选器实例可以在其创建时的请求范围之外重用。
- 不应与依赖于具有除单一实例以外的生命周期的服务的筛选器一起使用。
ASP.NET Core 运行时不保证:
- 将创建筛选器的单一实例。
- 稍后不会从 DI 容器重新请求筛选器。
警告
仅当筛选器的来源明确、筛选器是无状态的,且筛选器可以安全地跨多个 HTTP 请求使用时,才将 IFilterFactory.IsReusable 配置为返回 true
。 例如,如果 IFilterFactory.IsReusable
返回 true
,则不从注册为作用域或瞬态的 DI 返回筛选器。
可以使用自定义属性实现来实现 IFilterFactory
作为另一种创建筛选器的方法:
public class ResponseHeaderFilterFactory : Attribute, IFilterFactory
{
public bool IsReusable => false;
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider) =>
new InternalResponseHeaderFilter();
private class InternalResponseHeaderFilter : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context) =>
context.HttpContext.Response.Headers.Add(
nameof(OnActionExecuting), nameof(InternalResponseHeaderFilter));
public void OnActionExecuted(ActionExecutedContext context) { }
}
在以下代码中应用了筛选器:
[ResponseHeaderFilterFactory]
public IActionResult Index() =>
Content($"- {nameof(FilterFactoryController)}.{nameof(Index)}");
在属性上实现 IFilterFactory
实现 IFilterFactory
的筛选器可用于以下筛选器:
- 不需要传递参数。
- 具备需要由 DI 填充的构造函数依赖项。
TypeFilterAttribute 可实现 IFilterFactory。 IFilterFactory
公开用于创建 IFilterMetadata 实例的 CreateInstance 方法。 CreateInstance
从服务容器 (DI) 中加载指定的类型。
public class SampleActionTypeFilterAttribute : TypeFilterAttribute
{
public SampleActionTypeFilterAttribute()
: base(typeof(InternalSampleActionFilter)) { }
private class InternalSampleActionFilter : IActionFilter
{
private readonly ILogger<InternalSampleActionFilter> _logger;
public InternalSampleActionFilter(ILogger<InternalSampleActionFilter> logger) =>
_logger = logger;
public void OnActionExecuting(ActionExecutingContext context)
{
_logger.LogInformation(
$"- {nameof(InternalSampleActionFilter)}.{nameof(OnActionExecuting)}");
}
public void OnActionExecuted(ActionExecutedContext context)
{
_logger.LogInformation(
$"- {nameof(InternalSampleActionFilter)}.{nameof(OnActionExecuted)}");
}
}
}
以下代码显示应用筛选器的三种方法:
[SampleActionTypeFilter]
public IActionResult WithDirectAttribute() =>
Content($"- {nameof(FilterFactoryController)}.{nameof(WithDirectAttribute)}");
[TypeFilter(typeof(SampleActionTypeFilterAttribute))]
public IActionResult WithTypeFilterAttribute() =>
Content($"- {nameof(FilterFactoryController)}.{nameof(WithTypeFilterAttribute)}");
[ServiceFilter(typeof(SampleActionTypeFilterAttribute))]
public IActionResult WithServiceFilterAttribute() =>
Content($"- {nameof(FilterFactoryController)}.{nameof(WithServiceFilterAttribute)}");
在前面的代码中,首选应用筛选器的第一种方法。
在筛选器管道中使用中间件
资源筛选器的工作方式与中间件类似,即涵盖管道中的所有后续执行。 但筛选器又不同于中间件,它们是运行时的一部分,这意味着它们有权访问上下文和构造。
若要将中间件用作筛选器,可创建一个具有 Configure
方法的类型,该方法可指定要注入到筛选器管道的中间件。 以下示例使用中间件设置响应标头:
public class FilterMiddlewarePipeline
{
public void Configure(IApplicationBuilder app)
{
app.Use(async (context, next) =>
{
context.Response.Headers.Add("Pipeline", "Middleware");
await next();
});
}
}
使用 MiddlewareFilterAttribute 运行中间件:
[MiddlewareFilter(typeof(FilterMiddlewarePipeline))]
public class FilterMiddlewareController : Controller
{
public IActionResult Index() =>
Content($"- {nameof(FilterMiddlewareController)}.{nameof(Index)}");
}
中间件筛选器与资源筛选器在筛选器管道的相同阶段运行,即,在模型绑定之前以及管道的 rest 之后。
线程安全性
将筛选器的实例传递给 Add
(而不是 Type
)时,筛选器是单一实例且不是线程安全的。
其他资源
作者:Kirk Larkin、Rick Anderson、Tom Dykstra 和 Steve Smith
通过使用 ASP.NET Core 中的筛选器,可在请求处理管道中的特定阶段之前或之后运行代码。
内置筛选器处理任务,例如:
- 授权(防止用户访问未获授权的资源)。
- 响应缓存(对请求管道进行短路出路,以便返回缓存的响应)。
可以创建自定义筛选器,用于处理横切关注点。 横切关注点的示例包括错误处理、缓存、配置、授权和日志记录。 筛选器可以避免复制代码。 例如,错误处理异常筛选器可以合并错误处理。
本文档适用于 Razor Pages、API 控制器和具有视图的控制器。 筛选器无法直接与 Razor 组件一起使用。 筛选器只能在以下情况下间接影响组件:
- 该组件嵌入在页面或视图中。
- 页面或控制器和视图使用此筛选器。
筛选器的工作原理
筛选器在 ASP.NET Core 操作调用管道(有时称为筛选器管道)内运行。 筛选器管道在 ASP.NET Core 选择了要执行的操作之后运行。
筛选器类型
每种筛选器类型都在筛选器管道中的不同阶段执行:
授权筛选器最先运行,用于确定是否已针对请求为用户授权。 如果请求未获授权,授权筛选器可以让管道短路。
-
- 授权后运行。
- OnResourceExecuting 在筛选器管道的 rest 之前运行代码。 例如,
OnResourceExecuting
在模型绑定之前运行代码。 - OnResourceExecuted 在管道的 rest 完成之后运行代码。
-
- 在调用操作方法之前和之后立即运行代码。
- 可以更改传递到操作中的参数。
- 可以更改从操作返回的结果。
- 不可在 Razor Pages 中使用。
异常筛选器在向响应正文写入任何内容之前,对未经处理的异常应用全局策略。
结果筛选器在执行操作结果之前和之后立即运行代码。 仅当操作方法成功执行时,它们才会运行。 对于必须围绕视图或格式化程序的执行的逻辑,它们很有用。
下图展示了筛选器类型在筛选器管道中的交互方式。
实现
筛选器通过不同的接口定义支持同步和异步实现。
同步筛选器在其管道阶段之前和之后运行代码。 例如,OnActionExecuting 在调用操作方法之前调用。 OnActionExecuted 在操作方法返回之后调用。
public class MySampleActionFilter : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
// Do something before the action executes.
MyDebug.Write(MethodBase.GetCurrentMethod(), context.HttpContext.Request.Path);
}
public void OnActionExecuted(ActionExecutedContext context)
{
// Do something after the action executes.
MyDebug.Write(MethodBase.GetCurrentMethod(), context.HttpContext.Request.Path);
}
}
异步筛选器定义 On-Stage-ExecutionAsync
方法。 例如,OnActionExecutionAsync:
public class SampleAsyncActionFilter : IAsyncActionFilter
{
public async Task OnActionExecutionAsync(
ActionExecutingContext context,
ActionExecutionDelegate next)
{
// Do something before the action executes.
// next() calls the action method.
var resultContext = await next();
// resultContext.Result is set.
// Do something after the action executes.
}
}
在前面的代码中,SampleAsyncActionFilter
具有执行操作方法的 ActionExecutionDelegate (next
)。
多个筛选器阶段
可以在单个类中实现多个筛选器阶段的接口。 例如,ActionFilterAttribute 类可实现:
筛选器接口的同步和异步版本任意实现一个,而不是同时实现 。 运行时会先查看筛选器是否实现了异步接口,如果是,则调用该接口。 如果不是,则调用同步接口的方法。 如果在一个类中同时实现异步和同步接口,则仅调用异步方法。 使用抽象类(如 ActionFilterAttribute)时,将为每种筛选器类型仅重写同步方法或仅重写异步方法。
内置筛选器属性
ASP.NET Core 包含许多可子类化和自定义的基于属性的内置筛选器。 例如,以下结果筛选器会向响应添加标头:
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 });
base.OnResultExecuting(context);
}
}
通过使用属性,筛选器可接收参数,如前面的示例所示。 将 AddHeaderAttribute
添加到控制器或操作方法,并指定 HTTP 标头的名称和值:
[AddHeader("Author", "Rick Anderson")]
public class SampleController : Controller
{
public IActionResult Index()
{
return Content("Examine the headers using the F12 developer tools.");
}
使用浏览器开发人员工具等工具来检查标头。 在响应标头下,将显示 author: Rick Anderson
。
以下代码实现了 ActionFilterAttribute
:
- 从配置系统读取标题和名称。 与前面的示例不同,以下代码不需要将筛选器参数添加到代码中。
- 将标题和名称添加到响应标头。
public class MyActionFilterAttribute : ActionFilterAttribute
{
private readonly PositionOptions _settings;
public MyActionFilterAttribute(IOptions<PositionOptions> options)
{
_settings = options.Value;
Order = 1;
}
public override void OnResultExecuting(ResultExecutingContext context)
{
context.HttpContext.Response.Headers.Add(_settings.Title,
new string[] { _settings.Name });
base.OnResultExecuting(context);
}
}
使用选项模式从配置系统中提供配置选项。 例如从 appsettings.json
文件:
{
"Position": {
"Title": "Editor",
"Name": "Joe Smith"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}
在 StartUp.ConfigureServices
中:
PositionOptions
类已通过"Position"
配置区域添加到服务容器。MyActionFilterAttribute
已添加到服务容器。
public void ConfigureServices(IServiceCollection services)
{
services.Configure<PositionOptions>(
Configuration.GetSection("Position"));
services.AddScoped<MyActionFilterAttribute>();
services.AddControllersWithViews();
}
以下代码显示 PositionOptions
类:
public class PositionOptions
{
public string Title { get; set; }
public string Name { get; set; }
}
以下代码将 MyActionFilterAttribute
应用于 Index2
方法:
[AddHeader("Author", "Rick Anderson")]
public class SampleController : Controller
{
public IActionResult Index()
{
return Content("Examine the headers using the F12 developer tools.");
}
[ServiceFilter(typeof(MyActionFilterAttribute))]
public IActionResult Index2()
{
return Content("Header values by configuration.");
}
在响应标头下,author: Rick Anderson
和 Editor: Joe Smith
在调用 Sample/Index2
终结点时显示。
以下代码将 MyActionFilterAttribute
和 AddHeaderAttribute
应用于 Razor 页面:
[AddHeader("Author", "Rick Anderson")]
[ServiceFilter(typeof(MyActionFilterAttribute))]
public class IndexModel : PageModel
{
public void OnGet()
{
}
}
无法将筛选器应用于 Razor 页面处理程序方法。 它们可以应用于 Razor 页面模型或全局应用。
多种筛选器接口具有相应属性,这些属性可用作自定义实现的基类。
筛选器属性:
- ActionFilterAttribute
- ExceptionFilterAttribute
- ResultFilterAttribute
- FormatFilterAttribute
- ServiceFilterAttribute
- TypeFilterAttribute
筛选器作用域和执行顺序
可以将筛选器添加到管道中的三个作用域之一:
- 在控制器操作上使用属性。 无法将筛选器属性应用于 Razor 页面处理程序方法。
- 在控制器或 Razor 页面上使用属性。
- 所有控制器、操作和 Razor 页面的全局筛选器,如下面的代码所示:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews(options =>
{
options.Filters.Add(typeof(MySampleActionFilter));
});
}
默认执行顺序
当管道的某个特定阶段有多个筛选器时,作用域可确定筛选器执行的默认顺序。 全局筛选器涵盖类筛选器,类筛选器又涵盖方法筛选器。
在筛选器嵌套模式下,筛选器的 after 代码会按照与 before 代码相反的顺序运行。 筛选器序列:
- 全局筛选器的 before 代码。
- 控制器筛选器和 Razor 页面筛选器的 before 代码。
- 操作方法筛选器的 before 代码。
- 操作方法筛选器的 after 代码。
- 控制器筛选器和 Razor 页面筛选器的 after 代码。
- 控制器筛选器和 Razor 页面筛选器的 before 代码。
- 全局筛选器的 after 代码。
下面的示例阐释了为同步操作筛选器调用筛选器方法的顺序。
序列 | 筛选器作用域 | 筛选器方法 |
---|---|---|
1 | 全球 | OnActionExecuting |
2 | 控制器或 Razor 页面 | OnActionExecuting |
3 | 方法 | OnActionExecuting |
4 | 方法 | OnActionExecuted |
5 | 控制器或 Razor 页面 | OnActionExecuted |
6 | 全球 | OnActionExecuted |
控制器级别筛选器
继承自 Controller 基类的每个控制器都包括 Controller.OnActionExecuting、Controller.OnActionExecutionAsync 和 Controller.OnActionExecutedOnActionExecuted
方法。 这些方法:
- 覆盖为给定操作运行的筛选器。
OnActionExecuting
在所有操作筛选器之前调用。OnActionExecuted
在所有操作筛选器之后调用。OnActionExecutionAsync
在所有操作筛选器之前调用。next
之后的筛选器中的代码在操作方法之后运行。
例如,在下载示例中,启动时全局应用 MySampleActionFilter
。
TestController
:
- 将
SampleActionFilterAttribute
([SampleActionFilter]
) 应用于FilterTest2
操作。 - 重写
OnActionExecuting
和OnActionExecuted
。
public class TestController : Controller
{
[SampleActionFilter(Order = int.MinValue)]
public IActionResult FilterTest2()
{
return ControllerContext.MyDisplayRouteInfo();
}
public override void OnActionExecuting(ActionExecutingContext context)
{
// Do something before the action executes.
MyDebug.Write(MethodBase.GetCurrentMethod(), HttpContext.Request.Path);
base.OnActionExecuting(context);
}
public override void OnActionExecuted(ActionExecutedContext context)
{
// Do something after the action executes.
MyDebug.Write(MethodBase.GetCurrentMethod(), HttpContext.Request.Path);
base.OnActionExecuted(context);
}
}
MyDisplayRouteInfo 由 Rick.Docs.Samples.RouteInfo NuGet 包提供,会显示路由信息。
导航到 https://localhost:5001/Test/FilterTest2
运行以下代码:
TestController.OnActionExecuting
MySampleActionFilter.OnActionExecuting
SampleActionFilterAttribute.OnActionExecuting
TestController.FilterTest2
SampleActionFilterAttribute.OnActionExecuted
MySampleActionFilter.OnActionExecuted
TestController.OnActionExecuted
控制器级别筛选器将 Order 属性设置为 int.MinValue
。 控制器级别筛选器无法设置为在将筛选器应用于方法之后运行。 在下一节对 Order 进行了介绍。
对于 Razor Pages,请参阅通过重写筛选器方法实现 Razor 页面筛选器。
重写默认顺序
可以通过实现 IOrderedFilter 来重写默认执行序列。 IOrderedFilter
公开了 Order 属性来确定执行顺序,该属性优先于作用域。 具有较低的 Order
值的筛选器:
- 在具有较高的
Order
值的筛选器之前运行 before 代码。 - 在具有较高的
Order
值的筛选器之后运行 after 代码。
使用构造函数参数设置了 Order
属性:
[SampleActionFilter(Order = int.MinValue)]
请考虑以下控制器中的两个操作筛选器:
[MyAction2Filter]
public class Test2Controller : Controller
{
public IActionResult FilterTest2()
{
return ControllerContext.MyDisplayRouteInfo();
}
public override void OnActionExecuting(ActionExecutingContext context)
{
// Do something before the action executes.
MyDebug.Write(MethodBase.GetCurrentMethod(), HttpContext.Request.Path);
base.OnActionExecuting(context);
}
public override void OnActionExecuted(ActionExecutedContext context)
{
// Do something after the action executes.
MyDebug.Write(MethodBase.GetCurrentMethod(), HttpContext.Request.Path);
base.OnActionExecuted(context);
}
}
在 StartUp.ConfigureServices
中添加了全局筛选器:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews(options =>
{
options.Filters.Add(typeof(MySampleActionFilter));
});
}
3 个筛选器按下列顺序运行:
Test2Controller.OnActionExecuting
MySampleActionFilter.OnActionExecuting
MyAction2FilterAttribute.OnActionExecuting
Test2Controller.FilterTest2
MyAction2FilterAttribute.OnResultExecuting
MySampleActionFilter.OnActionExecuted
Test2Controller.OnActionExecuted
在确定筛选器的运行顺序时,Order
属性重写作用域。 先按顺序对筛选器排序,然后使用作用域消除并列问题。 所有内置筛选器实现 IOrderedFilter
并将默认 Order
值设为 0。 如前所述,控制器级别筛选器将 Order 属性设置为 int.MinValue
。对于内置筛选器,作用域会确定顺序,除非将 Order
设为非零值。
在前面的代码中,MySampleActionFilter
具有全局作用域,因此它在具有控制器作用域的 MyAction2FilterAttribute
之前运行。 若要首先运行 MyAction2FilterAttribute
,请将顺序设置为 int.MinValue
:
[MyAction2Filter(int.MinValue)]
public class Test2Controller : Controller
{
public IActionResult FilterTest2()
{
return ControllerContext.MyDisplayRouteInfo();
}
public override void OnActionExecuting(ActionExecutingContext context)
{
// Do something before the action executes.
MyDebug.Write(MethodBase.GetCurrentMethod(), HttpContext.Request.Path);
base.OnActionExecuting(context);
}
public override void OnActionExecuted(ActionExecutedContext context)
{
// Do something after the action executes.
MyDebug.Write(MethodBase.GetCurrentMethod(), HttpContext.Request.Path);
base.OnActionExecuted(context);
}
}
若要首先运行全局筛选器 MySampleActionFilter
,请将 Order
设置为 int.MinValue
:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews(options =>
{
options.Filters.Add(typeof(MySampleActionFilter),
int.MinValue);
});
}
取消和设置短路
通过设置提供给筛选器方法的 ResourceExecutingContext 参数上的 Result 属性,可以使筛选器管道短路。 例如,以下资源筛选器将阻止执行管道的 rest:
public class ShortCircuitingResourceFilterAttribute : Attribute, IResourceFilter
{
public void OnResourceExecuting(ResourceExecutingContext context)
{
context.Result = new ContentResult()
{
Content = "Resource unavailable - header not set."
};
}
public void OnResourceExecuted(ResourceExecutedContext context)
{
}
}
在下面的代码中,ShortCircuitingResourceFilter
和 AddHeader
筛选器都以 SomeResource
操作方法为目标。 ShortCircuitingResourceFilter
:
- 先运行,因为它是资源筛选器且
AddHeader
是操作筛选器。 - 对管道的 rest 进行短路处理。
这样 AddHeader
筛选器就不会为 SomeResource
操作运行。 如果这两个筛选器都应用于操作方法级别,只要 ShortCircuitingResourceFilter
先运行,此行为就不会变。 先运行 ShortCircuitingResourceFilter
(考虑到它的筛选器类型),或显式使用 Order
属性。
[AddHeader("Author", "Rick Anderson")]
public class SampleController : Controller
{
public IActionResult Index()
{
return Content("Examine the headers using the F12 developer tools.");
}
[ServiceFilter(typeof(MyActionFilterAttribute))]
public IActionResult Index2()
{
return Content("Header values by configuration.");
}
[ShortCircuitingResourceFilter]
public IActionResult SomeResource()
{
return Content("Successful access to resource - header is set.");
}
[AddHeaderWithFactory]
public IActionResult HeaderWithFactory()
{
return Content("Examine the headers using the F12 developer tools.");
}
}
依赖关系注入
可按类型或实例添加筛选器。 如果添加实例,该实例将用于每个请求。 如果添加类型,则将激活该类型。 激活类型的筛选器意味着:
- 将为每个请求创建一个实例。
- 依赖关系注入 (DI) 将填充所有构造函数依赖项。
如果将筛选器作为属性实现并直接添加到控制器类或操作方法中,则该筛选器不能由依赖关系注入 (DI) 提供构造函数依赖项。 无法由 DI 提供构造函数依赖项,因为:
- 属性在应用时必须提供自己的构造函数参数。
- 这是属性工作原理上的限制。
以下筛选器支持从 DI 提供的构造函数依赖项:
可以将前面的筛选器应用于控制器或操作方法:
可以从 DI 获取记录器。 但是,避免创建和使用筛选器仅用于日志记录。 内置框架日志记录通常提供日志记录所需的内容。 添加到筛选器的日志记录:
- 应重点关注业务域问题或特定于筛选器的行为。
- 不应记录操作或其他框架事件。 内置筛选器记录操作和框架事件。
ServiceFilterAttribute
在 ConfigureServices
中注册服务筛选器实现类型。 ServiceFilterAttribute 可从 DI 检索筛选器实例。
以下代码显示 AddHeaderResultServiceFilter
:
public class AddHeaderResultServiceFilter : IResultFilter
{
private ILogger _logger;
public AddHeaderResultServiceFilter(ILoggerFactory loggerFactory)
{
_logger = loggerFactory.CreateLogger<AddHeaderResultServiceFilter>();
}
public void OnResultExecuting(ResultExecutingContext context)
{
var headerName = "OnResultExecuting";
context.HttpContext.Response.Headers.Add(
headerName, new string[] { "ResultExecutingSuccessfully" });
_logger.LogInformation("Header added: {HeaderName}", headerName);
}
public void OnResultExecuted(ResultExecutedContext context)
{
// Can't add to headers here because response has started.
_logger.LogInformation("AddHeaderResultServiceFilter.OnResultExecuted");
}
}
在以下代码中,AddHeaderResultServiceFilter
将添加到 DI 容器中:
public void ConfigureServices(IServiceCollection services)
{
// Add service filters.
services.AddScoped<AddHeaderResultServiceFilter>();
services.AddScoped<SampleActionFilterAttribute>();
services.AddControllersWithViews(options =>
{
options.Filters.Add(new AddHeaderAttribute("GlobalAddHeader",
"Result filter added to MvcOptions.Filters")); // An instance
options.Filters.Add(typeof(MySampleActionFilter)); // By type
options.Filters.Add(new SampleGlobalActionFilter()); // An instance
});
}
在以下代码中,ServiceFilter
属性将从 DI 中检索 AddHeaderResultServiceFilter
筛选器的实例:
[ServiceFilter(typeof(AddHeaderResultServiceFilter))]
public IActionResult Index()
{
return View();
}
使用 ServiceFilterAttribute
时,设置 ServiceFilterAttribute.IsReusable:
提供以下提示:筛选器实例可能在其创建的请求范围之外被重用。 ASP.NET Core 运行时不保证:
- 将创建筛选器的单一实例。
- 稍后不会从 DI 容器重新请求筛选器。
不应与依赖于生命周期不同于单一实例的服务的筛选器一起使用。
ServiceFilterAttribute 可实现 IFilterFactory。 IFilterFactory
公开用于创建 IFilterMetadata 实例的 CreateInstance 方法。 CreateInstance
从 DI 中加载指定的类型。
TypeFilterAttribute
TypeFilterAttribute 与 ServiceFilterAttribute 类似,但不会直接从 DI 容器解析其类型。 它使用 Microsoft.Extensions.DependencyInjection.ObjectFactory 对类型进行实例化。
因为不会直接从 DI 容器解析 TypeFilterAttribute
类型:
- 使用
TypeFilterAttribute
引用的类型不需要注册在 DI 容器中。 它们具备由 DI 容器实现的依赖项。 TypeFilterAttribute
可以选择为类型接受构造函数参数。
使用 TypeFilterAttribute
时,设置 TypeFilterAttribute.IsReusable:
提供提示:筛选器实例可能在其创建的请求范围之外被重用。 ASP.NET Core 运行时不保证将创建筛选器的单一实例。
不应与依赖于生命周期不同于单一实例的服务的筛选器一起使用。
下面的示例演示如何使用 TypeFilterAttribute
将参数传递到类型:
[TypeFilter(typeof(LogConstantFilter),
Arguments = new object[] { "Method 'Hi' called" })]
public IActionResult Hi(string name)
{
return Content($"Hi {name}");
}
授权筛选器
授权筛选器:
- 是筛选器管道中运行的第一个筛选器。
- 控制对操作方法的访问。
- 具有在它之前的执行的方法,但没有之后执行的方法。
自定义授权筛选器需要自定义授权框架。 建议配置授权策略或编写自定义授权策略,而不是编写自定义筛选器。 内置授权筛选器:
- 调用授权系统。
- 不授权请求。
不会在授权筛选器中引发异常:
- 不会处理异常。
- 异常筛选器不会处理异常。
在授权筛选器出现异常时请小心应对。
详细了解授权。
资源筛选器
资源筛选器:
- 实现 IResourceFilter 或 IAsyncResourceFilter 接口。
- 执行会覆盖筛选器管道的绝大部分。
- 只有授权筛选器在资源筛选器之前运行。
如果要使大部分管道短路,资源筛选器会很有用。 例如,如果缓存命中,则缓存筛选器可以绕开管道的其余部分。
资源筛选器示例:
之前显示的短路资源筛选器。
DisableFormValueModelBindingAttribute:
- 可以防止模型绑定访问表单数据。
- 用于上传大型文件,以防止表单数据被读入内存。
操作筛选器
操作筛选器不应用于 Razor Pages。 Razor Pages 支持 IPageFilter 和 IAsyncPageFilter。 有关详细信息,请参阅 Razor Pages 的筛选方法。
操作筛选器:
- 实现 IActionFilter 或 IAsyncActionFilter 接口。
- 它们的执行围绕着操作方法的执行。
以下代码显示示例操作筛选器:
public class MySampleActionFilter : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
// Do something before the action executes.
MyDebug.Write(MethodBase.GetCurrentMethod(), context.HttpContext.Request.Path);
}
public void OnActionExecuted(ActionExecutedContext context)
{
// Do something after the action executes.
MyDebug.Write(MethodBase.GetCurrentMethod(), context.HttpContext.Request.Path);
}
}
ActionExecutingContext 提供以下属性:
- ActionArguments - 用于读取操作方法的输入。
- Controller - 用于处理控制器实例。
- Result - 设置
Result
会使操作方法和后续操作筛选器的执行短路。
在操作方法中引发异常:
- 防止运行后续筛选器。
- 与设置
Result
不同,结果被视为失败而不是成功。
ActionExecutedContext 提供 Controller
和 Result
以及以下属性:
Canceled - 如果操作执行已被另一个筛选器设置短路,则为 true。
Exception - 如果操作或之前运行的操作筛选器引发了异常,则为非 NULL 值。 将此属性设置为 null:
- 有效地处理异常。
- 执行
Result
,从操作方法中将它返回。
对于 IAsyncActionFilter
,一个向 ActionExecutionDelegate 的调用可以达到以下目的:
- 执行所有后续操作筛选器和操作方法。
- 返回
ActionExecutedContext
。
若要设置短路,可将 Microsoft.AspNetCore.Mvc.Filters.ActionExecutingContext.Result 分配到某个结果实例,并且不调用 next
(ActionExecutionDelegate
)。
该框架提供一个可子类化的抽象 ActionFilterAttribute。
OnActionExecuting
操作筛选器可用于:
- 验证模型状态。
- 如果状态无效,则返回错误。
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext
context)
{
if (!context.ModelState.IsValid)
{
context.Result = new BadRequestObjectResult(
context.ModelState);
}
}
注意
使用 [ApiController]
属性注释的控制器会自动验证模型状态并返回 400 响应。 有关详细信息,请参阅自动 HTTP 400 响应。
OnActionExecuted
方法在操作方法之后运行:
可通过 Result 属性查看和处理操作结果。
如果操作执行已被另一个筛选器设置短路,则 Canceled 设置为 true。
如果操作或后续操作筛选器引发了异常,则 Exception 设置为非 NULL 值。 将
Exception
设置为 null:- 有效地处理异常。
- 执行
ActionExecutedContext.Result
,从操作方法中将它正常返回。
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext
context)
{
if (!context.ModelState.IsValid)
{
context.Result = new BadRequestObjectResult(
context.ModelState);
}
}
public override void OnActionExecuted(ActionExecutedContext
context)
{
var result = context.Result;
// Do something with Result.
if (context.Canceled == true)
{
// Action execution was short-circuited by another filter.
}
if(context.Exception != null)
{
// Exception thrown by action or action filter.
// Set to null to handle the exception.
context.Exception = null;
}
base.OnActionExecuted(context);
}
}
异常筛选器
异常筛选器:
- 实现 IExceptionFilter 或 IAsyncExceptionFilter。
- 可用于实现常见的错误处理策略。
下面的异常筛选器示例使用自定义错误视图,显示在开发应用时发生的异常的相关详细信息:
public class CustomExceptionFilter : IExceptionFilter
{
private readonly IWebHostEnvironment _hostingEnvironment;
private readonly IModelMetadataProvider _modelMetadataProvider;
public CustomExceptionFilter(
IWebHostEnvironment hostingEnvironment,
IModelMetadataProvider modelMetadataProvider)
{
_hostingEnvironment = hostingEnvironment;
_modelMetadataProvider = modelMetadataProvider;
}
public void OnException(ExceptionContext context)
{
if (!_hostingEnvironment.IsDevelopment())
{
return;
}
var result = new ViewResult {ViewName = "CustomError"};
result.ViewData = new ViewDataDictionary(_modelMetadataProvider,
context.ModelState);
result.ViewData.Add("Exception", context.Exception);
// TODO: Pass additional detailed data via ViewData
context.Result = result;
}
}
以下代码测试异常筛选器:
[TypeFilter(typeof(CustomExceptionFilter))]
public class FailingController : Controller
{
[AddHeader("Failing Controller",
"Won't appear when exception is handled")]
public IActionResult Index()
{
throw new Exception("Testing custom exception filter.");
}
}
异常筛选器:
- 没有之前和之后的事件。
- 实现 OnException 或 OnExceptionAsync。
- 处理 Razor 页面或控制器创建、模型绑定、操作筛选器或操作方法中发生的未经处理的异常。
- 请不要捕获资源筛选器、结果筛选器或 MVC 结果执行中发生的异常。
若要处理异常,请将 ExceptionHandled 属性设置为 true
,或分配 Result 属性。 这将停止传播异常。 异常筛选器无法将异常转变为“成功”。 只有操作筛选器才能执行该转变。
异常筛选器:
- 非常适合捕获发生在操作中的异常。
- 并不像错误处理中间件那么灵活。
建议使用中间件处理异常。 基于所调用的操作方法,仅当错误处理不同时,才使用异常筛选器。 例如,应用可能具有用于 API 终结点和视图/HTML 的操作方法。 API 终结点可能返回 JSON 形式的错误信息,而基于视图的操作可能返回 HTML 形式的错误页。
结果筛选器
结果筛选器:
- 实现接口:
- 它们的执行围绕着操作结果的执行。
IResultFilter 和 IAsyncResultFilter
以下代码显示一个添加 HTTP 标头的结果筛选器:
public class AddHeaderResultServiceFilter : IResultFilter
{
private ILogger _logger;
public AddHeaderResultServiceFilter(ILoggerFactory loggerFactory)
{
_logger = loggerFactory.CreateLogger<AddHeaderResultServiceFilter>();
}
public void OnResultExecuting(ResultExecutingContext context)
{
var headerName = "OnResultExecuting";
context.HttpContext.Response.Headers.Add(
headerName, new string[] { "ResultExecutingSuccessfully" });
_logger.LogInformation("Header added: {HeaderName}", headerName);
}
public void OnResultExecuted(ResultExecutedContext context)
{
// Can't add to headers here because response has started.
_logger.LogInformation("AddHeaderResultServiceFilter.OnResultExecuted");
}
}
要执行的结果类型取决于所执行的操作。 返回视图的操作会将所有 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
:
public class MyAsyncResponseFilter : IAsyncResultFilter
{
public async Task OnResultExecutionAsync(ResultExecutingContext context,
ResultExecutionDelegate next)
{
if (!(context.Result is EmptyResult))
{
await next();
}
else
{
context.Cancel = true;
}
}
}
该框架提供一个可子类化的抽象 ResultFilterAttribute
。 前面所示的 AddHeaderAttribute 类是一种结果筛选器属性。
IAlwaysRunResultFilter 和 IAsyncAlwaysRunResultFilter
IAlwaysRunResultFilter 和 IAsyncAlwaysRunResultFilter 接口声明了一个针对所有操作结果运行的 IResultFilter 实现。 这包括由以下对象生成的操作结果:
- 设置短路的授权筛选器和资源筛选器。
- 异常筛选器。
例如,以下筛选器始终运行并在内容协商失败时设置具有“422 无法处理的实体”状态代码的操作结果 (ObjectResult):
public class UnprocessableResultFilter : Attribute, IAlwaysRunResultFilter
{
public void OnResultExecuting(ResultExecutingContext context)
{
if (context.Result is StatusCodeResult statusCodeResult &&
statusCodeResult.StatusCode == (int) HttpStatusCode.UnsupportedMediaType)
{
context.Result = new ObjectResult("Can't process this!")
{
StatusCode = (int) HttpStatusCode.UnsupportedMediaType,
};
}
}
public void OnResultExecuted(ResultExecutedContext context)
{
}
}
IFilterFactory
IFilterFactory 可实现 IFilterMetadata。 因此,IFilterFactory
实例可在筛选器管道中的任意位置用作 IFilterMetadata
实例。 当运行时准备调用筛选器时,它会尝试将其转换为 IFilterFactory
。 如果转换成功,则调用 CreateInstance 方法来创建将调用的 IFilterMetadata
实例。 这提供了一种很灵活的设计,因为无需在应用启动时显式设置精确的筛选器管道。
IFilterFactory.IsReusable
:
- 是工厂的提示,即工厂创建的筛选器实例可以在其创建时的请求范围之外重用。
- 不应与依赖于具有除单一实例以外的生命周期的服务的筛选器一起使用。
ASP.NET Core 运行时不保证:
- 将创建筛选器的单一实例。
- 稍后不会从 DI 容器重新请求筛选器。
警告
仅当筛选器的来源明确、筛选器是无状态的,且筛选器可以安全地跨多个 HTTP 请求使用时,才将 IFilterFactory.IsReusable 配置为返回 true
。 例如,如果 IFilterFactory.IsReusable
返回 true
,则不从注册为作用域或瞬态的 DI 返回筛选器。
可以使用自定义属性实现来实现 IFilterFactory
作为另一种创建筛选器的方法:
public class AddHeaderWithFactoryAttribute : Attribute, IFilterFactory
{
// Implement IFilterFactory
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
return new InternalAddHeaderFilter();
}
private class InternalAddHeaderFilter : IResultFilter
{
public void OnResultExecuting(ResultExecutingContext context)
{
context.HttpContext.Response.Headers.Add(
"Internal", new string[] { "My header" });
}
public void OnResultExecuted(ResultExecutedContext context)
{
}
}
public bool IsReusable
{
get
{
return false;
}
}
}
在以下代码中应用了筛选器:
[AddHeader("Author", "Rick Anderson")]
public class SampleController : Controller
{
public IActionResult Index()
{
return Content("Examine the headers using the F12 developer tools.");
}
[ServiceFilter(typeof(MyActionFilterAttribute))]
public IActionResult Index2()
{
return Content("Header values by configuration.");
}
[ShortCircuitingResourceFilter]
public IActionResult SomeResource()
{
return Content("Successful access to resource - header is set.");
}
[AddHeaderWithFactory]
public IActionResult HeaderWithFactory()
{
return Content("Examine the headers using the F12 developer tools.");
}
}
通过运行下载示例来测试前面的代码:
- 调用 F12 开发人员工具。
- 导航到
https://localhost:5001/Sample/HeaderWithFactory
。
F12 开发人员工具显示示例代码添加的以下响应标头:
- author:
Rick Anderson
- globaladdheader:
Result filter added to MvcOptions.Filters
- internal:
My header
前面的代码创建 internal: My header
响应标头。
在属性上实现 IFilterFactory
实现 IFilterFactory
的筛选器可用于以下筛选器:
- 不需要传递参数。
- 具备需要由 DI 填充的构造函数依赖项。
TypeFilterAttribute 可实现 IFilterFactory。 IFilterFactory
公开用于创建 IFilterMetadata 实例的 CreateInstance 方法。 CreateInstance
从服务容器 (DI) 中加载指定的类型。
public class SampleActionFilterAttribute : TypeFilterAttribute
{
public SampleActionFilterAttribute()
:base(typeof(SampleActionFilterImpl))
{
}
private class SampleActionFilterImpl : IActionFilter
{
private readonly ILogger _logger;
public SampleActionFilterImpl(ILoggerFactory loggerFactory)
{
_logger = loggerFactory.CreateLogger<SampleActionFilterAttribute>();
}
public void OnActionExecuting(ActionExecutingContext context)
{
_logger.LogInformation("SampleActionFilterAttribute.OnActionExecuting");
}
public void OnActionExecuted(ActionExecutedContext context)
{
_logger.LogInformation("SampleActionFilterAttribute.OnActionExecuted");
}
}
}
以下代码显示应用 [SampleActionFilter]
的三种方法:
[SampleActionFilter]
public IActionResult FilterTest()
{
return Content("From FilterTest");
}
[TypeFilter(typeof(SampleActionFilterAttribute))]
public IActionResult TypeFilterTest()
{
return Content("From TypeFilterTest");
}
// ServiceFilter must be registered in ConfigureServices or
// System.InvalidOperationException: No service for type '<filter>'
// has been registered. Is thrown.
[ServiceFilter(typeof(SampleActionFilterAttribute))]
public IActionResult ServiceFilterTest()
{
return Content("From ServiceFilterTest");
}
在前面的代码中,使用 [SampleActionFilter]
修饰方法是应用 SampleActionFilter
的首选方法。
在筛选器管道中使用中间件
资源筛选器的工作方式与中间件类似,即涵盖管道中的所有后续执行。 但筛选器又不同于中间件,它们是运行时的一部分,这意味着它们有权访问上下文和构造。
若要将中间件用作筛选器,可创建一个具有 Configure
方法的类型,该方法可指定要注入到筛选器管道的中间件。 下面的示例使用本地化中间件为请求建立当前区域性:
public class LocalizationPipeline
{
public void Configure(IApplicationBuilder applicationBuilder)
{
var supportedCultures = new[]
{
new CultureInfo("en-US"),
new CultureInfo("fr")
};
var options = new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture(
culture: "en-US",
uiCulture: "en-US"),
SupportedCultures = supportedCultures,
SupportedUICultures = supportedCultures
};
options.RequestCultureProviders = new[]
{ new RouteDataRequestCultureProvider() {
Options = options } };
applicationBuilder.UseRequestLocalization(options);
}
}
使用 MiddlewareFilterAttribute 运行中间件:
[Route("{culture}/[controller]/[action]")]
[MiddlewareFilter(typeof(LocalizationPipeline))]
public IActionResult CultureFromRouteData()
{
return Content(
$"CurrentCulture:{CultureInfo.CurrentCulture.Name},"
+ $"CurrentUICulture:{CultureInfo.CurrentUICulture.Name}");
}
中间件筛选器与资源筛选器在筛选器管道的相同阶段运行,即,在模型绑定之前以及管道的 rest 之后。
线程安全性
将筛选器的实例传递给 Add
(而不是 Type
)时,筛选器是单一实例且不是线程安全的。
后续操作
- 请参阅 Razor Pages 的筛选器方法。
- 若要尝试使用筛选器,请下载、测试并修改 GitHub 示例。