关于Aop的介绍
面向切面编程,是指在不破坏原有业务逻辑的基础上对业务实现横向扩展,以便达到一个对原有的代码无侵入的目的。提及AOP,我们不得不提到过滤器Filter,它是实现Aop的一种技术手段。
关于Filter的介绍
1、什么是Filter
FIlter是MVC流程中可针对MVC逻辑进行横向扩展的一种过滤机制,能够在请求中按照开发者实际的业务对原有流程进行拦截加工和过滤,涉及到的Filter有如下几种:
- AuthorizeFilter
- ExceptionFilter
- ActionFilter
- ResultFilter
- ResourceFilter(.netcore中新增)
采用官方的两张图来展示Filter在处理请求中位置和Filter Pipeline中各个Filter的执行顺序:
2、Filter的注册方式
我们怎么把Filter加入到我们的流程中去呢?在开发过程中我们使用的Filter的注册方式有如下几种:
- 全局注册
- Controller注册
- Action注册
下面会介绍一下这几种注册方式,我们挑选ExceptionFilter作为示例。
首先我们新建一个.net core webapi的项目,在项目中我们添加一个Action并人为抛出一个异常,代码如下:
[Route("fun1")] public IActionResult Fun1() { int a = 0, b = 13; return Content((b / a).ToString()); }
运行程序,意料之中的会报异常
常规做法是我们在程序中使用try...catch方式进行异常捕捉处理,但是如果涉及的地方很多,或者对于异常的处理逻辑都是一样的,一个个去改显得很繁琐而且不能实现代码的复用,并且在原有的代码上进行修改也存在风险,
通过ExcetionFilter便能实现一个自定义异常处理器。
首先自定义一个过滤器继承自ExcetionFilter,然后在Action上使用它,直接上代码:
1 public class CustomExceptionFilterAttribute:ExceptionFilterAttribute 2 { 3 /// <summary> 4 /// 当程序发生异常时,会进入到此方法 5 /// </summary> 6 /// <param name="context"></param> 7 public override void OnException(ExceptionContext context) 8 { 9 if(!context.ExceptionHandled) 10 { 11 context.Result = new JsonResult(new 12 { 13 Result = false, 14 Msg = "a error occured,please contract Sys Manager!" 15 }); 16 17 context.ExceptionHandled = true; 18 } 19 } 20 } 21 22 [CustomExceptionFilter] 23 [Route("fun1")] 24 public IActionResult Fun1() 25 { 26 int a = 0, b = 13; 27 return Content((b / a).ToString()); 28 }
运行程序:
将特性标注在Action是对Action进行注册,并且只能对添加的Action生效,如果需要对Controller中的所有Action都注册,那我们可以通过控制器注册的方式将[CustomExceptionFilter]标注在Controller上便可实现对某个控制器生效,而如果需要对所有控制器生效,
那么我们则需要使用全局注册的方式进行实现。在startup中注册MVC服务时设置相应的配置参数便可实现全局注册。
// This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddControllers(configure=> { configure.Filters.Add(typeof(CustomExceptionFilterAttribute)); }); }
3、特性注册的三种方式
对于在Controller和Action上使用特性进行注册时,下面这种情况是我们可能碰见,也是我们需要去考虑的。比如我们需要在自定义的过滤器中输出日志,使用ILogger在构造函数进行注入
1 public class CustomExceptionFilterWithLogAttribute : ExceptionFilterAttribute 2 { 3 private ILogger<CustomExceptionFilterWithLogAttribute> _logger; 4 public CustomExceptionFilterWithLogAttribute(ILogger<CustomExceptionFilterWithLogAttribute> logger) 5 { 6 _logger = logger; 7 } 8 9 /// <summary> 10 /// 当程序发生异常时,会进入到此方法 11 /// </summary> 12 /// <param name="context"></param> 13 public override void OnException(ExceptionContext context) 14 { 15 if (!context.ExceptionHandled) 16 { 17 _logger.LogError("occur a error"); 18 context.Result = new JsonResult(new 19 { 20 Result = false, 21 Msg = "a error occured,please contract Sys Manager!" 22 }); 23 24 context.ExceptionHandled = true; 25 } 26 } 27 }
此时我们使用特性注册过滤器则无法实现
有三种方式可以实现:
ServiceFilter
TypeFilter
IFilterFactory
对于前两种我们可以使用已有的特性进行标注
[ServiceFilter(typeof(CustomExceptionFilterWithLogAttribute))]
[TypeFilter(typeof(CustomExceptionFilterWithLogAttribute))]
区别在于使用ServiceFilter时,需要在startup中进行IOC注册:services.AddSingleton(typeof(CustomExceptionFilterWithLogAttribute)),而TypeFilter不需要。下面我们来看看使用IFilterFactory。
public class CustomExceptionFilterFactoryAttribute : Attribute, IFilterFactory { private readonly Type _type; public CustomExceptionFilterFactoryAttribute(Type type) { _type = type; } public bool IsReusable => true; public IFilterMetadata CreateInstance(IServiceProvider serviceProvider) { return (IFilterMetadata)serviceProvider.GetService(_type); } }
然后使用[CustomExceptionFilterFactory(typeof(CustomExceptionFilterWithLogAttribute))]进行标注同时注册一下IOC,会发现可以实现和ServiceFilter一样的效果,其实翻看ServiceFilter源码会发现其实现与我们上面自定义的特性基本一样
4、其他的几种过滤器介绍
AuthorizeFilter
AuthorizeFilter是最先触发的,用于判断是否授权,未授权则终止流程,但是一般我们不通过自定义AuthorizeFilter来实现授权校验,因为自定义则需要自定义授权框架,所以一般授权采用系统内置的授权机制。
ResourceFilter
授权通过后,则会进入该过滤器,可用来做缓存使请求进行短路,从而避免执行Action。我们定义一个ResourceFilter来实现缓存来进行说明
/// <summary> /// 请求过的路由则会进行缓存 /// </summary> public class CustomResourceFilterAttribute : Attribute, IResourceFilter { private Dictionary<string,IActionResult> _routeDatas = new Dictionary<string, IActionResult>(); public void OnResourceExecuted(ResourceExecutedContext context) { var routeData = context.RouteData; _routeDatas.Add($"{routeData.Values["controller"]}/{routeData.Values["action"]}", context.Result); } public void OnResourceExecuting(ResourceExecutingContext context) { var routeData = context.RouteData; string key = $"{routeData.Values["controller"]}/{routeData.Values["action"]}"; if (this._routeDatas.Keys.Contains(key)) { context.Result = this._routeDatas[key]; } } } [ApiController] [Route("[controller]")] [CustomResourceFilter] public class WeatherForecastController : ControllerBase { [Route("fun4")] public IActionResult Fun4() { return Content($"This is Fun4 on{DateTime.Now.ToString("yyyyMMdd HH:mm:ss.fff")}"); } }
多次执行会发现页面显示的时间始终未变,这也代表,第一次请求之后,后续的请求始终未进入此Action
ActionFilter
此Filter可以在Action执行前后进行自定义逻辑处理
public class CustomActionFilterAttribute : Attribute, IActionFilter { /// <summary> /// action执行后触发 /// </summary> /// <param name="context"></param> public void OnActionExecuted(ActionExecutedContext context) { Console.WriteLine("OnActionExecuted"); } /// <summary> /// action执行前触发 /// </summary> /// <param name="context"></param> public void OnActionExecuting(ActionExecutingContext context) { Console.WriteLine("OnActionExecuting"); } }
ResultFilter
在操作方法成功之后执行,用于执行操作结果前后调用对应事件
public class CustomResultFilterAttribute : Attribute, IResultFilter { public void OnResultExecuted(ResultExecutedContext context) { Console.WriteLine("OnResultExecuted"); } public void OnResultExecuting(ResultExecutingContext context) { Console.WriteLine("OnResultExecuting"); } }
5、局限性
因为Filter属于MVC流程,所以只有等到请求到达MVC时才可以进行干涉,在MVC流程之外的是无法进行干涉的。