.NET 过滤器、中间件、AOP、拦截器及其应用
一、过滤器(Filter)
一共5种、 Authorization Filter,Resource Filter,Exception Filter,Action Filter,Result Filter
1.1、Exception Filter
新增全局异常过滤器GlobalExceptionFilter.cs。
当出现异常时进入此方法,可在这针对不同的异常做相关处理并返回指定数据,避免直接把错误暴露给用户。
public class GlobalExceptionFilter : IExceptionFilter { public void OnException(ExceptionContext context) { Exception ex = context.Exception; string errMsg = "GlobalExceptionFilter-OnException:" + ex.Message; if (context.Exception.GetType() == typeof(ExecuteException)) { //针对不同的自定义异常,做不同处理 MsgModel<string> msgModel = new MsgModel<string>() { Status = false, Msg = errMsg, Errcode = "AA001" }; context.Result = new JsonResult(msgModel); context.ExceptionHandled = true; } else { context.Result = new JsonResult(errMsg); context.ExceptionHandled = true; } LogHelper.Error(errMsg); } }
然后在Startup注册:
1.2、Action Filte
新增全局过滤器GlobalActionFilter.cs
在方法执行前后,会跳转至以下两个方法,方便追踪接口执行情况
过去返回的参数:
var result = context.Result; ObjectResult objectResult= result as ObjectResult; var resultLog = $"{DateTime.Now} 调用 {context.RouteData.Values["action"]} api 完成;执行结果:{Newtonsoft.Json.JsonConvert.SerializeObject(objectResult.Value)}";
获取请求的参数:
var actionLog = $"{DateTime.Now} 开始调用 {context.RouteData.Values["action"]} api;参数为:{Newtonsoft.Json.JsonConvert.SerializeObject(context.ActionArguments)}";
public class GlobalActionFilter : IActionFilter { public void OnActionExecuted(ActionExecutedContext context) { //LogHelper.Info("OnActionExecuted"); //执行方法后执行这 } public void OnActionExecuting(ActionExecutingContext context) { //LogHelper.Info("OnActionExecuting"); //执行方法前先执行这 } }
1.3、Authonization Filter
权限控制过滤器
通过 Authonization Filter 可以实现复杂的权限角色认证
、登陆授权
等操作
/// <summary> /// 实现自定义授权 /// </summary> public class AuthorizeFilter : IAuthorizationFilter { /// <summary> /// 请求验证,当前验证部分不要抛出异常,ExceptionFilter不会处理 /// </summary> /// <param name="context"></param> public void OnAuthorization(AuthorizationFilterContext context) { //这里可以做复杂的权限控制操作 //if (context.HttpContext.User.Identity.Name != "1") //简单的做一个示范 //{ // //未通过验证则跳转到无权限提示页 // RedirectToActionResult content = new RedirectToActionResult("NoAuth", "Exception", null); // context.Result = content; // } }
1.4、Resource Filter
资源过滤器
可以通过Resource Filter 进行资源缓存
、防盗链
等操作
使用Resource Filter 要求实现IResourceFilter 抽象接口
public class ResourceFilter : Attribute,IResourceFilter { public void OnResourceExecuted(ResourceExecutedContext context) { // 执行完后的操作 } public void OnResourceExecuting(ResourceExecutingContext context) { // 执行中的过滤器管道 } }
1.5、Result Filter
结果过滤器,可以对结果进行格式化、大小写转换等一系列操作。
使用Result Filter 需要实现IResultFilter 抽象接口,接口要求实现 OnResultExecuting
方法 和OnResultExecuted
方法
OnResultExecuting
方法:Called before the action result executes. 在操作结果执行之前调用
OnResultExecuted
方法:Called after the action result executes. 在操作结果执行之后调用
public class ResultFilter : Attribute, IResultFilter { public void OnResultExecuted(ResultExecutedContext context) { // 在结果执行之后调用的操作... } public void OnResultExecuting(ResultExecutingContext context) { // 在结果执行之前调用的一系列操作 } }
完毕 可以在全局注入
二、中间件
中间件是一种装配到应用管道以处理请求和响应的软件。 每个组件:
1、选择是否将请求传递到管道中的下一个组件;
2、可在管道中的下一个组件前后执行工作。
请求委托用于生成请求管道。 请求委托处理每个 HTTP 请求。
/// <summary> /// 中间件 /// 记录请求和响应数据 /// </summary> public class RequestMiddleware { private readonly RequestDelegate _next; /// <summary> /// 日志接口 /// </summary> private static Logger logger = LogManager.GetCurrentClassLogger(); private Stopwatch _stopwatch; public RequestMiddleware(RequestDelegate next) { _stopwatch = new Stopwatch(); _next = next; } public async Task InvokeAsync(HttpContext context) { // 过滤,只有接口 if (context.Request.Path.Value.ToLower().Contains("api")) { context.Request.EnableBuffering(); Stream originalBody = context.Response.Body; _stopwatch.Restart(); // 获取 Api 请求内容 var requestContent = await GetRequesContent(context); // 获取 Api 返回内容 using (var ms = new MemoryStream()) { context.Response.Body = ms; await _next(context); ms.Position = 0; await ms.CopyToAsync(originalBody); } context.Response.Body = originalBody; _stopwatch.Stop(); var eventInfo = new LogEventInfo(); eventInfo.Message = "Success"; eventInfo.Properties["Elapsed"] = _stopwatch.ElapsedMilliseconds; eventInfo.Properties["RequestBody"] = requestContent; logger.Trace(eventInfo); } else { await _next(context); } } private async Task<string> GetRequesContent(HttpContext context) { var request = context.Request; var sr = new StreamReader(request.Body); var content = $"{await sr.ReadToEndAsync()}"; if (!string.IsNullOrEmpty(content)) { request.Body.Position = 0; } return content; } }
然后在Startup注册
// 请求日志监控 app.UseMiddleware<RequestMiddleware>();
三、AOP
一般而言,我们管切入到指定类指定方法的代码片段称为切面,而切入到哪些类、哪些方法则叫切入点。有了AOP,我们就可以把几个类共有的代码,抽取到一个切片中,等到需要时再切入对象中去,从而改变其原有的行为。这样看来,AOP其实只是OOP的补充而已。OOP从横向上区分出一个个的类来,而AOP则从纵向上向对象中加入特定的代码。有了AOP,OOP变得立体了。如果加上时间维度,AOP使OOP由原来的二维变为三维了,由平面变成立体了。从技术上来说,AOP基本上是通过代理机制实现的。AOP在编程历史上可以说是里程碑式的,对OOP编程是一种十分有益的补充。
AOP是是OOP(面向对象)的补充和完善
拦截器是在面向切面编程中应用的,就是在你的service或者一个方法前调用一个方法,或者在方法后调用一个方法。
AOP说白了就是在运行时,动态的将代码切入到类的指定方法的指定位置上,这种思想就是面向切面的编程思想。
就是 在不修改源代码的基础上 添加新业务,比如 日志 性能检测。
从思想上来说,Filter(过滤器)跟AOP(拦截器)极其接近。
但是区别明显:
1、Filter只能拦截request的请求;
2、Spring中的AOP,一般而言,是在Service层,拦截Bean(实例)的访问。
例如使用@Transcational 来拦截dao的应用,让它实现事务的管理。从思想上来说,Filter跟AOP极其接近
1、拦截器是基于java的反射机制,过滤器是基于java的函数回调
2、拦截器不依赖于servlet容器,而过滤器依赖于servlet容器
3、拦截器只能对action请求起作用,过滤器几乎对所有的请求起作用
4、拦截器可以访问action上下文,值栈里的对象,而过滤器不能访问
5、在action生命周期中,拦截器可以被多次调用,过滤器只能在servlet初始化时调用一次
6、拦截器可以获取IOC容器中的各个bean,过滤器不行,在拦截器中注入一个service可以调用逻辑业务
Aop 其实就是动态代理
四、拦截器(Interceptor)
4.1、概念
java里的拦截器是动态拦截Action调用的对象,它提供了一种机制可以使开发者在一个Action执行的前后执行一段代码,也可以在一个Action
执行前阻止其执行,同时也提供了一种可以提取Action中可重用部分代码的方式。在AOP中,拦截器用于在某个方法或者字段被访问之前,进行拦截
然后再之前或者之后加入某些操作。目前,我们需要掌握的主要是Spring的拦截器,Struts2的拦截器不用深究,知道即可。
4.2、原理
大部分时候,拦截器方法都是通过代理的方式来调用的。Struts2的拦截器实现相对简单。当请求到达Struts2的ServletDispatcher时,Struts2
会查找配置文件,并根据配置实例化相对的拦截器对象,然后串成一个列表(List),最后一个一个的调用列表中的拦截器。Struts2的拦截器是可
插拔的,拦截器是AOP的一个实现。Struts2拦截器栈就是将拦截器按一定的顺序连接成一条链。在访问被拦截的方法或者字段时,Struts2拦截器链
中的拦截器就会按照之前定义的顺序进行调用。
4.3、自定义拦截器的步骤
第一步:自定义一个实现了Interceptor接口的类,或者继承抽象类AbstractInterceptor。
第二步:在配置文件中注册定义的拦截器。
第三步:在需要使用Action中引用上述定义的拦截器,为了方便也可以将拦截器定义为默认的拦截器,这样在不加特殊说明的情况下,所有的Action都被这个拦截器拦截。
4.4、过滤器与拦截器的区别
过滤器可以简单的理解为“取你所想取”,过滤器关注的是web请求;拦截器可以简单的理解为“拒你所想拒”,拦截器关注的是方法调用,比如拦截敏感词汇。
1,拦截器是基于java反射机制来实现的,而过滤器是基于函数回调来实现的。(有人说,拦截器是基于动态代理来实现的)
2,拦截器不依赖servlet容器,过滤器依赖于servlet容器。
3,拦截器只对Action起作用,过滤器可以对所有请求起作用。
4,拦截器可以访问Action上下文和值栈中的对象,过滤器不能。
5,在Action的生命周期中,拦截器可以多次调用,而过滤器只能在容器初始化时调用一次。
4.5、例子
LogInterceptor
安装Castle.Core,Autofac.Extras.DynamicProxy
新建LogInterceptor.cs ,继承IInterceptor
public class LogInterceptor : IInterceptor { public void Intercept(IInvocation invocation) { try { invocation.Proceed(); Dapper.Logger.LogHelper.logger.Info(invocation.Method.Name); } catch (Exception ex) { Dapper.Logger.LogHelper.logger.Error(invocation.Method.Name + " " + ex.ToString()); } } }
在Starup中,新增以下代码:
4.6、针对某个类或者某个方法做拦截时
新建一个拦截器 MyInterceptor
public class MyInterceptor : IInterceptor { public void Intercept(IInvocation invocation) { try { invocation.Proceed(); NLogHelper.logger.Info(invocation.Method.Name); } catch (Exception ex) { NLogHelper.logger.Error(invocation.Method.Name + " " + ex.ToString()); } } }
然后Startup.cs 中ConfigureContainer代码如下
把LogInterceptor 代码注释,但是要保留接口拦截EnableInterfaceInterceptors() ,注入MyInterceptor
public void ConfigureContainer(ContainerBuilder builder) { //builder.RegisterType<LogInterceptor>(); builder.RegisterType<MyInterceptor>(); builder.RegisterType<DbFactory>().As<IDbFactory>(); //业务逻辑层所在程序集命名空间 Assembly service = Assembly.Load("Summer.Service"); //注:webapi要引用接口和类,不然这里读不到 //接口层所在程序集命名空间 Assembly repository = Assembly.Load("Summer.IService"); //自动注入 builder.RegisterAssemblyTypes(service, repository) .Where(t => t.Name.EndsWith("Service")) .AsImplementedInterfaces() .InstancePerLifetimeScope() .EnableInterfaceInterceptors() //开启接口拦截 //.InterceptedBy(typeof(LogInterceptor)) //设置全局拦截器,统一由LogInterceptor拦截所有接口的调用 ; }
然后在需要拦截的接口中添加以下代码
拦截器设置完毕,当调用ITestService 的全部方法都会跳转拦截器
以下代码是一个示例,利用反射查看权限
public class AuthInterceptor : IInterceptor { public void Intercept(IInvocation invocation) { Type tt = invocation.InvocationTarget.GetType(); DoMain.Model.SYS.SYS_User userinfo = null; var p = tt.GetProperties().FirstOrDefault(q => q.Name == "UserInfo"); userinfo = (DoMain.Model.SYS.SYS_User)p.GetValue(invocation.InvocationTarget); var authkey = invocation.TargetType.ToString() + "." + invocation.Method.Name; if (AuthChecker.isAuth(userinfo, authkey)) { invocation.Proceed(); } else { throw new Core.AuthException("没有操作权限"); } //方法执行后的操作 }
Filter和 LogInterceptor 可以同时共存,执行顺序是:
ActionFilter 的OnActionExecuting =》LogInterceptor 的Intercept =》ActionFilter 的OnActionExecuted
如果接口有异常,不会跳转LogInterceptor ,而是进入ExceptionFilter,顺序是:
ActionFilter 的OnActionExecuting =》ActionFilter 的OnActionExecuted =》ExceptionFilter 的OnException
五、AOP的应用
5.1、引用三个包,通过Nuget安装,Autofac开头,如下:
注: 其中Autofac.Extras.DynamicProxy就是AOP相关组件,其中包含了Castle.Core,所以不用单独安装Castle.Core
5.2、模拟编写用户维护相关逻辑,代码如下:
接口:
实现:
5.3、编写拦截器逻辑,即代理:
5.4、集成Autofac将用户维护服务这块进行注册到容器中
首先在Startup中增加方法,如下:
5.5、然后在program中添加Autofac的工厂,如下:
5.6、增加UserController方法,如下:
5.7、运行测试,为了方便看见控制台打印,用项目启动方式进行运行,结果如下:
直接在浏览器中输入http://localhost:5000/api/User/AddUser?name=sss&age=12,然后回车,然后看控制台打印:
5.8、总结
AOP在做一些业务前置或后置处理上时很有用的,使用比较灵活,无需修改原有代码逻辑,比起修改原有代码维护相对好多啦。