這個系列的初衷是便於自己總結與回顧,把筆記本上面的東西轉移到這裡,態度不由得謹慎許多,下麵是我參考的資源: ASP.NET Core 中文文檔目錄 官方文檔 記在這裡的東西我會不斷的完善豐滿,對於文章裡面一些局限於我自己知識積累的觀點,希望沒有跳走堅持看完的朋友,能夠予以指正和鼓勵. 這個系列的初衷 ...
這個系列的初衷是便於自己總結與回顧,把筆記本上面的東西轉移到這裡,態度不由得謹慎許多,下麵是我參考的資源:
記在這裡的東西我會不斷的完善豐滿,對於文章裡面一些局限於我自己知識積累的觀點,希望沒有跳走堅持看完的朋友,能夠予以指正和鼓勵.
系列目錄
中間件
中間件是一種裝配到應用管道以處理請求和響應的軟體。 每個組件:
- 選擇是否將請求傳遞到管道中的下一個組件
- 可在管道中的下一個組件前後執行工作
請求委托用於生成請求管道。 請求委托處理每個 HTTP 請求。
請求委托通過使用 IApplicationBuilder 類型的 Run、Map 以及 Use 擴展方法來配置,併在Starup類中傳給configure方法 。每個單獨的請求委托都可以被指定為一個 內嵌匿名方法,或其定義在一個可重用的類中。這些可重用的類被稱作中間件或中間件組件。每個位於請求管道內的中間件組件負責調用管道中下一個組件,或適時短路調用鏈。
使用 IApplicationBuilder 創建中間件管道
ASP.NET Core 請求管道包含一系列請求委托,依次調用
這系列委托並不是一條路走到底:每個委托均可在下一個委托前後執行操作,使得請求管道短路.
短路的方式存在兩種:
1任何委托都能選擇停止傳遞到下一個委托,轉而自己處理該請求。這被叫做請求管道的短路
1 public void Configure(IApplicationBuilder app) 2 { 3 //只會列印"Hello, World!",下麵的已經被短路 4 app.Run(async context => 5 { 6 await context.Response.WriteAsync("Hello, World!"); 7 }); 8 9 app.Run(async context => 10 { 11 await context.Response.WriteAsync("Hello, World, Again!"); 12 }); 13 }
所以,最簡單的 ASP.NET 應用程式只需要單個請求委托來處理所有請求即可。而在這種情況下並不存在所謂的“管道”,調用單個匿名函數以響應每個 HTTP 請求
2委托可以不將請求傳遞給下一個委托時,它被稱為“讓請求管道短路”。
1 public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) 2 { 3 var logger = loggerFactory.CreateLogger(""); 4 app.Use(async (context, next) => 5 { 6 logger.LogInformation("Handling request."); 7 //await next.Invoke(); //next 參數表示管道內下一個委托.註釋則短路 8 }); 9 10 app.Run(async (context) => 11 { 12 await context.Response.WriteAsync("the page lost"); 13 }); 14 }
那麼用 Use 將多個請求委托鏈接(參見下麵順序一節示例)在一起時。 就可以通過不調用 next 參數使管道短路。
短路的存在是非常有意義的,這樣可以避免不必要的工作。 例如,靜態文件中間件可以處理對靜態文件的請求,並讓管道的其餘部分短路,從而起到終端中間件的作用。 如果
中間件添加到管道中,且位於終止進一步處理的中間件前,它們仍處理next.Invoke 語句後面的代碼。
Warnning
響應發送到客戶端後,請勿調用next.Invoke。 響應開始之後,對HttpResponse的更改將拋出異常。
例如,設置響應頭,狀態代碼等更改將會引發異常。在調用next之後寫入響應體:
- 可能導致協議違規。 例如,寫入超過content-length所述內容長度。
- 可能會破壞響應內容格式。 例如,將HTML頁腳寫入CSS文件。
HttpResponse.HasStarted是一個有用的提示,指示是否已發送響應頭和/或正文已寫入。
順序
你添加中間件組件的順序通常會影響到它們處理請求的順序,然後在響應時則以相反的順序返回。這對應用程式安全、性能和功能很關鍵。
以下為常見應用方案中間件組件順序:
- 錯誤處理(同時針對於開發環境和非開發環境)
- 靜態文件伺服器
- 身份驗證
- MVC
對應的代碼如下:
1 public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) 2 { 3 loggerFactory.AddConsole(Configuration.GetSection("Logging")); 4 loggerFactory.AddDebug(); 5 6 if (env.IsDevelopment()) 7 { 8 app.UseDeveloperExceptionPage(); 9 app.UseDatabaseErrorPage(); 10 app.UseBrowserLink(); 11 } 12 else 13 { 14 app.UseExceptionHandler("/Home/Error"); 15 } 16 17 app.UseStaticFiles(); 18 19 app.UseIdentity(); 20 21 app.UseMvc(routes => 22 { 23 routes.MapRoute( 24 name: "default", 25 template: "{controller=Home}/{action=Index}/{id?}"); 26 }); 27 }
上面的代碼中(在非開發環境時):
UseExceptionHandler 是第一個被加入到管道中的中間件,因此將會捕獲之後調用中出現的任何異常。
靜態文件模塊 不提供授權檢查,由它提供的任何文件,包括那些位於wwwroot 下的文件都是公開的可被訪問的。如果你想基於授權來提供這些文件:
- 將它們存放在 wwwroot 外面以及任何靜態文件中間件都可訪問得到的目錄。
- 利用控制器操作來判斷授權是否允許,如果允許則通過返回 FileResult 來提供它們。
被靜態文件模塊處理的請求會在管道中被短路。如果該請求不是由靜態文件模塊處理,那麼它就會被傳給 Identity 模塊 執行身份驗證。如果未通過身份驗證,則管道將被短路。如果請求的身份驗證沒有失敗,則管道的最後一站是 MVC 框架。
如果修改Configure方法中間件添加的順序,則上述預期的的功能將會發生變
Run Use Map
使用 Run、Map 和 Use 配置 HTTP 管道。
Run 方法將會短路管道,所以Run 應該只能在你的管道尾部被調用。並且某些中間件組件可公開在管道末尾運行的 Run[Middleware] 方法
Use用來構建請求管道,例如上面那套常用的請求管道,當然它也可以用來短路,發揮和run一樣的作用,
Map*擴展被用於分支管道,支持基於請求路徑或使用謂詞來進入分支,Map 只接受路徑,並配置單獨的中間件管道的功能。
在下例中,任何基於路徑 /maptest 的請求都會被管道中所配置的 HandleMapTest 方法所處理。
1 private static void HandleMapTest1(IApplicationBuilder app) 2 { 3 app.Run(async context => 4 { 5 await context.Response.WriteAsync("Map Test 1"); 6 }); 7 } 8 private static void HandleMapTest2(IApplicationBuilder app) 9 { 10 app.Run(async context => 11 { 12 await context.Response.WriteAsync("Map Test 2"); 13 }); 14 } 15 public void Configure(IApplicationBuilder app) 16 { 17 app.Map("/map1", HandleMapTest1); 18 app.Map("/map2", HandleMapTest2); 19 app.Run(async (context) => 20 { 21 await context.Response.WriteAsync("the page lost"); 22 }); 23 }
請求響應結果如下:
請求 | 響應 |
localhost:xxxx | the page lost |
localhost:xxxx/map1 | Map Test 1 |
localhost:xxxx/map2 | Map Test 2 |
localhost:xxxx/map3 | the page lost |
在使用 Map 時,將從 HttpRequest.Path 中刪除匹配的線段,並針對每個請求將該線段追加到HttpRequest.PathBase 。
MapWhen基於給定謂詞的結果創建請求管道分支。 Func<HttpContext, bool> 類型的任何謂詞均可用於將請求映射到管道的新分支。 在以下示例中,謂詞用於檢測查詢字元串變數branch 是否存在:
1 private static void HandleBranch(IApplicationBuilder app) 2 { 3 app.Run(async context => 4 { 5 var branchVer = context.Request.Query["branch"]; 6 await context.Response.WriteAsync($"Branch used = {branchVer}"); 7 }); 8 } 9 public void Configure(IApplicationBuilder app) 10 { 11 app.MapWhen(context => context.Request.Query.ContainsKey("branch"), HandleBranch); 12 app.Run(async context => 13 { 14 await context.Response.WriteAsync("Hello from non-Map delegate. <p>"); 15 }); 16 }
請求響應結果如下:
請求 | 響應 |
localhost:xxxx | Hello from non-Map delegate. |
localhost:xxxx/?branch=master | Branch used = master |
Map 支持嵌套,例如:
1 app.Map("/level1", level1App => 2 { 3 level1App.Map("/level2a", HandleMapTest1);// /level2a/level2a 4 level1App.Map("/level2b", HandleMapTest2);// /level2a/level2b 5 });
內置中間件
ASP.NET Core 附帶以下中間件組件。 “順序”列提供備註,以說明中間件在請求處理管道中的放置,以及中間件可能會終止請求處理的條件。 如果中間件讓請求處理管道短路,並阻止下游中間件進一步處理請求,它被稱為“終端中間件”,例如:靜態文件中間件
中間件 | 描述 | 順序 |
Authentication | 提供身份驗證支持。 | 在需要 HttpContext.User 之前。OAuth 回調的終端。 |
Cookie Policy | 跟蹤用戶是否同意存儲個人信息,並強制實施 cookie 欄位(如 secure和 SameSite )的最低標準 | 在發出 cookie 的中間件之前。 示例:身份驗證、會話、MVC (TempData) |
CORS | 配置跨域資源共用 | 資源共用。 在使用 CORS 的組件之前。 |
Exception Handling | 處理異常 | 在生成錯誤的組件之前。 |
Forwarded Headers | 將代理標頭轉發到當前請求 | 在使用已更新欄位的組件之前。 示例:方案、主機、客戶端 IP、方法。 |
Health Check | 檢查 ASP.NET Core 應用及其依賴項的運行狀況,如檢查資料庫可用性。 |
如果請求與運行狀況檢查終結點匹 |
HTTP Method Override | 允許傳入 POST 請求重寫方法 | 在使用已更新方法的組件之前。 |
HTTPS Redirection |
將所有 HTTP 請求重定向到 |
在使用 URL 的組件之前。 |
HTTP Strict Transport Security (HSTS) |
添加特殊響應標頭的安全增強中間 |
在發送響應之前,修改請求的組件之 |
MVC | 用 MVC/Razor Pages 處理請求(ASP.NET Core 2.0 或更高版本)。 | 如果請求與路由匹配,則為終端。 |
OWIN | 與基於 OWIN 的應用、伺服器和中間件進行互操作。 | 如果 OWIN 中間件處理完請求,則為終端。 |
Response Caching | 提供對緩存響應的支持。 | 在需要緩存的組件之前。 |
Response Compression | 提供對壓縮響應的支持。 | 在需要壓縮的組件之前。 |
Request Localization | 提供本地化支持 | 在對本地化敏感的組件之前。 |
Routing | 定義和約束請求路由。 | 用於匹配路由的終端。 |
Session | 提供對管理用戶會話的支持。 | 在需要會話的組件之前。 |
Static Files | 為提供靜態文件和目錄瀏覽提供支持。 | 如果請求與文件匹配,則為終端。 |
URL Rewriting | 提供對重寫 URL 和重定向請求的支持。 | 在使用 URL 的組件之前。 |
WebSockets | 啟用 WebSockets 協議。 | 在接受 WebSocket 請求所需的組件之前。 |
自定義中間件
對於更複雜的請求處理功能,ASP.NET 團隊推薦在自己的類中實現中間件,並暴露 IApplicationBuilder 擴展方法,這樣就能通過 Configure方法來被調用如下是一個自定義中間件實例,它從查詢字元串設置當前請求的Culture:
1 public class RequestCultureMiddleWare 2 { 3 private readonly RequestDelegate _next; 4 public RequestCultureMiddleWare(RequestDelegate next) 5 { 6 _next = next; 7 } 8 9 public async Task InvokeAsync(HttpContext context) 10 { 11 var cultureQuery = context.Request.Query["culture"]; 12 if (!string.IsNullOrWhiteSpace(cultureQuery)) 13 { 14 var culture = new CultureInfo(cultureQuery); 15 CultureInfo.CurrentCulture = culture; 16 CultureInfo.CurrentUICulture = culture; 17 } 18 await _next(context); 19 } 20 }
通過IApplicationBuilder的擴展方法暴露中間件:
1 public static class RequestMiddleWareExtensions 2 { 3 public static IApplicationBuilder UserRequestCultrue(this IApplicationBuilder builder) 4 { 5 return builder.UseMiddleware<RequestCultureMiddleWare>(); 6 } 7 8 }
通過 Startup下Configure 方法調用中間件:
1 public void Configure(IApplicationBuilder app) 2 { 3 app.UseRequestCulture(); 4 app.Run(async (context) => 5 { 6 await context.Response.WriteAsync( 7 $"Hello {CultureInfo.CurrentCulture.DisplayName}"); 8 }); 9 }
請求依賴項
由於中間件是在應用啟動時構造的,而不是按請求構造的,因此在每個請求過程中,中間件構造函數使用的範圍內生存期服務不與其他依賴關係註入類型共用。 如果必須在中間件和其他類型之間共用範圍內服務,請將這些服務添加到Invoke方法的簽名Invoke,方法可接受由 DI 填充的其他參數:
1 public class CustomMiddleware 2 { 3 private readonly RequestDelegate _next; 4 public CustomMiddleware(RequestDelegate next) 5 { 6 _next = next; 7 } 8 // IMyScopedService is injected into Invoke 9 public async Task Invoke(HttpContext httpContext, IMyScopedService svc) 10 { 11 svc.MyProperty = 1000; 12 await _next(httpContext); 13 } 14 }
(終)
文檔信息
- 發表作者: 半路獨行
- 發表出處: 博客園
- 原文地址: https://www.cnblogs.com/banluduxing/p/10723130.html
- 版權信息:本作品採用知識共用署名-非商業性使用-相同方式共用 4.0 國際許可協議進行許可。
感謝您的閱讀,如果您覺得閱讀本文對您有幫助,請點一下“推薦”按鈕。本文歡迎各位轉載,但是轉載文章之後必須在文章頁面中給出作者和原文連接。