ASP.NET沒有魔法——ASP.NET MVC 過濾器(Filter)


  上一篇文章介紹了使用Authorize特性實現了ASP.NET MVC中針對Controller或者Action的授權功能,實際上這個特性是MVC功能的一部分,被稱為過濾器(Filter),它是一種面向切面編程(AOP)的實現,本章將從以下幾個方面來介紹ASP.NET MVC中的過濾器。

  ● ASP.NET MVC 中的過濾器及其類型
  ● ASP.NET MVC 中常用的過濾器
  ● ASP.NET MVC 過濾器的應用方法
  ● ASP.NET MVC Action方法的調用與Filter的執行
  ● ASP.NET MVC 過濾器的創建與獲取
  ● ASP.NET MVC Action及Result過濾器的管道執行

ASP.NET MVC中的過濾器及其類型

  在之前的Entity Framework文章中介紹了EF自帶的攔截器(interceptors)功能,ASP.NET MVC中的過濾器也和攔截器一樣是一種面向切面(AOP)的編程方式,是一種不修改源代碼的前提下對應用程序進行拓展的編程方式。一般AOP用來處理日志記錄、性能統計、安全控制、事務處理、異常處理等不會對原有業務數據進行修改的功能。
  ASP.NET MVC中把過濾器分為以下幾類,每一類都是通過一個對應的接口定義的:
  ● 身份驗證過濾器(IAuthenticationFilter):這個過濾器是在MVC5中加入的,它是所有過濾器中優先級最高的,使用身份驗證過濾器可以為Action、Controller或者所有的Controller添加身份驗證邏輯。身份驗證過濾器的核心在於根據請求信息創建一個Principal對象(注:使用Identity的身份驗證功能實際上也是創建一個Principal對象),以下是IAuthenticationFilter的定義:

  

  其中身份驗證上下文有一個IPrincipal的屬性:

  

  ● 授權過濾器(IAuthorizationFilter):授權過濾器用來處理Controller以及Action的訪問限制。
  ● Action方法過濾器(IActionFilter):Action過濾器可用於在Action方法執行前和執行后添加邏輯。
  ● 結果過濾器(IResultFilter):結果過濾器可以在結果執行前和執行后添加邏輯。(注:ASP.NET MVC中的Action返回結果為ActionResult類型,該抽象類型定義了一個執行方法ExecuteResult,結果的執行實際上是對返回結果的處理)

  

  比如FileResult的執行實際上是在Http響應頭中添加了適當的參數然后將文件的二進制數據寫到了響應體中,相當於文件的下載功能。

  

  更多結果執行內容會在后續文章中介紹。

  ● 異常過濾器(IExceptionFilter):異常過濾器就是Action方法在執行的過程中拋出異常時,用來添加異常處理邏輯的過濾器。

ASP.NET MVC 中常用的過濾器

  上面介紹了過濾器的類別,現在介紹一下每一個類別下常用的過濾器有哪些:
  ● 身份驗證過濾器(IAuthenticationFilter):由於身份驗證過程可以使用Identity等成熟組件來完成,所以身份驗證過濾器暫時沒有找到適合的可以用的過濾器。如果系統有需求可自定義。
  ● 授權過濾器(IAuthorizationFilter):
    ○ Authorize:基於用戶名、角色的用戶授權。
    ○ RequireHttps:基於Https的訪問授權。
    ○ ValidateInput:ASP.NET MVC在執行前會驗證請求信息中是否包含HTML等不合法信息以避免XSS攻擊,但是有的時候需求就是要提交HTML數據,在提交這些數據時可以使用該過濾器將EnableValidation設為false,MVC將跳過數據驗證。
    ○ ValidateAntiForgeryToken:該過濾器可以對HtmlHelper的AntiForgeryToken方法生成防偽令牌進行校驗,以避免CSRF跨站偽造攻擊。
  ● Action過濾器(IActionFilter):一般根據需求自定義實現。
  ● 異常過濾器(IExceptionFilter):
    ○ HandleError:用於處理Action方法拋出的異常(默認的MVC模板會添加一個全局HandleError過濾器)。

  另外還需要注意的是ASP.NET MVC中的Controller實際上也是一個過濾器,因為Controller基類實現了所有過濾器接口:

  

  所以如果某一Controller中有特殊的處理需求,無需定義過濾器,在Controller中實現重載對應過濾器的方法即可:

  

ASP.NET MVC 過濾器的應用方法 

  ASP.NET MVC中的過濾器可以通過以下幾種方法使用:
  1. 通過特性的方式在Controller以及Action上標記使用,但是要注意的是以特性方式使用的過濾器除了實現對應的過濾器接口外還需要將其封裝為一個.Net特性並實現IMvcFilter接口,最為方便的是直接繼承FilterAttribute類型實現,如:

  

  2. 通過全局過濾器表添加過濾器,這樣添加的過濾器會對所有Controller的Action方法生效。

  

  3. 在Controller類型中通過重載對應過濾器方法的方式實現,上面說明了Controller本身就是一個實現了所有過濾器的類型。

ASP.NET MVC Action方法的調用與Filter的執行

  過濾器是在Action方法執行的過程中調用執行的,所以首先要了解Action的執行過程,在之前的文章中介紹了Controller的創建與執行《ASP.NET沒有魔法——ASP.NET MVC Controller的實例化與執行》,而這里就基於該文章,來對Action的執行過程進行介紹,Controller的執行是通過Controller類型的ExecuteCore方法完成的:

  

  而從代碼中也可以看到Controller的執行實際上是通過ActionInvoker根據Action的名稱來調用Action方法的執行,在ASP.NET MVC中默認使用一個名為 AsyncControllerActionInvoker的異步Action調用器:

  

  它除了異步功能外還繼承了同步的ControllerActionInvoker類型,異步主要是為了提高請求處理的吞吐量,這里將使用同步版本的代碼進行Action與Filter執行介紹。

  ControllerActionInvoker:

   

  從代碼定義中可以看出以下幾點:
  1. 它的核心方法是InvokeAction,它處理了所有的過濾器、Action方法的調用處理邏輯。
  2. GetFilters方法,它用於獲取所有相關的過濾器。
  3. InvokeActionMethodWithFilters、InvokActionResultWitherFilters、InvokeAuthenticationFilters、InvokeAuthenticationFiltersChallenge、InvokeAuthorizationFilters、InvokeExceptionFilters等相關方法就是用來調用對應類型的過濾器執行的方法。
  這里通過源碼分析的方式來介紹過濾器在ActionInvoker的執行過程:

 1 /// <summary>Invokes the specified action by using the specified controller context.</summary>
 2         /// <returns>The result of executing the action.</returns>
 3         /// <param name="controllerContext">The controller context.</param>
 4         /// <param name="actionName">The name of the action to invoke.</param>
 5         /// <exception cref="T:System.ArgumentNullException">The <paramref name="controllerContext" /> parameter is null.</exception>
 6         /// <exception cref="T:System.ArgumentException">The <paramref name="actionName" /> parameter is null or empty.</exception>
 7         /// <exception cref="T:System.Threading.ThreadAbortException">The thread was aborted during invocation of the action.</exception>
 8         /// <exception cref="T:System.Exception">An unspecified error occurred during invocation of the action.</exception>
 9         public virtual bool InvokeAction(ControllerContext controllerContext, string actionName)
10         {
11             if (controllerContext == null)
12             {
13                 throw new ArgumentNullException("controllerContext");
14             }
15             if (string.IsNullOrEmpty(actionName) && !controllerContext.RouteData.HasDirectRouteMatch())
16             {
17                 throw new ArgumentException(MvcResources.Common_NullOrEmpty, "actionName");
18             }
19             ControllerDescriptor controllerDescriptor = this.GetControllerDescriptor(controllerContext);
20             ActionDescriptor actionDescriptor = this.FindAction(controllerContext, controllerDescriptor, actionName);//根據Controller信息及Action名稱獲取Action的描述信息
21             if (actionDescriptor != null)
22             {
23                 FilterInfo filters = this.GetFilters(controllerContext, actionDescriptor);//獲取所有過濾器
24                 try
25                 {
26                     AuthenticationContext authenticationContext = this.InvokeAuthenticationFilters(controllerContext, filters.AuthenticationFilters, actionDescriptor);//調用身份驗證過濾器
27                     if (authenticationContext.Result != null)
28                     {
29                         AuthenticationChallengeContext authenticationChallengeContext = this.InvokeAuthenticationFiltersChallenge(controllerContext, filters.AuthenticationFilters, actionDescriptor, authenticationContext.Result);
30                         this.InvokeActionResult(controllerContext, authenticationChallengeContext.Result ?? authenticationContext.Result);
31                     }
32                     else
33                     {
34                         AuthorizationContext authorizationContext = this.InvokeAuthorizationFilters(controllerContext, filters.AuthorizationFilters, actionDescriptor);//調用授權過濾器
35                         if (authorizationContext.Result != null)
36                         {
37                             AuthenticationChallengeContext authenticationChallengeContext2 = this.InvokeAuthenticationFiltersChallenge(controllerContext, filters.AuthenticationFilters, actionDescriptor, authorizationContext.Result);
38                             this.InvokeActionResult(controllerContext, authenticationChallengeContext2.Result ?? authorizationContext.Result);
39                         }
40                         else
41                         {
42                             if (controllerContext.Controller.ValidateRequest)//判斷是否需要驗證請求,使用ValidateInput特性並設置EnableValidation為False時跳過驗證
43                             {
44                                 ControllerActionInvoker.ValidateRequest(controllerContext);
45                             }
46                             IDictionary<string, object> parameterValues = this.GetParameterValues(controllerContext, actionDescriptor);
47                             ActionExecutedContext actionExecutedContext = this.InvokeActionMethodWithFilters(controllerContext, filters.ActionFilters, actionDescriptor, parameterValues);//執行Action過濾器和Action方法
48                             AuthenticationChallengeContext authenticationChallengeContext3 = this.InvokeAuthenticationFiltersChallenge(controllerContext, filters.AuthenticationFilters, actionDescriptor, actionExecutedContext.Result);
49                             this.InvokeActionResultWithFilters(controllerContext, filters.ResultFilters, authenticationChallengeContext3.Result ?? actionExecutedContext.Result);//執行Result過濾器及Result
50                         }
51                     }
52                 }
53                 catch (ThreadAbortException)
54                 {
55                     throw;
56                 }
57                 catch (Exception exception)
58                 {
59                     ExceptionContext exceptionContext = this.InvokeExceptionFilters(controllerContext, filters.ExceptionFilters, exception);//當捕獲異常時執行異常過濾器
60                     if (!exceptionContext.ExceptionHandled)//如果異常過濾器並沒有對異常進行處理則繼續拋出異常
61                     {
62                         throw;
63                     }
64                     this.InvokeActionResult(controllerContext, exceptionContext.Result);
65                 }
66                 return true;
67             }
68             return false;
69         }
View Code

  通過對上面代碼的分析得出以下幾個結論:
  1. 通過Controller上下文及Action的信息找到真實的Action方法后,獲取所有過濾器。
  2. 先執行身份驗證過濾器。
  3. 通過身份驗證過濾器后執行授權過濾器。
  4. 授權過濾器通過后,執行Action過濾器及Action方法。
  5. 執行Result過濾器及Result。

ASP.NET MVC 過濾器的創建與獲取

  根據上面的介紹知道了可以通過全局過濾器、特性標記以及重載Controller過濾器方法這三種方式來應用過濾器的,那么在執行過程中是如何通過ActionInvoker的GetFilters方法創建和獲取它們的呢?
  ● 過濾器提供器(FilterProvider):ASP.NET MVC中有一個過濾器提供器的概念和實際對象,它有三種實現分別對應上述的三種應用方式:
    ○ GlobalFilterCollection:用於保存全局過濾器實例,可以直接通過它添加和獲取過濾器實例,通過它創建的過濾器的Scope為Gobal,創建時可以通過Order參數來決定全局過濾器的執行順序:

  

    ○ FilterAttributeFilterProvider:過濾器特性提供器,通過查找Controller以及Action上的特性來創建過濾器,根據特性標記位置將Scope分為Controller以及Action,在應用特性時可以設置特性的Order屬性來決定過濾器的執行順序:

    

    ○ ControllerInstanceFilterProvider:控制器實例過濾器提供器,它用於獲取當前Controller實例的過濾器,並且過濾器的Scope為First:

    

  ● 過濾器提供器集合(FilterProviderCollection):它包含了上述的所有過濾器提供器,ActionInvoker就是通過它來獲取所有相關過濾器的:

  

  FitlerProviderCollection獲取過濾器時通過以上三種提供器獲取所有相關的過濾器並根據Scope以及Order對過濾器進行排序,以決定過濾器執行順序。

ASP.NET MVC Action及Result過濾器的管道執行

  在Action和Result過濾器的定義中都有兩個方法,分別是OnXXXExecuting以及OnXXXExecuted,它們對應Action或者Reuslt執行前和執行后。當一個Action上存在多個Action或者Result過濾器時就會形成一個過濾器管道,其執行方式如下圖所示:

  

小結

  本文除了介紹ASP.NET MVC的過濾器功能及常用的過濾器外,還通過代碼的形式分析了它創建與執行的過程。在一般的項目中使用ASP.NET MVC自帶的過濾器就可以滿足需求,如授權、錯誤處理等,但過濾器作為ASP.NET MVC中的一種重要的AOP拓展方式,合理的運用過濾器可以實現,如日志記錄、性能分析、Action的事務執行(http://blog.gauffin.org/2012/06/how-to-handle-transactions-in-asp-net-mvc3/,這篇文章就利用Action過濾器實現了數據庫的事務)等等功能,並且可以靈活的在不影響原有代碼邏輯的情況下對系統進行拓展。

參考:
  https://docs.microsoft.com/en-us/aspnet/mvc/overview/older-versions-1/controllers-and-routing/understanding-action-filters-cs
  http://blog.gauffin.org/2012/06/how-to-handle-transactions-in-asp-net-mvc3/

本文鏈接:http://www.cnblogs.com/selimsong/p/7839459.html 

ASP.NET沒有魔法——目錄


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM