ASP.NET Core 中間件

2019-04-17 08:58 更新

中間件是一種裝配到應用管道以處理請求和響應的軟件。 每個組件:

  • 選擇是否將請求傳遞到管道中的下一個組件。
  • 可在管道中的下一個組件前后執(zhí)行工作。

請求委托用于生成請求管道。 請求委托處理每個 HTTP 請求。

使用 RunMap 和 Use 擴展方法來配置請求委托。 可將一個單獨的請求委托并行指定為匿名方法(稱為并行中間件),或在可重用的類中對其進行定義。 這些可重用的類和并行匿名方法即為中間件,也叫中間件組件。 請求管道中的每個中間件組件負責調用管道中的下一個組件,或使管道短路。 當中間件短路時,它被稱為“終端中間件”,因為它阻止中間件進一步處理請求。

將 HTTP 處理程序和模塊遷移到 ASP.NET Core 中間件 介紹了 ASP.NET Core 和 ASP.NET 4.x 中請求管道之間的差異,并提供了更多的中間件示例。

使用 IApplicationBuilder 創(chuàng)建中間件管道

ASP.NET Core 請求管道包含一系列請求委托,依次調用。 下圖演示了這一概念。 沿黑色箭頭執(zhí)行。

請求處理模式顯示請求到達、通過三個中間件進行處理以及響應離開應用。

每個委托均可在下一個委托前后執(zhí)行操作。 應盡早在管道中調用異常處理委托,這樣它們就能捕獲在管道的后期階段發(fā)生的異常。

盡可能簡單的 ASP.NET Core 應用設置了處理所有請求的單個請求委托。 這種情況不包括實際請求管道。 調用單個匿名函數(shù)以響應每個 HTTP 請求。

C#

public class Startup
{
    public void Configure(IApplicationBuilder app)
    {
        app.Run(async context =>
        {
            await context.Response.WriteAsync("Hello, World!");
        });
    }
}

第一個 Run 委托終止了管道。

用 Use 將多個請求委托鏈接在一起。 next 參數(shù)表示管道中的下一個委托。 可通過不調用 next 參數(shù)使管道短路。 通??稍谙乱粋€委托前后執(zhí)行操作,如以下示例所示:

C#

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.");
        });
    }
}

當委托不將請求傳遞給下一個委托時,它被稱為“讓請求管道短路”。 通常需要短路,因為這樣可以避免不必要的工作。 例如,靜態(tài)文件中間件可以處理對靜態(tài)文件的請求,并讓管道的其余部分短路,從而起到終端中間件的作用。 如果中間件添加到管道中,且位于終止進一步處理的中間件前,它們仍處理 next.Invoke 語句后面的代碼。 不過,請參閱下面有關嘗試對已發(fā)送的響應執(zhí)行寫入操作的警告。

 警告

在向客戶端發(fā)送響應后,請勿調用 next.Invoke。 響應啟動后,針對 HttpResponse 的更改將引發(fā)異常。 例如,設置標頭和狀態(tài)代碼更改將引發(fā)異常。 調用 next 后寫入響應正文:

  • 可能導致違反協(xié)議。 例如,寫入的長度超過規(guī)定的 Content-Length。
  • 可能損壞正文格式。 例如,向 CSS 文件中寫入 HTML 頁腳。

HasStarted 是一個有用的提示,指示是否已發(fā)送標頭或已寫入正文。

順序

向 Startup.Configure 方法添加中間件組件的順序定義了針對請求調用這些組件的順序,以及響應的相反順序。 此排序對于安全性、性能和功能至關重要。

以下 Startup.Configure 方法將為常見應用方案添加中間件組件:

  1. 異常/錯誤處理
  2. HTTP 嚴格傳輸安全協(xié)議
  3. HTTPS 重定向
  4. 靜態(tài)文件服務器
  5. Cookie 策略實施
  6. 身份驗證
  7. 會話
  8. MVC

C#

public void Configure(IApplicationBuilder app)
{
    if (env.IsDevelopment())
    {
        // When the app runs in the Development environment:
        //   Use the Developer Exception Page to report app runtime errors.
        //   Use the Database Error Page to report database runtime errors.
        app.UseDeveloperExceptionPage();
        app.UseDatabaseErrorPage();
    }
    else
    {
        // When the app doesn't run in the Development environment:
        //   Enable the Exception Handler Middleware to catch exceptions
        //     thrown in the following middlewares.
        //   Use the HTTP Strict Transport Security Protocol (HSTS)
        //     Middleware.
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }

    // Use HTTPS Redirection Middleware to redirect HTTP requests to HTTPS.
    app.UseHttpsRedirection();

    // Return static files and end the pipeline.
    app.UseStaticFiles();

    // Use Cookie Policy Middleware to conform to EU General Data 
    // Protection Regulation (GDPR) regulations.
    app.UseCookiePolicy();

    // Authenticate before the user accesses secure resources.
    app.UseAuthentication();

    // If the app uses session state, call Session Middleware after Cookie 
    // Policy Middleware and before MVC Middleware.
    app.UseSession();

    // Add MVC to the request pipeline.
    app.UseMvc();
}

在前面的示例代碼中,每個中間件擴展方法都通過 Microsoft.AspNetCore.Builder 命名空間在 IApplicationBuilder 上公開。

UseExceptionHandler 是添加到管道的第一個中間件組件。 因此,異常處理程序中間件可捕獲稍后調用中發(fā)生的任何異常。

盡早在管道中調用靜態(tài)文件中間件,以便它可以處理請求并使其短路,而無需通過剩余組件。 靜態(tài)文件中間件不提供授權檢查。 可公開訪問由靜態(tài)文件中間件服務的任何文件,包括 wwwroot 下的文件。 若要了解如何保護靜態(tài)文件,請參閱 ASP.NET Core 中的靜態(tài)文件。

如果靜態(tài)文件中間件未處理請求,則請求將被傳遞給執(zhí)行身份驗證的身份驗證中間件 (UseAuthentication)。 身份驗證不使未經身份驗證的請求短路。 雖然身份驗證中間件對請求進行身份驗證,但僅在 MVC 選擇特定 Razor 頁或 MVC 控制器和操作后,才發(fā)生授權(和拒絕)。

以下示例演示中間件排序,其中靜態(tài)文件的請求在響應壓縮中間件前由靜態(tài)文件中間件進行處理。使用此中間件順序不壓縮靜態(tài)文件。 可以壓縮來自 UseMvcWithDefaultRoute 的 MVC 響應。

C#

public void Configure(IApplicationBuilder app)
{
    // Static files not compressed by Static File Middleware.
    app.UseStaticFiles();
    app.UseResponseCompression();
    app.UseMvcWithDefaultRoute();
}

Use、Run 和 Map

使用 Use、Run 和 Map 配置 HTTP 管道。 Use 方法可使管道短路(即不調用 next 請求委托)。 Run 是一種約定,并且某些中間件組件可公開在管道末尾運行的 Run[Middleware] 方法。

Map 擴展用作約定來創(chuàng)建管道分支。 Map* 基于給定請求路徑的匹配項來創(chuàng)建請求管道分支。 如果請求路徑以給定路徑開頭,則執(zhí)行分支。

C#

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:1234Hello from non-Map delegate.
localhost:1234/map1Map Test 1
localhost:1234/map2Map Test 2
localhost:1234/map3Hello from non-Map delegate.

使用 Map 時,將從 HttpRequest.Path 中刪除匹配的線段,并針對每個請求將該線段追加到 HttpRequest.PathBase。

MapWhen 基于給定謂詞的結果創(chuàng)建請求管道分支。 Func<HttpContext, bool> 類型的任何謂詞均可用于將請求映射到管道的新分支。 在以下示例中,謂詞用于檢測查詢字符串變量 branch 是否存在:

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:1234 的請求和響應。

請求響應
localhost:1234Hello from non-Map delegate.
localhost:1234/?branch=masterBranch used = master

Map 支持嵌套,例如:

C#

app.Map("/level1", level1App => {
    level1App.Map("/level2a", level2AApp => {
        // "/level1/level2a" processing
    });
    level1App.Map("/level2b", level2BApp => {
        // "/level1/level2b" processing
    });
});

此外,Map 還可同時匹配多個段:

C#

public class Startup
{
    private static void HandleMultiSeg(IApplicationBuilder app)
    {
        app.Run(async context =>
        {
            await context.Response.WriteAsync("Map multiple segments.");
        });
    }

    public void Configure(IApplicationBuilder app)
    {
        app.Map("/map1/seg1", HandleMultiSeg);

        app.Run(async context =>
        {
            await context.Response.WriteAsync("Hello from non-Map delegate.");
        });
    }
}

內置中間件

ASP.NET Core 附帶以下中間件組件。 “順序”列提供備注,以說明中間件在請求處理管道中的放置,以及中間件可能會終止請求處理的條件。 如果中間件讓請求處理管道短路,并阻止下游中間件進一步處理請求,它被稱為“終端中間件”。 若要詳細了解短路,請參閱使用 IApplicationBuilder 創(chuàng)建中間件管道部分。

中間件描述順序
身份驗證提供身份驗證支持。在需要 HttpContext.User 之前。 OAuth 回叫的終端。
Cookie 策略跟蹤用戶是否同意存儲個人信息,并強制實施 cookie 字段(如 secure 和 SameSite)的最低標準。在發(fā)出 cookie 的中間件之前。 示例:身份驗證、會話、MVC (TempData)。
CORS配置跨域資源共享。在使用 CORS 的組件之前。
異常處理處理異常。在生成錯誤的組件之前。
轉接頭將代理標頭轉發(fā)到當前請求。在使用已更新字段的組件之前。 示例:方案、主機、客戶端 IP、方法。
運行狀況檢查檢查 ASP.NET Core 應用及其依賴項的運行狀況,如檢查數(shù)據(jù)庫可用性。如果請求與運行狀況檢查終結點匹配,則為終端。
HTTP 方法重寫允許傳入 POST 請求重寫方法。在使用已更新方法的組件之前。
HTTPS 重定向將所有 HTTP 請求重定向到 HTTPS(ASP.NET Core 2.1 或更高版本)。在使用 URL 的組件之前。
HTTP 嚴格傳輸安全性 (HSTS)添加特殊響應標頭的安全增強中間件(ASP.NET Core 2.1 或更高版本)。在發(fā)送響應之前,修改請求的組件之后。 示例:轉接頭、URL 重寫。
MVC用 MVC/Razor Pages 處理請求(ASP.NET Core 2.0 或更高版本)。如果請求與路由匹配,則為終端。
OWIN與基于 OWIN 的應用、服務器和中間件進行互操作。如果 OWIN 中間件處理完請求,則為終端。
響應緩存提供對緩存響應的支持。在需要緩存的組件之前。
響應壓縮提供對壓縮響應的支持。在需要壓縮的組件之前。
請求本地化提供本地化支持。在對本地化敏感的組件之前。
路由定義和約束請求路由。用于匹配路由的終端。
會話提供對管理用戶會話的支持。在需要會話的組件之前。
靜態(tài)文件為提供靜態(tài)文件和目錄瀏覽提供支持。如果請求與文件匹配,則為終端。
URL 重寫提供對重寫 URL 和重定向請求的支持。在使用 URL 的組件之前。
WebSockets啟用 WebSockets 協(xié)議。在接受 WebSocket 請求所需的組件之前。


以上內容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號