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

来源:http://www.cnblogs.com/durow/archive/2016/08/08/5748124.html
-Advertisement-
Play Games

.NET Core中間件的註冊和管道的構建(2) 用UseMiddleware擴展方法註冊中間件類 0x00 為什麼要引入擴展方法 有的中間件功能比較簡單,有的則比較複雜,並且依賴其它組件。除了直接用ApplicationBuilder的Use()方法註冊中間件外,還可以使用ApplicationB ...


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

0x00 為什麼要引入擴展方法

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

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

0x01 一個最簡單的例子

先看一個中間件類的最簡單的例子:

 

上一篇文章中說過了,中間件本質就是一個方法,這個方法接收一個HttpContext參數,返回Task。在上面這個中間件類中Invoke就是這個方法。為了能夠調用下一個中間件,當前中間件還需要保存下一個中間件的引用。這個引用是通過構造函數傳進來的,如果當前中間件不需要調用後面中間件的話,這個引用完全可以不保存。如果要註冊這個中間件,我們可以這樣做:

但如果我們這個中間件比較複雜,依賴很多其他模塊,那麼我們在註冊的時候需要構造依賴模塊的實例,併在中間件類的構造函數中把這些依賴傳進去。這加強了中間件和依賴模塊之間的耦合度。為了能減少這種耦合,同時享受到依賴註入帶來的便利,提供了UseMiddleware<T>擴展方法來註冊中間件類T。

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

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

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

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

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

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。


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

-Advertisement-
Play Games
更多相關文章
  • HTML5 History API提供了一種功能,能讓開發人員在不刷新整個頁面的情況下修改站點的URL。這個功能很有用,例如通過一段JavaScript代碼局部載入頁面的內容,你希望通過改變當前頁面的URL來反應出頁面內容的變化,這時該功能可以派上用場。 舉個例子,當用戶從首頁進入幫助頁面時,我們通 ...
  • 非行間樣式案例 IE獲取非行間樣式 Chrome/FF獲取非行間樣式 我的相容性寫法 我的擴展性寫法 ==註意== 以上只能獲取非行間樣式,不能設置非房間樣式的值。 ...
  • 其實,圖片預覽功能非常地常見。很意外,之前遇到上傳圖片的時候都不需要預覽,也一直沒有去實現過。現在手上的項目又需要有圖片預覽功能,所以就動手做了一個小插件。在此分享一下思路。 一、實現圖片預覽的一些方法。 瞭解了一下,其實方法都是大同小異的。大概有以下幾種方式: ①訂閱input[type=file ...
  • 一、變數的作用域要懂得閉包,起首必須懂得Javascript特別的變數作用域。變數的作用域無非就是兩種:全局變數和局部變數。Javascript說話的特別之處,就在於函數內部可以直接讀取全局變數。Js代碼 var n=999; function f1(){ alert(n); } f1(); // ...
  • DECLARE @tb1 Table( drive varchar(20), [MB 可用空間] varchar(20)) INSERT INTO @tb1 Exec master.dbo.xp_fixeddrives select drive , CAST( CAST((CAST([MB 可用空間 ...
  • ...
  • 聲明:本系列為原創,分享本人現用框架,未經本人同意,禁止轉載!http://yuangang.cnblogs.com 希望大家好好一步一步做,所有的技術和項目,都毫無保留的提供,希望大家能自己跟著做一套,還有,請大家放心,只要大家喜歡,有人需要,絕對不會爛尾,我會堅持寫完~ 如果你感覺文章有幫助,點 ...
  • 存儲過程:就像函數一樣的會保存在資料庫中--》可編程性 --》 存儲過程創建存儲過程:create proc JiaFa--需要的參數@a int,@b intas --存儲過程的內容 declare @c int; set @c = @a + @b; return @c;go public int ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...