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

来源:http://www.cnblogs.com/selimsong/archive/2017/11/17/7839459.html
-Advertisement-
Play Games

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


  上一篇文章介紹了使用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沒有魔法——目錄


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 背景: 一段明顯的字元串,可能潛伏著看不見 的 幽靈字元。 某些字元 比較常見、常用,比如: \r \n \t 但是,有些 幽靈字元(保守估計 >200~1000個),不僅不常見,而且基本沒價值。 這些幽靈字元,潛伏在 正常字元串中,有的偽裝成空格符,有的直接隱形。 當你要 處理字元串時,這些幽靈字 ...
  • 在使用由Angular,React,Vue等應用程式框架構建的客戶端應用程式時,您總是會處理HTML5客戶端路由,它將完全在瀏覽器中處理到頁面和組件的客戶端路由。幾乎完全在瀏覽器中... HTML5客戶端路由在客戶端上工作的很好,但是當深入鏈接到一個站點或在瀏覽器中按刷新時,客戶端路由有一個惡習,變 ...
  • DEV控制項GridControl和TreeList的數據導出操作 ...
  • 學習:C#綜合揭秘——Entity Framework 併發處理詳解 帖子筆記 ,該帖子使用的是objectContext , 一、併發相關概念 併發的類型: 第一種模式稱為悲觀式併發,即當一個用戶已經在修改某條記錄時,系統將拒絕其他用戶同時修改此記錄。第二種模式稱為樂觀式併發,即系統允許多個用戶同... ...
  • Visual Studio 2017是微軟為了配合.NET戰略推出的IDE開發環境,同時也是目前開發C#程式最新的工具,本節以Visual Studio 2017社區版的安裝為例講解具體的安裝步驟。 說明:Visual Studio 2017 社區版是完全免費的,其下載地址為:https://www ...
  • 一、簡介 Topshelf可用於創建和管理Windows服務。其優勢在於不需要創建windows服務,創建控制台程式就可以。便於調試。 二、官方地址: 1、官網:http://topshelf-project.com/ 2、官方文檔:https://topshelf.readthedocs.io/e ...
  • 關於WCF即可以寄宿於IIS,也可以自我寄宿,本文采用的是自我寄宿方式。之所以採用自我寄宿方式,很大程度上,在一些特殊的場景,例如下載大文件(如幾百MB、1G等)、圖片、文檔等,如果以IIS為宿主,可能會產生記憶體不夠用。所以這裡採用自我寄宿的方式為例子。WCF是由微軟開發的一系列支持數據通信的應用程... ...
  • 問題描述 在發佈項目的時候,有一些文件是json文件,在網頁中進行載入,但是在IIS7發佈的時候,json文件居然是404,無法找到,在URL上輸入地址也一樣。 錯誤原因 IIS內部機制,不支持直接訪問json擴展名文件,沒有mime映射。因此IIS不認Json文件,如需要支持訪問json文件時,需 ...
一周排行
    -Advertisement-
    Play Games
  • 1. 說明 /* Performs operations on System.String instances that contain file or directory path information. These operations are performed in a cross-pla ...
  • 視頻地址:【WebApi+Vue3從0到1搭建《許可權管理系統》系列視頻:搭建JWT系統鑒權-嗶哩嗶哩】 https://b23.tv/R6cOcDO qq群:801913255 一、在appsettings.json中設置鑒權屬性 /*jwt鑒權*/ "JwtSetting": { "Issuer" ...
  • 引言 集成測試可在包含應用支持基礎結構(如資料庫、文件系統和網路)的級別上確保應用組件功能正常。 ASP.NET Core 通過將單元測試框架與測試 Web 主機和記憶體中測試伺服器結合使用來支持集成測試。 簡介 集成測試與單元測試相比,能夠在更廣泛的級別上評估應用的組件,確認多個組件一起工作以生成預 ...
  • 在.NET Emit編程中,我們探討了運算操作指令的重要性和應用。這些指令包括各種數學運算、位操作和比較操作,能夠在動態生成的代碼中實現對數據的處理和操作。通過這些指令,開發人員可以靈活地進行算術運算、邏輯運算和比較操作,從而實現各種複雜的演算法和邏輯......本篇之後,將進入第七部分:實戰項目 ...
  • 前言 多表頭表格是一個常見的業務需求,然而WPF中卻沒有預設實現這個功能,得益於WPF強大的控制項模板設計,我們可以通過修改控制項模板的方式自己實現它。 一、需求分析 下圖為一個典型的統計表格,統計1-12月的數據。 此時我們有一個需求,需要將月份按季度劃分,以便能夠直觀地看到季度統計數據,以下為該需求 ...
  • 如何將 ASP.NET Core MVC 項目的視圖分離到另一個項目 在當下這個年代 SPA 已是主流,人們早已忘記了 MVC 以及 Razor 的故事。但是在某些場景下 SSR 還是有意想不到效果。比如某些靜態頁面,比如追求首屏載入速度的時候。最近在項目中回歸傳統效果還是不錯。 有的時候我們希望將 ...
  • System.AggregateException: 發生一個或多個錯誤。 > Microsoft.WebTools.Shared.Exceptions.WebToolsException: 生成失敗。檢查輸出視窗瞭解更多詳細信息。 內部異常堆棧跟蹤的結尾 > (內部異常 #0) Microsoft ...
  • 引言 在上一章節我們實戰了在Asp.Net Core中的項目實戰,這一章節講解一下如何測試Asp.Net Core的中間件。 TestServer 還記得我們在集成測試中提供的TestServer嗎? TestServer 是由 Microsoft.AspNetCore.TestHost 包提供的。 ...
  • 在發現結果為真的WHEN子句時,CASE表達式的真假值判斷會終止,剩餘的WHEN子句會被忽略: CASE WHEN col_1 IN ('a', 'b') THEN '第一' WHEN col_1 IN ('a') THEN '第二' ELSE '其他' END 註意: 統一各分支返回的數據類型. ...
  • 在C#編程世界中,語法的精妙之處往往體現在那些看似微小卻極具影響力的符號與結構之中。其中,“_ =” 這一組合突然出現還真不知道什麼意思。本文將深入剖析“_ =” 的含義、工作原理及其在實際編程中的廣泛應用,揭示其作為C#語法奇兵的重要角色。 一、下劃線 _:神秘的棄元符號 下劃線 _ 在C#中並非 ...