CodeSnippet.Cn
代码片段
Csharp
架构设计
.NetCore
西班牙语
kubernetes
MySql
Redis
Algorithm
Ubuntu
Linux
Other
.NetMvc
VisualStudio
Git
pm
Python
WPF
java
Plug-In
分布式
CSS
微服务架构
JavaScript
DataStructure
Shared
理解ASP.NET Core - 过滤器(Filters)
0
.NetCore
小笨蛋
发布于:2022年12月24日
更新于:2022年12月24日
93
#custom-toc-container
### Filter概览 如果你是从ASP.NET一路走过来的,那么你一定对过滤器(Filter)不陌生。当然,ASP.NET Core仍然继承了过滤器机制。 过滤器运行在过滤器管道中,这是一张官方的图,很好地解释了过滤器管道在HTTP请求管道中的位置: ![图片alt](/uploads/images/20221224/220639-0186f12340f84e3a98ca36f90e7f194d.png '代码片段:Www.CodeSnippet.Cn') 可以看到,只有当路由选择了MVC Action之后,过滤器管道才有机会执行。 过滤器不止一种,而是有多种类型。为了让各位对各过滤器执行顺序更容易理解一下,我把官方的图魔改了一下,如下: ![图片alt](/uploads/images/20221224/220654-a8d15961c5d2421d9bd871332071848d.png '代码片段:Www.CodeSnippet.Cn') - Authorization Filters(授权过滤器):该过滤器位于所有过滤器的顶端,首先被执行。授权过滤器用于确认请求用户是否已授权,如未授权,则可以将管道短路,禁止请求继续传递。 - Resource Filters(资源过滤器):当授权通过后,可以在过滤器管道的其他阶段(如模型绑定)之前和之后执行自定义逻辑 - Action Filters(操作过滤器):在调用Action之前和之后执行自定义逻辑。通过操作过滤器,可以修改要传入Action的参数,也可以设置或修改Action的返回结果。另外,其也可以首先捕获到Action中抛出的未处理异常并进行处理。 - Exception Filters(异常过滤器):当Controller创建时、模型绑定、Action Filters和Action执行中抛出未处理的异常时,异常过滤器可以捕获并进行处理,需要注意的是,在此之前,响应正文还未被写入,这意味着你可以设置返回结果。 - Result Filters(结果过滤器):仅当Action的执行未抛出异常,或Action Filter处理了异常时,才会执行结果过滤器,允许你在操作结果执行之前和之后执行自定义逻辑。 东西有点多,你要忍一下。等看过下面的详细介绍之后,再来回顾上面,就很容易理解了。 > 这些过滤器,均实现了IFilterMetadata接口,该接口不包含任何行为,仅仅是用于标记这是MVC请求管道中的过滤器。 另外,如Resource Filters、Action Filters和Result Filters这种,他们拥有两个行为,分别在管道阶段的之前和之后执行,并按照习惯,将之前命名为OnXXXing,如 OnActionExecuting,将之后命名为OnXXXExecuted,如 OnActionExecuted ### 过滤器的作用域和注册方式 由于过滤器的种类繁多,为了方便大家边学习边测试,所以先介绍一下过滤器的作用域和注册方式。 #### 过滤器的作用域范围和执行顺序 同样的,在介绍过滤器之前,先给大家介绍一下过滤器的作用域范围和执行顺序。 过滤器的作用域范围,可分为三种,从小到大是: - 某个Controller中的某个Action上(不支持Razor Page中的处理方法) - 某个Controller或Razor Page上 - 全局,应用到所有Controller、Action和Razor Page上 不同过滤器的执行顺序,我们通过上面那幅图可以很清楚的知晓了,但是对于不同作用域的同一类型的过滤器,执行顺序又是怎样的呢? 以IActionFilter举例说明,执行顺序为: - 全局过滤器的 OnActionExecuting - Controller和Razor Page过滤器的 OnActionExecuting - Action过滤器的 OnActionExecuting - Action过滤器的 OnActionExecuted - Controller和Razor Page过滤器的 OnActionExecuted - 全局过滤器的 OnActionExecuted 也就是说,**对于不同作用域的同一类型的过滤器,执行顺序是由作用域范围大到小,然后再由小到大** #### 过滤器的注册方式 接下来,看一下如何将过滤器注册为不同的作用域: ##### 全局 注册为全局比较简单,直接配置MvcOptions.Filters即可: ```csharp public void ConfigureServices(IServiceCollection services) { services.AddMvc(options => options.Filters.Add
()); // or services.AddControllers(options => options.Filters.Add
()); // or services.AddControllersWithViews(options => options.Filters.Add
()); } ``` ##### Controller、Razor Page 或 Action 作用域为 Controller、Razor Page 或 Action 在注册方式上来说,实际上都是差不多的,都是以**特性**的方式进行标注。 最简单的,过滤器构造函数无参数或这些参数均无需由DI来提供,此时只需要过滤器继承Attribute即可: ```csharp class MyFilterAttribute : Attribute, IActionFilter { } [MyFilter] public class HomeController : Controller { } ``` 另一种,过滤器的构造函数参数均需要DI来提供,此时就需要用到ServiceFilterAttribute了: ```csharp class MyFilter :IActionFilter { public MyFilter(IWebHostEnvironment env) { } } public void ConfigureServices(IServiceCollection services) { // 将过滤器添加到 DI 容器 services.AddScoped
(); } [ServiceFilter(typeof(MyFilter))] public class HomeController : Controller { } ``` 那ServiceFilterAttribute是如何创建这种类型过滤器的实例的呢?看下它的结构你就明白了: ```csharp public interface IFilterFactory : IFilterMetadata { // 过滤器实例是否可跨请求重用 bool IsReusable { get; } // 通过 IServiceProvider 创建指定过滤器类型的实例 IFilterMetadata CreateInstance(IServiceProvider serviceProvider); } public class ServiceFilterAttribute : Attribute, IFilterFactory, IFilterMetadata, IOrderedFilter { // type 就是要创建的过滤器的类型 public ServiceFilterAttribute(Type type) { ServiceType = type ?? throw new ArgumentNullException(nameof(type)); } public int Order { get; set; } // 获取过滤器的类型,也就是构造函数中传进来的 public Type ServiceType { get; } // 过滤器实例是否可跨请求重用,默认 false public bool IsReusable { get; set; } // 通过 IServiceProvider.GetRequiredService 创建指定过滤器类型的实例 // 所以要求该过滤器和构造函数参数要在DI容器中注册 public IFilterMetadata CreateInstance(IServiceProvider serviceProvider) { var filter = (IFilterMetadata)serviceProvider.GetRequiredService(ServiceType); if (filter is IFilterFactory filterFactory) { // 展开 IFilterFactory filter = filterFactory.CreateInstance(serviceProvider); } return filter; } } ``` 如果你想要使过滤器实例在其作用域之外被重用,可以通过指定IsReusable = true来达到目的,需要注意的是要保证该过滤器所依赖的服务生命周期一定是单例的。另外,这并不能保证该过滤器实例是单例,也有可能出现多个。 好了,还有最后一种最复杂的,就是过滤器的构造函数部分不需要DI来提供,部分又需要DI来提供,此时就需要用到TypeFilterAttribute了: ```csharp class MyFilter : IActionFilter { // 第一个参数 caller 不是通过DI提供的 // 第二个参数 env 是通过DI提供的 public MyFilter(string caller, IWebHostEnvironment env) { } } // ... 注意,这里就不需要将 MyFilter 注册到DI容器了,记得将注册代码删除 // Arguments 里面存放的参数就是无需DI提供的参数 [TypeFilter(typeof(MyFilter), Arguments = new object[] { "HomeController" })] public class HomeController : Controller { } ``` 同样,看一下TypeFilterAttribute的结构: ```csharp public class TypeFilterAttribute : Attribute, IFilterFactory, IFilterMetadata, IOrderedFilter { private ObjectFactory _factory; // type 就是要创建的过滤器的类型 public TypeFilterAttribute(Type type) { ImplementationType = type ?? throw new ArgumentNullException(nameof(type)); } // 要传递给过滤器构造函数的非DI容器提供的参数 public object[] Arguments { get; set; } // 获取过滤器的类型,也就是构造函数中传进来的 public Type ImplementationType { get; } public int Order { get; set; } public bool IsReusable { get; set; } // 通过 ObjectFactory 创建指定过滤器类型的实例 public IFilterMetadata CreateInstance(IServiceProvider serviceProvider) { if (_factory == null) { var argumentTypes = Arguments?.Select(a => a.GetType())?.ToArray(); _factory = ActivatorUtilities.CreateFactory(ImplementationType, argumentTypes ?? Type.EmptyTypes); } var filter = (IFilterMetadata)_factory(serviceProvider, Arguments); if (filter is IFilterFactory filterFactory) { // 展开 IFilterFactory filter = filterFactory.CreateInstance(serviceProvider); } return filter; } } ``` ### 过滤器上下文 过滤器中的行为,都会有一个上下文参数,这些上下文参数都继承自抽象类FilterContext,而FilterContext又继承自ActionContext(这也从侧面说明了,过滤器就是为Action服务的): ```csharp public class ActionContext { // Action相关的信息 public ActionDescriptor ActionDescriptor { get; set; } // HTTP上下文 public HttpContext HttpContext { get; set; } // 模型绑定和验证 public ModelStateDictionary ModelState { get; } // 路由数据 public RouteData RouteData { get; set; } } public abstract class FilterContext : ActionContext { public virtual IList
Filters { get; } public bool IsEffectivePolicy
(TMetadata policy) where TMetadata : IFilterMetadata {} public TMetadata FindEffectivePolicy
() where TMetadata : IFilterMetadata {} } ``` 当我们自定义一个过滤器时,免不了要和上下文进行交互,所以,了解上下文的结构,是不可或缺的。下面就挑两个重要的参数探究一下。 我们先来看ActionDescriptor,它里面包含了和Action相关的信息: ```csharp public class ActionDescriptor { // 标识该Action的唯一标识,其实就是一个Guid public string Id { get; } // 路由字典,包含了controller、action的名字等 public IDictionary
RouteValues { get; set; } // 特性路由的相关信息 public AttributeRouteInfo? AttributeRouteInfo { get; set; } // Action的约束列表 public IList
? ActionConstraints { get; set; } // 终结点元数据,咱们一般用不到 public IList
EndpointMetadata { get; set; } // 路由中的参数列表,包含参数名、参数类型、绑定信息等 public IList
Parameters { get; set; } public IList
BoundProperties { get; set; } // 过滤器管道中与当前Action有关的过滤器列表 public IList
FilterDescriptors { get; set; } // Action的个性化名称 public virtual string? DisplayName { get; set; } // 共享元数据 public IDictionary
Properties { get; set; } } ``` 下面的HttpContext这个就不说了,太大了。不过你得知道,有了它,你可以针对请求和响应做自己想做的操作。 接下来就是ModelState,它是用于校验模型绑定的,通过它,可以知道模型是否绑定成功,也可以得到绑定失败的校验信息。相关细节将在后续关于模型绑定的文章中进行介绍。 然后就是RouteData,很显然,它存储了和路由有关的信息,那就看一下它包括什么吧: ```csharp public class RouteData { // 当前路由路径上由路由生成的数据标记 public RouteValueDictionary DataTokens { get; } // Microsoft.AspNetCore.Routing.IRouter 的实例列表 public IList
Routers { get; } // 路由值,包含了 ActionDescriptor.RouteValues 中的数据 public RouteValueDictionary Values { get; } } ``` 后面,就来到了Filters,看到IFilterMetadata我相信你也已经猜到了,它表示过滤器管道中与当前Action有关的过滤器列表。 ### Authorization Filters 授权过滤器是过滤器管道的第一个被执行的过滤器,用于系统授权。一般不会编写自定义的授权过滤器,而是配置授权策略或编写自定义授权策略。详细内容将在后续文章介绍。 ### Resource Filters 资源过滤器,在授权过滤器执行后执行,该过滤器包含“之前”和“之后”两个行为,包裹了模型绑定、操作过滤器、Action执行、异常过滤器、结果过滤器以及结果执行。 通过实现IResourceFilter或IAsyncResourceFilter接口: ```csharp public interface IResourceFilter : IFilterMetadata { void OnResourceExecuting(ResourceExecutingContext context); void OnResourceExecuted(ResourceExecutedContext context); } public interface IAsyncResourceFilter : IFilterMetadata { Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next); } ``` 当拦截到请求时,你可以得到资源信息的上下文: ```csharp public class ResourceExecutingContext : FilterContext { // 获取或设置该Action的执行结果 public virtual IActionResult? Result { get; set; } // Action参数绑定源提供器工厂,比如 Form、Route、QueryString、JQueryForm、FormFile等 public IList
ValueProviderFactories { get; } } public class ResourceExecutedContext : FilterContext { // 指示Action的执行是否已取消 public virtual bool Canceled { get; set; } // 如果捕获到未处理的异常,会存放到此处 public virtual Exception? Exception { get; set; } public virtual ExceptionDispatchInfo? ExceptionDispatchInfo { get; set; } // 指示异常是否已被处理 public virtual bool ExceptionHandled { get; set; } // 获取或设置该Action的执行结果 public virtual IActionResult? Result { get; set; } } ``` > 类似的,一旦设置了Result,就可以使过滤器管道短路。 对于ResourceExecutedContext,有两种方式来处理异常: - 将Exception或ExceptionDispatchInfo置为null - 将ExceptionHandled置为true 单纯的仅设置Result是行不通的。所以我建议大家,在处理异常时,除了设置Result外,也将ExceptionHandled设置为true,这样也让读代码的人更容易理解代码逻辑。 另外,ResourceExecutedContext.Canceled,用于指示Action的执行是否已取消。当在 OnResourceExecuting 中手动设置 ResourceExecutingContext.Result 时,会将 Canceled 置为 true。需要注意的是,想要测试这种情况,至少要注册两个资源过滤器,并且在第二个资源过滤器中设置Result,才能够在第一个过滤器中看到效果。 ### Action Filters 操作过滤器,在模型绑定后执行,该过滤器同样包含“之前”和“之后”两个行为,包裹了Action的执行(不包含Controller的创建)。 如果Action执行过程中或后续操作过滤器中抛出异常,首先捕获到异常的是操作过滤器的OnActionExecuted,而不是异常过滤器。 通过实现IActionFilter或IAsyncActionFilter接口: ```csharp public interface IActionFilter : IFilterMetadata { void OnActionExecuting(ActionExecutingContext context); void OnActionExecuted(ActionExecutedContext context); } public interface IAsyncActionFilter : IFilterMetadata { Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next); } ``` 同样地,看一下上下文结构: ```csharp public class ActionExecutingContext : FilterContext { // 获取或设置该Action的执行结果 public virtual IActionResult? Result { get; set; } // Action的参数字典,key是参数名,value是参数值 public virtual IDictionary
ActionArguments { get; } // 获取该Action所属的Controller public virtual object Controller { get; } } public class ActionExecutedContext : FilterContext { // 指示Action的执行是否已取消 public virtual bool Canceled { get; set; } // 获取该Action所属的Controller public virtual object Controller { get; } // 如果捕获到未处理的异常,会存放到此处 public virtual Exception? Exception { get; set; } public virtual ExceptionDispatchInfo? ExceptionDispatchInfo { get; set; } // 指示异常是否已被处理 public virtual bool ExceptionHandled { get; set; } // 获取或设置该Action的执行结果 public virtual IActionResult Result { get; set; } } ``` 关于ActionExecutedContext.Canceled属性和异常处理相关的知识点,均与资源过滤器类似,这里就不再赘述了。 由于操作过滤器常常在应用中的使用比较频繁,所以这里详细介绍一下它的使用。ASP.NET Core框架提供了一个抽象类ActionFilterAttribute,该抽象类实现了多个接口,还继承了Attribute,允许我们以特性的方式使用。所以,一般比较建议大家通过继承该抽象类来自定义操作过滤器: ```csharp [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] public abstract class ActionFilterAttribute : Attribute, IActionFilter, IAsyncActionFilter, IResultFilter, IAsyncResultFilter, IOrderedFilter { public int Order { get; set; } public virtual void OnActionExecuting(ActionExecutingContext context) { } public virtual void OnActionExecuted(ActionExecutedContext context) { } public virtual async Task OnActionExecutionAsync( ActionExecutingContext context, ActionExecutionDelegate next) { // 删除了一些空校验代码... OnActionExecuting(context); if (context.Result == null) { OnActionExecuted(await next()); } } public virtual void OnResultExecuting(ResultExecutingContext context) { } public virtual void OnResultExecuted(ResultExecutedContext context) { } public virtual async Task OnResultExecutionAsync( ResultExecutingContext context, ResultExecutionDelegate next) { // 删除了一些空校验代码... OnResultExecuting(context); if (!context.Cancel) { OnResultExecuted(await next()); } } } ``` 可以看到,ActionFilterAttribute同时实现了同步和异步接口,不过,我们在使用时,只需要实现同步或异步接口就可以了,不要同时实现。这是因为,运行时会先检查过滤器是否实现了异步接口,如果是,则调用该异步接口。否则,就调用同步接口。 如果在一个类中同时实现了异步和同步接口,则仅会调用异步接口。 当要全局进行验证模型绑定状态时,使用操作过滤器再合适不过了! ```csharp public class ModelStateValidationFilterAttribute : ActionFilterAttribute { public override void OnActionExecuting(ActionExecutingContext context) { if (!context.ModelState.IsValid) { if (context.HttpContext.Request.AcceptJson()) { var errorMsg = string.Join(Environment.NewLine, context.ModelState.Values.SelectMany(v => v.Errors.Select(e => e.ErrorMessage))); context.Result = new BadRequestObjectResult(AjaxResponse.Failed(errorMsg)); } else { context.Result = new ViewResult(); } } } } public static class HttpRequestExtensions { public static bool AcceptJson(this HttpRequest request) { if (request == null) throw new ArgumentNullException(nameof(request)); var regex = new Regex(@"^(\*|application)/(\*|json)$"); return request.Headers[HeaderNames.Accept].ToString() .Split(',') .Any(type => regex.IsMatch(type)); } } ``` ### Exception Filters 异常过滤器,可以捕获Controller创建时(也就是只捕获构造函数中抛出的异常)、模型绑定、Action Filter和Action中抛出的未处理异常。 再着重说明一下:如果Action执行过程中或非首个操作过滤器中抛出异常,首先捕获到异常的是操作过滤器的OnActionExecuted,而不是异常过滤器。但是,如果在Controller创建时抛出异常,那首先捕获到异常的就是异常过滤器了。 我知道大家在初时异常过滤器的时候,有的人会误认为它可以捕获程序中的任何异常,这是不对的! 异常过滤器: - 通过实现接口IExceptionFilter或IAsyncExceptionFilter来自定义异常过滤器 - 可以捕获Controller创建时(也就是只捕获构造函数中抛出的异常)、模型绑定、Action Filter和Action中抛出的未处理异常 - 其他地方抛出的异常不会捕获 先来看一下这两个接口: ```csharp // 仅具有标记作用,标记其为 mvc 请求管道的过滤器 public interface IFilterMetadata { } public interface IExceptionFilter : IFilterMetadata { // 当抛出异常时,该方法会捕获 void OnException(ExceptionContext context); } public interface IAsyncExceptionFilter : IFilterMetadata { // 当抛出异常时,该方法会捕获 Task OnExceptionAsync(ExceptionContext context); } ``` OnException和OnExceptionAsync方法都包含一个类型为ExceptionContext参数,很显然,它就是与异常有关的上下文,我们的异常处理逻辑离不开它。那接着来看一下它的结构吧: ```csharp public class ExceptionContext : FilterContext { // 捕获到的未处理异常 public virtual Exception Exception { get; set; } public virtual ExceptionDispatchInfo? ExceptionDispatchInfo { get; set; } // 指示异常是否已被处理 // true:表示异常已被处理,异常不会再向上抛出 // false:表示异常未被处理,异常仍会继续向上抛出 public virtual bool ExceptionHandled { get; set; } // 设置响应的 IActionResult // 如果设置了结果,也表示异常已被处理,异常不会再向上抛出 public virtual IActionResult? Result { get; set; } } ``` 下面,我们就来实现一个自定义的异常处理器: ```csharp public class MyExceptionFilterAttribute : ExceptionFilterAttribute { private readonly IModelMetadataProvider _modelMetadataProvider; public MyExceptionFilterAttribute(IModelMetadataProvider modelMetadataProvider) { _modelMetadataProvider = modelMetadataProvider; } public override void OnException(ExceptionContext context) { if (!context.ExceptionHandled) { // 此处仅为简单演示 var exception = context.Exception; var result = new ViewResult() { ViewName = "Error", ViewData = new ViewDataDictionary(_modelMetadataProvider, context.ModelState) { // 记得给 ErrorViewModel 加上 Message 属性 Model = new ErrorViewModel { Message = exception.ToString() } } }; context.Result = result; // 标记异常已处理 context.ExceptionHandled = true; } } } ``` 接着,找到/Views/Shared/Error.cshtml,展示一下错误消息: ```csharp @model ErrorViewModel @{ ViewData["Title"] = "Error"; }
@Model.Message
``` 最后,注册一下MyExceptionFilterAttribute: ```csharp public void ConfigureServices(IServiceCollection services) { services.AddScoped
(); services.AddControllersWithViews(); } ``` 现在,我们将该异常处理器加在/Home/Index上,并抛个异常: ```csharp public class HomeController : Controller { [ServiceFilter(typeof(MyExceptionFilterAttribute))] public IActionResult Index() { throw new Exception("Home Index Error"); return View(); } } ``` 当请求/Home/Index时,你会得到如下页面: ![图片alt](/uploads/images/20221224/221434-eaabdd3071474831bcf5d96e7c5b8109.png '代码片段:Www.CodeSnippet.Cn') ### Result Filters 结果过滤器,包裹了操作结果的执行。所谓操作结果的执行,可以是Razor视图的处理操作,也可以是Json结果的序列化操作等。 通过实现IResultFilter或IAsyncResultFilter接口: ```csharp public interface IResultFilter : IFilterMetadata { void OnResultExecuting(ResultExecutingContext context); void OnResultExecuted(ResultExecutedContext context); } public interface IAsyncResultFilter : IFilterMetadata { Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next); } ``` 当实现这两个接口其一时,则仅当Action或Action Filters生成Result时,才会执行结果过滤器。像授权、资源过滤器使管道短路或异常过滤器通过生成Result来处理异常等,都不会执行结果过滤器。 > 如果在 OnResultExecuting 中抛异常了,就会导致短路,Action结果和后续的结果过滤器都不会执行,并且执行结果也被视为失败。 同样地,看一下上下文结构: ```csharp public class ResultExecutingContext : FilterContext { // 获取该Action所属的Controller public virtual object Controller { get; } // 获取或设置该Action的结果 public virtual IActionResult Result { get; set; } // 指示结果过滤器是否应该被短路,若短路,Action结果和后续的的结果过滤器,都不会执行 public virtual bool Cancel { get; set; } } public class ResultExecutedContext : FilterContext { // 指示结果过滤器是否被短路,若短路,Action结果和后续的的结果过滤器,都不会执行 public virtual bool Canceled { get; set; } // 获取该Action所属的Controller public virtual object Controller { get; } // 获取或设置结果或结果过滤器执行过程中抛出的未处理异常 public virtual Exception? Exception { get; set; } public virtual ExceptionDispatchInfo? ExceptionDispatchInfo { get; set; } // 异常是否已被处理 public virtual bool ExceptionHandled { get; set; } // 获取或设置该Action的执行结果 public virtual IActionResult Result { get; } } ``` 可以通过继承抽象类ResultFilterAttribute来实现自定义结果过滤器: ```csharp class MyResultFilter : ResultFilterAttribute { private readonly ILogger
_logger; public MyResultFilter(ILogger
logger) { _logger = logger; } public override void OnResultExecuted(ResultExecutedContext context) { context.HttpContext.Response.Headers.Add("CustomHeaderName", "CustomHeaderValue"); } public override void OnResultExecuting(ResultExecutingContext context) { if (context.HttpContext.Response.HasStarted) { _logger.LogInformation("Response has started!"); } } } ``` 上面说过,IResultFilter或IAsyncResultFilter接口有一定的局限性,当授权、资源过滤器使管道短路或异常过滤器通过生成Result来处理异常等,会导致结果过滤器不被执行。但是,如果在这种情况下,我们也想要执行结果过滤器,那该咋办呢?别慌,ASP.NET Core已经想到这种情况了。 那就是实现IAlwaysRunResultFilter或IAsyncAlwaysRunResultFilter接口,看这名字就够直接了吧——始终运行: ```csharp public interface IAlwaysRunResultFilter : IResultFilter, IFilterMetadata { } public interface IAsyncAlwaysRunResultFilter : IAsyncResultFilter, IFilterMetadata { } ``` ### 中间件过滤器 中间件过滤器,其实是在过滤器管道中加入中间件管道。中间件过滤器的执行时机与资源过滤器一样,即模型绑定之前和管道的其余部分执行之后执行。 要创建中间件过滤器,需要满足一个条件,那就是该中间件必须包含一个Configure方法(一般来说还会包含一个IApplicationBuilder参数用于配置中间件管道,不过这不是强制的)。 例如: ```csharp class MyPipeline { public void Configure(IApplicationBuilder app) { System.Console.WriteLine("MyPipeline"); } } [MiddlewareFilter(typeof(MyPipeline))] public class HomeController : Controller { } ``` ### 其他 #### IOrderedFilter 针对同一类型的过滤器,我们可以有多个实现,这些实现,可以注册到不同的作用域中,而且同一个作用域可以有多个该过滤器类型的实现。如果我们将这样的多个实现作用于同一个Action,这些过滤器实例的执行顺序就是我们所要关心的了。 默认的,如果将同一作用域的同一类型的过滤器的多个实现作用到某个Action上,则这些过滤器实例的执行顺序是按照注册的顺序进行的。 例如,我们现在有两个操作过滤器——MyActionFilter1和MyActionFilter2: ```csharp public class MyActionFilter1 : ActionFilterAttribute { public override void OnActionExecuting(ActionExecutingContext context) { Console.WriteLine("OnActionExecuting: MyActionFilter1"); } public override void OnResultExecuted(ResultExecutedContext context) { Console.WriteLine("OnResultExecuted: MyActionFilter1"); } } public class MyActionFilter2 : ActionFilterAttribute { public override void OnActionExecuting(ActionExecutingContext context) { Console.WriteLine("OnActionExecuting: MyActionFilter2"); } public override void OnResultExecuted(ResultExecutedContext context) { Console.WriteLine("OnResultExecuted: MyActionFilter2"); } } ``` 然后将其作用到HomeController.Index方法上,并且,先注册MyActionFilter2,再注册MyActionFilter1: ```csharp public class HomeController : Controller { [MyActionFilter2] [MyActionFilter1] public IActionResult Index() { return View(); } } ``` 当请求Home/Index时,控制台的输出如下: ```csharp OnActionExecuting: MyActionFilter2 OnActionExecuting: MyActionFilter1 OnResultExecuted: MyActionFilter1 OnResultExecuted: MyActionFilter2 ``` 但是,我们在开发过程中,很容易手滑将注册顺序弄错,这时我们就需要一个手动指定执行顺序的机制,这就用到了IOrderedFilter接口。 ```csharp public interface IOrderedFilter : IFilterMetadata { // 执行顺序 int Order { get; } } ``` IOrderedFilter接口很简单,只有一个Order属性,表示执行顺序,默认值为0。Order值越小,则过滤器的Before方法越先执行,After方法越后执行。 下面我们改造一下MyActionFilter1和MyActionFilter2,让MyActionFilter1先执行: ```csharp public class MyActionFilter1 : ActionFilterAttribute { public MyActionFilter1() { Order = -1; } public override void OnActionExecuting(ActionExecutingContext context) { Console.WriteLine("OnActionExecuting: MyActionFilter1"); } public override void OnResultExecuted(ResultExecutedContext context) { Console.WriteLine("OnResultExecuted: MyActionFilter1"); } } public class MyActionFilter2 : ActionFilterAttribute { public MyActionFilter2() { Order = 1; } public override void OnActionExecuting(ActionExecutingContext context) { Console.WriteLine("OnActionExecuting: MyActionFilter2"); } public override void OnResultExecuted(ResultExecutedContext context) { Console.WriteLine("OnResultExecuted: MyActionFilter2"); } } ``` 此时,再次请求Home/Index,控制台的输出如下: ```csharp OnActionExecuting: MyActionFilter1 OnActionExecuting: MyActionFilter2 OnResultExecuted: MyActionFilter2 OnResultExecuted: MyActionFilter1 ``` 现在,我们看一下不同作用域的情况下,Order是否生效。将MyActionFilter2作用域提升到控制器上。 ```csharp [MyActionFilter2] public class HomeController : Controller { [MyActionFilter1] public IActionResult Index() { return View(); } } ``` 此时,再次请求Home/Index,控制台的输出如下: ```csharp OnActionExecuting: MyActionFilter1 OnActionExecuting: MyActionFilter2 OnResultExecuted: MyActionFilter2 OnResultExecuted: MyActionFilter1 ``` 哇,神奇的事情发生了,作用域为Action的MyActionFilter1竟然优先于作用域为Controller的MyActionFilter2执行。 实际上,Order会重写作用域,即先按Order对过滤器进行排序,然后再通过作用域消除并列问题。 另外,若要始终首先执行全局过滤器,则请将Order设置为int.MinValue: ```csharp public void ConfigureServices(IServiceCollection services) { services.AddControllersWithViews(options => { options.Filters.Add
(int.MinValue); }); } ```
这里⇓感觉得写点什么,要不显得有点空,但还没想好写什么...
返回顶部
About
京ICP备13038605号
© 代码片段 2024