ASP.NET Core 管道再探
幾乎任何服務(wù)器端處理環(huán)境都有自己的直通組件管道,用于檢查、重路由或修改傳入請求和傳出響應(yīng)。經(jīng)典 ASP.NET 圍繞 HTTP 模塊理念進(jìn)行排列,而 ASP.NET Core 采用基于中間件組件的更現(xiàn)代的體系結(jié)構(gòu)。最終目的是相同的 - 允許可配置的外部模塊以請求(以及稍后的響應(yīng))在服務(wù)器環(huán)境中傳遞的方式進(jìn)行干預(yù)。中間件組件的主要目的是以某種方式改變和篩選數(shù)據(jù)流(在某些特定情況下,只是使請求短路,停止任何進(jìn)一步的處理)。
ASP.NET Core 管道自框架 1.0 版發(fā)布以來幾乎沒有變化,但即將發(fā)布的 ASP.NET Core 3.0 邀請了一些關(guān)于當(dāng)前體系結(jié)構(gòu)的評論,這些評論在很大程度上被忽略了。因此,在本文中,我將重新討論 ASP.NET Core 運(yùn)行時(shí)管道的整體功能,并著重介紹 HTTP 終結(jié)點(diǎn)的角色和可能的實(shí)現(xiàn)。
適用于 Web 后端的 ASP.NET Core
特別是在過去幾年,構(gòu)建前端和后端完全解耦的 Web 應(yīng)用程序已經(jīng)變得相當(dāng)普遍。因此,大多數(shù) ASP.NET Core 項(xiàng)目現(xiàn)在都是簡單的沒有 UI 的 Web API 項(xiàng)目,僅為單頁面和/或移動應(yīng)用程序提供 HTTP 外觀,大多數(shù)情況下,這些應(yīng)用通過 Angular、React、Vue 及其移動對等項(xiàng)構(gòu)建而成。
當(dāng)你意識到這一點(diǎn),將出現(xiàn)一個(gè)問題:在不使用任何 Razor 工具的應(yīng)用程序中,綁定到 MVC 應(yīng)用程序模型是否仍有意義?MVC 模型并不是免費(fèi)的,事實(shí)上,在某種程度上,一旦停止使用控制器來提供操作結(jié)果,它甚至可能不是最輕量級的選項(xiàng)。進(jìn)一步追問:如果大部分 ASP.NET Core 代碼編寫只是為了返回 JSON 有效負(fù)載,那么操作結(jié)果概念本身是否絕對必要?
帶著這些想法,讓我們回顧一下 ASP.NET Core 管道和中間件組件的內(nèi)部結(jié)構(gòu),以及可以在啟動期間綁定到的內(nèi)置運(yùn)行時(shí)服務(wù)列表。
啟動類
在任何 ASP.NET Core 應(yīng)用程序中,一個(gè)類被指定為應(yīng)用程序引導(dǎo)程序。大多數(shù)情況下,此類采用名稱“Startup”。該類在 Web 主機(jī)配置中聲明為啟動類,Web 主機(jī)通過反射實(shí)例化并調(diào)用該類。該類可以有兩種方法 - ConfigureServices(可選)和 Configure。在第一個(gè)方法中,你將接收當(dāng)前(默認(rèn))運(yùn)行時(shí)服務(wù)列表,并需要添加更多服務(wù)來為實(shí)際應(yīng)用程序邏輯做準(zhǔn)備。在 Configure 方法中,為默認(rèn)服務(wù)和顯式請求支持應(yīng)用程序的服務(wù)進(jìn)行配置。
Configure 方法接收至少一個(gè)應(yīng)用程序生成器類實(shí)例。可以將此實(shí)例視為傳遞給代碼的 ASP.NET 運(yùn)行時(shí)管道的工作實(shí)例,以便根據(jù)需要進(jìn)行配置。一旦 Configure 方法返回,管道工作流將配置完畢,并用于執(zhí)行從連接的客戶端發(fā)送的任何進(jìn)一步請求。圖 1 提供了 Startup 類的 Configure 方法的實(shí)現(xiàn)示例。
圖 1 Startup 類中 Configure 方法的基本示例
Use 擴(kuò)展方法是用于將中間件代碼添加到其他空管道工作流的主要方法。注意,添加的中間件越多,服務(wù)器需要執(zhí)行更多的工作來滿足任何傳入請求。最小的是管道,最快的是客戶端至第一字節(jié)的時(shí)間 (TTFB)。
可以使用 lambdas 或臨時(shí)中間件類向管道添加中間件代碼。這完全由你來決定:lambda 更直接,但類(最好是一些擴(kuò)展方法)將使整個(gè)過程更易于閱讀和維護(hù)。中間件代碼獲取請求的 HTTP 上下文和對管道中下一個(gè)中間件的引用(如果有的話)。圖 2 顯示了各種中間件組件鏈接在一起的總體視圖。
圖 2 ASP.NET Core 運(yùn)行時(shí)管道
每個(gè)中間件組件都有兩次機(jī)會介入正在進(jìn)行的請求的生命周期。它可以預(yù)先處理從先前注冊運(yùn)行的組件鏈接收到的請求,然后它會被鏈中的下一個(gè)組件取代。當(dāng)鏈中的最后一個(gè)組件有機(jī)會預(yù)先處理請求時(shí),請求會被傳遞給終止中間件,以便進(jìn)行實(shí)際處理,目的是生成具體輸出。之后,組件鏈按相反順序返回,如圖 2 所示,每個(gè)中間件都有第二次處理的機(jī)會 - 不過,這一次將是后處理操作。在圖 1 代碼中,預(yù)處理代碼與后處理代碼的分割線為:
終止中間件
在圖 2 所示的體系結(jié)構(gòu)中,關(guān)鍵是終止中間件的角色,它是 Configure 方法底部的代碼,用于終止鏈并處理請求。所有演示 ASP.NET Core 應(yīng)用程序都有一個(gè)終止的 lambda,如下所示:
lambda 接收 HttpContext 對象,并在應(yīng)用程序上下文中執(zhí)行它應(yīng)執(zhí)行的任何操作。
實(shí)際上,有意不生成到下一個(gè)組件的中間件組件將終止鏈,進(jìn)而導(dǎo)致響應(yīng)被發(fā)送到請求的客戶端。UseStaticFiles 中間件就是一個(gè)很好的例子,它在指定的 Web 根文件夾下提供靜態(tài)資源,并終止請求。另一個(gè)示例是 UseRewriter,它可以命令客戶端重定向到新 URL。在沒有終止中間件的情況下,請求很難在客戶端上產(chǎn)生一些可見輸出,盡管響應(yīng)仍然通過運(yùn)行中間件(例如,通過添加 HTTP 標(biāo)頭或響應(yīng) cookie)以修改后的形式發(fā)送。
還有兩個(gè)專用的中間件工具也可用于短路請求:app.Map 和 app.MapWhen。前者檢查請求路徑是否匹配參數(shù)并運(yùn)行自己的終止中間件,如下所示:
而后者僅在驗(yàn)證了指定布爾條件后才運(yùn)行自己的終止中間件。布爾條件來自接受 HttpContext 的函數(shù)計(jì)算。圖 3 中的代碼提供了一個(gè)非常精簡的小型 Web API,它僅為單個(gè)終結(jié)點(diǎn)提供服務(wù),并且在沒有任何類似于控制器類項(xiàng)的情況下執(zhí)行該操作。
圖 3 極小型 ASP.NET Core Web API
如果 URL 匹配 /country,終止中間件將從查詢字符串中讀取一個(gè)參數(shù),并安排對存儲庫的調(diào)用,以獲得匹配國家/地區(qū)列表。然后,對列表對象以 JSON 格式直接手動序列化到輸出流。通過添加一些其他映射路由,你甚至可以擴(kuò)展 Web API。再簡單不過了。
MVC 怎么樣?
在 ASP.NET Core 中,整個(gè) MVC 機(jī)制作為黑盒運(yùn)行時(shí)服務(wù)提供。只需綁定到 ConfigureServices 方法中的服務(wù),并在 Configure 方法中配置其路由,如下面的代碼所示:
此時(shí),如果你打算提供 HTML,歡迎填充常用的 Controllers 文件夾,甚至 Views 文件夾。注意在 ASP.NET Core 還可以使用 POCO 控制器,它是簡單的 C# 類,經(jīng)修飾后可以被識別為控制器,并與 HTTP 上下文斷開連接。
MVC 機(jī)制是終止中間件的另一個(gè)很好的示例。一旦請求被 MVC 中間件捕獲,一切都在它的控制之下,管道會突然終止。
有趣的是,MVC 機(jī)制在內(nèi)部運(yùn)行自己的自定義管道。它不以中間件為中心,但它仍然是一個(gè)完整的運(yùn)行時(shí)管道,控制請求如何路由到控制器操作方法,并最終將生成的操作結(jié)果呈現(xiàn)到輸出流。MVC 管道由在各個(gè)控制器方法前后運(yùn)行的各種類型的操作篩選器(操作名稱選擇器、授權(quán)篩選器、異常處理程序、自定義操作結(jié)果管理器)組成。在 ASP.NET Core 中,內(nèi)容協(xié)商也隱藏在運(yùn)行時(shí)管道中。
從更深刻的角度來看,整個(gè) ASP.NET MVC 機(jī)制看起來就像是被固定在最新的、經(jīng)重新設(shè)計(jì)的、以中間件為中心的 ASP.NET Core 管道的上方。就像 ASP.NET Core 管道和 MVC 機(jī)制是不同類型的實(shí)體,只是以某種方式連接在一起。總體情況與 MVC 被固定在現(xiàn)已被摒棄的 Web 窗體運(yùn)行時(shí)之上的方式?jīng)]有太大不同。實(shí)際上,在該上下文中,如果處理請求無法與物理文件匹配(很可能是 ASPX 文件),MVC 就會通過專用的 HTTP 處理程序啟動。
這有問題嗎?很可能沒有。也許問題還沒出現(xiàn)!
將 SignalR 置于循環(huán)中
將 SignalR 添加到 ASP.NET Core 應(yīng)用程序時(shí),需要做的就是創(chuàng)建一個(gè)中心類來公開終結(jié)點(diǎn)。有趣的是中心類可能與控制器完全不相關(guān)。不需要 MVC 來運(yùn)行 SignalR,但中心類的行為類似于外部請求的前端控制器。從中心類公開的方法可以執(zhí)行任何工作 - 甚至與框架的跨應(yīng)用通知性質(zhì)無關(guān)的工作,如圖 4 所示。
圖 4 從中心類公開方法
能看到圖片嗎?
SignalR 中心類可以被視為一個(gè)控制器類,不需要整個(gè) MVC 機(jī)制,非常適合無 UI(或者更確切地說,是 Razor)響應(yīng)。
將 gRPC 置于循環(huán)中
在 3.0 版本中,ASP.NET Core 還提供了對 gRPC 框架的本機(jī)支持。該框架設(shè)計(jì)為與 RPC 準(zhǔn)則一起使用,是圍繞接口定義語言的一層代碼,接口定義語言完全定義終結(jié)點(diǎn),并且能夠使用 HTTP/2 上的 Protobuf 二進(jìn)制序列化觸發(fā)連接方之間的通信。從 ASP.NET Core 3.0 角度來看,gRPC 還是另一個(gè)可調(diào)用外觀,可以進(jìn)行服務(wù)器端計(jì)算并返回值。下面介紹了如何啟用 ASP.NET Core 服務(wù)器應(yīng)用程序以支持 gRPC:
另請注意全局路由的使用,以使應(yīng)用程序支持沒有 MVC 機(jī)制的路由。可以將 UseRouting 視為定義 app.Map 中間件塊的一種更結(jié)構(gòu)化的方法。
前面代碼的凈效果是支持從客戶端應(yīng)用程序到映射服務(wù)的 RPC 樣式調(diào)用,即 Greeter-Service 類。有趣的是,GreeterService 類在概念上等同于 POCO 控制器,只不過它不需要被識別為控制器類,如下所示:
基類(GreeterBase 是一個(gè)封裝在靜態(tài)類中的抽象類)包含執(zhí)行請求/響應(yīng)通信所需的管道。gRPC 服務(wù)類與 ASP.Net Core 基礎(chǔ)結(jié)構(gòu)完全集成,可以注入外部引用。
底線
特別是隨著 ASP.NET Core 3.0 的發(fā)布,將會出現(xiàn)另外兩種情況,在這些情況下,使用無 MVC 的控制器樣式外觀將會很有幫助。SignalR 有一個(gè)中心類,gRPC 有一個(gè)服務(wù)類,但關(guān)鍵是它們在概念上是相同的,必須在不同的場景以不同方式實(shí)現(xiàn)這些類。MVC 機(jī)制已或多或少被移植到 ASP.NET Core,因?yàn)樗畛跏菫榻?jīng)典 ASP.NET 設(shè)計(jì)的,并圍繞控制器和操作結(jié)果維護(hù)自己的內(nèi)部管道。與此同時(shí),隨著 ASP.NET Core 越來越廣泛地被用作普通后端服務(wù)提供程序(不支持視圖),對 HTTP 終結(jié)點(diǎn)可能統(tǒng)一的 RPC 樣式外觀的需求也在增長。
原文地址:https://msdn.microsoft.com/zh-cn/magazine/mt833464
.NET社區(qū)新聞,深度好文,歡迎訪問公眾號文章匯總?http://www.csharpkit.com?
總結(jié)
以上是生活随笔為你收集整理的ASP.NET Core 管道再探的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: kubernetes实战篇之helm示例
- 下一篇: .NET开发框架(五)-IIS上部署AS