ASP.NET Core 中介軟體
阿新 • • 發佈:2020-11-14
ASP.NET Core 中介軟體(Middleware)詳解
什麼是中介軟體(Middleware)?
中介軟體是一種裝配到應用管道以處理請求和響應的軟體。每個元件:
將 HTTP 處理程式和模組遷移到 ASP.NET Core中介軟體介紹了 ASP.NET Core 和 ASP.NET 4.x 中請求管道之間的差異,並提供了更多的中介軟體示例。
使用 IApplicationBuilder 建立中介軟體管道
ASP.NET Core 請求管道包含一系列請求委託,依次呼叫。下圖演示了這一概念。沿黑色箭頭執行。
每個委託可以在下一個委託之前和之後執行操作。委託還可以決定不將請求傳遞給下一個委託,這稱為請求管道的短路。短路通常是可取的,因為它避免了不必要的工作。例如,靜態檔案中介軟體可以返回一個靜態檔案的請求,並使管道的其餘部分短路。需要在管道早期呼叫異常處理委託,因此它們可以捕獲後面管道的異常。
最簡單的可能是ASP.NET Core應用程式建立一個請求的委託,處理所有的請求。此案例不包含實際的請求管道。相反,針對每個HTTP請求都呼叫一個匿名方法。
使用Map時,將從HttpRequest.Path中刪除匹配的路徑段,並針對每個請求將該路徑段追加到HttpRequest.PathBase。
Map支援巢狀,例如:
內建中介軟體
ASP.NET Core附帶以下中介軟體元件:
編寫中介軟體 中介軟體通常封裝在一個類中,並使用擴充套件方法進行暴露。 檢視以下中介軟體,它從查詢字串設定當前請求的Culture:
- 選擇是否將請求傳遞到管道中的下一個元件。
- 可在管道中的下一個元件前後執行工作。
using第一個app.Run委託終止管道。 有如下程式碼: 通過瀏覽器訪問,發現確實在第一個app.Run終止了管道。 您可以將多個請求委託與app.Use連線在一起。next引數表示管道中的下一個委託。 (請記住,您可以通過不呼叫下一個引數來結束流水線。)通常可以在下一個委託之前和之後執行操作,如下例所示: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!"); }); } }
public class Startup { public void Configure(IApplicationBuilder app) { app.Use(async (context, next) => { await context.Response.WriteAsync("進入第一個委託 執行下一個委託之前\r\n"); //呼叫管道中的下一個委託 await next.Invoke(); await context.Response.WriteAsync("結束第一個委託 執行下一個委託之後\r\n"); }); app.Run(async context => { await context.Response.WriteAsync("進入第二個委託\r\n"); await context.Response.WriteAsync("Hello from 2nd delegate.\r\n"); await context.Response.WriteAsync("結束第二個委託\r\n"); }); } }使用瀏覽器訪問有如下結果: 可以看出請求委託的執行順序是遵循上面的流程圖的。 注意: 響應傳送到客戶端後,請勿呼叫next.Invoke。 響應開始之後,對HttpResponse的更改將丟擲異常。 例如,設定響應頭,狀態程式碼等更改將會引發異常。在呼叫next之後寫入響應體。
- 可能導致協議違規。 例如,寫入超過content-length所述內容長度。
- 可能會破壞響應內容格式。 例如,將HTML頁尾寫入CSS檔案。
- 在使用單個使用者帳戶建立新的 Web 應用時未新增的中介軟體已被註釋掉。
- 並非所有中介軟體都需要準確按照此順序執行,但許多中介軟體必須遵循這個順序。例如,UseCors、UseAuthentication和UseAuthorization必須按照上述順序執行。
- 當應用在開發環境中執行時:
- 開發人員異常頁中介軟體 (UseDeveloperExceptionPage) 報告應用執行時錯誤。
- 資料庫錯誤頁中介軟體報告資料庫執行時錯誤。
- 當應用在生產環境中執行時:
- 異常處理程式中介軟體 (UseExceptionHandler) 捕獲以下中介軟體中引發的異常。
- HTTP 嚴格傳輸安全協議 (HSTS) 中介軟體 (UseHsts) 新增Strict-Transport-Security標頭。
- 身份驗證中介軟體 (UseAuthentication) 嘗試對使用者進行身份驗證,然後才會允許使用者訪問安全資源。
- 用於授權使用者訪問安全資源的授權中介軟體 (UseAuthorization)。
- 會話中介軟體 (UseSession) 建立和維護會話狀態。如果應用使用會話狀態,請在 Cookie 策略中介軟體之後和 MVC 中介軟體之前呼叫會話中介軟體。
- 用於將 Razor Pages 終結點新增到請求管道的終結點路由中介軟體(帶有MapRazorPages的UseEndpoints)。
- 允許所有其他中介軟體首先響應匹配的請求。
- 允許具有客戶端側路由的 SPA 針對伺服器應用無法識別的所有路由執行。
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:1234的請求和響應:
請求 | 響應 |
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. |
app.Map("/level1", level1App => { level1App.Map("/level2a", level2AApp => { // "/level1/level2a" //... }); level1App.Map("/level2b", level2BApp => { // "/level1/level2b" //... }); });此外,Map還可同時匹配多個段: app.Map("/level1/level2", HandleMultiSeg); MapWhen基於給定謂詞的結果建立請求管道分支。Func<httpcontext, bool="">型別的任何謂詞均可用於將請求對映到管道的新分支。在以下示例中,謂詞用於檢測查詢字串變數branch是否存在:
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的請求和響應:
請求 | 響應 |
localhost:19219 | Hello from non-Map delegate. |
localhost:19219/?branch=1 | Branch used = master |
中介軟體 | 描述 | 順序 |
Authentication | 提供身份驗證支援 | 在需要HttpContext.User之前。OAuth 回叫的終端。 |
Authorization | 提供身份驗證支援。跟蹤使用者是否同意儲存個人資訊,並強制實施 | 緊接在身份驗證中介軟體之後。 |
Cookie Policy | cookie 欄位(如secure和SameSite)的最低標準。 | 在發出 cookie 的中介軟體之前。示例:身份驗證、會話、MVC (TempData)。 |
CORS | 配置跨域資源共享 | 在使用 CORS 的元件之前。 |
Diagnostics | 提供新應用的開發人員異常頁、異常處理、狀態內碼表和預設網頁的幾個單獨的中介軟體。 | 在生成錯誤的元件之前。異常終端或為新應用提供預設網頁的終端。 |
Forwarded Headers | 將代理標頭轉發到當前請求。 | 在使用已更新欄位的元件之前。示例:方案、主機、客戶端 IP、方法。 |
Health Check | 檢查 ASP.NET Core 應用及其依賴項的執行狀況,如檢查資料庫可用性。 | 檢查資料庫可用性。如果請求與執行狀況檢查終結點匹配,則為終端。 |
HTTP Method Override | 允許傳入 POST 請求重寫方法。 | 在使用已更新方法的元件之前。 |
HTTPS Redirection | 將所有 HTTP 請求重定向到 HTTPS。 | 在使用 URL 的元件之前。 |
HTTP Strict Transport Security (HSTS) | 新增特殊響應標頭的安全增強中介軟體。 | 在傳送響應之前,修改請求的元件之後。示例:轉接頭、URL 重寫。 |
MVC | 用 MVC/Razor Pages 處理請求。 | 如果請求與路由匹配,則為終端。 |
OWIN | 與基於 OWIN 的應用、伺服器和中介軟體進行互操作。 | 如果 OWIN 中介軟體處理完請求,則為終端。 |
Response Caching | 提供快取響應支援 | 在需要快取的元件之前。 |
Response Compression | 提供響應壓縮支援 | 在需要壓縮的元件之前。 |
Request Localization | 提供本地化支援。 | 在對本地化敏感的元件之前。 |
Endpoint Routing | 定義和約束請求路由。 | 用於匹配路由的終端。 |
Session | 提供對管理使用者會話的支援。 | 在需要會話的元件之前 |
Static Files | 為靜態檔案和目錄瀏覽提供服務提供支援 | 如果請求與檔案匹配,則為終端。 |
URL Rewrite | 用於重寫 Url,並將請求重定向的支援 | 在使用 URL 的元件之前。 |
WebSockets | 啟用 WebSockets 協議。 | 在接受 WebSocket 請求所需的元件之前。 |
編寫中介軟體 中介軟體通常封裝在一個類中,並使用擴充套件方法進行暴露。 檢視以下中介軟體,它從查詢字串設定當前請求的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}"); }); } }您可以通過傳遞Culture來測試中介軟體,例如http://localhost:19219/?culture=zh-CN 以下程式碼將中介軟體委託移動到一個類:
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); } } }以下通過IApplicationBuilder的擴充套件方法暴露中介軟體:
using Microsoft.AspNetCore.Builder; namespace Culture { public static class RequestCultureMiddlewareExtensions { public static IApplicationBuilder UseRequestCulture( this IApplicationBuilder builder) { return builder.UseMiddleware<RequestCultureMiddleware>(); } } }以下程式碼從Configure呼叫中介軟體:
public class Startup { public void Configure(IApplicationBuilder app) { app.UseRequestCulture(); app.Run(async (context) => { await context.Response.WriteAsync( $"Hello {CultureInfo.CurrentCulture.DisplayName}"); }); } }中介軟體應該遵循顯式依賴原則,通過在其建構函式中暴露其依賴關係。 中介軟體在應用程式生命週期構建一次。 如果您需要在請求中與中介軟體共享服務,請參閱以下請求相關性。 中介軟體元件可以通過構造方法引數來解析依賴注入的依賴關係。 UseMiddleware也可以直接接受其他引數。 每個請求的依賴關係 因為中介軟體是在應用程式啟動時構建的,而不是每個請求,所以在每個請求期間,中介軟體建構函式使用的作用域生命週期服務不會與其他依賴注入型別共享。 如果您必須在中介軟體和其他型別之間共享作用域服務,請將這些服務新增到Invoke方法的簽名中。 Invoke方法可以接受由依賴注入填充的其他引數。 例如:
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); } }