1. 程式人生 > 實用技巧 >ASP.NET Core 中介軟體

ASP.NET Core 中介軟體

ASP.NET Core 中介軟體(Middleware)詳解 什麼是中介軟體(Middleware)? 中介軟體是一種裝配到應用管道以處理請求和響應的軟體。每個元件:
  • 選擇是否將請求傳遞到管道中的下一個元件。
  • 可在管道中的下一個元件前後執行工作。
請求委託用於生成請求管道。請求委託處理每個 HTTP 請求。 使用RunMapUse擴充套件方法來配置請求委託。可將一個單獨的請求委託並行指定為匿名方法(稱為並行中介軟體),或在可重用的類中對其進行定義。這些可重用的類和並行匿名方法即為中介軟體 ,也叫中介軟體元件 。請求管道中的每個中介軟體元件負責呼叫管道中的下一個元件,或使管道短路。當中間件短路時,它被稱為“終端中介軟體” ,因為它阻止中介軟體進一步處理請求。
將 HTTP 處理程式和模組遷移到 ASP.NET Core中介軟體
介紹了 ASP.NET Core 和 ASP.NET 4.x 中請求管道之間的差異,並提供了更多的中介軟體示例。 使用 IApplicationBuilder 建立中介軟體管道 ASP.NET Core 請求管道包含一系列請求委託,依次呼叫。下圖演示了這一概念。沿黑色箭頭執行。 每個委託可以在下一個委託之前和之後執行操作。委託還可以決定不將請求傳遞給下一個委託,這稱為請求管道的短路。短路通常是可取的,因為它避免了不必要的工作。例如,靜態檔案中介軟體可以返回一個靜態檔案的請求,並使管道的其餘部分短路。需要在管道早期呼叫異常處理委託,因此它們可以捕獲後面管道的異常。 最簡單的可能是ASP.NET Core應用程式建立一個請求的委託,處理所有的請求。此案例不包含實際的請求管道。相反,針對每個HTTP請求都呼叫一個匿名方法。
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!"); }); } }
第一個app.Run委託終止管道。 有如下程式碼: 通過瀏覽器訪問,發現確實在第一個app.Run終止了管道。 您可以將多個請求委託與app.Use連線在一起。next引數表示管道中的下一個委託。 (請記住,您可以通過不呼叫下一個引數來結束流水線。)通常可以在下一個委託之前和之後執行操作,如下例所示:
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檔案。
HttpResponse.HasStarted是一個有用的提示,指示是否已傳送響應頭和/或正文已寫入。 中介軟體順序 向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?}"); }); } 在上述程式碼中:
  • 在使用單個使用者帳戶建立新的 Web 應用時未新增的中介軟體已被註釋掉。
  • 並非所有中介軟體都需要準確按照此順序執行,但許多中介軟體必須遵循這個順序。例如,UseCors、UseAuthentication和UseAuthorization必須按照上述順序執行。
以下Startup.Configure方法將為常見應用方案新增中介軟體元件: 1.異常/錯誤處理
  • 當應用在開發環境中執行時:
  • 開發人員異常頁中介軟體 (UseDeveloperExceptionPage) 報告應用執行時錯誤。
  • 資料庫錯誤頁中介軟體報告資料庫執行時錯誤。
    • 當應用在生產環境中執行時:
  • 異常處理程式中介軟體 (UseExceptionHandler) 捕獲以下中介軟體中引發的異常。
  • HTTP 嚴格傳輸安全協議 (HSTS) 中介軟體 (UseHsts) 新增Strict-Transport-Security標頭。
2.HTTPS 重定向中介軟體 (UseHttpsRedirection) 將 HTTP 請求重定向到 HTTPS。 3.靜態檔案中介軟體 (UseStaticFiles) 返回靜態檔案,並簡化進一步請求處理。 4.Cookie 策略中介軟體 (UseCookiePolicy) 使應用符合歐盟一般資料保護條例 (GDPR) 規定。 5.用於路由請求的路由中介軟體 (UseRouting)。
  1. 身份驗證中介軟體 (UseAuthentication) 嘗試對使用者進行身份驗證,然後才會允許使用者訪問安全資源。
  2. 用於授權使用者訪問安全資源的授權中介軟體 (UseAuthorization)。
  3. 會話中介軟體 (UseSession) 建立和維護會話狀態。如果應用使用會話狀態,請在 Cookie 策略中介軟體之後和 MVC 中介軟體之前呼叫會話中介軟體。
  4. 用於將 Razor Pages 終結點新增到請求管道的終結點路由中介軟體(帶有MapRazorPages的UseEndpoints)。
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.UseAuthentication(); app.UseAuthorization(); app.UseSession(); app.UseEndpoints(endpoints => { endpoints.MapRazorPages(); }); } 在前面的示例程式碼中,每個中介軟體擴充套件方法都通過Microsoft.AspNetCore.Builder名稱空間在IApplicationBuilder上公開。 UseExceptionHandler是新增到管道的第一個中介軟體元件。因此,異常處理程式中介軟體可捕獲稍後呼叫中發生的任何異常。 儘早在管道中呼叫靜態檔案中介軟體,以便它可以處理請求並使其短路,而無需通過剩餘元件。靜態檔案中介軟體不 提供授權檢查。可公開訪問由靜態檔案中介軟體服務的任何檔案,包括 wwwroot 下的檔案。若要了解如何保護靜態檔案,請參閱ASP.NET Core 中的靜態檔案。 如果靜態檔案中介軟體未處理請求,則請求將被傳遞給執行身份驗證的身份驗證中介軟體 (UseAuthentication)。身份驗證不使未經身份驗證的請求短路。雖然身份驗證中介軟體對請求進行身份驗證,但僅在 MVC 選擇特定 Razor 頁或 MVC 控制器和操作後,才發生授權(和拒絕)。 以下示例演示中介軟體排序,其中靜態檔案的請求在響應壓縮中介軟體前由靜態檔案中介軟體進行處理。使用此中介軟體順序不壓縮靜態檔案。可以壓縮 Razor Pages 響應。 public void Configure(IApplicationBuilder app) { // Static files aren't compressed by Static File Middleware. app.UseStaticFiles(); app.UseResponseCompression(); app.UseEndpoints(endpoints => { endpoints.MapRazorPages(); }); } 對於單頁應用程式,SPA 中介軟體UseSpaStaticFiles通常是中介軟體管道中的最後一個。SPA 中介軟體處於最後的作用是:
  • 允許所有其他中介軟體首先響應匹配的請求。
  • 允許具有客戶端側路由的 SPA 針對伺服器應用無法識別的所有路由執行。
有關單頁應用程式的詳細資訊,請參閱ReactAngular專案模板的指南。 對中間管道進行分支
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.
使用Map時,將從HttpRequest.Path中刪除匹配的路徑段,並針對每個請求將該路徑段追加到HttpRequest.PathBase。 Map支援巢狀,例如:
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

內建中介軟體 ASP.NET Core附帶以下中介軟體元件:
中介軟體 描述 順序
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);
    }
}