ASP.NET Core 中間件

2019-04-17 08:58 更新

中間件是一種裝配到應(yīng)用管道以處理請(qǐng)求和響應(yīng)的軟件。 每個(gè)組件:

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

請(qǐng)求委托用于生成請(qǐng)求管道。 請(qǐng)求委托處理每個(gè) HTTP 請(qǐng)求。

使用 RunMap 和 Use 擴(kuò)展方法來(lái)配置請(qǐng)求委托。 可將一個(gè)單獨(dú)的請(qǐng)求委托并行指定為匿名方法(稱(chēng)為并行中間件),或在可重用的類(lèi)中對(duì)其進(jìn)行定義。 這些可重用的類(lèi)和并行匿名方法即為中間件,也叫中間件組件。 請(qǐng)求管道中的每個(gè)中間件組件負(fù)責(zé)調(diào)用管道中的下一個(gè)組件,或使管道短路。 當(dāng)中間件短路時(shí),它被稱(chēng)為“終端中間件”,因?yàn)樗柚怪虚g件進(jìn)一步處理請(qǐng)求。

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

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

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

請(qǐng)求處理模式顯示請(qǐng)求到達(dá)、通過(guò)三個(gè)中間件進(jìn)行處理以及響應(yīng)離開(kāi)應(yīng)用。

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

盡可能簡(jiǎn)單的 ASP.NET Core 應(yīng)用設(shè)置了處理所有請(qǐng)求的單個(gè)請(qǐng)求委托。 這種情況不包括實(shí)際請(qǐng)求管道。 調(diào)用單個(gè)匿名函數(shù)以響應(yīng)每個(gè) HTTP 請(qǐng)求。

C#

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

第一個(gè) Run 委托終止了管道。

用 Use 將多個(gè)請(qǐng)求委托鏈接在一起。 next 參數(shù)表示管道中的下一個(gè)委托。 可通過(guò)不調(diào)用 next 參數(shù)使管道短路。 通??稍谙乱粋€(gè)委托前后執(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.");
        });
    }
}

當(dāng)委托不將請(qǐng)求傳遞給下一個(gè)委托時(shí),它被稱(chēng)為“讓請(qǐng)求管道短路”。 通常需要短路,因?yàn)檫@樣可以避免不必要的工作。 例如,靜態(tài)文件中間件可以處理對(duì)靜態(tài)文件的請(qǐng)求,并讓管道的其余部分短路,從而起到終端中間件的作用。 如果中間件添加到管道中,且位于終止進(jìn)一步處理的中間件前,它們?nèi)蕴幚?nbsp;next.Invoke 語(yǔ)句后面的代碼。 不過(guò),請(qǐng)參閱下面有關(guān)嘗試對(duì)已發(fā)送的響應(yīng)執(zhí)行寫(xiě)入操作的警告。

 警告

在向客戶(hù)端發(fā)送響應(yīng)后,請(qǐng)勿調(diào)用 next.Invoke。 響應(yīng)啟動(dòng)后,針對(duì) HttpResponse 的更改將引發(fā)異常。 例如,設(shè)置標(biāo)頭和狀態(tài)代碼更改將引發(fā)異常。 調(diào)用 next 后寫(xiě)入響應(yīng)正文:

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

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

順序

向 Startup.Configure 方法添加中間件組件的順序定義了針對(duì)請(qǐng)求調(diào)用這些組件的順序,以及響應(yīng)的相反順序。 此排序?qū)τ诎踩?、性能和功能至關(guān)重要。

以下 Startup.Configure 方法將為常見(jiàn)應(yīng)用方案添加中間件組件:

  1. 異常/錯(cuò)誤處理
  2. HTTP 嚴(yán)格傳輸安全協(xié)議
  3. HTTPS 重定向
  4. 靜態(tài)文件服務(wù)器
  5. Cookie 策略實(shí)施
  6. 身份驗(yàn)證
  7. 會(huì)話
  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();
}

在前面的示例代碼中,每個(gè)中間件擴(kuò)展方法都通過(guò) Microsoft.AspNetCore.Builder 命名空間在 IApplicationBuilder 上公開(kāi)。

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

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

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

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

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 方法可使管道短路(即不調(diào)用 next 請(qǐng)求委托)。 Run 是一種約定,并且某些中間件組件可公開(kāi)在管道末尾運(yùn)行的 Run[Middleware] 方法。

Map 擴(kuò)展用作約定來(lái)創(chuàng)建管道分支。 Map* 基于給定請(qǐng)求路徑的匹配項(xiàng)來(lái)創(chuàng)建請(qǐng)求管道分支。 如果請(qǐng)求路徑以給定路徑開(kāi)頭,則執(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>");
        });
    }
}

下表使用前面的代碼顯示來(lái)自 http://localhost:1234 的請(qǐng)求和響應(yīng)。

請(qǐng)求響應(yīng)
localhost:1234Hello from non-Map delegate.
localhost:1234/map1Map Test 1
localhost:1234/map2Map Test 2
localhost:1234/map3Hello from non-Map delegate.

使用 Map 時(shí),將從 HttpRequest.Path 中刪除匹配的線段,并針對(duì)每個(gè)請(qǐng)求將該線段追加到 HttpRequest.PathBase。

MapWhen 基于給定謂詞的結(jié)果創(chuàng)建請(qǐng)求管道分支。 Func<HttpContext, bool> 類(lèi)型的任何謂詞均可用于將請(qǐng)求映射到管道的新分支。 在以下示例中,謂詞用于檢測(cè)查詢(xún)字符串變量 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>");
        });
    }
}

下表使用前面的代碼顯示來(lái)自 http://localhost:1234 的請(qǐng)求和響應(yīng)。

請(qǐng)求響應(yīng)
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 還可同時(shí)匹配多個(gè)段:

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

內(nèi)置中間件

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

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


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)