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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...