在日常使用ASP.NET Core開發的過程中我們多多少少會設計到使用中間件的場景,ASP.NET Core預設也為我們内置了許多的中間件,甚至有時候我們需要自定義中間件來幫我們處理一些請求管道過程中的處理。接下來,我們将圍繞着以下幾個問題來簡單探究一下,關于ASP.NET Core中間件是如何初始化的
首先,使用UseMiddleware注冊自定義中間件和直接Use的方式有何不同
其次,使用基于約定的方式定義中間件和使用實作IMiddleware接口的方式定義中間件有何不同
再次,使用基于約定的方式自定義中間件的究竟是如何限制我們編寫的類和方法格式的
最後,使用約定的方式定義中間件,通過構造注入和通過Invoke方法注入的方式有何不同
接下來我們将圍繞這幾個核心點來逐漸探究關于ASP.NET Core關于中間件初始化的神秘面紗,來指導我們以後使用它的時候需要有注意點,來減少踩坑的次數。
使用自定義中間件的方式有好幾種,咱們簡單來示範一下三種比較常用方式。
首先,也是最直接最簡單的使用Use的方式,比如
然後使用UseMiddleware也是我們比較常用的一種方式,這種方式使用起來相對于第一種來說,雖然使用起來可能會稍微繁瑣一點,畢竟需要定義一個類,但是更好的符合符合面向對象的封裝思想,它的使用方式大緻如下,首先定義一個Middleware的類
編寫完成之後,需要手動的将類注冊到管道中才能生效,注冊方式如下所示
還有一種方式是實作IMiddleware接口的方式,這種方式比如前兩種方式常用,但是也确确實實的存在于ASP.NET Core中,既然存在也就有它存在的理由,我們也可以探究一下,它的使用方式也是需要自定義一個類去實作IMiddleware接口,如下所示
這種方式和第二種方式略有不同,需要手動将中間件注冊到容器中,至于聲明周期也沒做特殊要求,可以直接注冊為單例模式
完成上步操作之後,同樣也需要将其注冊到管道中去
這種方式相對于第二種方式的主要差別在于靈活性方面的差異,它實作了IMiddleware接口,那就要受到IMiddleware接口的限制,也就是我們常說的裡氏代換原則,首先我們可以先來看下IMiddleware接口的定義[點選檢視源碼👈]
通過這個接口也就看出來InvokeAsync隻能接受HttpContext和RequestDelegate參數,無法定義其他形式的參數,也沒辦法通過注入的方式編寫InvokeAsync方法參數,說白了就是沒有第二種方式靈活,受限較大。
關于常用的自定義中間件的方式,我們就先說到這裡,我們也知道了如何定義使用中間件。接下來我們就來探讨一下,這麼多種方式之間到底存在怎樣的聯系。
上面我們已經示範了關于使用中間件的幾種方式,那麼這麼幾種使用方式之間有啥聯系或差別,我們隻看到了表面的,接下來我們來看一下關于中間件初始化的源碼來一探究竟。
首先,無論那種形式都是基于IApplicationBuilder這個接口擴充而來的,是以我們先從這裡下手,找到源碼IApplicationBuilder位置[點選檢視源碼👈]可以看到以下代碼
IApplicationBuilder接口裡隻有Use的方式可以添加中間件,由此我們可以大緻猜到兩點資訊
其它添加中間件的方式,都是在擴充自IApplicationBuilder,并不是IApplicationBuilder本身的方法。
其它添加中間件的形式,最終都會轉換為Use的方式。
上面我們看到了IApplicationBuilder隻包含了一個Use方法,但是我們日常程式設計中最常使用到的卻并不是這一個,而是來自UseExtensions擴充類的Use擴充方法,實作如下所示[點選檢視源碼👈]
如預料的那樣,Use的擴充方法最終都會轉換為Use(Func<RequestDelegate, RequestDelegate> middleware)的形式去執行。Use擴充方法的形式還是比較清晰的,畢竟也是基于委托的形式,而且參數是固定的。
上面我們看到了Use的擴充方法,它最終還是轉換為Use(Func<RequestDelegate, RequestDelegate> middleware)的形式去執行。接下來我們來看下通過編寫類的形式定義中間件會是怎樣的轉換操作。找到UseMiddleware擴充方法所在的地方,也就是UseMiddlewareExtensions擴充類裡[點選檢視源碼👈],我們最常用的是UseMiddleware這個方法,而且這個方法是UseMiddlewareExtensions擴充類的入口方法[點選檢視源碼👈],說白了就是它是完全調用别的方法沒有自己的實作邏輯
繼續向下看找到它調用的擴充方法,在展示該方法之前我們先羅列一下該類的常量屬性,因為類中的方法有用到,如下所示
從這裡我們可以得到一個資訊,基于約定的形式自定義的中間件觸發方法名可以是Invoke或InvokeAsync
繼續看執行方法的實作代碼
這個方法其實是工作的核心方法,通過這裡可以看出來,自定義中間件的大緻執行過程。代碼中的注釋我寫的比較詳細,有興趣的可以仔細了解一下,如果懶得看我們就大緻總結一下大緻的核心點
首先UseMiddleware的本質确實還是執行的Use方法
實作IMiddleware接口的中間件走的是獨立的處理邏輯,而且構造函數傳遞自定義的參數,因為它的資料來自于容器的注入。
基于約定定義中間件的情況,即不實作IMiddleware的情況下。
①基于約定定義的中間件,構造函數的第一個參數需要是RequestDelegate類型
②查找方法名可以為Invoke或InvokeAsync,且存在而且隻能存在一個
③Invoke或InvokeAsync方法傳回值需為Task,且方法的第一個參數必須為HttpContext類型
④Invoke或InvokeAsync方法如果隻包含HttpContext類型參數,則該方法直接轉換為RequestDelegate
⑤我們之是以可以通過構造注入在中間件中擷取服務是因為基于約定的方式是通過ActivatorUtilities類建立的執行個體
通過上面的源碼我們了解到了實作IMiddleware接口的方式自定義中間件的方式是單獨處理的即在UseMiddlewareInterface方法中[點選檢視源碼👈],接下來我們檢視一下該方法的代碼
通過上面的代碼我們可以看到,IMiddleware執行個體是通過IMiddlewareFactory執行個體建立而來,ASP.NET Core中IMiddlewareFactory預設注冊的實作類是MiddlewareFactory,接下來我們看下這個類的實作[點選檢視源碼👈]
好吧,其實就是在容器中擷取的IMiddleware執行個體,通過這個我們就可以總結出來實作IMiddleware接口的形式建立中間件的操作
需要實作IMiddleware接口,來限制中間件的行為,方法名隻能為InvokeAsync
需要手動注冊IMiddleware和實作類到容器中,生命周期可自行限制,如果生命周期為Scope或瞬時,那麼每次請求都會建立新的中間件執行個體
沒辦法通過InvokeAsync方法注入服務,因為受到了IMiddleware接口的限制
上面我們看到了實作IMiddleware接口的方式中間件是如何被初始化的,接下來我們繼續來看,基于約定的方式定義的中間件是如何被初始化的。通過上面我們展示的源碼可知,實作邏輯在Compile方法中,該方法整體實作方式就是基于Expression,主要原因個人猜測有兩點,一個是形式比較靈活能應對的場景較多,二是性能稍微比反射好一點。在此之前,我們先展示一下Compile方法依賴的操作,首先反射是擷取UseMiddlewareExtensions類的GetService方法操作
其中GetService方法的實作如下所示,其實就是在容器ServiceProvider中擷取指定類型執行個體
好了上面已将Compile外部依賴已經展示出來了,接下來我們就可以繼續探究Compile方法了[點選檢視源碼👈]
上面的代碼比較抽象,其實主要是因為它是基于表達式樹進行各種操作的,如果對表達式樹比較熟悉的話,可能對上面的代碼了解起來還好一點,如果不熟悉表達式樹的話,可能了解起來比較困難,不過還是建議簡單學習一下Expression相關的操作,慢慢的發現還是挺有意思的,它的性能整體來說比傳統的反射性能也會更好一點。其實Compile主要實作的操作轉化為我們比較容易了解的代碼的話就是下面所示的操作,如果我們編寫了一個如下的中間件代碼
那麼通過Compile方法将轉換為類似以下形式的操作,這樣說的話可能會好了解一點
通過上面的源碼分析我們了解到,基于約定的方式定義的中間件執行個體是通過ActivatorUtilities類建立的,而且建立執行個體是在傳回RequestDelegate委托之前,IApplicationBuilder的Use方法隻會在首次運作的時候執行,後續管道串聯執行的其實正是它傳回的結果RequestDelegate這個委托。但是執行轉換Invoke或InvokeAsync方法為執行委托的操作卻是在傳回的RequestDelegate委托當中,也就是我們每次請求管道會處理的邏輯中。這個邏輯可以在IApplicationBuilder預設的實作類ApplicationBuilder類的Build方法中可以得知[點選檢視源碼👈],它的實作邏輯如下所示
通過上面的代碼我們可以清楚的看到,管道最終執行的就是執行Func<RequestDelegate, RequestDelegate>這個委托的傳回結果RequestDelegate。
由此得到結論,基于約定的中間件形式,通構造函數注入的服務執行個體,是和應用程式的生命周期一緻的。通過Invoke或InvokeAsync方法注入的服務執行個體每次請求都會被執行到,即生命周期是Scope的。
通過本次對源碼的研究,我們認識到了自定義的ASP.NET Core中間件是如何被初始化的。雖然自定義的中間件的形式有許多種方式,但是最終還都是轉換為IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware)這種方式。将中間件抽離為獨立的類有兩種方式,即基于約定的方式和實作IMiddleware接口的形式,通過分析源碼我們也更深刻的了解兩種方式的不同之處。基于約定的方式更靈活,它的聲明周期是單例的,但是通過它的Invoke或InvokeAsync方法注入的服務執行個體生命周期是Scope的。實作IMiddleware接口的方式生命周期取決于自己注冊服務執行個體時候聲明的周期,而且這種方式沒辦法通過方法注入服務,因為有IMiddleware接口InvokeAsync方法的限制。
當然不僅僅是我們在總結中說的的這些,還存在更多的細節,這些我們在分析源碼的時候都有涉及,相信閱讀文章比較仔細的同學肯定會注意到這些。閱讀源碼收獲正是這些,解決心中的疑問,了解更多的細節,有助于在實際使用中避免一些不必要的麻煩。本次講解就到這裡,願各位能有所收獲。
👇歡迎掃碼關注我的公衆号👇
