什么是Filter 过滤器(Filter)是 AOP[^AOP] 思想的一种实现,让我们在执行管道(pipeline)的特定阶段(之前或之后)执行代码,管道在选择了要执行的操作之后运行。
下面图片显示了管道所在位置
通过使用过滤器可以实现 短路请求、缓存请求结果、日志统一记录、参数合法性验证、异常统一处理、返回值格式化 等,同时使业务代码更加简洁单纯,避免很多重复代码。
5种Filter 简单概括
Authorizaion Filter :授权过滤器
Authorization是五种Filter中最先运行的,用于确定是否已针对请求为用户授权。 如果请求未获授权,可以让管道短路。
作用 :主要用于实现复杂的权限角色认证
、登陆授权
等操作。
Resource Filter :资源过滤器
OnResourceExecuting:在Authorization之后,Model Binding之前执行,对实现缓存或者对过滤管道进行短路 特别有用。
OnResourceExecuted:在管道的其余阶段完成之后运行代码。
作用 :主要用于进行资源缓存
、防盗链
等操作。
Exception Filter :异常过滤器
异常处理的Filter,在向响应正文写入任何内容之前,对未经处理的异常应用全局策略。
作用 :可以进行全局的异常日志收集
、错误信息友好化处理
等操作。
Action Filter :操作过滤器
最常使用的Filter,实际上包围了整个Action。
OnActionExecuting:执行实际Action操作前触发,更改传递到操作中的参数。
OnActionExecuted:执行实际Action操作后触发,更改从操作返回的结果。
作用 :做法太多了,可以用于执行操作日志
、参数验证
,权限控制
等一系列操作。
Result Filter :结果过滤器
在执行操作结果之前和之后立即运行代码。仅当操作方法成功执行时,它们才会运行 。对于必须围绕视图或格式化程序的执行的逻辑,它们很有用。
OnResultExecuting:在操作结果执行之前调用。
OnResultExecuted:在操作结果执行之后调用。
作用 :可以对结果进行格式化
、大小写转换
、缓存结果
等一系列操作。
流程图 下图是我自制的一个比较完整的请求在Filter Pipeline
中的流动的流程图。
需要特别注意的是Exception Filter
的位置:
所有Filter均可通过不同的接口定义支持同步和异步的实现。根据需要执行的任务类型,选择同步或异步实现。
通过设置 Context.Result 来截断请求,可以是使Filter管道短路。
注意: 下面的例子优先使用同步方式实现。下面的例子是一个WebApi项目。
Authorizaion Filter 同步:继承IResourceFilter
接口,实现OnAuthorizationAsync
方法
异步:继承IAsyncResourceFilter
接口,实现AuthorizationFilterAsync
方法
下面是一个实际的鉴权例子,由我以前一个WebApi项目改造而来
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 using Microsoft.AspNetCore.Authorization;using Microsoft.AspNetCore.Mvc;using Microsoft.AspNetCore.Mvc.Controllers;using Microsoft.AspNetCore.Mvc.Filters;using System;using System.Reflection;using System.Threading.Tasks;namespace FilterTest.Filters { public class AuthorizationFilter : IAuthorizationFilter { public void OnAuthorization (AuthorizationFilterContext context ) { Console.WriteLine($"AuthorizationFilter.OnAuthorization, Request Path: {context.HttpContext.Request.Path} " ); var action = context.ActionDescriptor as ControllerActionDescriptor; var allowAnonymousAttr = action.MethodInfo.GetCustomAttribute<AllowAnonymousAttribute>(); bool isAllowAnonymous = allowAnonymousAttr == null ? false : true ; try { if (isAllowAnonymous == false ) { } } catch (Exception ex) { context.Result = new ObjectResult(new { rtnCode = 500 , msg = "鉴权服务发生错误,请稍后重试或联系管理人员" }); ; } } } } public void ConfigureServices (IServiceCollection services ){ services.AddControllers(config => { config.Filters.Add<AuthorizationFilter>(); }); }
需要注意的是如果在Authorizaion Filter中抛出的异常,那 并不会被Exception Filter所捕获
Resource Filter 同步:继承IResourceFilter
接口,实现OnResourceExecuting
与OnResourceExecuted
方法
异步:继承IAsyncResourceFilter
接口,实现OnResourceExecutionAsync
方法,注意没有OnResourceExecutedAsync
这个方法
下面是简单的页面缓存例子,在OnResourceExecuted
获得缓存,在
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 using Microsoft.AspNetCore.Mvc.Filters;using System;namespace FilterTest.Filters { public class ResourceFilter : IResourceFilter { public void OnResourceExecuting (ResourceExecutingContext context ) { Console.WriteLine($"ResourceFilter.OnResourceExecuting, Request Path: {context.HttpContext.Request.Path} " ); var cacheKey = context.HttpContext.Request.Path; } public void OnResourceExecuted (ResourceExecutedContext context ) { Console.WriteLine($"ResourceFilter.OnResourceExecuted, Request Path: {context.HttpContext.Request.Path} " ); var cacheKey = context.HttpContext.Request.Path; } } } public void ConfigureServices (IServiceCollection services ){ services.AddControllers(config => { config.Filters.Add<ResourceFilter>(); }); }
Exception Filter 同步:继承IExceptionFilter
接口,实现OnException
方法
异步:继承IAsyncExceptionFilter
接口,实现OnExceptionAsync
方法
下面例子catch了所有错误并进行处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 using Microsoft.AspNetCore.Mvc;using Microsoft.AspNetCore.Mvc.Filters;using Microsoft.Extensions.Configuration;using System;namespace FilterTest.Filters { public class ExceptionFilter : IExceptionFilter { private readonly IConfiguration configuration; public ExceptionFilter (IConfiguration configuration ) { this .configuration = configuration; } public void OnException (ExceptionContext context ) { Console.WriteLine($"ExceptionFilter.OnException, Request Path: {context.HttpContext.Request.Path} " ); ObjectResult result; var isShowDevExceptionMsg = "false" ; if (!string .IsNullOrEmpty(isShowDevExceptionMsg) && isShowDevExceptionMsg.ToLower() == "true" ) { result = new ObjectResult(new { rtnCode = 500 , msg = "服务发生错误,请稍后重试或联系管理人员" , devMsg = context.Exception.Message }); } else { result = new ObjectResult(new { rtnCode = 500 , msg = "服务发生错误,请稍后重试或联系管理人员" }); } result.StatusCode = 500 ; context.Result = result; context.ExceptionHandled = true ; } } } public void ConfigureServices (IServiceCollection services ){ services.AddControllers(config => { config.Filters.Add<ExceptionFilter>(); }); } [HttpGet("TestException" ) ] public IActionResult TestException (){ throw null ; }
需要注意Context.ExceptionHandled = true
,设置后 标记异常已被处理,异常不会再抛出,后续的Exception Filter不会再触发 。
Action Filter 同步:继承IActionFilter
接口,实现OnActionExecuting
与OnActionExecuted
方法
异步:继承IAsyncActionFilter
接口,实现OnActionExecutionAsync
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 using Microsoft.AspNetCore.Mvc.Filters;using System;namespace FilterTest.Filters { public class ActionFilter : IActionFilter { public void OnActionExecuting (ActionExecutingContext context ) { } public void OnActionExecuted (ActionExecutedContext context ) { } } } public void ConfigureServices (IServiceCollection services ){ services.AddControllers(config => { config.Filters.Add<ActionFilter>(); }); }
需要注意异步的写法,await next();
会调用Action
或下一个Action Filter
1 2 3 4 5 6 7 8 9 public class ActionFilterAsync : IAsyncActionFilter { public async Task OnActionExecutionAsync (ActionExecutingContext context, ActionExecutionDelegate next ) { await next(); } }
注意,同步于异步是能同时使用的
使用Action Filter的案例:
案例:自动启用事务的ActionFilter
案例:对请求限速的ActionFilter
Result Filter 同步:继承IResultFilter
接口,实现OnResultExecuting
与OnResultExecuted
方法
异步:继承IAsyncResultFilter
接口,实现OnResultExecutionAsync
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 using Microsoft.AspNetCore.Mvc.Filters;using System;namespace FilterTest.Filters { public class ResultFilter : IResultFilter { public void OnResultExecuting (ResultExecutingContext context ) { context.HttpContext.Response.Headers.Add("MoreInfo" , "Just a test" ); } public void OnResultExecuted (ResultExecutedContext context ) { } } } public void ConfigureServices (IServiceCollection services ){ services.AddControllers(config => { config.Filters.Add<ResultFilter>(); }); }
异步,与Action Filter
的异步类似
1 2 3 4 5 6 7 8 9 public class ResultFilterAsync : IAsyncResultFilter { public async Task OnResultExecutionAsync (ResultExecutingContext context, ResultExecutionDelegate next ) { await next(); } }
Filter的注册 Filter有三种注册方式Action
、Controller
、全局
,其作用域由低到高
。
Action注册 只针对特定的 Action,影响最小,适合于对特定 Action 进行特殊处理。
使用[TypeFilter(Type)]
进行注册,如注册上面例子的 ResourceFilter。
1 2 3 4 5 6 [HttpGet ] [TypeFilter(typeof(ResourceFilter)) ] public IActionResult Get (){ return Ok(); }
Controller注册 影响该 Controller 下的所有 Action。
与上面Action注册
的办法一样,通过[TypeFilter(Type)]
进行注册。
1 2 3 4 5 6 7 8 9 10 11 [Route("api/[controller]" ) ] [ApiController ] [TypeFilter(typeof(ResourceFilter)) ] public class FilterTestController : ControllerBase { [HttpGet ] public IActionResult Get () { return Ok(); } }
全局注册 前面的例子使用的就是全局注册方式,该方式会影响到所有的进入Filter Pipeline
的请求。
该注册方式能够很好的进行一些需要影响全局的处理,如:全局的异常处理、全局的日志记录
1 2 3 4 5 6 7 8 9 10 public void ConfigureServices (IServiceCollection services ){ services.AddControllers(config => { config.Filters.Add<ResourceFilter>(); }); }
TypeFilter 和 ServiceFilter 注册方式 前面的Action注册
与Controller注册
使用的都是TypeFilter
的方式注册,实际还可以用ServiceFilter
来注册,两者的异同为:
ServiceFilter和TypeFilter都实现了IFilterFactory,因此都可以使用注册的Filter中构造函数中注入的对象,不需要特殊处理。
ServiceFilter需要对自定义的Filter进行注册,TypeFilter不需要。
ServiceFilter的Filter生命周期源自于您如何注册,而TypeFilter每次都会创建一个新的实例。
使用ServiceFilter
进行Action注册
例子如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 [HttpGet("TestException" ) ] [ServiceFilter(typeof(ExceptionFilter)) ] public IActionResult TestException (){ throw null ; return Ok(); } public void ConfigureServices (IServiceCollection services ){ services.AddTransient<ExceptionFilter>(); }
特性化 编写Filter时多继承Attribute类就能像普通特性一般使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 using Microsoft.AspNetCore.Mvc.Filters;using Microsoft.Extensions.Configuration;using System;namespace FilterTest.Attributes { public class MyActionFilterAttribute : Attribute , IActionFilter { public void OnActionExecuting (ActionExecutingContext context ) { Console.WriteLine($"****MyActionFilterAttribute.OnActionExecuting, Request Path: {context.HttpContext.Request.Path} , Time: {DateTime.Now.ToString("hh:mm:ss ffff" )} " ); } public void OnActionExecuted (ActionExecutedContext context ) { Console.WriteLine($"****MyActionFilterAttribute.OnActionExecuted, Request Path: {context.HttpContext.Request.Path} , Time: {DateTime.Now.ToString("hh:mm:ss ffff" )} " ); } } } [HttpGet ] [MyActionFilter ] public IActionResult Get (){ return Ok(); }
但是对于有构造函数的,且其构造函数的参数是由DI容器注入的,则需要在已编写完成的Filter外再加一层,通过继承TypeFilterAttribute
来实现,而TypeFilterAttribute
实现IFilterFactory
接口,IFilterFactory
公开用于创建IFilterMetadata 实例的CreateInstance 方法,CreateInstance
从服务容器 (DI) 中加载指定的类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 public class MyActionFilterWithDIAttribute : TypeFilterAttribute { public MyActionFilterWithDIAttribute () : base (typeof (ActionFilterWithDI )) { } } public class ActionFilterWithDI : IActionFilter { private readonly IConfiguration configuration; public ActionFilterWithDI (IConfiguration configuration ) { this .configuration = configuration; } public void OnActionExecuting (ActionExecutingContext context ) { Console.WriteLine($"****ActionFilterWithDI.OnActionExecuting, Request Path: {context.HttpContext.Request.Path} , Time: {DateTime.Now.ToString("hh:mm:ss ffff" )} " ); } public void OnActionExecuted (ActionExecutedContext context ) { Console.WriteLine($"****ActionFilterWithDI.OnActionExecuted, Request Path: {context.HttpContext.Request.Path} , Time: {DateTime.Now.ToString("hh:mm:ss ffff" )} " ); } } [HttpGet ] [MyActionFilterWithDI ] public IActionResult Get (){ return Ok(); }
Filter的执行顺序 相同类型的Filter不同注册方式的执行顺序 首先对于同类型Filter同样注册方式的Filter,其执行顺序默认时先加的先执行。
而对于同类型Filter不同注册方式(action、Controller、全局)的Filter,以Aciton Filter为例其默认执行顺序如下:
OnActionExecuting(全局)
OnActionExecuted(全局)
可以看到非常符合AOP的风格。
改变执行顺序 虽然默认的执行顺序如上,但是我们是可以更改其执行顺序的,只要我们创建Filter的时候同时实现IOrderedFilter 接口,就能在注册时附带上排序值,数值越小优先权越高,没设置的默认值为0,默认值相等的按照上面的规则执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class MyActionFilterAttribute : Attribute , IActionFilter , IOrderedFilter { public int Order { get ; set ; } public void OnActionExecuting (ActionExecutingContext context ) { Console.WriteLine($"****MyActionFilterAttribute.OnActionExecuting, Request Path: {context.HttpContext.Request.Path} , Time: {DateTime.Now.ToString("hh:mm:ss ffff" )} " ); } public void OnActionExecuted (ActionExecutedContext context ) { Console.WriteLine($"****MyActionFilterAttribute.OnActionExecuted, Request Path: {context.HttpContext.Request.Path} , Time: {DateTime.Now.ToString("hh:mm:ss ffff" )} " ); } } [HttpGet ] [MyActionFilter(Order = -1) ] public IActionResult Get (){ return Ok(); }
参考 ASP.NET Core Filters
Asp.Net Core Filter 深入浅出的那些事-AOP
ASP.NET Core 2 学习笔记(十四)Filters
[^AOP]:面向切面编程(AOP是Aspect Oriented Program的首字母缩写),在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。