中間件是一種裝配到應用管道以處理請求和響應的軟件。 每個組件:
- 選擇是否將請求傳遞到管道中的下一個組件。
- 可在管道中的下一個組件前后執行工作。
請求委托用於生成請求管道。 請求委托處理每個 HTTP 請求。
使用 RunMap 和 Use 擴展方法來配置請求委托。 可將一個單獨的請求委托並行指定為匿名方法(稱為並行中間件),或在可重用的類中對其進行定義。 這些可重用的類和並行匿名方法即為中間件 ,也叫中間件組件 。 請求管道中的每個中間件組件負責調用管道中的下一個組件,或使管道短路。 當中間件短路時,它被稱為“終端中間件” ,因為它阻止中間件進一步處理請求。
將 HTTP 處理程序和模塊遷移到 ASP.NET Core 中間件介紹了 ASP.NET Core 和 ASP.NET 4.x 中請求管道之間的差異,並提供了更多的中間件示例。
使用 IApplicationBuilder 創建中間件管道
ASP.NET Core 請求管道包含一系列請求委托,依次調用。 下圖演示了這一概念。 沿黑色箭頭執行。
每個委托均可在下一個委托前后執行操作。 應盡早在管道中調用異常處理委托,這樣它們就能捕獲在管道的后期階段發生的異常。
盡可能簡單的 ASP.NET Core 應用設置了處理所有請求的單個請求委托。 這種情況不包括實際請求管道。 調用單個匿名函數以響應每個 HTTP 請求。
public class Startup { public void Configure(IApplicationBuilder app) { app.Run(async context => { await context.Response.WriteAsync("Hello, World!"); }); } }
用 Use 將多個請求委托鏈接在一起。 next
參數表示管道中的下一個委托。 可通過不 調用 next 參數使管道短路。 通常可在下一個委托前后執行操作,如以下示例所示:
public class Startup { public void Configure(IApplicationBuilder app) { app.Use(async (context, next) => { // Do work that doesn't write to the Response. await next.Invoke(); // Do logging or other work that doesn't write to the Response. }); app.Run(async context => { await context.Response.WriteAsync("Hello from 2nd delegate."); }); } }
當委托不將請求傳遞給下一個委托時,它被稱為“讓請求管道短路” 。 通常需要短路,因為這樣可以避免不必要的工作。 例如,靜態文件中間件可以處理對靜態文件的請求,並讓管道的其余部分短路,從而起到終端中間件 的作用。 如果中間件添加到管道中,且位於終止進一步處理的中間件前,它們仍處理 next.Invoke
語句后面的代碼。 不過,請參閱下面有關嘗試對已發送的響應執行寫入操作的警告。
警告
在向客戶端發送響應后,請勿調用 next.Invoke
。 響應啟動后,針對 HttpResponse 的更改將引發異常。 例如,設置標頭和狀態代碼更改將引發異常。 調用 next
后寫入響應正文:
- 可能導致違反協議。 例如,寫入的長度超過規定的
Content-Length
。 - 可能損壞正文格式。 例如,向 CSS 文件中寫入 HTML 頁腳。
HasStarted 是一個有用的提示,指示是否已發送標頭或已寫入正文。
Run 委托不會收到 next
參數。 第一個 Run
委托始終為終端,用於終止管道。 Run
是一種約定。 某些中間件組件可能會公開在管道末尾運行的 Run[Middleware]
方法:
public class Startup { public void Configure(IApplicationBuilder app) { app.Use(async (context, next) => { // Do work that doesn't write to the Response. await next.Invoke(); // Do logging or other work that doesn't write to the Response. }); app.Run(async context => { await context.Response.WriteAsync("Hello from 2nd delegate."); }); } }
在前面的示例中,Run
委托將 "Hello from 2nd delegate."
寫入響應,然后終止管道。 如果在 Run
委托之后添加了另一個 Use
或 Run
委托,則不會調用該委托。
中間件順序
向 Startup.Configure
方法添加中間件組件的順序定義了針對請求調用這些組件的順序,以及響應的相反順序。 此順序對於安全性、性能和功能至關重要。
下面的 Startup.Configure
方法按照建議的順序增加與安全相關的中間件組件:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); app.UseDatabaseErrorPage(); } else { app.UseExceptionHandler("/Error"); app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); // app.UseCookiePolicy(); app.UseRouting(); // app.UseRequestLocalization(); // app.UseCors(); app.UseAuthentication(); app.UseAuthorization(); // app.UseSession(); app.UseEndpoints(endpoints => { endpoints.MapRazorPages(); endpoints.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); }); }
對中間件管道進行分支
Map 擴展用作約定來創建管道分支。 Map
基於給定請求路徑的匹配項來創建請求管道分支。 如果請求路徑以給定路徑開頭,則執行分支。
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>"); }); } }