ASP.NET Core 中文文档 第三章 原理(2)中间件
原文:Middleware
作者:Steve Smith?and?Rick Anderson
翻譯:劉怡(AlexLEWIS)
校對:許登洋(Seay)
章節(jié):
什么是中間件
用 IApplicationBuilder 創(chuàng)建中間件管道
內(nèi)置中間件
編寫中間件
擴(kuò)展資源
查看或下載樣例代碼
什么是中間件
中間件是用于組成應(yīng)用程序管道來處理請求和響應(yīng)的組件。管道內(nèi)的每一個組件都可以選擇是否將請求交給下一個組件、并在管道中調(diào)用下一個組件之前和之后執(zhí)行某些操作。請求委托被用來建立請求管道,請求委托處理每一個 HTTP 請求。
請求委托通過使用?IApplicationBuilder?類型的?Run、Map?以及?Use?擴(kuò)展方法來配置,并在?Startup?類中傳給?Configure?方法 。每個單獨(dú)的請求委托都可以被指定為一個內(nèi)嵌匿名方法,或其定義在一個可重用的類中。這些可重用的類被稱作?中間件?或?中間件組件。每個位于請求管道內(nèi)的中間件組件負(fù)責(zé)調(diào)用管道中下一個組件,或適時短路調(diào)用鏈。
Migrating HTTP Modules to Middleware?解釋了請求管道在 ASP.NET Core 和之前版本之間的區(qū)別,并提供了更多中間件樣例。
用 IApplicationBuilder 創(chuàng)建中間件管道
ASP.NET 請求管道由一系列的請求委托所構(gòu)成,它們一個接著一個被調(diào)用,如圖所示(該執(zhí)行線程按黑色箭頭的順序執(zhí)行):
每個委托在下一個委托之前和之后都有機(jī)會執(zhí)行操作。任何委托都能選擇停止傳遞到下一個委托,轉(zhuǎn)而自己處理該請求。這被叫做請求管道的短路,而且是一種有意義的設(shè)計(jì),因?yàn)樗梢员苊獠槐匾墓ぷ鳌1确秸f,一個授權(quán)(authorization)中間件只有在通過身份驗(yàn)證之后才調(diào)用下一個委托,否則它就會被短路并返回 “Not Authorized” 的響應(yīng)。異常處理委托需要在管道的早期被調(diào)用,這樣它們就能夠捕捉到發(fā)生在管道內(nèi)更深層次出現(xiàn)的異常了。
你可以看一下 Visual Studio 2015 附帶的默認(rèn) Web 站點(diǎn)模板關(guān)于請求管道設(shè)置的例子。Configure?方法增加了下列這些中間件組件:
錯誤處理(同時針對于開發(fā)環(huán)境和非開發(fā)環(huán)境)
靜態(tài)文件服務(wù)器
身份驗(yàn)證
MVC
復(fù)制代碼
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?}");}); }上面的代碼中(在非開發(fā)環(huán)境時),UseExceptionHandler?是第一個被加入到管道中的中間件,因此將會捕獲之后調(diào)用中出現(xiàn)的任何異常。
靜態(tài)文件模塊?不提供授權(quán)檢查,由它提供的任何文件,包括那些位于wwwroot?下的文件都是公開的可被訪問的。如果你想基于授權(quán)來提供這些文件:
將它們存放在?wwwroot?外面以及任何靜態(tài)文件中間件都可訪問得到的目錄。
利用控制器操作來判斷授權(quán)是否允許,如果允許則通過返回?FileResult?來提供它們。
被靜態(tài)文件模塊處理的請求會在管道中被短路(參見?Working with Static Files)。如果該請求不是由靜態(tài)文件模塊處理,那么它就會被傳給?Identity 模塊?執(zhí)行身份驗(yàn)證。如果未通過身份驗(yàn)證,則管道將被短路。如果請求的身份驗(yàn)證沒有失敗,則管道的最后一站是 MVC 框架。
注意
你添加中間件組件的順序通常會影響到它們處理請求的順序,然后在響應(yīng)時則以相反的順序返回。這對應(yīng)用程序安全、性能和功能很關(guān)鍵。在上面的代碼中,靜態(tài)文件中間件?在管道的早期被調(diào)用,這樣就能處理并及時短路管道,以避免請求走到不必要的組件中。身份驗(yàn)證中間件被添加在任何需要身份認(rèn)證的處理請求的前面。異常處理必須被注冊在其它中間件之前以便捕獲其它組件的異常。
最簡單的 ASP.NET 應(yīng)用程序是使用單個請求委托來處理所有請求。事實(shí)上在這種情況下并不存在所謂的“管道”,調(diào)用單個匿名函數(shù)以相應(yīng)每個 HTTP 請求。
復(fù)制代碼
app.Run(async context => { ? ?await context.Response.WriteAsync("Hello, World!"); });第一個?App.Run?委托中斷了管道。在下面的例子中,只有第一個委托(“Hello, World!”)會被運(yùn)行。
復(fù)制代碼
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?參數(shù)表示管道內(nèi)下一個委托。通過?不?調(diào)用?next?參數(shù),你可以中斷(短路)管道。你通常可以在執(zhí)行下一個委托之前和之后執(zhí)行一些操作,如下例所示:
復(fù)制代碼
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);//手工高亮}); }警告
應(yīng)當(dāng)避免在修改了?HttpResponse?之后還調(diào)用管道內(nèi)下一個會修改響應(yīng)的組件,從而導(dǎo)致它被送到客戶端處。
提示
當(dāng)應(yīng)用程序運(yùn)行的環(huán)境設(shè)置為?LogInline?時,這個?ConfigureLogInline?方法就會被調(diào)動。要了解更多請?jiān)L問環(huán)境?Working with Multiple Environments?一章。本文剩下的篇幅將使用變化的?Configure[Environment]?來展示不同的選項(xiàng)。 Visual Studio 中運(yùn)行示例代碼的最簡單辦法是使用?web?命令,該命令由?project.json?文件所配置。也可參考?Application Startup?。
在上例中,調(diào)用?await?next.Invoke()?將會調(diào)用下一個委托await?context.Response.WriteAsync("Hello?from?"?+?_environment);??蛻舳藢⑹盏筋A(yù)期的響應(yīng)(“Hello from LogInline”),同時服務(wù)端這邊的控制臺將先后輸出如下信息:
Run,Map 與 Use
你可以使用?Run、Map?和?Use?配置 HTTP 管道。Run?方法將會短路管道(因?yàn)樗粫{(diào)用?next?請求委托)。因此,Run?應(yīng)該只能在你的管道尾部被調(diào)用。Run?是一種慣例,有些中間件組件可能會暴露他們自己的 Run[Middleware] 方法,而這些方法只能在管道末尾處運(yùn)行。下面這兩個中間件等價(jià)的,其中有用到?Use?的版本沒有使用?next?參數(shù):
復(fù)制代碼
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?方法,因此從技術(shù)上來說它們并不完全是?擴(kuò)展?方法。
我們已經(jīng)看了幾個關(guān)于如何通過?Use?構(gòu)建請求管道的例子,同時約定了?Map*擴(kuò)展被用于分支管道。當(dāng)前的實(shí)現(xiàn)已支持基于請求路徑或使用謂詞來進(jìn)入分支。Map?擴(kuò)展方法用于匹配基于請求路徑的請求委托。Map?只接受路徑,并配置單獨(dú)的中間件管道的功能。在下例中,任何基于路徑?/maptest?的請求都會被管道中所配置的?HandleMapTest?方法所處理。
復(fù)制代碼
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);//手工高亮}注意
當(dāng)使用了?Map,每個請求所匹配的路徑段將從?HttpRequest.Path?中移除,
并附加到?HttpRequest.PathBase?中。
除基于路徑的映射外,MapWhen?方法還支持基于謂詞的中間件分支,允許以非常靈活的方式構(gòu)建單獨(dú)的管道。任何?Func<HttpContext,?bool>?類型的謂語都被用于將請求映射到新的管到分支。在下例中使用了一個簡單的謂詞來檢測查詢字符串變量?branch?是否存在:
復(fù)制代碼
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);}); }使用了上述設(shè)置后,任何包含請求字符?branch?的請求將使用定義于HandleBranch?方法內(nèi)的管道(其響應(yīng)就將是“Branch used.”)。其他請求(即沒有為?branch?定義查詢字符串值)將被第 17 行所定義的委托處理。
你也可以嵌套映射:
復(fù)制代碼
app.Map("/level1", level1App => {level1App.Map("/level2a", level2AApp => { ? ? ? ?// "/level1/level2a"//...});level1App.Map("/level2b", level2BApp => { ? ? ? ?// "/level1/level2b"//...}); });內(nèi)置中間件
ASP.NET 帶來了下列中間件組件:
| 身份驗(yàn)證(Authentication) | 提供身份驗(yàn)證支持。 |
| 跨域資源共享(CORS) | 配置跨域資源共享。CORS 全稱為 Cross-Origin Resource Sharing。 |
| 路由(Routing) | 定義和約定請求路由。 |
| 會話(Session) | 提供對管理用戶會話(session)的支持。 |
| 靜態(tài)文件 | 提供對靜態(tài)文件服務(wù)于目錄瀏覽的支持。 |
編寫中間件
CodeLabs 中間件教程?提供了一個清晰介紹用于編寫中間件。
對于更復(fù)雜的請求處理功能,ASP.NET 團(tuán)隊(duì)推薦在他們自己的類中實(shí)現(xiàn)中間件,并暴露?IApplicationBuilder?擴(kuò)展方法,這樣就能通過?Configure?方法來被調(diào)用。之前演示的簡易日志中間件就能被轉(zhuǎn)換為一個中間件類(middleware class):只要在其構(gòu)造函數(shù)中獲得下一個?RequestDelegate?并提供一個?Invoke方法,如下所示:
復(fù)制代碼
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.");}} }中間件遵循?顯式依賴原則?并在其構(gòu)造函數(shù)中暴露所有依賴項(xiàng)。中間件能夠利用到?UseMiddleware ?擴(kuò)展方法的優(yōu)勢,直接通過它們的構(gòu)造函數(shù)注入服務(wù),就像下面的例子所示。依賴注入服務(wù)是自動完成填充的,擴(kuò)展所用到的?params?參數(shù)數(shù)組被用于非注入?yún)?shù)。
復(fù)制代碼
public static class RequestLoggerExtensions{ ? ?public static IApplicationBuilder UseRequestLogger(this IApplicationBuilder builder) ? ?{ ? ? ? ?return builder.UseMiddleware<RequestLoggerMiddleware>();//手工高亮} }通過使用擴(kuò)展方法和相關(guān)中間件類,Configure?方法變得非常簡潔和高可讀性。
復(fù)制代碼
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?在其構(gòu)造函數(shù)中需要?ILoggerFactory?參數(shù),但無論是?Startup?類還是?UseRequestLogger?擴(kuò)展方法都不需要顯式依賴之。相反,它將自動地通過內(nèi)置的?UseMiddleware<T>?來執(zhí)行依賴注入以提供之。
測試中間件(通過給?LogMiddleware?設(shè)置?Hosting:Environment?環(huán)境變量)會輸出下圖的結(jié)果(當(dāng)時用了 WebListener 時):
注意
UseStaticFiles?擴(kuò)展方法(該方法會創(chuàng)建?StaticFileMiddleware)同樣也使用了?UseMiddleware<T>。所以除了?StaticFileOptions?參數(shù)被傳入之外,構(gòu)造函數(shù)的其他參數(shù)都由?UseMiddleware<T>?和依賴注入所提供。
原文地址:http://www.cnblogs.com/dotNETCoreSG/p/aspnetcore-3_2-middleware.html
.NET社區(qū)新聞,深度好文,微信中搜索dotNET跨平臺或掃描二維碼關(guān)注
贊賞
人贊賞
總結(jié)
以上是生活随笔為你收集整理的ASP.NET Core 中文文档 第三章 原理(2)中间件的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: .NET 4.6.2正式发布带来众多特性
- 下一篇: ASP.NET Core 中文文档 第三