ASP.NET?Core的中間件與管道介紹
今天來討論一個ASP.NET Core 很重要概念管道和中間件,在ASP.NET Core中,針對HTTP請求采用pipeline也就是通常說的管道方式來處理,而管道容器內(nèi)可以掛載很多中間件(處理邏輯)“串聯(lián)”來處理HTTP請求,每一個中間件都有權(quán)決定是否需要執(zhí)行下一個中間件,或者直接做出響應。這樣的機制使得HTTP請求能夠很好的被層層處理和控制,并且層次清晰處理起來甚是方便。 示意圖如下:
為了再次說明管道和中間件的概念,舉一個官方給出的權(quán)限驗證的例子,中間件A,B分別按順序掛載在管道容器中,A為權(quán)限驗證中間件,只有通過A的權(quán)限驗證才能執(zhí)行B,如果沒有通過A的驗證,A有權(quán)中斷管道處理直接返回相應的錯誤提示例如401等。這樣必須由上一節(jié)點來調(diào)用的串行遞歸執(zhí)行方式就是pipeline,而每一個節(jié)點就是中間件或者叫中間組件?,F(xiàn)在我們來看看如何在ASP.NET Core中使用中間件和管理自己的HTTP管道
環(huán)境配置與Startup
在了解中間件之前我們需要先知道Startup這個類具體運作方式,我們以下面這段代碼為例:
/// <summary> /// web宿主的入口類 /// </summary> public class Startup { //加入服務項到容器, 這個方法將會被runtime調(diào)用 public void ConfigureServices(IServiceCollection services) { } /// <summary> /// 配置HTTP請求管道 /// </summary> /// <param name="app">被用于構(gòu)建應用程序的請求管道 只可以在Startup中的Configure方法里使用</param> /// <param name="env">提供了訪問應用程序?qū)傩裕绛h(huán)境變量</param> /// <param name="loggerFactory">提供了創(chuàng)建日志的機制</param> public void Configure(IApplicationBuilder app,IHostingEnvironment env,ILoggerFactory loggerFactory) { loggerFactory.AddConsole(); if (env.IsDevelopment()) //根據(jù)配置的環(huán)境為開發(fā)環(huán)境,則會配置拋出異常錯誤界面 { app.UseDeveloperExceptionPage(); //拋出詳細的異常錯誤界面 } //管道斷路 app.Run(async (context) => { await context.Response.WriteAsync("Hello World!"); }); } }
可以看到 Startup.cs 內(nèi)有兩個方法,一個是用來配置接口服務到管道容器中的ConfigureServices, 一個是用來配置管道中間件的Configure。
為什么必須是這兩個方法名?
其實這兩個方法名并不是規(guī)定死的,但也不是任意規(guī)定的,他是根據(jù)容器的環(huán)境變量來判斷的,這里先給出官方文檔《多環(huán)境下工作》。
我們可以在文檔中了解到,Core使用“ASPNETCORE_ENVIRONMENT”字段來描述當前運行環(huán)境名稱這就是上文中提到的環(huán)境配置,官方預設了3個環(huán)境名分別是Development(開發(fā)環(huán)境), Staging(測試環(huán)境), Production(生產(chǎn)環(huán)境),如果您使用的是VSCode您可以在.vscode文件夾下的launch.json中找到“ASPNETCORE_ENVIRONMENT”字段,可以發(fā)現(xiàn)默認情況下是Development,那說這些到底有什么用呢?
在Startup中規(guī)定,配置服務和中間件兩個方法可以根據(jù)環(huán)境名稱來命名和選擇調(diào)用,命名規(guī)則為ConfigureServices{ENVIRONMENT}和Configure{ENVIRONMENT}。如ASPNETCORE_ENVIRONMENT = “Development” 則ConfigureServices和Configure 可以寫成ConfigureServicesDevelopment 和ConfigureDevelopment ,其他也是如此。這樣就可以通過配置ASPNETCORE_ENVIRONMENT 來決定該調(diào)用哪一個配置方法了。
ConfigureServices和Configure是什么環(huán)境下的呢?
ConfigureServices和Configure就好像Switch 語句中的 default一樣的道理,如果沒有找到任何符合環(huán)境名的方法名,就會執(zhí)行調(diào)用這兩個方法。如配置了Development,但卻沒有給出ConfigureServicesDevelopment ,這時就會執(zhí)行ConfigureServices,如果都沒有就會拋出異常。
必須設置成預設環(huán)境名嗎?
環(huán)境名配置的參數(shù)名不必是預設值,你可以自己寫一個,比如LogEnv等等。
接下來我們看一下實現(xiàn)的代碼:
/// <summary> /// web宿主的入口類 /// </summary> public class Startup { //加入服務項到容器, 這個方法將會被runtime調(diào)用 public void ConfigureServices(IServiceCollection services) { } /// <summary> /// Log環(huán)境下配置HTTP請求管道 /// </summary> /// <param name="app"></param> public void ConfigureLogHelp(IApplicationBuilder app){ app.Run(async (context) => { await context.Response.WriteAsync("Hello World - ConfigureLogHelp"); }); } /// <summary> /// 開發(fā)環(huán)境下配置HTTP請求管道 /// </summary> /// <param name="app"></param> public void ConfigureDevelopment(IApplicationBuilder app){ app.Run(async (context) => { await context.Response.WriteAsync("Hello World - ConfigureDevelopment"); }); } /// <summary> /// 默認情況下配置HTTP請求管道 /// </summary> /// <param name="app">被用于構(gòu)建應用程序的請求管道。只可以在 Startup 中的 Configure 方法里使用</param> /// <param name="env">提供了訪問應用程序?qū)傩裕绛h(huán)境變量</param> /// <param name="loggerFactory">提供了創(chuàng)建日志的機制</param> public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { //管道斷路 app.Run(async (context) => { await context.Response.WriteAsync("Hello World!"); }); }
當ASPNETCORE_ENVIRONMENT = “Development”
當ASPNETCORE_ENVIRONMENT = “LogHelp”
這樣做的好處就是你可以寫自己的測試配置而不會影響到其他人或者開發(fā)過程。當然環(huán)境的作用還在于前端應該引用什么樣的CSS和JS,關(guān)于這些我們之后在MVC的章節(jié)再來討論, 想了解的博友可以看官方文檔
管道配置與Startup
說完環(huán)境配置和Startup的關(guān)系,我們回來接著聊管道的事情,現(xiàn)在我們來說說Configure{ENVIRONMENT}一下Configure簡稱這個方法。
Configure這個方法是用于配置中間件到中管道容器(IApplicationBuilder),所以這個方法必須要包含一個IApplicationBuilder參數(shù)用來接受管道容器,方便開發(fā)者配置。當然他還可以接受其他的可選參數(shù)供開發(fā)者使用如下:
(注:下圖來源于ASP.NET Core中文文檔)
需要提一下的是,剛剛我們上文中說的環(huán)境名在IHostingEnvironment中可以獲取,對于預設值官方還做了判斷封裝,當然你可以重構(gòu)它來封裝自己的環(huán)境名判斷。
HTTP管道容器由三個擴展的方法來控制中間件的路由、掛載等等,分別是Run, Map, User。
a.Run方法會使得可以使管道短路,顧名思義就是終結(jié)管道向下執(zhí)行不會調(diào)用next()委托,所以Run方法最好放在管道的最后來執(zhí)行,如下面的代碼:
/// <summary> /// 開發(fā)環(huán)境下配置HTTP請求管道 /// </summary> /// <param name="app"></param> public void ConfigureDevelopment(IApplicationBuilder app){ app.Run(async (context) => { await context.Response.WriteAsync("Hello World - ConfigureDevelopment"); }); app.Run(async (context) => { await context.Response.WriteAsync("Hello World - ConfigureDevelopment 不會被執(zhí)行"); }); }
執(zhí)行結(jié)果:
b.Use不會主動短路整個HTTP管道,但是也不會主動調(diào)用下一個中間件,必須自行調(diào)用await next.Invoke(); 如果不使用這個方法去調(diào)用下一個中間件那么Use此時的效果其實和Run是相同的,我們來看正常的代碼:
/// <summary> /// 開發(fā)環(huán)境下配置HTTP請求管道 /// </summary> /// <param name="app"></param> public void ConfigureDevelopment(IApplicationBuilder app){ var order =""; app.Use(async (context, next) => { order = $"{order}|Use start"; await next.Invoke(); order = $"{order}|Use end"; }); app.Run(async context => { await context.Response.WriteAsync($"{order}|Run ext"); }); }
執(zhí)行結(jié)果如下:
可以看到,Use end并沒有被執(zhí)行到,因為在調(diào)用下一個中間件時采用了Run,管道被終止了。
再來看看如果不顯式調(diào)用next.Invoke()時的代碼:
/// <summary> /// 開發(fā)環(huán)境下配置HTTP請求管道 /// </summary> /// <param name="app"></param> public void ConfigureDevelopment(IApplicationBuilder app){ var order =""; app.Use(async (context, next) => { order = $"{order}|Use start"; //去掉顯示調(diào)用下一個中間件 //await next.Invoke(); order = $"{order}|Use end"; await context.Response.WriteAsync(order); }); app.Run(async context => { await context.Response.WriteAsync($"{order}|Run ext"); }); }
其結(jié)果如下:
可以發(fā)現(xiàn)Run這個中間件并沒有被執(zhí)行,而只是單純的執(zhí)行了Use這個中間件。所以說 在不顯式調(diào)用下一個中間件的情況下,效果和Run時一樣的會使管道短路。
c.Map可以根據(jù)提供的URL來路由中間件,如下代碼判斷URL中訪問"/test"時就會執(zhí)行某個中間件邏輯:
/// <summary> /// 開發(fā)環(huán)境下配置HTTP請求管道 /// </summary> /// <param name="app"></param> public void ConfigureDevelopment(IApplicationBuilder app){ app.Map("/test", HandleMapTest); } /// <summary> /// maptest 處理方法 /// </summary> public void HandleMapTest(IApplicationBuilder app){ app.Run(async (context) => { await context.Response.WriteAsync("HandleMapTest Handler"); }); }
結(jié)果如下:
如果訪問/test就會執(zhí)行相應的中間件,反之則不會執(zhí)行。
MapWhen是Map的一個條件判斷的擴展方法,可以通過它來判斷某個條件適合的時候執(zhí)行某一個中間件,如:當攜帶某一個參數(shù)名稱時,執(zhí)行某一個中間件或者反之,代碼如下:
/// <summary> /// 開發(fā)環(huán)境下配置HTTP請求管道 /// </summary> /// <param name="app"></param> public void ConfigureDevelopment(IApplicationBuilder app){ app.MapWhen(context => { return context.Request.Query.ContainsKey("username"); }, HandleUserName); app.Run(async context => { await context.Response.WriteAsync("default ext"); }); } /// <summary> /// /// </summary> public void HandleUserName(IApplicationBuilder app){ app.Run(async context => { await context.Response.WriteAsync("UserName Map"); }); }
結(jié)果如下:
Map還可以進行嵌套路由中間件,這里不再描述,大家可以參看這里。
到此這篇關(guān)于ASP.NET Core中間件與管道介紹的文章就介紹到這了。希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Linux下使用Jenkins自動化構(gòu)建.NET?Core應用
這篇文章介紹了Linux下使用Jenkins自動化構(gòu)建.NET?Core應用的方法,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2022-04-04ASP.Net?Core?MVC基礎(chǔ)系列之項目創(chuàng)建
這篇文章介紹了創(chuàng)建ASP.Net?Core?MVC項目的方法,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2022-02-02Entity Framework系統(tǒng)架構(gòu)與原理介紹
這篇文章介紹了Entity Framework系統(tǒng)架構(gòu)與原理,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2022-03-03.Net行為型設計模式之模板方法模式(Template?Method)
這篇文章介紹了.Net行為型設計模式之模板方法模式(Template?Method),文中通過示例代碼介紹的非常詳細。對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-05-05.Net結(jié)構(gòu)型設計模式之組合模式(Composite)
這篇文章介紹了.Net結(jié)構(gòu)型設計模式之組合模式(Composite),文中通過示例代碼介紹的非常詳細。對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-05-05