.NET Core中間件的註冊和管道的構建(2)---- 用UseMiddleware擴展方法註冊中間件類

 .NET Core中間件的註冊和管道的構建(2)---- 用UseMiddleware擴展方法註冊中間件類

0x00 爲何要引入擴展方法

有的中間件功能比較簡單,有的則比較複雜,而且依賴其它組件。除了直接用ApplicationBuilder的Use()方法註冊中間件外,還可使用ApplicationBuilder的擴展方法UseMiddleware()註冊中間件。這種狀況下能夠註冊類型,這個方法會經過反射解析這個類型,並把它包裝成Func<ReuqestDelegate,RequestDelegate>而後調用Use()方法註冊。html

遇到這種狀況通常直覺上是經過繼承一個抽象類並實現其中的方法在寫一箇中間件。不過.NET Core不是這麼作的。中間件類使用約定而不是繼承來進行約束。這裏說的約定就是約定本來的意思,例如約定好了中間件類中必須包含一個叫Invoke的方法,叫別的就不行,有重載也不行。由於中間件類沒有任何繼承上的約束,在註冊過程當中就是經過反射去尋找名字爲Invoke的方法,而後把它包裝成RequestDelegate的。這篇文章就是要說一下寫一箇中間件類都有哪些約定以及中間件類的註冊。函數

0x01 一個最簡單的例子

先看一箇中間件類的最簡單的例子:測試

 

上一篇文章中說過了,中間件本質就是一個方法,這個方法接收一個HttpContext參數,返回Task。在上面這個中間件類中Invoke就是這個方法。爲了可以調用下一個中間件,當前中間件還須要保存下一個中間件的引用。這個引用是經過構造函數傳進來的,若是當前中間件不須要調用後面中間件的話,這個引用徹底能夠不保存。若是要註冊這個中間件,咱們能夠這樣作:ui

但若是咱們這個中間件比較複雜,依賴不少其餘模塊,那麼咱們在註冊的時候須要構造依賴模塊的實例,並在中間件類的構造函數中把這些依賴傳進去。這增強了中間件和依賴模塊之間的耦合度。爲了能減小這種耦合,同時享受到依賴注入帶來的便利,提供了UseMiddleware<T>擴展方法來註冊中間件類T。spa

UseMiddleware擴展方法會找到上面中間件類中的Invoke方法,建立上面類的實例,在建立實例時遇到須要注入的類型會嘗試注入,而後把Invoke方法包裝爲ReuqestDelegate,進而包裝爲Func<RequestDelegate,RequestDelegate>,而後經過ApplicatonBuilder的Use方法(上篇文章講過了)註冊到IList<Func<RequestDelegaet,RequestDelegate>中。3d

從上面的SimpleMiddleware咱們能夠看到這個類沒有任何顯示的繼承關係,那麼咱們在寫一箇中間件類時須要注意哪些約束呢?咱們只要看一下UseMiddleware註冊中間件的過程就明白了。下面是對UseMiddleware()方法的分析,對代碼分析不感興趣的能夠跳過直接看後面的結論和測試。htm

0x02 擴展方法註冊中間件類的過程

使用UseMiddleware<T>擴展方法註冊中間件類T主要包含如下幾個關鍵步驟:中間件

1.找到中間件類的Invoke方法。UseMiddleware方法會經過反射獲取註冊的中間件類的全部public且非static的方法列表,而後從其中找出名字叫Invoke的方法,確認Invoke方法沒有重載,確認Invoke方法返回Task,確認Invoke方法第一個參數是HttpContext,最後這兩個檢查是爲了能把Invoke方法包裝爲RequestDelegate。blog

2.選取最佳構造函數。把下一個中間件的引用next插入到從UseMiddleware傳入的參數列表的第一個,做爲給定的參數列表。繼承

而後獲取中間件類的全部構造函數,從給定的參數列表中依次取出參數,和構造函數的參數進行類型匹配,匹配最多的構造函數選爲最佳構造函數。匹配相同的以代碼中排在前面的構造函數爲準(這其中省略了不少匹配最佳構造函數的細節,感興趣的能夠自行查看代碼)。

值得注意的是若是存在給定的參數列表中存在某個參數P,在當前構造函數參數列表中找不到與之匹配的類型,那麼這個構造函數不能做爲最佳構造函數。也就是說選中的最佳構造函數的參數列表必需要是給定參數列表的超集。剛剛上面也說了,下一個中間件next被插入到了給定參數列表的第一個,所以選中的最佳構造函數參數中必須包含參數RequestDelegate。若是全部構造函數都不包含RequestDelegate,那麼會拋出異常。

3.構造中間件類的實例。找到了最佳構造函數後,接下來就使用該構造函數構造中間件類的實例。對於構造函數中的全部參數,可以從給定的參數列表中找到類型匹配的,從給定的參數列表中獲取參數。從參數列表中找不到的,則嘗試從依賴注入容器中獲取,依賴注入容器中也找不到的檢查是否是有默認值,默認值也沒有就拋出異常。

4.實例構造完成後,若是Invoke方法只有一個參數(HttpContext)會把這個實例的Invoke方法包裝爲RequestDelegate,進而包裝爲Func<RequestDelegate,RequestDelegate>而後使用Use方法註冊。若是有多個參數,不符合RequestDelegate約束,則對Invoke進行二次包裝以符合RequestDelegate。在二次包裝中會嘗試從依賴注入容器中獲取Invoke參數中的依賴。

0x03一些結論

下面總結一下中間件類的一些約定,主要是基於對代碼的理解,有錯誤或不全的地方請指正。

關於中間件的方法:

1.中間件的方法必須叫Invoke,且爲public,非static。

2.Invoke方法第一個參數必須是HttpContext類型。

3.Invoke方法必須返回Task。

4.Invoke方法能夠有多個參數,除HttpContext外其它參數會嘗試從依賴注入容器中獲取。

5.Invoke方法不能有重載。

 

關於構造函數:

1.構造函數必須包含RequestDelegate參數,該參數傳入的是下一個中間件。

2.構造函數參數中的RequestDelegate參數不是必須放在第一個,能夠是任意位置。

3.構造函數能夠有多個參數,參數會優先從給定的參數列表中找,其次會從依賴注入容器中獲取,獲取失敗會嘗試獲取默認值,都失敗會拋出異常。

4.構造函數能夠有多個,屆時會根據構造函數參數列表和給定的參數列表選擇匹配度最高的一個。

 

我的建議,真的僅僅是我的的一些建議:

1.除及特殊狀況外只保留一個構造函數,以省去多餘的構造函數匹配檢查。

2.在構造函數中注入所需依賴而不是Invoke中。

3.關於構造函數參數的順序,把RequestDelegate放在第一個;以後是UseMiddleware方法中給出的參數,並且構造函數中參數順序和給定參數列表中的順序最好也相同;而後是須要注入的參數;最後是有默認值的參數。以上除了默認值參數必須放在最後外其他的順序都不是必須的,但按照上面的順序會比較清晰,並且能使實例建立的開銷最小。

4.Invoke方法只保留一個HttpContext參數。這樣能夠省去對Invoke方法的二次包裝。

5.進一步擴展ApplicationBuilder,建立語義更加明確的方法代替Use/UseMiddleware,例如UseMVC、UseStaticFiles。

其中1中所說的及特殊的狀況,我能想到的就是給UseMiddleware提供不一樣的參數列表,進而匹配到不一樣的構造函數建立實例。具體使用場景沒有想到。

0x04 測試

上篇文章中咱們寫過一個記錄後面全部中間件耗時的中間件。當時直接用Use方法註冊的。如今咱們把它寫爲一箇中間件類,而且把計時功能寫爲一個StopWatch類,並添加到依賴注入容器中。

下面是計時器類的代碼:

下面是中間件類的代碼

下面是向依賴注入容器中添加StopWatch

下面是使用UseMiddleware擴展方法添加TimeMiddleware中間件代碼

固然,也能夠不把StopWatch添加到依賴注入容器中,而是在UserMiddleware方法中直接給出參數。

若是既在依賴注入容器中添加了StopWatch,又在UseMiddleware註冊時提供了StopWatch,那麼按照參數匹配順序最終使用的是註冊時提供的StopWatch。

運行一下能夠看到與上篇文章一樣的效果。

0x05 寫在最後

UseMiddleware方法使註冊中間件變得容易,同事也減少了中間件和其它依賴模塊間的耦合。不過無論哪一種擴展方法,最終都是經過Use方法實現中間件的註冊。下一篇文章將寫一下注冊中間件的其它擴展方法Map、MapWhen和Run。

0x06 相關文章

.NET Core中間件的註冊和管道的構建(1)---- 註冊和構建原理

.NET Core中間件的註冊和管道的構建(2)---- 用UseMiddleware擴展方法註冊中間件類

.NET Core中間件的註冊和管道的構建(3) ---- 使用Map/MapWhen擴展方法

 


更多內容歡迎訪問個人博客:http://www.durow.vip

相關文章
相關標籤/搜索