詳解ASP.NET Core中間件Middleware
本文為官方文檔譯文,官方文檔現(xiàn)已非機(jī)器翻譯 https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/middleware/?view=aspnetcore-2.1
什么是中間件(Middleware)?
中間件是組裝到應(yīng)用程序管道中以處理請(qǐng)求和響應(yīng)的軟件。 每個(gè)組件:
- 選擇是否將請(qǐng)求傳遞給管道中的下一個(gè)組件。
- 可以在調(diào)用管道中的下一個(gè)組件之前和之后執(zhí)行工作。
請(qǐng)求委托(Request delegates)用于構(gòu)建請(qǐng)求管道,處理每個(gè)HTTP請(qǐng)求。
請(qǐng)求委托使用Run
,Map
和Use
擴(kuò)展方法進(jìn)行配置。單獨(dú)的請(qǐng)求委托可以以內(nèi)聯(lián)匿名方法(稱(chēng)為內(nèi)聯(lián)中間件)指定,或者可以在可重用的類(lèi)中定義它。這些可重用的類(lèi)和內(nèi)聯(lián)匿名方法是中間件或中間件組件。請(qǐng)求流程中的每個(gè)中間件組件都負(fù)責(zé)調(diào)用流水線中的下一個(gè)組件,如果適當(dāng),則負(fù)責(zé)鏈接短路。
將HTTP模塊遷移到中間件解釋了ASP.NET Core和以前版本(ASP.NET)中的請(qǐng)求管道之間的區(qū)別,并提供了更多的中間件示例。
使用 IApplicationBuilder 創(chuàng)建中間件管道
ASP.NET Core請(qǐng)求流程由一系列請(qǐng)求委托組成,如下圖所示(執(zhí)行流程遵循黑色箭頭):
每個(gè)委托可以在下一個(gè)委托之前和之后執(zhí)行操作。委托還可以決定不將請(qǐng)求傳遞給下一個(gè)委托,這稱(chēng)為請(qǐng)求管道的短路。短路通常是可取的,因?yàn)樗苊饬瞬槐匾墓ぷ?。例如,靜態(tài)文件中間件可以返回一個(gè)靜態(tài)文件的請(qǐng)求,并使管道的其余部分短路。需要在管道早期調(diào)用異常處理委托,因此它們可以捕獲后面管道的異常。
最簡(jiǎn)單的可能是ASP.NET Core應(yīng)用程序建立一個(gè)請(qǐng)求的委托,處理所有的請(qǐng)求。此案例不包含實(shí)際的請(qǐng)求管道。相反,針對(duì)每個(gè)HTTP請(qǐng)求都調(diào)用一個(gè)匿名方法。
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; public class Startup { public void Configure(IApplicationBuilder app) { app.Run(async context => { await context.Response.WriteAsync("Hello, World!"); }); } }
第一個(gè) app.Run
委托終止管道。
有如下代碼:
通過(guò)瀏覽器訪問(wèn),發(fā)現(xiàn)確實(shí)在第一個(gè)app.Run
終止了管道。
您可以將多個(gè)請(qǐng)求委托與app.Use
連接在一起。 next
參數(shù)表示管道中的下一個(gè)委托。 (請(qǐng)記住,您可以通過(guò)不調(diào)用下一個(gè)參數(shù)來(lái)結(jié)束流水線。)通??梢栽谙乱粋€(gè)委托之前和之后執(zhí)行操作,如下例所示:
public class Startup { public void Configure(IApplicationBuilder app) { app.Use(async (context, next) => { await context.Response.WriteAsync("進(jìn)入第一個(gè)委托 執(zhí)行下一個(gè)委托之前\r\n"); //調(diào)用管道中的下一個(gè)委托 await next.Invoke(); await context.Response.WriteAsync("結(jié)束第一個(gè)委托 執(zhí)行下一個(gè)委托之后\r\n"); }); app.Run(async context => { await context.Response.WriteAsync("進(jìn)入第二個(gè)委托\(zhòng)r\n"); await context.Response.WriteAsync("Hello from 2nd delegate.\r\n"); await context.Response.WriteAsync("結(jié)束第二個(gè)委托\(zhòng)r\n"); }); } }
使用瀏覽器訪問(wèn)有如下結(jié)果:
可以看出請(qǐng)求委托的執(zhí)行順序是遵循上面的流程圖的。
注意:
響應(yīng)發(fā)送到客戶端后,請(qǐng)勿調(diào)用next.Invoke
。 響應(yīng)開(kāi)始之后,對(duì)HttpResponse的更改將拋出異常。 例如,設(shè)置響應(yīng)頭,狀態(tài)代碼等更改將會(huì)引發(fā)異常。在調(diào)用next
之后寫(xiě)入響應(yīng)體。
- 可能導(dǎo)致協(xié)議違規(guī)。 例如,寫(xiě)入超過(guò)
content-length
所述內(nèi)容長(zhǎng)度。 - 可能會(huì)破壞響應(yīng)內(nèi)容格式。 例如,將HTML頁(yè)腳寫(xiě)入CSS文件。
HttpResponse.HasStarted是一個(gè)有用的提示,指示是否已發(fā)送響應(yīng)頭和/或正文已寫(xiě)入。
順序
在Startup。Configure
方法中添加中間件組件的順序定義了在請(qǐng)求上調(diào)用它們的順序,以及響應(yīng)的相反順序。 此排序?qū)τ诎踩裕阅芎凸δ苤陵P(guān)重要。
Startup.Configure
方法(如下所示)添加了以下中間件組件:
- 異常/錯(cuò)誤處理
- 靜態(tài)文件服務(wù)
- 身份認(rèn)證
- MVC
public void Configure(IApplicationBuilder app) { app.UseExceptionHandler("/Home/Error"); // Call first to catch exceptions // thrown in the following middleware. app.UseStaticFiles(); // Return static files and end pipeline. app.UseAuthentication(); // Authenticate before you access // secure resources. app.UseMvcWithDefaultRoute(); // Add MVC to the request pipeline. }
上面的代碼,UseExceptionHandler
是添加到管道中的第一個(gè)中間件組件,因此它捕獲以后調(diào)用中發(fā)生的任何異常。
靜態(tài)文件中間件在管道中提前調(diào)用,因此可以處理請(qǐng)求和短路,而無(wú)需通過(guò)剩余的組件。 靜態(tài)文件中間件不提供授權(quán)檢查。 由其提供的任何文件,包括wwwroot下的文件都是公開(kāi)的。
如果請(qǐng)求沒(méi)有被靜態(tài)文件中間件處理,它將被傳遞給執(zhí)行身份驗(yàn)證的Identity中間件(app.UseAuthentication)。 身份不會(huì)使未經(jīng)身份驗(yàn)證的請(qǐng)求發(fā)生短路。 雖然身份認(rèn)證請(qǐng)求,但授權(quán)(和拒絕)僅在MVC選擇特定的Razor頁(yè)面或控制器和操作之后才會(huì)發(fā)生。
授權(quán)(和拒絕)僅在MVC選擇特定的Razor頁(yè)面或Controller和Action之后才會(huì)發(fā)生。
以下示例演示了中間件順序,其中靜態(tài)文件的請(qǐng)求在響應(yīng)壓縮中間件之前由靜態(tài)文件中間件處理。 靜態(tài)文件不會(huì)按照中間件的順序進(jìn)行壓縮。 來(lái)自UseMvcWithDefaultRoute的MVC響應(yīng)可以被壓縮。
public void Configure(IApplicationBuilder app) { app.UseStaticFiles(); // Static files not compressed app.UseResponseCompression(); app.UseMvcWithDefaultRoute(); }
Use, Run, 和 Map
你可以使用Use
,Run
和Map
配置HTTP管道。Use
方法可以使管道短路(即,可以不調(diào)用下一個(gè)請(qǐng)求委托)。Run
方法是一個(gè)約定, 并且一些中間件組件可能暴露在管道末端運(yùn)行的Run [Middleware]方法。Map*
擴(kuò)展用作分支管道的約定。映射根據(jù)給定的請(qǐng)求路徑的匹配來(lái)分支請(qǐng)求流水線,如果請(qǐng)求路徑以給定路徑開(kāi)始,則執(zhí)行分支。
public class Startup { private static void HandleMapTest1(IApplicationBuilder app) { app.Run(async context => { await context.Response.WriteAsync("Map Test 1"); }); } private static void HandleMapTest2(IApplicationBuilder app) { app.Run(async context => { await context.Response.WriteAsync("Map Test 2"); }); } public void Configure(IApplicationBuilder app) { app.Map("/map1", HandleMapTest1); app.Map("/map2", HandleMapTest2); app.Run(async context => { await context.Response.WriteAsync("Hello from non-Map delegate. <p>"); }); } }
下表顯示了使用以前代碼的 http://localhost:19219 的請(qǐng)求和響應(yīng):
請(qǐng)求 | 響應(yīng) |
---|---|
localhost:1234 | Hello from non-Map delegate. |
localhost:1234/map1 | Map Test 1 |
localhost:1234/map2 | Map Test 2 |
localhost:1234/map3 | Hello from non-Map delegate. |
當(dāng)使用Map時(shí),匹配的路徑段將從HttpRequest.Path
中刪除,并為每個(gè)請(qǐng)求追加到Http Request.PathBase
。
MapWhen
根據(jù)給定謂詞的結(jié)果分支請(qǐng)求流水線。 任何類(lèi)型為Func<HttpContext,bool>
的謂詞都可用于將請(qǐng)求映射到管道的新分支。 在以下示例中,謂詞用于檢測(cè)查詢字符串變量分支的存在:
public class Startup { private static void HandleBranch(IApplicationBuilder app) { app.Run(async context => { var branchVer = context.Request.Query["branch"]; await context.Response.WriteAsync($"Branch used = {branchVer}"); }); } public void Configure(IApplicationBuilder app) { app.MapWhen(context => context.Request.Query.ContainsKey("branch"), HandleBranch); app.Run(async context => { await context.Response.WriteAsync("Hello from non-Map delegate. <p>"); }); } }
以下下表顯示了使用上面代碼 http://localhost:19219 的請(qǐng)求和響應(yīng):
請(qǐng)求 | 響應(yīng) |
---|---|
localhost:1234 | Hello from non-Map delegate. |
localhost:1234/?branch=1 | Branch used = master |
Map
支持嵌套,例如:
app.Map("/level1", level1App => { level1App.Map("/level2a", level2AApp => { // "/level1/level2a" //... }); level1App.Map("/level2b", level2BApp => { // "/level1/level2b" //... }); });
Map
也可以一次匹配多個(gè)片段,例如:
app.Map("/level1/level2", HandleMultiSeg);
內(nèi)置中間件
ASP.NET Core附帶以下中間件組件:
中間件 | 描述 |
---|---|
Authentication | 提供身份驗(yàn)證支持 |
CORS | 配置跨域資源共享 |
Response Caching | 提供緩存響應(yīng)支持 |
Response Compression | 提供響應(yīng)壓縮支持 |
Routing | 定義和約束請(qǐng)求路由 |
Session | 提供用戶會(huì)話管理 |
Static Files | 為靜態(tài)文件和目錄瀏覽提供服務(wù)提供支持 |
URL Rewriting Middleware | 用于重寫(xiě) Url,并將請(qǐng)求重定向的支持 |
編寫(xiě)中間件
中間件通常封裝在一個(gè)類(lèi)中,并使用擴(kuò)展方法進(jìn)行暴露。 查看以下中間件,它從查詢字符串設(shè)置當(dāng)前請(qǐng)求的Culture:
public class Startup { public void Configure(IApplicationBuilder app) { app.Use((context, next) => { var cultureQuery = context.Request.Query["culture"]; if (!string.IsNullOrWhiteSpace(cultureQuery)) { var culture = new CultureInfo(cultureQuery); CultureInfo.CurrentCulture = culture; CultureInfo.CurrentUICulture = culture; } // Call the next delegate/middleware in the pipeline return next(); }); app.Run(async (context) => { await context.Response.WriteAsync( $"Hello {CultureInfo.CurrentCulture.DisplayName}"); }); } }
您可以通過(guò)傳遞Culture來(lái)測(cè)試中間件,例如 http://localhost:19219/?culture=zh-CN
以下代碼將中間件委托移動(dòng)到一個(gè)類(lèi):
using Microsoft.AspNetCore.Http; using System.Globalization; using System.Threading.Tasks; namespace Culture { public class RequestCultureMiddleware { private readonly RequestDelegate _next; public RequestCultureMiddleware(RequestDelegate next) { _next = next; } public Task Invoke(HttpContext context) { var cultureQuery = context.Request.Query["culture"]; if (!string.IsNullOrWhiteSpace(cultureQuery)) { var culture = new CultureInfo(cultureQuery); CultureInfo.CurrentCulture = culture; CultureInfo.CurrentUICulture = culture; } // Call the next delegate/middleware in the pipeline return this._next(context); } } }
以下通過(guò)IApplicationBuilder的擴(kuò)展方法暴露中間件:
using Microsoft.AspNetCore.Builder; namespace Culture { public static class RequestCultureMiddlewareExtensions { public static IApplicationBuilder UseRequestCulture( this IApplicationBuilder builder) { return builder.UseMiddleware<RequestCultureMiddleware>(); } } }
以下代碼從Configure
調(diào)用中間件:
public class Startup { public void Configure(IApplicationBuilder app) { app.UseRequestCulture(); app.Run(async (context) => { await context.Response.WriteAsync( $"Hello {CultureInfo.CurrentCulture.DisplayName}"); }); } }
中間件應(yīng)該遵循顯式依賴(lài)原則,通過(guò)在其構(gòu)造函數(shù)中暴露其依賴(lài)關(guān)系。 中間件在應(yīng)用程序生命周期構(gòu)建一次。 如果您需要在請(qǐng)求中與中間件共享服務(wù),請(qǐng)參閱以下請(qǐng)求相關(guān)性。
中間件組件可以通過(guò)構(gòu)造方法參數(shù)來(lái)解析依賴(lài)注入的依賴(lài)關(guān)系。 UseMiddleware也可以直接接受其他參數(shù)。
每個(gè)請(qǐng)求的依賴(lài)關(guān)系
因?yàn)橹虚g件是在應(yīng)用程序啟動(dòng)時(shí)構(gòu)建的,而不是每個(gè)請(qǐng)求,所以在每個(gè)請(qǐng)求期間,中間件構(gòu)造函數(shù)使用的作用域生命周期服務(wù)不會(huì)與其他依賴(lài)注入類(lèi)型共享。 如果您必須在中間件和其他類(lèi)型之間共享作用域服務(wù),請(qǐng)將這些服務(wù)添加到Invoke方法的簽名中。 Invoke方法可以接受由依賴(lài)注入填充的其他參數(shù)。 例如:
public class MyMiddleware { private readonly RequestDelegate _next; public MyMiddleware(RequestDelegate next) { _next = next; } public async Task Invoke(HttpContext httpContext, IMyScopedService svc) { svc.MyProperty = 1000; await _next(httpContext); } }
到此這篇關(guān)于ASP.NET Core中間件Middleware詳解的文章就介紹到這了。希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
.Net創(chuàng)建型設(shè)計(jì)模式之抽象工廠模式(Abstract?Factory)
這篇文章介紹了.Net設(shè)計(jì)模式之抽象工廠模式(Abstract?Factory),文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-05-05.Net結(jié)構(gòu)型設(shè)計(jì)模式之組合模式(Composite)
這篇文章介紹了.Net結(jié)構(gòu)型設(shè)計(jì)模式之組合模式(Composite),文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-05-05微軟 Visual Studio 2010官方下載地址給大家
昨天VS2010在網(wǎng)上報(bào)道都已經(jīng)發(fā)布了,現(xiàn)在今天在網(wǎng)上找到Visual Studio 2010官方下載地址,提供給大家下載。2010-04-04ASP.NET?Core托管模型CreateDefaultBuilder()方法
這篇文章介紹了ASP.NET?Core托管模型CreateDefaultBuilder()方法,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-02-02