(注意 如果在單個控制器添加特性 需要注入 單獨 [GlobalExceptionFilter] 不行 需要加上TypeFilter(typeof(GlobalExceptionFilter)))
為什么filter可以依賴注入 因為繼承了IFilterFactory
Filter(過濾器)
總共有五種,Authorization Filter,Resource Filter,Exception Filter,Action Filter,Result Filter
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.cs 注入過濾器
Action Filter
新增全局過濾器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"); //執行方法前先執行這 } }
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;
//
}
}
Resource Filter
資源過濾器
可以通過Resource Filter 進行資源緩存
、防盜鏈
等操作。
使用Resource Filter 要求實現IResourceFilter 抽象接口
public class ResourceFilter : Attribute,IResourceFilter { public void OnResourceExecuted(ResourceExecutedContext context) { // 執行完后的操作 } public void OnResourceExecuting(ResourceExecutingContext context) { // 執行中的過濾器管道 } }
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) { // 在結果執行之前調用的一系列操作 } }
完畢 可以在全局注入
中間件
中間件是一種裝配到應用管道以處理請求和響應的軟件。 每個組件:
- 選擇是否將請求傳遞到管道中的下一個組件。
- 可在管道中的下一個組件前后執行工作。
請求委托用於生成請求管道。 請求委托處理每個 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是Aspect Oriented Program的首字母縮寫) ,我們知道,面向對象的特點是繼承、多態和封裝。而封裝就要求將功能分散到不同的對象中去,這在軟件設計中往往稱為職責分配。實際上也就是說,讓不同的類設計不同的方法。這樣代碼就分散到一個個的類中去了。這樣做的好處是降低了代碼的復雜程度,使類可重用。
但是人們也發現,在分散代碼的同時,也增加了代碼的重復性。什么意思呢?比如說,我們在兩個類中,可能都需要在每個方法中做日志。按面向對象的設計方法,我們就必須在兩個類的方法中都加入日志的內容。也許他們是完全相同的,但就是因為面向對象的設計讓類與類之間無法聯系,而不能將這些重復的代碼統一起來。
也許有人會說,那好辦啊,我們可以將這段代碼寫在一個獨立的類獨立的方法里,然后再在這兩個類中調用。但是,這樣一來,這兩個類跟我們上面提到的獨立的類就有耦合了,它的改變會影響這兩個類。那么,有沒有什么辦法,能讓我們在需要的時候,隨意地加入代碼呢?這種在運行時,動態地將代碼切入到類的指定方法、指定位置上的編程思想就是面向切面的編程。
一般而言,我們管切入到指定類指定方法的代碼片段稱為切面,而切入到哪些類、哪些方法則叫切入點。有了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(攔截器)
1,攔截器的概念
java里的攔截器是動態攔截Action調用的對象,它提供了一種機制可以使開發者在一個Action執行的前后執行一段代碼,也可以在一個Action
執行前阻止其執行,同時也提供了一種可以提取Action中可重用部分代碼的方式。在AOP中,攔截器用於在某個方法或者字段被訪問之前,進行攔截
然后再之前或者之后加入某些操作。目前,我們需要掌握的主要是Spring的攔截器,Struts2的攔截器不用深究,知道即可。
2,攔截器的原理
大部分時候,攔截器方法都是通過代理的方式來調用的。Struts2的攔截器實現相對簡單。當請求到達Struts2的ServletDispatcher時,Struts2
會查找配置文件,並根據配置實例化相對的攔截器對象,然后串成一個列表(List),最后一個一個的調用列表中的攔截器。Struts2的攔截器是可
插拔的,攔截器是AOP的一個實現。Struts2攔截器棧就是將攔截器按一定的順序連接成一條鏈。在訪問被攔截的方法或者字段時,Struts2攔截器鏈
中的攔截器就會按照之前定義的順序進行調用。
3,自定義攔截器的步驟
第一步:自定義一個實現了Interceptor接口的類,或者繼承抽象類AbstractInterceptor。
第二步:在配置文件中注冊定義的攔截器。
第三步:在需要使用Action中引用上述定義的攔截器,為了方便也可以將攔截器定義為默認的攔截器,這樣在不加特殊說明的情況下,所有的
Action都被這個攔截器攔截。
4,過濾器與攔截器的區別
過濾器可以簡單的理解為“取你所想取”,過濾器關注的是web請求;攔截器可以簡單的理解為“拒你所想拒”,攔截器關注的是方法調用,比如攔截
敏感詞匯。
4.1,攔截器是基於java反射機制來實現的,而過濾器是基於函數回調來實現的。(有人說,攔截器是基於動態代理來實現的)
4.2,攔截器不依賴servlet容器,過濾器依賴於servlet容器。
4.3,攔截器只對Action起作用,過濾器可以對所有請求起作用。
4.4,攔截器可以訪問Action上下文和值棧中的對象,過濾器不能。
4.5,在Action的生命周期中,攔截器可以多次調用,而過濾器只能在容器初始化時調用一次。
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()); } } }
在Startup.cs 新增以下代碼
針對某個類或者某個方法做攔截時
首先新建一個攔截器 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的應用
引入三個包,通過Nuget安裝,Autofac開頭,如下
注: 其中Autofac.Extras.DynamicProxy就是AOP相關組件,其中包含了Castle.Core,所以不用單獨安裝Castle.Core.
總結:
AOP在做一些業務前置或后置處理上時很有用的,使用比較靈活,無需修改原有代碼邏輯,比起修改原有代碼維護相對好多啦!!!