一. 簡介
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 }
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 }
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 }
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 }
下面展示以特性的形式作用於控制器或控制器中的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 }
