1. 程式人生 > 實用技巧 >MVC中AOP思想的體現(四種過濾器)並結合專案案例說明過濾器的實際用法

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     }

轉自:https://www.cnblogs.com/yaopengfei/p/7910763.html