MVC中AOP思想的體現(四種過濾器)並結合專案案例說明過濾器的實際用法
第四節:MVC中AOP思想的體現(四種過濾器)並結合專案案例說明過濾器的實際用法
一. 簡介
MVC中的過濾器可以說是MVC框架中的一種靈魂所在,它是MVC框架中AOP思想的具體體現,所以它以面向切面的形式無侵入式的作用於程式碼的業務邏輯,與業務邏輯程式碼分離,一經推出,廣受開發者的喜愛。
那麼過濾器到底是什麼呢?它又有什麼作用呢?
使用者通過URL訪問Web系統不一定都能得到相應的內容,一方面不同的使用者許可權不同,另一方面是為了保護系統,防止被攻擊,這就是過濾器的核心所在,我們總計一下過濾器都有哪些作用:
①:判斷使用者是否登入以及不同使用者對應不同的許可權問題。
②:防盜鏈、防爬蟲。
③:系統中語言版本的切換(本地化和國際化)。
④:許可權管理系統中動態Action。
⑤:決策輸出快取。
知道到了過濾器的作用,那麼過濾器分哪幾類呢?如下圖1:
二. 執行順序
從上圖①可知,過濾器分四類,總共重寫了六個方法,在這六個方法裡可以處理相應的業務邏輯,那麼如果四種過濾器的六個重寫方法同時存在,它們的執行順序是什麼呢?
首先要將OnException方法除外,該方法不和其餘五個方法參與排序問題,該方法獨立存在,什麼時間報錯,什麼時候呼叫。
其餘三種過濾器中的五個重寫方法的執行順序:
三. 自定義實現形式
1. 直接在控制器中重寫方法或者利用控制器間的繼承
新建任何一個控制器,它均繼承Controller類,F12進入Controller類中,發現Controller類中已經實現了過濾器需要實現的介面,並且提供虛方法供我們重寫,程式碼如下:
基於以上原理,這樣在控制器級別上我們就有兩種思路來實現過濾器。
方案一:直接在當前控制器重寫相應的過濾器方法,則該過濾器的方法作用於當前控制器的所有Action。
方案二:新建一個父類控制器,在父類控制器中重寫過濾器的方法,然後子類控制器繼承該父類控制器,則該該過濾器作用於子類控制器中的所有Action。
【該方法和接下來以特性的形式作用於控制器的效果是一致的】
1 ///<summary> 2 /// 控制器繼承該控制器,和特性作用在控制器上效果一致 3 /// </summary> 4 public class MyBaseFilterController : Controller 5 { 6 //需要用protected型別,不能用public型別 7 protected override void OnAuthorization(AuthorizationContext filterContext) 8 { 9 //1.如果保留如下程式碼,則會執行.net framework定義好的身份驗證,如果希望自定義身份驗證,則刪除如下程式碼 10 // base.OnAuthorization(filterContext); 11 12 //2.獲取區域名字 13 // string strAreaName = filterContext.RouteData.DataTokens["area"].ToString().ToLower(); 14 15 //3.獲取控制器作用的Controller和action的名字 16 string controllerName = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName.ToLower(); 17 string actionName = filterContext.ActionDescriptor.ActionName.ToLower(); 18 filterContext.HttpContext.Response.Write("身份驗證過濾器作用於" + controllerName + "控制器下的" + actionName + "方法</br>"); 19 } 20 }
2. 自定義類繼承MVC中過濾器實現類或過濾器介面,特性的形式作用於控制器或Action
特別補充:MVC框架中的AuthorizeAttirbute、ActionFilterAttribute、HandleErrorAttribute類已經實現了過濾器對應的介面,所以我們在自定義過濾器的時候,可以直接繼承以上三個類;或者實現相應的介面:IAuthorizationFilter、IActionFilter、IResultFilter、IExceptionFilter。(該方案在實現相應介面的同時,需要繼承FilterAttribute,使自定義的類成為一個特性)。
下面以繼承MVC中實現類的形式來自定義四種過濾器:
A:身份驗證過濾器
1 /// <summary> 2 /// 身份驗證過濾器 3 /// 1. 在非MVC框架專案中使用MVC過濾器,需要通過nuget把MVC的程式集新增進去 4 /// 2. 繼承AuthorizeAttribute類,然後對OnAuthorization方法進行 override 覆寫 5 /// 3. 在Action執行之前首先執行該過濾器 6 /// </summary> 7 public class MyAuthorize : AuthorizeAttribute 8 { 9 public override void OnAuthorization(AuthorizationContext filterContext) 10 { 11 //1.如果保留如下程式碼,則會執行.net framework定義好的身份驗證,如果希望自定義身份驗證,則刪除如下程式碼 12 // base.OnAuthorization(filterContext); 13 14 //2.獲取區域名字 15 // string strAreaName = filterContext.RouteData.DataTokens["area"].ToString().ToLower(); 16 17 //3.獲取控制器作用的Controller和action的名字 18 string controllerName = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName.ToLower(); 19 string actionName = filterContext.ActionDescriptor.ActionName.ToLower(); 20 filterContext.HttpContext.Response.Write("身份驗證過濾器作用於" + controllerName + "控制器下的" + actionName + "方法</br>"); 21 } 22 }View Code
B: 行為過濾器
1 /// <summary> 2 /// 行為過濾器 3 /// 1. 在非MVC框架專案中使用MVC過濾器,需要通過nuget把MVC的程式集新增進去 4 /// 2. 繼承ActionFilterAttribute類,然後對OnActionExecuting方法和OnActionExecuted方法進行 override 覆寫 5 /// 3. OnActionExecuting方法:在action方法執行之前,且OnAuthorization過濾器執行之後呼叫 6 /// OnActionExecuted方法:在action方法執行之後呼叫 7 /// </summary> 8 public class MyAction: ActionFilterAttribute 9 { 10 11 /// <summary> 12 /// 在action方法執行之前呼叫 13 /// </summary> 14 /// <param name="filterContext"></param> 15 public override void OnActionExecuting(ActionExecutingContext filterContext) 16 { 17 //1.如果保留如下程式碼,則會執行.net framework定義好的行為驗證,如果希望自定義行為驗證,則刪除如下程式碼 18 // base.OnActionExecuting(filterContext); 19 20 //2.獲取區域名字 21 // string strAreaName = filterContext.RouteData.DataTokens["area"].ToString().ToLower(); 22 23 //3.獲取控制器作用的Controller和action的名字 24 string controllerName = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName.ToLower(); 25 string actionName = filterContext.ActionDescriptor.ActionName.ToLower(); 26 filterContext.HttpContext.Response.Write("行為過濾器OnActionExecuting作用於" + controllerName + "控制器下的" + actionName + "方法執行之前</br>"); 27 } 28 /// <summary> 29 /// 在action方法執行之後呼叫 30 /// </summary> 31 /// <param name="filterContext"></param> 32 public override void OnActionExecuted(ActionExecutedContext filterContext) 33 { 34 //1.如果保留如下程式碼,則會執行.net framework定義好的行為驗證,如果希望自定義行為驗證,則刪除如下程式碼 35 // base.OnActionExecuted(filterContext); 36 37 //2.獲取區域名字 38 // string strAreaName = filterContext.RouteData.DataTokens["area"].ToString().ToLower(); 39 40 //3.獲取控制器作用的Controller和action的名字 41 string controllerName = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName.ToLower(); 42 string actionName = filterContext.ActionDescriptor.ActionName.ToLower(); 43 filterContext.HttpContext.Response.Write("行為過濾器OnActionExecuted作用於" + controllerName + "控制器下的" + actionName + "方法執行之後</br>"); 44 } 45 }View Code
C:結果過濾器
1 /// <summary> 2 /// 結果過濾器 3 /// 1. 在非MVC框架專案中使用MVC過濾器,需要通過nuget把MVC的程式集新增進去 4 /// 2. 繼承ActionFilterAttribute類,然後對OnResultExecuting方法和OnResultExecuted方法進行 override 覆寫 5 /// 3. OnResultExecuting方法:在執行結果之後(action之後),頁面渲染之前呼叫 6 /// OnResultExecuted方法:在頁面渲染之後呼叫 7 /// </summary> 8 public class MyResult : ActionFilterAttribute 9 { 10 11 /// <summary> 12 /// action執行之後(OnActionExecuting之後),頁面渲染之前呼叫 13 /// </summary> 14 /// <param name="filterContext"></param> 15 public override void OnResultExecuting(ResultExecutingContext filterContext) 16 { 17 //1.如果保留如下程式碼,則會執行.net framework定義好的結果驗證,如果希望自定義結果驗證,則刪除如下程式碼 18 // base.OnResultExecuting(filterContext); 19 20 //該方法中無法獲取是哪個控制器後 21 filterContext.HttpContext.Response.Write("結果過濾器OnResultExecuting作用於action執行之後,頁面載入之前"); 22 } 23 /// <summary> 24 /// 頁面渲染之後呼叫 25 /// </summary> 26 /// <param name="filterContext"></param> 27 public override void OnResultExecuted(ResultExecutedContext filterContext) 28 { 29 //1.如果保留如下程式碼,則會執行.net framework定義好的結果驗證,如果希望自定義結果驗證,則刪除如下程式碼 30 // base.OnResultExecuted(filterContext); 31 32 //該方法中無法獲取是哪個控制器後 33 filterContext.HttpContext.Response.Write("結果過濾器OnResultExecuted作用於頁面渲染之後"); 34 } 35 }View Code
D:異常過濾器
使用自定義異常處理,需要在web.config中為system.web新增<customErrors mode="On" />節點
1 /// <summary> 2 /// 異常過濾器 3 /// 需要注意的點: 4 /// ①:如果自定義異常過濾器且需要有作用於全域性,需要把FilterConfig中的 filters.Add(new HandleErrorAttribute());註釋掉, 5 /// 然後把自定義的異常過濾器新增到FilterConfig中。 6 /// ②:使用自定義異常處理,需要在web.config中為system.web新增<customErrors mode="On" />節點 7 /// </summary> 8 public class MyException: HandleErrorAttribute 9 { 10 public override void OnException(ExceptionContext filterContext) 11 { 12 //呼叫框架本身異常處理器的方法 13 base.OnException(filterContext); 14 15 //獲取異常資訊(可以根據實際需要寫到本地或資料庫中) 16 var errorMsg = filterContext.Exception; 17 18 //跳轉指定的錯誤頁面 19 filterContext.Result = new RedirectResult("/error.html"); 20 } 21 }View Code
下面展示以特性的形式作用於控制器或控制器中的Action:
3. 自定義類繼承MVC中實現類或介面,全域性註冊,作用於全部控制器
如果以上兩種方式均不能滿足你的過濾器的使用範圍,你可以在App_Start資料夾下的FilterConfig類中進行全域性註冊,使該過濾器作用於所有控制器中所有Action方法。
特別注意的一點是:自定義異常過濾器,需要把系統預設的filters.Add(new HandleErrorAttribute());註釋掉。
全域性註冊的程式碼如下:
1 public class FilterConfig 2 { 3 public static void RegisterGlobalFilters(GlobalFilterCollection filters) 4 { 5 //如果自定義異常過濾器,需要把預設的異常過濾器給註釋掉 6 //filters.Add(new HandleErrorAttribute()); 7 8 //自定義異常過濾器 9 filters.Add(new MyException()); 10 11 //全域性註冊身份驗證、行為、結果過濾器 12 //filters.Add(new MyAuthorize()); 13 //filters.Add(new MyAction()); 14 //filters.Add(new MyResult()); 15 16 //全域性註冊登入驗證(暫時註釋,使用的時候要開啟) 17 //filters.Add(new CheckLogin()); 18 } 19 }
四. 結合實際案例進行程式碼測試
1. 測試過濾器的執行順序
將上面的身份驗證過濾器、行為過濾器、結果過濾器以特性的形式作用於Action上,通過斷點監控或者檢視最後的輸出結果:
結果:
符合:OnAuthorization→OnActionExecuting-> Action方法執行 ->OnActionExecuted->OnResultExecuting/ -> Render View() (頁面渲染載入)->OnResultExecuted() 這一順序。
2. 全域性捕獲異常,記錄錯誤日誌案例
步驟1:編寫異常過濾器,通過var errorMsg = filterContext.Exception; 獲取異常資訊,可以寫入文字、存入資料庫、或者是Log4Net錯誤日誌框架進行處理。程式碼在上面。
步驟2:在web.config中為system.web新增<customErrors mode="On" />節點。
步驟3:新增到全域性註冊檔案中進行捕獲。
步驟4:在自定義的異常過濾器中新增斷點,並且自己製造一個錯誤。
捕獲到錯誤,進行頁面跳轉。
3. 登入驗證案例
業務背景:在90%以上的Web系統中,很多頁面都是登入成功以後才能看到的,當然也有很多頁面不需要登入,對於需要登入才能看到的頁面,即使你知道了訪問地址,也是不能訪問的,會退出到登入頁面提示讓你登入,對於不需要登入的頁面通過URL地址可以直接訪問。
分析:針對以上背景,過濾器對於大部分Action是需要過濾的,需要做登入驗證,對於一小部分是不需要過濾的。
解決思路:
①:自定義一個身份驗證過濾器,進行全域性註冊。
②:自定義一個Skip特性,將該特性加到不需要身份驗證的Action上。
③:重點說一下身份證過濾器中的邏輯:
a. 先判斷該Action上是否又Skip特性,如果有,停止不繼續執行;如果沒有,繼續下面的驗證邏輯。
b. 從Session中或Redis中讀取當前使用者,檢視是否為空,如果為空,表明沒有登入,返回到登入頁面;如果不為空,驗證通過,進行後面的業務邏輯。
程式碼段如下:
1 /// <summary> 2 /// 校驗系統是否登入的過濾器 3 /// 使用身份驗證過濾器進行編寫 4 /// </summary> 5 public class CheckLogin: AuthorizeAttribute 6 { 7 public override void OnAuthorization(AuthorizationContext filterContext) 8 { 9 //1. 校驗是否標記跨過登入驗證 10 if (filterContext.ActionDescriptor.IsDefined(typeof(skipAttribute), true) 11 || filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(skipAttribute), true)) 12 { 13 //表示該方法或控制器跨過登入驗證 14 return; 15 } 16 //2. 校驗是否登入 17 //可以使Session或資料庫或nosql 18 //這裡只是測試,所有統統當做沒有登入來處理 19 var sessionUser = HttpContext.Current.Session["CurrentUser"];//使用session 20 if (sessionUser == null) 21 { 22 HttpContext.Current.Session["CurrentUrl"] = filterContext.RequestContext.HttpContext.Request.RawUrl; 23 //如果沒有登入,則跳轉到錯誤頁面 24 filterContext.Result = new RedirectResult("/error.html"); 25 } 26 } 27 }