Filters in ASP.NET Core(Net6之过滤器)
如果觉得样式不好:跳转即可 (md文件复制过来有些样式会不一样)
原文地址:https://lifengying.site/archives/net6%E4%B9%8B%E8%BF%87%E6%BB%A4%E5%99%A8
Filters in ASP.NET Core
Filter
ASP.NET Core 中的过滤器*允许代码在请求处理管道中的特定阶段之前或之后运行。
内置过滤器处理以下任务:
-
授权,防止用户访问未经授权的资源。
-
响应缓存,将请求管道短路以返回缓存的响应。
可以创建自定义过滤器来处理横切关注点。横切关注点的示例包括错误处理、缓存、配置、授权和日志记录。过滤器避免重复代码。例如,错误处理异常过滤器可以合并错误处理。
本文档适用于 Razor 页面、API 控制器和具有视图的控制器。过滤器不能直接与Razor 组件一起使用。过滤器只能在以下情况下间接影响组件:
-
该组件嵌入在页面或视图中。
-
页面或控制器和视图使用过滤器。
过滤器的工作原理
筛选器在ASP.NET Core 操作调用管道(有时称为筛选器管道)中运行。过滤器管道在 ASP.NET Core 选择要执行的操作后运行:
过滤器类型
每个过滤器类型在过滤器管道的不同阶段执行:
-
-
run first 最先执行
-
确定用户是否有权执行请求。
-
如果请求未经授权,则将管道短路。
-
-
-
授权后运行。
-
OnResourceExecuting在过滤器管道的其余部分之前运行代码。例如,
OnResourceExecuting
在模型绑定之前运行代码。 -
OnResourceExecuted在管道的其余部分完成后运行代码。
-
-
-
在调用操作方法之前和之后立即运行。
-
可以更改传递给动作的参数。
-
可以更改从动作返回的结果。
-
Razor页面不支持。
-
-
将全局策略应用于在响应正文被写入之前发生的未处理异常。
-
-
在执行动作结果之前和之后立即运行。
-
仅在操作方法成功执行时运行。
-
对于必须围绕视图或格式化程序执行的逻辑很有用。
-
下图显示了过滤器类型如何在过滤器管道中交互:
总结:
Authorization filters、Exception filters没有前后2种方法(仅限同步方法)
Resource filters、Action filters、Result filters 有前后2种方法(仅限同步方法)
如果是异步的,则与中间件类似
before //等同于xxxExecuting
await netx();//action 执行xxx方法
after //等同于xxxExecuted
Razor Pages 还支持Razor Page 过滤器,它们在 Razor Page 处理程序之前和之后运行。
例子:ActionFilters
同步
using Microsoft.AspNetCore.Mvc.Filters;
namespace FilterMvc.Filters
{
public class SampleActionFilter : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
// Do something before the action executes.
Console.WriteLine("执行方法前做点什么呢?执行方法前");
}
public void OnActionExecuted(ActionExecutedContext context)
{
// Do something after the action executes.
Console.WriteLine("执行方法后做点什么呢?执行方法后");
}
}
}
异步:
using Microsoft.AspNetCore.Mvc.Filters;
namespace FilterMvc.Filters
{
public class SampleAsyncActionFilter : IAsyncActionFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
// Do something before the action executes.
Console.WriteLine("执行方法前做点什么呢?执行方法前");
await next();
// Do something after the action executes.
Console.WriteLine("执行方法后做点什么呢?执行方法后");
}
}
}
Multiple filter stages(复式filter)
多个过滤级的接口可以在单个类中实现。例如,ActionFilterAttribute类实现:
实现过滤器接口的同步或异步版本,而不是两者。运行时首先检查过滤器是否实现了异步接口,如果是,则调用它。如果不是,它调用同步接口的方法。如果在一个类中同时实现异步和同步接口,则只调用 async 方法。使用ActionFilterAttribute等抽象类时,仅覆盖每个过滤器类型的同步方法或异步方法。
内置Attributes Filter (Built-in filter attributes)
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.");
// ...
执行结果:
using FilterMvc.Filters;
using Microsoft.AspNetCore.Mvc;
namespace FilterMvc.Controllers
{
public class ResponseHeaderController : Controller
{
/* public IActionResult Index()
{
return View();
}
*/
[ResponseHeader("Filter-Header", "Filter Value")]
public IActionResult Index()
{
return 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
一些过滤器接口具有相应的属性,可以用作自定义实现的基类。
Filter attributes:
-
筛选器不能应用于 Razor 页面处理程序方法。它们可以应用于 Razor Page 模型或全局。
Filter范围和执行顺序(Filter scopes and order of execution)
可以在以下三个范围*之一将过滤器添加到管道中:
-
使用控制器或 Razor 页面上的属性。
-
在控制器操作上使用属性。筛选器属性不能应用于 Razor 页面处理程序方法。
-
全局用于所有控制器、操作和 Razor 页面,如以下代码所示:
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllersWithViews(options =>
{
options.Filters.Add<GlobalSampleActionFilter>();
});默认执行顺序
当管道的特定阶段有多个过滤器时,范围确定过滤器执行的默认顺序。全局过滤器围绕着类过滤器,而后者又围绕着方法过滤器。
由于过滤器嵌套,过滤器的后代码以与**前代码相反的顺序运行。过滤顺序:
以下示例说明了过滤器方法为同步操作过滤器运行的顺序:
序列 | Filter scope | Filter method |
---|---|---|
1 | Global | OnActionExecuting |
2 | Controller | OnActionExecuting |
3 | Action | OnActionExecuting |
4 | Action | OnActionExecuted |
5 | Controller | OnActionExecuted |
6 | Global | OnActionExecuted |
可以通过实现IOrderedFilter来覆盖默认的执行顺序。IOrderedFilter
公开Order属性,该属性优先于范围以确定执行顺序。具有较低Order
值的过滤器:
-
在具有较高值的过滤器之前运行之前
Order
的代码。 -
在具有更高值的过滤器之后运行after代码。
Order
在控制器级别过滤器示例中,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);
});
顺序总结:(这个指的是同一级别的Filter)
order值越小越先执行,由于是before action after 所以越小的after越后执行,和中间件类似
能用中间件实现的功能,优先考虑中间件,如果在Mvc里面的需求再考虑Filter
取消和短路
可以通过在提供给过滤器方法的ResourceExecutingContext参数上设置Result属性来短路过滤器管道。例如,以下资源过滤器会阻止管道的其余部分执行:
public class ShortCircuitingResourceFilterAttribute : Attribute, IResourceFilter
{
public void OnResourceExecuting(ResourceExecutingContext context)
{
//设置Result属性即可使ResourceFilter 之后的过滤器都不执行 在这里短路了
context.Result = new ContentResult
{
Content = nameof(ShortCircuitingResourceFilterAttribute)
};
}
public void OnResourceExecuted(ResourceExecutedContext context) { }
}
在下面的代码中,[ShortCircuitingResourceFilter]
和[ResponseHeader]
filter 都以Index
action 方法为目标。ShortCircuitingResourceFilterAttribute
过滤器:
-
首先运行,因为它是资源过滤器并且
ResponseHeaderAttribute
是操作过滤器。 -
使管道的其余部分短路。
因此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 检索过滤器的实例。
以下代码显示了LoggingResponseHeaderFilterService
使用 DI 的类:
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>();
在以下代码中,属性从 DIServiceFilter
检索过滤器的实例:LoggingResponseHeaderFilterService
[ServiceFilter(typeof(LoggingResponseHeaderFilterService))]
public IActionResult WithServiceFilter() =>
Content($"- {nameof(FilterDependenciesController)}.{nameof(WithServiceFilter)}");
使用时ServiceFilterAttribute
,设置ServiceFilterAttribute.IsReusable:
-
提供过滤器实例可以在创建它的请求范围之外重用的提示。ASP.NET Core 运行时不保证:
-
将创建过滤器的单个实例。
-
稍后将不会从 DI 容器重新请求过滤器。
-
-
不应与依赖于生命周期而非单例的服务的过滤器一起使用。
ServiceFilterAttribute实现IFilterFactory。IFilterFactory
公开用于创建IFilterMetadata实例的CreateInstance方法。从 DI 加载指定的类型。CreateInstance
总结
在要使用的地方如上述加上[ServiceFilter(typeof(LoggingResponseHeaderFilterService))]即可
执行结果:
类型过滤器属性(TypeFilterAttribute)
TypeFilterAttribute类似于ServiceFilterAttribute,但它的类型不是直接从 DI 容器解析的。它使用Microsoft.Extensions.DependencyInjection.ObjectFactory实例化类型。
因为TypeFilterAttribute
类型不是直接从 DI 容器中解析的:
-
使用 引用的类型
TypeFilterAttribute
不需要在 DI 容器中注册。它们的依赖项确实由 DI 容器实现。 -
TypeFilterAttribute
可以选择接受该类型的构造函数参数。
使用时TypeFilterAttribute
,设置TypeFilterAttribute.IsReusable:
-
提供过滤器实例可以在创建它的请求范围之外重用的提示。ASP.NET Core 运行时不保证将创建过滤器的单个实例。
-
不应与依赖于生命周期而非单例的服务的过滤器一起使用。
Filter:
using Microsoft.AspNetCore.Mvc.Filters;
namespace FilterMvc.Filters
{
public class LoggingResponseHeaderFilter : IActionFilter
{
private readonly string _name;
private readonly string _value;
public LoggingResponseHeaderFilter(string name, string value) =>
(_name, _value) = (name, value);
public void OnActionExecuted(ActionExecutedContext context)
{
Console.WriteLine("=============方法执行后===========");
}
public void OnActionExecuting(ActionExecutingContext context)
{
Console.WriteLine("=============方法执行前===========");
context.HttpContext.Response.Headers.Add(_name, _value);
}
}
}
以下示例显示了如何使用 将参数传递给类型TypeFilterAttribute
:
[TypeFilter(typeof(LoggingResponseHeaderFilter),
Arguments = new object[] { "Filter-Header", "Filter Value" })]
public IActionResult WithTypeFilter() =>
Content($"- {nameof(FilterDependenciesController)}.{nameof(WithTypeFilter)}");
执行结果:
过滤器工厂(IFilterFactory)
IFilterFactory实现IFilterMetadata。因此,IFilterFactory
实例可以用作IFilterMetadata
过滤器管道中任何位置的实例。当运行时准备调用过滤器时,它会尝试将其转换为IFilterFactory
. 如果该转换成功,则调用CreateInstance方法来创建所IFilterMetadata
调用的实例。这提供了一种灵活的设计,因为在应用程序启动时不需要明确设置精确的过滤器管道。
IFilterFactory.IsReusable
:
-
工厂提示工厂创建的过滤器实例可以在创建它的请求范围之外重用。
-
不应与依赖于生命周期而非单例的服务的过滤器一起使用。
ASP.NET Core 运行时不保证:
-
将创建过滤器的单个实例。
-
稍后将不会从 DI 容器重新请求过滤器。
警告
仅当过滤器的来源明确、过滤器是无状态的并且过滤器可以安全地跨多个 HTTP 请求使用时,才将IFilterFactory.IsReusable配置为返回。例如,如果返回true
,则不要从注册为作用域或瞬态的 DI 中返回过滤器。IFilterFactory.IsReusable``true
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.IsReusable(可复用) 这个属性直接关系到生命周期以及使用场景。根据自己具体项目来看
True则时单例,不能与非单例的Filter一起使用。
IFilterFactory 在属性(Attribute)上实现
实现IFilterFactory
的过滤器对于以下过滤器很有用:
-
不需要传递参数。
-
具有需要由 DI 填充的构造函数依赖项。
TypeFilterAttribute实现IFilterFactory。IFilterFactory
公开用于创建IFilterMetadata实例的CreateInstance方法。从服务容器 (DI) 加载指定的类型。CreateInstance
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))]//MiddlewareFilter
public class FilterMiddlewareController : Controller
{
public IActionResult Index() =>
Content($"- {nameof(FilterMiddlewareController)}.{nameof(Index)}");
}
中间件过滤器与资源过滤器在过滤器管道的同一阶段运行,在模型绑定之前和管道的其余部分之后。
线程安全
When passing an instance of a filter into Add
, instead of its Type
, the filter is a singleton and is not thread-safe.
当将过滤器的实例Add
传递给而不是其Type
时,过滤器是单例并且不是线程安全的。(机翻)
例子: T t = new T();
Add(t);
授权过滤器
授权过滤器:
-
第一个过滤器是否在过滤器管道中运行。
-
控制对操作方法的访问。
-
有 before 方法,但没有 after 方法。
自定义授权过滤器需要自定义授权框架。优先配置授权策略或编写自定义授权策略而不是编写自定义过滤器。内置授权过滤器:
-
调用授权系统。
-
不授权请求。
不要在授权过滤器中抛出异常:
-
将不处理异常。
-
异常过滤器不会处理异常。
考虑在授权过滤器中发生异常时发出质询。
了解有关授权的更多信息。
资源过滤器
资源过滤器:
-
执行包装了大部分过滤器管道。
-
只有授权过滤器在资源过滤器之前运行。
资源过滤器可用于使大部分管道短路。例如,缓存过滤器可以避免缓存命中的其余管道。
资源过滤器示例:
-
DisableFormValueModelBindingAttribute:
-
防止模型绑定访问表单数据。
-
用于大文件上传,防止表单数据被读入内存。
-
动作过滤器
操作过滤器不适用于 Razor 页面。Razor Pages 支持IPageFilter和IAsyncPageFilter。有关详细信息,请参阅Razor 页面的筛选方法。
动作过滤器:
-
它们的执行围绕动作方法的执行。
以下代码显示了一个示例操作过滤器:
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 - 允许读取操作方法的输入。
-
控制器- 启用操纵控制器实例。
-
结果- 设置
Result
短路执行操作方法和后续操作过滤器。
在操作方法中抛出异常:
-
防止运行后续过滤器。
-
与设置不同
Result
,被视为失败而不是成功的结果。
ActionExecutedContext提供并Controller
加上Result
以下属性:
-
Canceled - 如果操作执行被另一个过滤器短路,则为真。
-
Exception
- 如果操作或先前运行的操作过滤器引发异常,则为非 null。将此属性设置为 null:
-
有效地处理异常。
-
Result
就像从 action 方法返回一样执行。
-
对于一个IAsyncActionFilter
,调用ActionExecutionDelegate:
-
执行任何后续操作过滤器和操作方法。
-
returns
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设置为非空值。设置Exception为空:
-
有效地处理异常。
-
ActionExecutedContext.Result
就像从 action 方法正常返回一样执行。
-
异常过滤器
异常过滤器:
-
可用于实现常见的错误处理策略。
以下示例异常筛选器显示有关在开发应用程序时发生的异常的详细信息:
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)}");
}
异常过滤器:
-
没有前后事件。
-
处理 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
如果操作结果或后续结果过滤器抛出异常,则设置为非空值。设置Exception
为 null 可以有效地处理异常并防止在管道中稍后再次抛出异常。在结果过滤器中处理异常时,没有可靠的方法将数据写入响应。如果在操作结果引发异常时已将标头刷新到客户端,则没有可靠的机制来发送失败代码。
对于IAsyncResultFilterawait next
,对ResultExecutionDelegate的调用将执行任何后续结果过滤器和操作结果。要短路,请将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 Unprocessable Entity状态代码的操作结果 ( 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) { }
}
以下是图片