ASP.NET Core 中介軟體

Rick AndersonSteve Smith 撰寫

中介軟體為組成應用程式管線的軟體,用以處理要求與回應。 每個元件:

  • 可選擇是否要將要求傳送到管線中的下一個元件。
  • 可以下一個元件的前後執行工作。

要求委派用於建置要求管線, 其會處理每個 HTTP 要求。

要求委派的設定方式為使用 RunMapUse 擴充方法。 您可將個別要求委派指定為內嵌匿名方法 (在內嵌中介軟體中呼叫),或於可重複使用的類別中加以定義。 這些可重複使用的類別及內嵌匿名方法皆為「中介軟體」,也稱為「中介軟體元件」。 要求管線中的每個中介軟體元件負責叫用管線中下一個元件,或對管線執行最少運算。 當中介軟體短路時,稱為「終端中介軟體」,因為它會防止接下來的中介軟體處理要求。

將 HTTP 處理常式和模組遷移至 ASP.NET Core 中介軟體說明 ASP.NET Core 和 ASP.NET 4.x 之間的要求管線差異,並提供其他中介軟體範例。

中介軟體程式碼分析

ASP.NET Core 包含許多編譯器平台分析器,可檢查應用程式程式碼的品質。 如需詳細資訊,請參閱 ASP.NET Core 中的程式碼分析

使用 WebApplication 建立中介軟體管線

ASP.NET Core 要求管線由要求委派序列組成,並會一個接著一個呼叫。 下圖說明此概念。 執行緒遵循黑色箭號執行。

要求處理模式說明要求抵達,經過三個中介軟體所處理,然後應用程式送出回應。每個中介軟體會執行其邏輯,並於 next() 陳述式中將要求遞交至下個中介軟體。在第三個中介軟體處理要求後,要求會反向傳回經前兩個中介軟體,以在其 next() 陳述式後,至作為回應離開應用程式傳到用戶端前的期間內,進行額外處理。

每一個委派皆能在下個委派的前後執行作業。 處理例外狀況的委派必須提前在管線中呼叫,以便其可與管線後續階段中所發生的例外狀況達成一致。

最簡潔的 ASP.NET Core 應用程式會設定單一要求委派來處理所有要求。 此情況不包含實際要求管線。 反之,系統會呼叫單一匿名函式來回應每個 HTTP 要求。

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello world!");
});

app.Run();

將多個要求委派鏈結在一起的方法是使用 Usenext 參數代表管線中的下個委派。 您可以「不」呼叫 next 參數來對管線執行最少運算。 您通常可以在 next 委派的前後執行動作,如下列範例所示:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.Use(async (context, next) =>
{
    // Do work that can 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.");
});

app.Run();

當委派不將要求傳遞到下一個委派時,這就是所謂讓要求管線短路。 因為最少運算可避免不必要的工作,所以經常使用。 例如,靜態檔案中介軟體可以做為終端中介軟體使用,方式是處理靜態檔案的要求,並對剩餘的管線執行短路。 在中介軟體之前新增到管線且終結進一步處理的中介軟體在其 next.Invoke 陳述式之後仍然處理程式碼。 不過,查看下列有關嘗試寫入已傳送之回應的警告。

警告

請不要在回應已傳送給用戶端之後呼叫 next.Invoke。 回應啟動後,變更為 HttpResponse 會擲回例外狀況。 例如,設定標頭和狀態碼等都會擲回例外狀況。 若在呼叫 next 後寫入回應本文:

  • 可能導致違反通訊協定。 例如,寫入超過指定 Content-Length 的內容。
  • 可能損毀本文格式。 例如,將 HTML 頁尾寫入 CSS 檔。

HasStarted 是實用的提示,可表示是否已傳送標頭 (或) 是否已寫入本文。

Run 委派不會收到 next 參數。 第一個 Run 委派一律是終端機,且會終止管線。 Run 是一種慣例。 有些中介軟體元件可能將執行於管線尾端的 Run[Middleware] 方法公開:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.Use(async (context, next) =>
{
    // Do work that can 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.");
});

app.Run();

如果您想要查看翻譯為英文以外語言的程式碼註解,請在此 GitHub 討論問題中告訴我們。

在上述範例中,Run 委派會將 "Hello from 2nd delegate." 寫入回應,然後終止管線。 如果在 Run 委派之後新增另一個 UseRun 委派,則不會呼叫。

偏好需要將內容傳遞至下一個的 app.Use 多載

未配置 app.Use 擴充方法:

  • 需要將內容傳遞至 next
  • 儲存兩個內部個別要求配置,在使用其他多載時需要用到。

如需詳細資訊,請參閱這個 GitHub 問題 \(英文\)。

中介軟體順序

下圖顯示 ASP.NET Core MVC 和 Razor Pages 應用程式的完整要求處理管線。 您可以查看一般應用程式中現有中介軟體的排序方式,以及新增自訂中介軟體的位置。 您可以完全控制重新排序現有中介軟體的方式,或視需要插入新的自訂中介軟體,以用於您的案例。

ASP.NET Core 中介軟體管線

上圖中的 [端點] 中介軟體會針對對應的應用程式類型 (MVC 或 Razor Pages) 執行篩選管線。

上圖中的 [路由] 中介軟體顯示下列 [靜態檔案]。 這是專案範本藉由明確呼叫 app.UseRouting 應用程式所實作的順序。 如果您未呼叫 app.UseRouting,則 [路由] 中介軟體預設會在管線開頭執行。 如需詳細資訊,請參閱路由

ASP.NET Core 篩選器管線

Program.cs 檔案內中介軟體元件的新增順序可定義在要求時叫用中介軟體元件的順序及回應的反向順序。 對安全性、效能與功能性而言,此順序相當重要

Program.cs 中的下列醒目提示程式碼會以一般建議的順序新增安全性相關的中介軟體元件:

using IndividualAccountsExample.Data;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseMigrationsEndPoint();
}
else
{
    app.UseExceptionHandler("/Error");
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
// app.UseCookiePolicy();

app.UseRouting();
// app.UseRequestLocalization();
// app.UseCors();

app.UseAuthentication();
app.UseAuthorization();
// app.UseSession();
// app.UseResponseCompression();
// app.UseResponseCaching();

app.MapRazorPages();
app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();

在上述程式碼中:

  • 使用個別使用者帳戶建立新 Web 應用程式時,未新增的中介軟體會加以註解化。
  • 並非所有中介軟體都會以這個確切的順序出現,但有許多中介軟體都會依這個順序出現。 例如:
    • UseCorsUseAuthenticationUseAuthorization 必須以顯示的順序出現。
    • UseCors 目前必須出現在 UseResponseCaching 之前。 GitHub 問題 dotnet/aspnetcore #23218 中會說明這項需求。
    • UseRequestLocalization 必須出現在可能檢查要求文化特性 (例如 app.UseMvcWithDefaultRoute()) 的任何中介軟體之前。

在某些情況下,中介軟體有不同的排序。 例如,快取和壓縮排序是案例特定的,而且有多個有效的排序。 例如:

app.UseResponseCaching();
app.UseResponseCompression();

使用上述程式碼時,可藉由快取壓縮的回應來減少 CPU 使用量,但您最後可能會使用不同的壓縮演算法 (例如 Gzip 或 Brotli) 來快取資源的多個表示法。

下列順序結合了靜態檔案,可允許快取壓縮的靜態檔案:

app.UseResponseCaching();
app.UseResponseCompression();
app.UseStaticFiles();

下列 Program.cs 程式碼會新增適用於一般應用程式案例的中介軟體元件:

  1. 例外狀況/錯誤處理
    • 當應用程式在開發環境中執行時:
    • 當應用程式在生產環境中執行時:
      • 例外狀況處理常式中介軟體 (UseExceptionHandler) 會攔截在下列中介軟體中擲回的例外狀況。
      • HTTP 靜態傳輸安全性通訊協定 (HSTS) 中介軟體 (UseHsts) 會新增 Strict-Transport-Security 標頭。
  2. HTTPS 重新導向中介軟體 (UseHttpsRedirection) 會將 HTTP 要求重新導向到 HTTPS。
  3. 靜態檔案中介軟體 (UseStaticFiles) 會傳回靜態檔案並縮短進一步的要求處理時間。
  4. Cookie 原則中介軟體 (UseCookiePolicy) 會使應用程式符合歐盟一般資料保護歸調 (GDPR) 法規。
  5. 路由中介軟體 (UseRouting) 以路由要求。
  6. 驗證中介軟體 (UseAuthentication) 會嘗試在允許使用者存取安全資源之前先驗證使用者。
  7. 授權中介軟體 (UseAuthorization) 可授權使用者存取安全資源。
  8. 工作階段中介軟體 (UseSession) 會建立並維護工作階段狀態。 若應用程式使用工作階段狀態,請在 Cookie 原則中介軟體之後、MVC 中介軟體之前呼叫工作階段中介軟體。
  9. 端點路由中介軟體 (UseEndpointsMapRazorPages) 將 Razor Pages 端點新增至要求管線。
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.MapRazorPages();

在上面的範例程式碼中,每個中介軟體擴充方法都會透過 Microsoft.AspNetCore.Builder 命名空間在 WebApplicationBuilder 上公開。

UseExceptionHandler 是第一個新增到管道的中介軟體元件。 因此,例外處理常式中介軟體會攔截後續呼叫中發生的所有例外狀況。

靜態檔案中介軟體會提前在管線中呼叫,以便其無須逐一處理剩餘的元件,就能處理要求及執行最少運算。 靜態檔案中介軟體不會執行授權檢查。 靜態檔案中介軟體提供的所有檔案 (包括在 wwwroot 下的檔案) 皆可公開使用。 如需保護靜態檔案的方法,請參閱 ASP.NET Core 中的靜態檔案

若靜態檔案中介軟體未處理要求,該要求會繼續傳遞給執行驗證的驗證中介軟體 (UseAuthentication)。 驗證不會對未經驗證的要求執行最少運算。 雖然驗證中介軟體會驗證要求,但只有在 MVC 選取特定 Razor Page 或 MVC 控制器及動作後,才會進行驗證 (與拒絕)。

下列範例示範靜態檔案中介軟體在回應壓縮中介軟體之前處理靜態檔案要求之前的靜態檔案順序。 靜態檔案並不會以此中介軟體順序壓縮。 可以壓縮 Razor Pages 回應。

// Static files aren't compressed by Static File Middleware.
app.UseStaticFiles();

app.UseRouting();

app.UseResponseCompression();

app.MapRazorPages();

如需單頁應用程式的相關資訊,請參閱 ReactAngular 專案範本的指南。

UseCors 和 UseStaticFiles 順序

呼叫 UseCorsUseStaticFiles 的順序取決於應用程式。 如需詳細資訊,請參閱 UseCors 和 UseStaticFiles 順序

轉送的標頭中介軟體順序

應在其他中介軟體之前執行轉接標頭中介軟體。 這種排序可確保依賴轉送標頭資訊的中介軟體可以耗用用於處理的標頭值。 若要在診斷和錯誤處理中介軟體之後執行轉送標頭中介軟體,請參閱轉送標頭中介軟體順序

將中介軟體管線分支

Map 擴充方法則是用來分支管線的慣例。 Map 會依據指定要求路徑的相符項目將要求管線分支。 如果要求路徑以指定路徑為開頭,則會執行分支。

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.Map("/map1", HandleMapTest1);

app.Map("/map2", HandleMapTest2);

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

app.Run();

static void HandleMapTest1(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        await context.Response.WriteAsync("Map Test 1");
    });
}

static void HandleMapTest2(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        await context.Response.WriteAsync("Map Test 2");
    });
}

下表說明使用上述程式碼後,來自 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" processing
    });
    level1App.Map("/level2b", level2BApp => {
        // "/level1/level2b" processing
    });
});

Map 也可以一次比對多個線段:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.Map("/map1/seg1", HandleMultiSeg);

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

app.Run();

static void HandleMultiSeg(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        await context.Response.WriteAsync("Map Test 1");
    });
}

MapWhen 會依據指定述詞的結果將要求管線分支。 Func<HttpContext, bool> 類型的任何述詞皆可用來將要求對應至管線的新分支。 下列範例會使用述詞來偵測查詢字串變數 branch 是否存在:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapWhen(context => context.Request.Query.ContainsKey("branch"), HandleBranch);

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

app.Run();

static void HandleBranch(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        var branchVer = context.Request.Query["branch"];
        await context.Response.WriteAsync($"Branch used = {branchVer}");
    });
}

下表顯示使用上述程式碼後,來自 http://localhost:1234 的要求及回應:

要求 回應
localhost:1234 Hello from non-Map delegate.
localhost:1234/?branch=main Branch used = main

UseWhen 也會依據指定述詞的結果將要求管線分支。 不同於使用 MapWhen,如果分支未尋找最短路徑或包含終端中介軟體,則此分支會重新加入主要管線:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.UseWhen(context => context.Request.Query.ContainsKey("branch"),
    appBuilder => HandleBranchAndRejoin(appBuilder));

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

app.Run();

void HandleBranchAndRejoin(IApplicationBuilder app)
{
    var logger = app.ApplicationServices.GetRequiredService<ILogger<Program>>(); 

    app.Use(async (context, next) =>
    {
        var branchVer = context.Request.Query["branch"];
        logger.LogInformation("Branch used = {branchVer}", branchVer);

        // Do work that doesn't write to the Response.
        await next();
        // Do other work that doesn't write to the Response.
    });
}

在上述範例中,會針對所有要求撰寫 Hello from non-Map delegate. 的回應。 如果要求包含查詢字串變數 branch,則會在重新加入主要管線之前記錄其值。

內建的中介軟體

ASP.NET Core 隨附下列中介軟體元件。 「順序」欄說明 中介軟體在要求處理管線中的位置,以及中介軟體可終止要求處理的情況。 當中介軟體將要求處理管線短路並防止接下來的下游中介軟體處理要求時,這就是所謂的「終端中介軟體」。 如需詳細資訊,請參閱使用 IApplicationBuilder 建立中介軟體管線

中介軟體 描述
驗證 提供驗證支援。 在需要 HttpContext.User 之前。 OAuth 回呼的終端機。
授權 提供授權支援。 緊接在驗證中介軟體之後。
Cookie 原則 追蹤使用者對用於儲存個人資訊的同意,並強制執行 cookie 欄位的最低標準,例如 secureSameSite 在發出 cookie 的中介軟體之前。 範例:驗證、工作階段、MVC (TempData)。
CORS 設定跨原始來源資源共用。 在使用 CORS 的元件之前。 由於這個錯誤UseCors 目前必須在 UseResponseCaching 之前進行。
DeveloperExceptionPage 產生頁面,其中包含僅供 [開發] 環境中使用的錯誤資訊。 在產生錯誤的元件之前。 當環境為 [開發] 時,專案範本會自動將此中介軟體註冊為管線中的第一個中介軟體。
診斷 數個不同的中介軟體,可提供開發人員例外狀況頁面、例外狀況處理、狀態字碼頁,以及新應用程式的預設網頁。 在產生錯誤的元件之前。 例外狀況的終端機,或為新應用程式提供預設網頁。
轉送標頭 將設為 Proxy 的標頭轉送到目前要求。 在使用更新方法的欄位之前。 範例:配置、主機,用戶端 IP、方法。
健康狀態檢查 檢查 ASP.NET Core 應用程式及其相依性的健康狀態,例如檢查資料庫可用性。 若某項要求與健康狀態檢查端點相符,則會是終端機。
標頭傳播 將 HTTP 標頭從傳入要求傳播至傳出 HTTP 用戶端要求。
HTTP 記錄 記錄 HTTP 要求和回應。 中介軟體管線的開頭。
HTTP 方法覆寫 允許傳入的 POST 要求覆寫方法。 在使用更新方法的元件之前。
HTTPS 重新導向 將所有 HTTP 要求都重新導向至 HTTPS。 在使用 URL 的元件之前。
HTTP 嚴格的傳輸安全性 (HSTS) 增強安全性的中介軟體,可新增特殊的回應標頭。 在傳送回應前和修改要求的元件後。 範例:轉送的標頭、URL 重寫。
MVC 使用 MVC/Razor Pages 處理要求。 若要求符合路由則終止。
OWIN 以 OWIN 為基礎之應用程式、伺服器和中介軟體的 Interop。 若 OWIN 中介軟體完全處理要求則終止。
速率限制 提供速率限制端點的支援。 在應該限制的元件之前。
要求解壓縮 提供解壓縮要求的支援。 在讀取要求本文的元件之前。
回應快取 提供快取回應的支援。 在需要快取的元件之前。 UseCORS 必須位於 UseResponseCaching 之前。
回應壓縮 提供壓縮回應的支援。 在需要壓縮的元件之前。
要求當地語系化 提供當地語系化支援。 在偵測當地語系化的元件之前。 使用 RouteDataRequestCultureProvider 時,必須在路由中介軟體之後出現。
端點路由 定義並限制要求路由。 比對路由的終端機。
SPA 傳回單一頁面應用程式 (SPA) 的預設頁面,以處理中介軟體鏈結中從這裡開始的所有要求 鏈結中的晚期,讓用於提供靜態檔案、MVC 動作等的其他中介軟體優先。
工作階段 提供管理使用者工作階段的支援。 在需要工作階段的元件之前。
靜態檔案 支援靜態檔案的提供和目錄瀏覽。 若要求符合檔案則終止。
URL 重寫 提供重寫 URL 及重新導向要求的支援。 在使用 URL 的元件之前。
W3CLogging W3C 擴充記錄檔格式產生伺服器存取記錄。 中介軟體管線的開頭。
WebSocket 啟用 WebSockets 通訊協定。 在接受 WebSocket 要求的必要元件之前。

其他資源

Rick AndersonSteve Smith 撰寫

中介軟體為組成應用程式管線的軟體,用以處理要求與回應。 每個元件:

  • 可選擇是否要將要求傳送到管線中的下一個元件。
  • 可以下一個元件的前後執行工作。

要求委派用於建置要求管線, 其會處理每個 HTTP 要求。

要求委派的設定方式為使用 RunMapUse 擴充方法。 您可將個別要求委派指定為內嵌匿名方法 (在內嵌中介軟體中呼叫),或於可重複使用的類別中加以定義。 這些可重複使用的類別及內嵌匿名方法皆為「中介軟體」,也稱為「中介軟體元件」。 要求管線中的每個中介軟體元件負責叫用管線中下一個元件,或對管線執行最少運算。 當中介軟體短路時,稱為「終端中介軟體」,因為它會防止接下來的中介軟體處理要求。

將 HTTP 處理常式和模組遷移至 ASP.NET Core 中介軟體說明 ASP.NET Core 和 ASP.NET 4.x 之間的要求管線差異,並提供其他中介軟體範例。

使用 IApplicationBuilder 建立中介軟體管線

ASP.NET Core 要求管線由要求委派序列組成,並會一個接著一個呼叫。 下圖說明此概念。 執行緒遵循黑色箭號執行。

要求處理模式說明要求抵達,經過三個中介軟體所處理,然後應用程式送出回應。每個中介軟體會執行其邏輯,並於 next() 陳述式中將要求遞交至下個中介軟體。在第三個中介軟體處理要求後,要求會反向傳回經前兩個中介軟體,以在其 next() 陳述式後,至作為回應離開應用程式傳到用戶端前的期間內,進行額外處理。

每一個委派皆能在下個委派的前後執行作業。 處理例外狀況的委派必須提前在管線中呼叫,以便其可與管線後續階段中所發生的例外狀況達成一致。

最簡潔的 ASP.NET Core 應用程式會設定單一要求委派來處理所有要求。 此情況不包含實際要求管線。 反之,系統會呼叫單一匿名函式來回應每個 HTTP 要求。

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

將多個要求委派鏈結在一起的方法是使用 Usenext 參數代表管線中的下個委派。 您可以「不」呼叫「下一個」參數來對管線執行最少運算。 您通常可以在下個委派的前後執行動作,如下列範例所示:

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 的內容。
  • 可能損毀本文格式。 例如,將 HTML 頁尾寫入 CSS 檔。

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

如果您想要查看翻譯為英文以外語言的程式碼註解,請在此 GitHub 討論問題中告訴我們。

在上述範例中,Run 委派會將 "Hello from 2nd delegate." 寫入回應,然後終止管線。 如果在 Run 委派之後新增另一個 UseRun 委派,則不會呼叫。

中介軟體順序

下圖顯示 ASP.NET Core MVC 和 Razor Pages 應用程式的完整要求處理管線。 您可以查看一般應用程式中現有中介軟體的排序方式,以及新增自訂中介軟體的位置。 您可以完全控制重新排序現有中介軟體的方式,或視需要插入新的自訂中介軟體,以用於您的案例。

ASP.NET Core 中介軟體管線

上圖中的 [端點] 中介軟體會針對對應的應用程式類型 (MVC 或 Razor Pages) 執行篩選管線。

ASP.NET Core 篩選器管線

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.UseResponseCompression();
    // app.UseResponseCaching();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
        endpoints.MapControllerRoute(
            name: "default",
            pattern: "{controller=Home}/{action=Index}/{id?}");
    });
}

在上述程式碼中:

  • 使用個別使用者帳戶建立新 Web 應用程式時,未新增的中介軟體會加以註解化。
  • 並非所有中介軟體都會以這個確切的順序出現,但有許多中介軟體都會依這個順序出現。 例如:
    • UseCorsUseAuthenticationUseAuthorization 必須以顯示的順序出現。
    • 由於這個錯誤UseCors 目前必須在 UseResponseCaching 之前顯示。
    • UseRequestLocalization 必須出現在可能檢查要求文化特性 (例如 app.UseMvcWithDefaultRoute()) 的任何中介軟體之前。

在某些情況下,中介軟體有不同的排序。 例如,快取和壓縮排序是案例特定的,而且有多個有效的排序。 例如:

app.UseResponseCaching();
app.UseResponseCompression();

使用上述程式碼時,可藉由快取壓縮的回應來儲存 CPU,但您最後可能會使用不同的壓縮演算法 (例如 Gzip 或 Brotli) 來快取資源的多個表示法。

下列順序結合了靜態檔案,可允許快取壓縮的靜態檔案:

app.UseResponseCaching();
app.UseResponseCompression();
app.UseStaticFiles();

下列 Startup.Configure 方法會新增適用於一般應用程式案例的中介軟體元件:

  1. 例外狀況/錯誤處理
    • 當應用程式在開發環境中執行時:
      • 開發人員例外狀況頁面中介軟體 (UseDeveloperExceptionPage) 會回報應用程式執行階段錯誤。
      • 資料錯誤頁面中介軟體會回報資料庫執行階段錯誤。
    • 當應用程式在生產環境中執行時:
      • 例外狀況處理常式中介軟體 (UseExceptionHandler) 會攔截在下列中介軟體中擲回的例外狀況。
      • HTTP 靜態傳輸安全性通訊協定 (HSTS) 中介軟體 (UseHsts) 會新增 Strict-Transport-Security 標頭。
  2. HTTPS 重新導向中介軟體 (UseHttpsRedirection) 會將 HTTP 要求重新導向到 HTTPS。
  3. 靜態檔案中介軟體 (UseStaticFiles) 會傳回靜態檔案並縮短進一步的要求處理時間。
  4. Cookie 原則中介軟體 (UseCookiePolicy) 會使應用程式符合歐盟一般資料保護歸調 (GDPR) 法規。
  5. 路由中介軟體 (UseRouting) 以路由要求。
  6. 驗證中介軟體 (UseAuthentication) 會嘗試在允許使用者存取安全資源之前先驗證使用者。
  7. 授權中介軟體 (UseAuthorization) 可授權使用者存取安全資源。
  8. 工作階段中介軟體 (UseSession) 會建立並維護工作階段狀態。 若應用程式使用工作階段狀態,請在 Cookie 原則中介軟體之後、MVC 中介軟體之前呼叫工作階段中介軟體。
  9. 端點路由中介軟體 (UseEndpointsMapRazorPages) 將 Razor Pages 端點新增至要求管線。
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 Page 或 MVC 控制器及動作後,才會進行驗證 (與拒絕)。

下列範例示範靜態檔案中介軟體在回應壓縮中介軟體之前處理靜態檔案要求之前的靜態檔案順序。 靜態檔案並不會以此中介軟體順序壓縮。 可以壓縮 Razor Pages 回應。

public void Configure(IApplicationBuilder app)
{
    // Static files aren't compressed by Static File Middleware.
    app.UseStaticFiles();

    app.UseRouting();

    app.UseResponseCompression();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
    });
}

針對單頁應用程式 (SPA),SPA 中介軟體 UseSpaStaticFiles 通常在中介軟體管線中最後出現。 SPA 中介軟體最後出現:

  • 允許所有其他中介軟體先回應相符的要求。
  • 允許具有用戶端路由的 SPA 針對伺服器應用程式無法辨識的所有路由執行。

如需 SPA 的更多詳細資料,請參閱 ReactAngular 專案範本的指南。

轉送的標頭中介軟體順序

應在其他中介軟體之前執行轉接標頭中介軟體。 這種排序可確保依賴轉送標頭資訊的中介軟體可以耗用用於處理的標頭值。 若要在診斷和錯誤處理中介軟體之後執行轉送標頭中介軟體,請參閱轉送標頭中介軟體順序

將中介軟體管線分支

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

下表說明使用上述程式碼後,來自 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" processing
    });
    level1App.Map("/level2b", level2BApp => {
        // "/level1/level2b" processing
    });
});

Map 也可以一次比對多個線段:

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

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

下表顯示使用上述程式碼後,來自 http://localhost:1234 的要求及回應:

要求 回應
localhost:1234 Hello from non-Map delegate.
localhost:1234/?branch=main Branch used = main

UseWhen 也會依據指定述詞的結果將要求管線分支。 不同於使用 MapWhen,如果分支未尋找最短路徑或包含終端中介軟體,則此分支會重新加入主要管線:

public class Startup
{
    private void HandleBranchAndRejoin(IApplicationBuilder app, ILogger<Startup> logger)
    {
        app.Use(async (context, next) =>
        {
            var branchVer = context.Request.Query["branch"];
            logger.LogInformation("Branch used = {branchVer}", branchVer);

            // Do work that doesn't write to the Response.
            await next();
            // Do other work that doesn't write to the Response.
        });
    }

    public void Configure(IApplicationBuilder app, ILogger<Startup> logger)
    {
        app.UseWhen(context => context.Request.Query.ContainsKey("branch"),
                               appBuilder => HandleBranchAndRejoin(appBuilder, logger));

        app.Run(async context =>
        {
            await context.Response.WriteAsync("Hello from main pipeline.");
        });
    }
}

在上述範例中,會針對所有要求寫入「Hello from main pipeline.」的回應。 如果要求包含查詢字串變數 branch,則會在重新加入主要管線之前記錄其值。

內建的中介軟體

ASP.NET Core 隨附下列中介軟體元件。 「順序」欄說明 中介軟體在要求處理管線中的位置,以及中介軟體可終止要求處理的情況。 當中介軟體將要求處理管線短路並防止接下來的下游中介軟體處理要求時,這就是所謂的「終端中介軟體」。 如需詳細資訊,請參閱使用 IApplicationBuilder 建立中介軟體管線

中介軟體 描述
驗證 提供驗證支援。 在需要 HttpContext.User 之前。 OAuth 回呼的終端機。
授權 提供授權支援。 緊接在驗證中介軟體之後。
Cookie 原則 追蹤使用者對用於儲存個人資訊的同意,並強制執行 cookie 欄位的最低標準,例如 secureSameSite 在發出 cookie 的中介軟體之前。 範例:驗證、工作階段、MVC (TempData)。
CORS 設定跨原始來源資源共用。 在使用 CORS 的元件之前。 由於這個錯誤UseCors 目前必須在 UseResponseCaching 之前進行。
診斷 數個不同的中介軟體,可提供開發人員例外狀況頁面、例外狀況處理、狀態字碼頁,以及新應用程式的預設網頁。 在產生錯誤的元件之前。 例外狀況的終端機,或為新應用程式提供預設網頁。
轉送標頭 將設為 Proxy 的標頭轉送到目前要求。 在使用更新方法的欄位之前。 範例:配置、主機,用戶端 IP、方法。
健康狀態檢查 檢查 ASP.NET Core 應用程式及其相依性的健康狀態,例如檢查資料庫可用性。 若某項要求與健康狀態檢查端點相符,則會是終端機。
標頭傳播 將 HTTP 標頭從傳入要求傳播至傳出 HTTP 用戶端要求。
HTTP 方法覆寫 允許傳入的 POST 要求覆寫方法。 在使用更新方法的元件之前。
HTTPS 重新導向 將所有 HTTP 要求都重新導向至 HTTPS。 在使用 URL 的元件之前。
HTTP 嚴格的傳輸安全性 (HSTS) 增強安全性的中介軟體,可新增特殊的回應標頭。 在傳送回應前和修改要求的元件後。 範例:轉送的標頭、URL 重寫。
MVC 使用 MVC/Razor Pages 處理要求。 若要求符合路由則終止。
OWIN 以 OWIN 為基礎之應用程式、伺服器和中介軟體的 Interop。 若 OWIN 中介軟體完全處理要求則終止。
回應快取 提供快取回應的支援。 在需要快取的元件之前。 UseCORS 必須位於 UseResponseCaching 之前。
回應壓縮 提供壓縮回應的支援。 在需要壓縮的元件之前。
要求當地語系化 提供當地語系化支援。 在偵測當地語系化的元件之前。 使用 RouteDataRequestCultureProvider 時,必須在路由中介軟體之後出現。
端點路由 定義並限制要求路由。 比對路由的終端機。
SPA 傳回單一頁面應用程式 (SPA) 的預設頁面,以處理中介軟體鏈結中從這裡開始的所有要求 鏈結中的晚期,讓用於提供靜態檔案、MVC 動作等的其他中介軟體優先。
工作階段 提供管理使用者工作階段的支援。 在需要工作階段的元件之前。
靜態檔案 支援靜態檔案的提供和目錄瀏覽。 若要求符合檔案則終止。
URL 重寫 提供重寫 URL 及重新導向要求的支援。 在使用 URL 的元件之前。
WebSocket 啟用 WebSockets 通訊協定。 在接受 WebSocket 要求的必要元件之前。

其他資源