通過源碼瞭解ASP.NET MVC 幾種Filter的執行過程

来源:http://www.cnblogs.com/4littleProgrammer/archive/2016/03/14/5276438.html
-Advertisement-
Play Games

一、前言 之前也閱讀過MVC的源碼,並瞭解過各個模塊的運行原理和執行過程,但都沒有形成文章(所以也忘得特別快),總感覺分析源碼是大神的工作,而且很多人覺得平時根本不需要知道這些,會用就行了。其實閱讀源碼是個很好的習慣,它不只停留在知道怎麼用的階段,而是讓我們知道一系列的為什麼,為什麼這樣設計,為什麼


一、前言

  之前也閱讀過MVC的源碼,並瞭解過各個模塊的運行原理和執行過程,但都沒有形成文章(所以也忘得特別快),總感覺分析源碼是大神的工作,而且很多人覺得平時根本不需要知道這些,會用就行了。其實閱讀源碼是個很好的習慣,它不只停留在知道怎麼用的階段,而是讓我們知道一系列的為什麼,為什麼這樣設計,為什麼這樣使用...。很多朋友應該看過《asp.net x 框架揭秘》這本書,確實不錯,特別是邊看源碼邊看書,可以有不小的收穫。Ok,我不是大神,我只是心血來潮想看一下源碼!

二、幾種常見的Filter

  說到mvc里的Filter,自然會想到IAuthorizationFilter,IActionFilter,IResultFilter,IExceptionFilter,搜索一下也都知道怎麼用了。其實說白了,這些介面定義了一系列方法,這些方法在請求的不同時機被執行,所謂Filter,就是讓我們可以在不同時機進行攔截處理。

     這裡還涉及到一個特性:FilterAttribute,例如常用的AuthorizeAttribute就繼承了FilterAttribute和實現了IAuthorizationFilter介面。說到Attribute,馬上會關聯到:運行時、反射、性能。框架會在運行過程中,通過反射獲取標記屬性,並執行特定的操作;至於性能問題,通常可以通過緩存來優化。

  所以,我們可以做出猜測,以AuthorizeAttribute為例,msdn說它可以進行許可權驗證,也就是在Action執行前,框架會通過反射獲取標記在Action(或Controller)上的FilterAttribute,並執行IAuthorizationFilter定義的OnAuthorization方法,在該方法內部進行許可權驗證。所以如果我們要在Action執行前做某些判斷或處理,可以 1.定義一個Attribute繼承FilterAttribute,並實現IActionFilter介面(與IAuthorizationFilter不同的是,這個時候ModelBinding已經完成);2.實現IActionFilter中的方法;3.標記在Action(或Controller上)。ok,下麵就通過源碼來驗證這個過程。

三、源碼分析

  Action的執行是由ActionInvoker負責的,我們直接從這裡出發。IActionInvoker定義了ActionInvoker要實現的方法,該介面定義如下:  

    public interface IActionInvoker
    {
        bool InvokeAction(ControllerContext controllerContext, string actionName);
    }

  ControllerActionInvoker  實現了該介面,顧名思義,它用於執行Controller 的 Action方法。它的 InvokeAction如下:

        public virtual bool InvokeAction(ControllerContext controllerContext, string actionName)
        {
            ControllerDescriptor controllerDescriptor = GetControllerDescriptor(controllerContext);
            ActionDescriptor actionDescriptor = FindAction(controllerContext, controllerDescriptor, actionName);
            if (actionDescriptor != null)
            {
                //標記1
                FilterInfo filterInfo = GetFilters(controllerContext, actionDescriptor);

                try
                {
                    //標記2
                    AuthenticationContext authenticationContext = InvokeAuthenticationFilters(controllerContext, filterInfo.AuthenticationFilters, actionDescriptor);

                    if (authenticationContext.Result != null)
                    {
                        InvokeActionResult(controllerContext, authenticationContext.Result);
                    }
                    else
                    {
                        IPrincipal principal = authenticationContext.Principal;

                        if (principal != null)
                        {
                            Thread.CurrentPrincipal = principal;
                            HttpContext.Current.User = principal;
                        }

                        //標記3
                        AuthorizationContext authorizationContext = InvokeAuthorizationFilters(controllerContext, filterInfo.AuthorizationFilters, actionDescriptor);
                        if (authorizationContext.Result != null)
                        {
                            AuthenticationChallengeContext challengeContext =
                                InvokeAuthenticationFiltersChallenge(controllerContext, filterInfo.AuthenticationFilters, actionDescriptor, authorizationContext.Result);                           
                            InvokeActionResult(controllerContext, challengeContext.Result ?? authorizationContext.Result);
                        }
                        else
                        {
                            if (controllerContext.Controller.ValidateRequest)
                            {
                                ValidateRequest(controllerContext);
                            }

                            //標記4
                            IDictionary<string, object> parameters = GetParameterValues(controllerContext, actionDescriptor);
                            ActionExecutedContext postActionContext = InvokeActionMethodWithFilters(controllerContext, filterInfo.ActionFilters, actionDescriptor, parameters);
                            //標記5
                            InvokeActionResultWithFilters(controllerContext, filterInfo.ResultFilters, postActionContext.Result);
                        }
                    }
                }
                catch (Exception ex)
                {
                    //標記6
                    ExceptionContext exceptionContext = InvokeExceptionFilters(controllerContext, filterInfo.ExceptionFilters, ex);
                    if (!exceptionContext.ExceptionHandled)
                    {
                        throw;
                    }
                    InvokeActionResult(controllerContext, exceptionContext.Result);
                }

                return true;
            }
            return false;
        }

  其實這裡的6個標記已經印證了我們的猜測,先獲取各種Filter,然後在各個時機執行它們。上面標記2-6都是InvokeXXXFilters就是具體的執行方法。

  但是,到這裡上面我們說到的FilterAttribute還沒有出現。我們先把焦點放到標記1,GetFilters 上,它獲取一個FilterInfo。GetFilters的定義如下:

        protected virtual FilterInfo GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
        {
            return new FilterInfo(_getFiltersThunk(controllerContext, actionDescriptor));
        }

  _getFiltersThunk 是一個私有變數:

private Func<ControllerContext, ActionDescriptor, IEnumerable<Filter>> _getFiltersThunk = FilterProviders.Providers.GetFilters;

  通過定義可以看出,_getFiltersThunk 會返回一個Filter 集合(這裡的Filter是一個實際的類,而上面提到的是概念性的東西,或者叫過濾器更合適),Filter 對象包裝了IXXXFilter介面對象,具體是在其Instance 屬性中。這裡有點繞,但不影響,簡單的說就是 GetFilters 方法會根據 FilterProviders.Providers.GetFilters 返回的一個IEnumerable<Filter>包裝一個 FilterInfo對象。

  我們先看 IEnumerable<Filter> 是如何獲取的,它通過 FilterProviders.Providers.GetFilters 獲得,FilterProviders 定義如下:

        public static class FilterProviders
        {
            static FilterProviders()
            {
                Providers = new FilterProviderCollection();
                Providers.Add(GlobalFilters.Filters);
                Providers.Add(new FilterAttributeFilterProvider());
                Providers.Add(new ControllerInstanceFilterProvider());
            }

            public static FilterProviderCollection Providers { get; private set; }
        }

  這裡可以註冊自定義的FilterProvider,FilterProvider實際是實現了IFilterProvider(定義了GetFilters方法)的類。可以看到,mvc 預設已經準備兩個FilterProvider。調用GetFilters實際會遍歷每一個FilterProvider的GetFilters方法,以內置的FilterAttributeFilterProvider 為例,它的 GetFilters方法如下:

        public virtual IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
        {
            ControllerBase controller = controllerContext.Controller;
            var typeFilters = GetControllerAttributes(controllerContext, actionDescriptor)
                .Select(attr => new Filter(attr, FilterScope.Controller, null));
            var methodFilters = GetActionAttributes(controllerContext, actionDescriptor)
                .Select(attr => new Filter(attr, FilterScope.Action, null));

            return typeFilters.Concat(methodFilters).ToList();
        }

  這裡也可以看到,Filter對象包裝了具體的過濾器。其中GetControllerAttributes,實際它會調用ControllerDescriptor的 GetFilterAttribute,該方法定義如下:

        public virtual IEnumerable<FilterAttribute> GetFilterAttributes(bool useCache)
        {
            return GetCustomAttributes(typeof(FilterAttribute), inherit: true).Cast<FilterAttribute>();
        }

  ok,FilterAttribute 終於出現了!GetActionAttributes 也是類似的過程。

     獲取到Controller和Action的FilterAttribute,並包裝成Filter集合後,就會構建一個FilterInfo對象,該對象的作用可以從其構造函數看出:

        public FilterInfo(IEnumerable<Filter> filters)
        {
            // evaluate the 'filters' enumerable only once since the operation can be quite expensive
            var cache = filters.ToList();

            var overrides = cache.Where(f => f.Instance is IOverrideFilter);

            FilterScope actionOverride = SelectLastScope<IActionFilter>(overrides);
            FilterScope authenticationOverride = SelectLastScope<IAuthenticationFilter>(overrides);
            FilterScope authorizationOverride = SelectLastScope<IAuthorizationFilter>(overrides);
            FilterScope exceptionOverride = SelectLastScope<IExceptionFilter>(overrides);
            FilterScope resultOverride = SelectLastScope<IResultFilter>(overrides);

            _actionFilters.AddRange(SelectAvailable<IActionFilter>(cache, actionOverride));
            _authenticationFilters.AddRange(SelectAvailable<IAuthenticationFilter>(cache, authenticationOverride));
            _authorizationFilters.AddRange(SelectAvailable<IAuthorizationFilter>(cache, authorizationOverride));
            _exceptionFilters.AddRange(SelectAvailable<IExceptionFilter>(cache, exceptionOverride));
            _resultFilters.AddRange(SelectAvailable<IResultFilter>(cache, resultOverride));
        }

  很明顯,它將Filter 按照各自IXXXFilter介面進行分類。在InvokeAction方法內,就是根據這個分類,在各個時機進行相應的調用的。

四、總結

  通過一張圖來簡單總結一下:


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

-Advertisement-
Play Games
更多相關文章
  • 某一時候,為文本框(TextBox)裝飾個水印。它有兩種狀態,一是blur和focus。因此,我們可以在Javascript寫兩個事件: 演示:    
  • 【問】 在C#和Visual Basic的轉換中,以下一些轉換的用法和區別是什麼呢? [C#] [VB.NET] 【錯誤回答】 沒有區別,因為運行了之後都可以正常轉化。 【正解】 光從運行結果來看當然是毫無區別,因為題目所給出的僅僅是一部分的例子,不是全部。許多初學者容易產生“以偏概全”的錯誤認識。
  • 我希望能理解在瀏覽器輸入URL並敲擊回車來請求一個ASP.NET MVC網站的頁面之後發生的任何事情。 為什麼需要關心這些?有兩個原因。首先是因為ASP.NET MVC是一個擴展性非常強的框架。例如,我們可以插入不同的ViewEngine來控制網站內容呈現的方式。我們還可以定義控制器生成和分配到某個
  • 事件,定義了事件成員的類型允許類型或類型的實例通知其它對象發生了特定的事情。 按照我自己的理解而言,事件可以被(方法)關註,也可以被(方法)取消關註,事件發生後關註了事件的一方會瞭解到,並對事件做出相應的應對(執行方法)。(我每次都是這麼理解的,這樣從字面意義上更好理解一點) 眾所周知,事件實際上就
  • 什麼是Asp.Net頁面生命周期 當我們在瀏覽器地址欄中輸入網址,回車查看頁面時,這時會向伺服器端(IIS)發送一個request請求,伺服器就會判斷發送過來的請求頁面,  完全識別 HTTP 頁面處理程式類後,ASP.NET 運行時將調用處理程式的 ProcessRequest 方法來處理請求,來
  • 最近因為有個項目除了登錄還有其他很多地方需要用到驗證碼的功能,所以想到了採用HtmlHelper和ActionFilter封裝一個驗證碼的功能,以便能夠重覆調用。封裝好以後調用很方便,只需在View中調用Html擴展好的方法,相應的Action加上驗證功能的Filter就行了。 首先寫一個能夠隨機生
  • 由於現在面試需求,我必須有點瞭解MVC開發基礎,MVC是一個開發框架或者是一個開發模式,MVC讓軟體開發的過程大致切割成三個單元,分別是:Model(模型)、View(試圖)、Controller(控制器) MVC的好處是:MVC有很好的關註點分離,更好的維護軟體,使得網站更容易維護,以加速項目開發
  • Quartz.Net是一個開源的作業調度框架; 官網地址:http://www.quartz-scheduler.net/documentation/index.html 源碼地址:https://sourceforge.net/projects/quartznet/ 定時輪詢資料庫同步,定時郵件通
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...