中間件是一種裝配到應用管道以處理請求和響應的軟件。 每個組件:
請求委托用于生成請求管道。 請求委托處理每個 HTTP 請求。
使用 RunMap 和 Use 擴展方法來配置請求委托。 可將一個單獨的請求委托并行指定為匿名方法(稱為并行中間件),或在可重用的類中對其進行定義。 這些可重用的類和并行匿名方法即為中間件,也叫中間件組件。 請求管道中的每個中間件組件負責調用管道中的下一個組件,或使管道短路。 當中間件短路時,它被稱為“終端中間件”,因為它阻止中間件進一步處理請求。
將 HTTP 處理程序和模塊遷移到 ASP.NET Core 中間件 介紹了 ASP.NET Core 和 ASP.NET 4.x 中請求管道之間的差異,并提供了更多的中間件示例。
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 后寫入響應正文:
HasStarted 是一個有用的提示,指示是否已發(fā)送標頭或已寫入正文。
向 Startup.Configure 方法添加中間件組件的順序定義了針對請求調用這些組件的順序,以及響應的相反順序。 此排序對于安全性、性能和功能至關重要。
以下 Startup.Configure 方法將為常見應用方案添加中間件組件:
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 配置 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: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。
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:1234 | Hello from non-Map delegate. |
localhost:1234/?branch=master | Branch 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 請求所需的組件之前。 |
更多建議: