ASP.NET Core 中文文档 第三章 原理(2)中间件
原文:Middleware
作者:Steve Smith?and?Rick Anderson
翻譯:劉怡(AlexLEWIS)
校對:許登洋(Seay)
章節:
什么是中間件
用 IApplicationBuilder 創建中間件管道
內置中間件
編寫中間件
擴展資源
查看或下載樣例代碼
什么是中間件
中間件是用于組成應用程序管道來處理請求和響應的組件。管道內的每一個組件都可以選擇是否將請求交給下一個組件、并在管道中調用下一個組件之前和之后執行某些操作。請求委托被用來建立請求管道,請求委托處理每一個 HTTP 請求。
請求委托通過使用?IApplicationBuilder?類型的?Run、Map?以及?Use?擴展方法來配置,并在?Startup?類中傳給?Configure?方法 。每個單獨的請求委托都可以被指定為一個內嵌匿名方法,或其定義在一個可重用的類中。這些可重用的類被稱作?中間件?或?中間件組件。每個位于請求管道內的中間件組件負責調用管道中下一個組件,或適時短路調用鏈。
Migrating HTTP Modules to Middleware?解釋了請求管道在 ASP.NET Core 和之前版本之間的區別,并提供了更多中間件樣例。
用 IApplicationBuilder 創建中間件管道
ASP.NET 請求管道由一系列的請求委托所構成,它們一個接著一個被調用,如圖所示(該執行線程按黑色箭頭的順序執行):
每個委托在下一個委托之前和之后都有機會執行操作。任何委托都能選擇停止傳遞到下一個委托,轉而自己處理該請求。這被叫做請求管道的短路,而且是一種有意義的設計,因為它可以避免不必要的工作。比方說,一個授權(authorization)中間件只有在通過身份驗證之后才調用下一個委托,否則它就會被短路并返回 “Not Authorized” 的響應。異常處理委托需要在管道的早期被調用,這樣它們就能夠捕捉到發生在管道內更深層次出現的異常了。
你可以看一下 Visual Studio 2015 附帶的默認 Web 站點模板關于請求管道設置的例子。Configure?方法增加了下列這些中間件組件:
錯誤處理(同時針對于開發環境和非開發環境)
靜態文件服務器
身份驗證
MVC
復制代碼
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory){loggerFactory.AddConsole(Configuration.GetSection("Logging"));loggerFactory.AddDebug(); ? ?if (env.IsDevelopment()){app.UseDeveloperExceptionPage();//手工高亮app.UseDatabaseErrorPage();//手工高亮app.UseBrowserLink();//手工高亮} ? ?else{app.UseExceptionHandler("/Home/Error");//手工高亮}app.UseStaticFiles();//手工高亮app.UseIdentity();//手工高亮// Add external authentication middleware below. To configure them please see http://go.microsoft.com/fwlink/?LinkID=532715app.UseMvc(routes =>//手工高亮{routes.MapRoute(name: "default", ? ? ? ? ? ?template: "{controller=Home}/{action=Index}/{id?}");}); }上面的代碼中(在非開發環境時),UseExceptionHandler?是第一個被加入到管道中的中間件,因此將會捕獲之后調用中出現的任何異常。
靜態文件模塊?不提供授權檢查,由它提供的任何文件,包括那些位于wwwroot?下的文件都是公開的可被訪問的。如果你想基于授權來提供這些文件:
將它們存放在?wwwroot?外面以及任何靜態文件中間件都可訪問得到的目錄。
利用控制器操作來判斷授權是否允許,如果允許則通過返回?FileResult?來提供它們。
被靜態文件模塊處理的請求會在管道中被短路(參見?Working with Static Files)。如果該請求不是由靜態文件模塊處理,那么它就會被傳給?Identity 模塊?執行身份驗證。如果未通過身份驗證,則管道將被短路。如果請求的身份驗證沒有失敗,則管道的最后一站是 MVC 框架。
注意
你添加中間件組件的順序通常會影響到它們處理請求的順序,然后在響應時則以相反的順序返回。這對應用程序安全、性能和功能很關鍵。在上面的代碼中,靜態文件中間件?在管道的早期被調用,這樣就能處理并及時短路管道,以避免請求走到不必要的組件中。身份驗證中間件被添加在任何需要身份認證的處理請求的前面。異常處理必須被注冊在其它中間件之前以便捕獲其它組件的異常。
最簡單的 ASP.NET 應用程序是使用單個請求委托來處理所有請求。事實上在這種情況下并不存在所謂的“管道”,調用單個匿名函數以相應每個 HTTP 請求。
復制代碼
app.Run(async context => { ? ?await context.Response.WriteAsync("Hello, World!"); });第一個?App.Run?委托中斷了管道。在下面的例子中,只有第一個委托(“Hello, World!”)會被運行。
復制代碼
public void Configure(IApplicationBuilder app){app.Run(async context =>{ ? ? ? ?await context.Response.WriteAsync("Hello, World!");//手工高亮});app.Run(async context =>{ ? ? ? ?await context.Response.WriteAsync("Hello, World, Again!");});將多個請求委托彼此鏈接在一起;next?參數表示管道內下一個委托。通過?不?調用?next?參數,你可以中斷(短路)管道。你通??梢栽趫绦邢乱粋€委托之前和之后執行一些操作,如下例所示:
復制代碼
public void ConfigureLogInline(IApplicationBuilder app, ILoggerFactory loggerfactory){loggerfactory.AddConsole(minLevel: LogLevel.Information); ? ?var logger = loggerfactory.CreateLogger(_environment);app.Use(async (context, next) =>//手工高亮{logger.LogInformation("Handling request."); ? ? ? ?await next.Invoke();//手工高亮logger.LogInformation("Finished handling request.");});app.Run(async context =>{ ? ? ? ?await context.Response.WriteAsync("Hello from " + _environment);//手工高亮}); }警告
應當避免在修改了?HttpResponse?之后還調用管道內下一個會修改響應的組件,從而導致它被送到客戶端處。
提示
當應用程序運行的環境設置為?LogInline?時,這個?ConfigureLogInline?方法就會被調動。要了解更多請訪問環境?Working with Multiple Environments?一章。本文剩下的篇幅將使用變化的?Configure[Environment]?來展示不同的選項。 Visual Studio 中運行示例代碼的最簡單辦法是使用?web?命令,該命令由?project.json?文件所配置。也可參考?Application Startup?。
在上例中,調用?await?next.Invoke()?將會調用下一個委托await?context.Response.WriteAsync("Hello?from?"?+?_environment);??蛻舳藢⑹盏筋A期的響應(“Hello from LogInline”),同時服務端這邊的控制臺將先后輸出如下信息:
Run,Map 與 Use
你可以使用?Run、Map?和?Use?配置 HTTP 管道。Run?方法將會短路管道(因為它不會調用?next?請求委托)。因此,Run?應該只能在你的管道尾部被調用。Run?是一種慣例,有些中間件組件可能會暴露他們自己的 Run[Middleware] 方法,而這些方法只能在管道末尾處運行。下面這兩個中間件等價的,其中有用到?Use?的版本沒有使用?next?參數:
復制代碼
public void ConfigureEnvironmentOne(IApplicationBuilder app){app.Run(async context =>//手工高亮{ ? ? ? ?await context.Response.WriteAsync("Hello from " + _environment);}); }public void ConfigureEnvironmentTwo(IApplicationBuilder app){app.Use(async (context, next) =>//手工高亮{ ? ? ? ?await context.Response.WriteAsync("Hello from " + _environment);}); }注意
IApplicationBuilder?接口向外暴露了一個?Use?方法,因此從技術上來說它們并不完全是?擴展?方法。
我們已經看了幾個關于如何通過?Use?構建請求管道的例子,同時約定了?Map*擴展被用于分支管道。當前的實現已支持基于請求路徑或使用謂詞來進入分支。Map?擴展方法用于匹配基于請求路徑的請求委托。Map?只接受路徑,并配置單獨的中間件管道的功能。在下例中,任何基于路徑?/maptest?的請求都會被管道中所配置的?HandleMapTest?方法所處理。
復制代碼
private static void HandleMapTest(IApplicationBuilder app){app.Run(async context =>{ ? ? ? ?await context.Response.WriteAsync("Map Test Successful");}); }public void ConfigureMapping(IApplicationBuilder app){app.Map("/maptest", HandleMapTest);//手工高亮}注意
當使用了?Map,每個請求所匹配的路徑段將從?HttpRequest.Path?中移除,
并附加到?HttpRequest.PathBase?中。
除基于路徑的映射外,MapWhen?方法還支持基于謂詞的中間件分支,允許以非常靈活的方式構建單獨的管道。任何?Func<HttpContext,?bool>?類型的謂語都被用于將請求映射到新的管到分支。在下例中使用了一個簡單的謂詞來檢測查詢字符串變量?branch?是否存在:
復制代碼
private static void HandleBranch(IApplicationBuilder app){app.Run(async context =>{ ? ? ? ?await context.Response.WriteAsync("Branch used.");//手工高亮}); }public void ConfigureMapWhen(IApplicationBuilder app){app.MapWhen(context => {//手工高亮return context.Request.Query.ContainsKey("branch");//手工高亮}, HandleBranch);//手工高亮app.Run(async context =>{ ? ? ? ?await context.Response.WriteAsync("Hello from " + _environment);}); }使用了上述設置后,任何包含請求字符?branch?的請求將使用定義于HandleBranch?方法內的管道(其響應就將是“Branch used.”)。其他請求(即沒有為?branch?定義查詢字符串值)將被第 17 行所定義的委托處理。
你也可以嵌套映射:
復制代碼
app.Map("/level1", level1App => {level1App.Map("/level2a", level2AApp => { ? ? ? ?// "/level1/level2a"//...});level1App.Map("/level2b", level2BApp => { ? ? ? ?// "/level1/level2b"//...}); });內置中間件
ASP.NET 帶來了下列中間件組件:
| 身份驗證(Authentication) | 提供身份驗證支持。 |
| 跨域資源共享(CORS) | 配置跨域資源共享。CORS 全稱為 Cross-Origin Resource Sharing。 |
| 路由(Routing) | 定義和約定請求路由。 |
| 會話(Session) | 提供對管理用戶會話(session)的支持。 |
| 靜態文件 | 提供對靜態文件服務于目錄瀏覽的支持。 |
編寫中間件
CodeLabs 中間件教程?提供了一個清晰介紹用于編寫中間件。
對于更復雜的請求處理功能,ASP.NET 團隊推薦在他們自己的類中實現中間件,并暴露?IApplicationBuilder?擴展方法,這樣就能通過?Configure?方法來被調用。之前演示的簡易日志中間件就能被轉換為一個中間件類(middleware class):只要在其構造函數中獲得下一個?RequestDelegate?并提供一個?Invoke方法,如下所示:
復制代碼
using System.Threading.Tasks;using Microsoft.AspNetCore.Http;using Microsoft.Extensions.Logging;namespace MiddlewareSample{ ? ?public class RequestLoggerMiddleware{ ? ? ? ?private readonly RequestDelegate _next; ? ? ? ?private readonly ILogger _logger; ? ? ? ?public RequestLoggerMiddleware(RequestDelegate next, ILoggerFactory loggerFactory)//手工高亮 ? ? ? ?{_next = next;_logger = loggerFactory.CreateLogger<RequestLoggerMiddleware>();} ? ? ? ?public async Task Invoke(HttpContext context)//手工高亮 ? ? ? ?{_logger.LogInformation("Handling request: " + context.Request.Path); ? ? ? ? ? ?await _next.Invoke(context);_logger.LogInformation("Finished handling request.");}} }中間件遵循?顯式依賴原則?并在其構造函數中暴露所有依賴項。中間件能夠利用到?UseMiddleware ?擴展方法的優勢,直接通過它們的構造函數注入服務,就像下面的例子所示。依賴注入服務是自動完成填充的,擴展所用到的?params?參數數組被用于非注入參數。
復制代碼
public static class RequestLoggerExtensions{ ? ?public static IApplicationBuilder UseRequestLogger(this IApplicationBuilder builder) ? ?{ ? ? ? ?return builder.UseMiddleware<RequestLoggerMiddleware>();//手工高亮} }通過使用擴展方法和相關中間件類,Configure?方法變得非常簡潔和高可讀性。
復制代碼
public void ConfigureLogMiddleware(IApplicationBuilder app, ? ?ILoggerFactory loggerfactory){loggerfactory.AddConsole(minLevel: LogLevel.Information);app.UseRequestLogger();//手工高亮app.Run(async context =>{ ? ? ? ?await context.Response.WriteAsync("Hello from " + _environment);}); }盡管?RequestLoggerMiddleware?在其構造函數中需要?ILoggerFactory?參數,但無論是?Startup?類還是?UseRequestLogger?擴展方法都不需要顯式依賴之。相反,它將自動地通過內置的?UseMiddleware<T>?來執行依賴注入以提供之。
測試中間件(通過給?LogMiddleware?設置?Hosting:Environment?環境變量)會輸出下圖的結果(當時用了 WebListener 時):
注意
UseStaticFiles?擴展方法(該方法會創建?StaticFileMiddleware)同樣也使用了?UseMiddleware<T>。所以除了?StaticFileOptions?參數被傳入之外,構造函數的其他參數都由?UseMiddleware<T>?和依賴注入所提供。
原文地址:http://www.cnblogs.com/dotNETCoreSG/p/aspnetcore-3_2-middleware.html
.NET社區新聞,深度好文,微信中搜索dotNET跨平臺或掃描二維碼關注
贊賞
人贊賞
總結
以上是生活随笔為你收集整理的ASP.NET Core 中文文档 第三章 原理(2)中间件的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: .NET 4.6.2正式发布带来众多特性
- 下一篇: ASP.NET Core 中文文档 第三