您好,登錄后才能下訂單哦!
小編給大家分享一下ASP.NET Core中間件用法與官方常用中間件的示例分析,希望大家閱讀完這篇文章之后都有所收獲,下面讓我們一起去探討吧!
我們都知道,任何的一個web框架都是把http請求封裝成一個管道,每一次的請求都是經(jīng)過管道的一系列操作,最終才會到達我們寫的代碼中。而中間件就是用于組成應(yīng)用程序管道來處理請求和響應(yīng)的組件。管道內(nèi)的每一個組件都可以選擇是否將請求轉(zhuǎn)交給下一個組件,并在管道中調(diào)用下一個組件之前和之后執(zhí)行某些操作。請求委托被用來建立請求管道,請求委托處理每一個HTTP請求。
中間件可以認為有兩個基本的職責:
選擇是否將請求傳遞給管道中的下一個中間件。
可以在管道中的下一個中間件前后執(zhí)行一些工作。
請求委托通過使用IApplicationBuilder類型的Run、Map以及Use擴展方法來配置,并在Startup類中傳給Configure方法。每個單獨的請求委托都可以被指定為一個內(nèi)嵌匿名方法,或其定義在一個可重用的類中。這些可以重用的類被稱作“中間件”或“中間件組件”。每個位于請求管道內(nèi)的中間件組件負責調(diào)用管道中下一個組件,或適時短路調(diào)用鏈。中間件是一個典型的AOP應(yīng)用。
ASP.NET Core請求管道由一系列的請求委托所構(gòu)成,它們一個接著一個的被調(diào)用,看下面一張微軟官方的中間件請求管道圖(圖中執(zhí)行線程按黑色箭頭的順序執(zhí)行):
中間件短路:每一個委托在下一個委托之前和之后都有機會執(zhí)行操作。任何委托都能選擇停止傳遞到下一個委托,而是結(jié)束請求并開始響應(yīng),這就是請求管道的短路,這是一種有意義的設(shè)計,因為它可以避免一些不必要的工作。比如說,一個授權(quán)(authorization)中間件只有在通過身份驗證之后才能調(diào)用下一個委托,否則它就會被短路,并返回“Not Authorized”的響應(yīng)。異常處理委托需要在管道的早期被調(diào)用,這樣它們就能夠捕捉到發(fā)生在管道內(nèi)更深層次出現(xiàn)的異常了。短路可以用下面這張圖來表示:
在上圖中,我們可以把中間件1認為是身份認證的中間件,HTTP請求發(fā)送過來,首先經(jīng)過身份認證中間件,如果身份認證失敗,那么就直接給出響應(yīng)并返回,不會再把請求傳遞給下面的中間件2和中間件3.
中間件的執(zhí)行跟調(diào)用的順序有關(guān),然后在響應(yīng)時則以相反的順序返回。
請求在每一步都可能被短路,所以我們要以正確的順序添加中間件,如異常處理中間件,我們要添加在最開始的地方,這樣就能第一時間捕獲異常,以及后續(xù)中間可能發(fā)生的異常,然后最終做處理返回。
我們來看看Configure方法里面提供了哪些中間件:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { // 異常中間件 app.UseDeveloperExceptionPage(); } // 路由中間件 app.UseRouting(); // 授權(quán)中間件 app.UseAuthorization(); // 終結(jié)點中間件 app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); }
中間件和過濾器都是一種AOP的思想,他們的功能類似,那么他們有什么區(qū)別呢?
過濾器更加貼合業(yè)務(wù),它關(guān)注于應(yīng)用程序本身,關(guān)注的是如何實現(xiàn)業(yè)務(wù),比如對輸出結(jié)果進行格式化,對請求的ViewModel進行數(shù)據(jù)校驗,這時就肯定要使用過濾器了。過濾器是MVC的一部分,它可以攔截到你Action上下文的一些信息,而中間件是沒有這個能力的。可以認為過濾器是附加性的一種功能,它只是中間件附帶表現(xiàn)出來的特征。中間件是管道模型里重要的組成部分,不可或缺,而過濾器可以沒有。
中間件中定義了Run、Use、Map、MapWhen幾種方法,我們下面一一講解這幾種方法。
我們先來看到Run()方法的定義:
中定義中可以看出:Run()方法中只有一個RequestDelegate委托類型的參數(shù),沒有Next參數(shù),所以Run()方法也叫終端中間件,不會將請求傳遞給下一個中間件,也就是發(fā)生了“短路”??聪旅娴拇a:
// Run方法向應(yīng)用程序的請求管道中添加一個RequestDelegate委托 // 放在管道最后面,終端中間件 app.Run(handler: async context => { await context.Response.WriteAsync(text: "Hello World1\r\n"); }); app.Run(handler: async context => { await context.Response.WriteAsync(text: "Hello World2\r\n"); });
程序運行結(jié)果:
可以看到:只輸出了中間件1的信息,沒有輸出中間件2的信息,說明發(fā)生了短路。
注意:Run()方法被稱為終端中間件,要放在所有中間件的最后面,否則在Run()方法后面的中間件將不會被執(zhí)行。
我們先來看看Use()方法的定義:
可以看出:Use方法的參數(shù)是一個Func委托,輸入?yún)?shù)是一個RequestDelegate類型的委托,返回參數(shù)也是一個RequestDelegate類型的委托,這里表示調(diào)用下一個中間件,我們在來看看RequestDelegate委托的定義:
可以看出:RequestDelegate是一個委托,有一個HttpContext類型的參數(shù),HttPContext表示Http請求上下文,可以獲取請求信息,返回值是Task類型,明白了Use()方法的參數(shù)以后,我們寫一個自定義的Use()方法:
// 向應(yīng)用程序的請求管道中添加一個Func委托,這個委托其實就是所謂的中間件。 // context參數(shù)是HttpContext,表示HTTP請求的上下文對象 // next參數(shù)表示管道中的下一個中間件委托,如果不調(diào)用next,則會使管道短路 // 用Use可以將多個中間件鏈接在一起 app.Use(async (context, next) => { await context.Response.WriteAsync(text: "hello Use1\r\n"); // 調(diào)用下一個委托 await next(); }); app.Use(async (context, next) => { await context.Response.WriteAsync(text: "hello Use2\r\n"); // 調(diào)用下一個委托 await next(); });
程序運行結(jié)果:
我們在上面說過,可以在調(diào)用中間件之前和之后做一些工作,看下面的代碼:
// 向應(yīng)用程序的請求管道中添加一個Func委托,這個委托其實就是所謂的中間件。 // context參數(shù)是HttpContext,表示HTTP請求的上下文對象 // next參數(shù)表示管道中的下一個中間件委托,如果不調(diào)用next,則會使管道短路 // 用Use可以將多個中間件鏈接在一起 app.Use(async (context, next) => { // 解決中文亂碼問題 context.Response.ContentType = "text/plain; charset=utf-8"; await context.Response.WriteAsync(text: "中間件1:傳入請求\r\n"); // 調(diào)用下一個委托 await next(); await context.Response.WriteAsync(text: "中間件1:傳出響應(yīng)\r\n"); }); app.Use(async (context, next) => { await context.Response.WriteAsync(text: "中間件2:傳入請求\r\n"); // 調(diào)用下一個委托 await next(); await context.Response.WriteAsync(text: "中間件2:傳出響應(yīng)\r\n"); }); app.Run(handler:async context => { await context.Response.WriteAsync(text: "中間件3:處理請求并生成響應(yīng)\r\n"); });
程序運行結(jié)果:
我們可以總結(jié)上面代碼的執(zhí)行順序:
請求先到達中間件1,然后輸出(中間件1:傳入請求)
然后中間件1調(diào)用next()。next()會調(diào)用管道中的中間件2。
中間件2輸出(中間件2:傳入請求)。
然后中間件2會調(diào)用next()。next()在調(diào)用管道中的中間件3。
中間件3處理請求并生成響應(yīng),不在調(diào)用下一個中間件,所以我們看到輸出(中間件3:處理請求并生成響應(yīng))。
這時管理開始發(fā)生逆轉(zhuǎn)。
此時控制器將交回到中間件2,并將中間件3生成的響應(yīng)傳遞給它。中間件2輸出(中間件2:傳出響應(yīng))。
最后,中間件2在將控制權(quán)交給中間件1。
中間件1最后輸出(中間件1:傳出響應(yīng)),這就是我們最后看的的結(jié)果。
我們知道:Use()方法中有兩個參數(shù),next參數(shù)表示調(diào)用管道中的下一個中間件,如果不調(diào)用next,那么也會使管道發(fā)生短路,相當于Run()方法,看下面的代碼:
// 向應(yīng)用程序的請求管道中添加一個Func委托,這個委托其實就是所謂的中間件。 // context參數(shù)是HttpContext,表示HTTP請求的上下文對象 // next參數(shù)表示管道中的下一個中間件委托,如果不調(diào)用next,則會使管道短路 // 用Use可以將多個中間件鏈接在一起 app.Use(async (context, next) => { // 解決中文亂碼問題 context.Response.ContentType = "text/plain; charset=utf-8"; await context.Response.WriteAsync(text: "中間件1:傳入請求\r\n"); // 調(diào)用下一個委托 await next(); await context.Response.WriteAsync(text: "中間件1:傳出響應(yīng)\r\n"); }); app.Use(async (context, next) => { await context.Response.WriteAsync(text: "中間件2:傳入請求\r\n"); // 調(diào)用下一個委托 await next(); await context.Response.WriteAsync(text: "中間件2:傳出響應(yīng)\r\n"); }); //app.Run(handler:async context => //{ // await context.Response.WriteAsync(text: "中間件3:處理請求并生成響應(yīng)\r\n"); //}); // Use方法也可以不調(diào)用next,表示發(fā)生短路 app.Use(async (context, next) => { await context.Response.WriteAsync(text: "中間件3:處理請求并生成響應(yīng)\r\n"); });
程序運行結(jié)果:
可以看出:如果使用Use()方法,不調(diào)用next,實現(xiàn)的效果跟使用Run()方法一樣,都會使管道發(fā)生短路。
Map作為慣例,將管道分流。Map根據(jù)給定請求路徑匹配將請求管道分流。如果請求路徑以指定路徑開始,則執(zhí)行分支。看一下Map()方法的定義:
可以看到Map方法有兩個參數(shù):第一個參數(shù)是匹配規(guī)則,第二個參數(shù)是Action泛型委托,泛型委托參數(shù)是IApplicationBuilder類型,和Configure方法的第一個參數(shù)類型相同。這就表示可以把實現(xiàn)了Action泛型委托的方法添加到中間件管道中執(zhí)行。
我們首先定義一個方法,該方法的參數(shù)是IApplicationBuilder類型:
/// <summary> /// 自定義方法 /// </summary> /// <param name="app">IApplicationBuilder</param> private void HandleMap1(IApplicationBuilder app) { app.Run(handler: async context => { await context.Response.WriteAsync(text: "Hello Map1"); }); } /// <summary> /// 自定義方法 /// </summary> /// <param name="app">IApplicationBuilder</param> private void HandleMap2(IApplicationBuilder app) { app.Run(handler: async context => { await context.Response.WriteAsync(text: "Hello Map2"); }); }
然后看一下使用Map方法的代碼:
// Map可以根據(jù)匹配的URL來選擇執(zhí)行,簡單來說就是根據(jù)URL進行分支選擇執(zhí)行 // 有點類似于MVC中的路由 // 匹配的URL:http://localhost:5000/Map1 app.Map(pathMatch: "/Map1", configuration: HandleMap1); // 匹配的URL:http://localhost:5000/Map2 app.Map(pathMatch: "/Map2", configuration: HandleMap2);
運行程序,然后在瀏覽器地址欄里面輸入:http://localhost:5000/Map1,輸出結(jié)果:
在地址欄里面在輸入:http://localhost:5000/Map2,輸出結(jié)果:
Map還支持嵌套,看下面的代碼:
// 嵌套Map app.Map(pathMatch: "/Map1", configuration: App1 => { // App1.Map("/Map2",action=> { action.Run(async context => { await context.Response.WriteAsync("This is /Map1/Map2"); }); }); App1.Run(async context => { await context.Response.WriteAsync("This is no-map"); }); });
訪問http://localhost:5000/Map1/123輸出結(jié)果:
訪問http://localhost:5000/Map1輸出結(jié)果:
訪問http://localhost:5000/Map1/Map2輸出結(jié)果:
Map也可以同時匹配多個段,看下面的代碼:
運行程序,輸出結(jié)果:
訪問http://localhost:5000/Map1/Map2輸出結(jié)果:
MapWhen是基于給定的謂詞分支請求管道。任何使Func<HttpContext,bool>返回true的謂詞的請求都被映射到新的管道分支。
我們先來看看Mapwhen方法的定義:
可以看出:MapWhen方法有兩個參數(shù):第一個參數(shù)是Func類型的委托,輸入?yún)?shù)是HttpContext,輸出參數(shù)是bool類型。第二個參數(shù)是Action委托,參數(shù)是IApplicationBuilder類型,表示也可以把實現(xiàn)Action委托的方法添加到中間件管道中執(zhí)行。
看下面的例子,如果url中包括name查詢參數(shù),則執(zhí)行HandleName方法,如果包含age查詢參數(shù),則執(zhí)行HandleAge方法,否則執(zhí)行Run()方法。
HandleName和HandleAge方法定義如下:
private void HandleName(IApplicationBuilder app) { app.Run(handler: async context => { await context.Response.WriteAsync(text: $"This name is: {context.Request.Query["name"]}"); }); } private void HandleAge(IApplicationBuilder app) { app.Run(handler: async context => { await context.Response.WriteAsync(text: $"This age is: {context.Request.Query["age"]}"); }); }
對應(yīng)的MapWhen方法定義如下:
// 如果訪問的url參數(shù)中包含name,則執(zhí)行HandleName app.MapWhen( // Func委托,輸入?yún)?shù)是HttpContext,返回bool值 predicate: context => { // 判斷url參數(shù)中是否包含name return context.Request.Query.ContainsKey("name"); }, configuration: HandleName); // 如果訪問的url參數(shù)中包含name,則執(zhí)行HandleAge app.MapWhen( // Func委托,輸入?yún)?shù)是HttpContext,返回bool值 predicate: context => { // 判斷url參數(shù)中是否包含age return context.Request.Query.ContainsKey("age"); }, configuration: HandleAge); app.Run(async context => { await context.Response.WriteAsync("There is non-Map delegate \r\n"); });
運行程序,輸出結(jié)果:
在url里面添加name查詢參數(shù)輸出結(jié)果:
在url里面添加age查詢參數(shù)輸出結(jié)果:
在上面的例子中,我們都是使用的官方中間件自動的方法,其實我們也可以自己編寫一個中間件。
中間件遵循顯示依賴原則,并在其構(gòu)造函數(shù)中暴露所有依賴項。中間件能夠利用UseMiddleware<T>擴展方法的優(yōu)勢,直接通過它們的構(gòu)造函數(shù)注入服務(wù)。依賴注入服務(wù)是自動完成填充的。
ASP.NET Core約定中間件類必須包括以下內(nèi)容:
具有類型為RequestDelegate參數(shù)的公共構(gòu)造函數(shù)。
必須有名為Invoke或InvokeAsync的公共方法,此方法必須滿足兩個條件:方法返回類型是Task、方法的第一個參數(shù)必須是HttpContext類型。
我們自定義一個記錄IP的中間件,新建一個類RequestIPMiddleware,代碼如下:
using Microsoft.AspNetCore.Http; using System.Threading.Tasks; namespace MiddlewareDemo.Middleware { /// <summary> /// 記錄IP地址的中間件 /// </summary> public class RequestIPMiddleware { // 私有字段 private readonly RequestDelegate _next; /// <summary> /// 公共構(gòu)造函數(shù),參數(shù)是RequestDelegate類型 /// 通過構(gòu)造函數(shù)進行注入,依賴注入服務(wù)會自動完成注入 /// </summary> /// <param name="next"></param> public RequestIPMiddleware(RequestDelegate next) { _next = next; } /// <summary> /// Invoke方法 /// 返回值是Task,參數(shù)類型是HttpContext /// </summary> /// <param name="context">Http上下文</param> /// <returns></returns> public async Task Invoke(HttpContext context) { await context.Response.WriteAsync($"User IP:{context.Connection.RemoteIpAddress.ToString()}\r\n"); // 調(diào)用管道中的下一個委托 await _next.Invoke(context); } } }
然后創(chuàng)建一個擴展方法,對IApplicationBuilder進行擴展:
using Microsoft.AspNetCore.Builder; namespace MiddlewareDemo.Middleware { public static class RequestIPExtensions { /// <summary> /// 擴展方法,對IApplicationBuilder進行擴展 /// </summary> /// <param name="builder"></param> /// <returns></returns> public static IApplicationBuilder UseRequestIP(this IApplicationBuilder builder) { // UseMiddleware<T> return builder.UseMiddleware<RequestIPMiddleware>(); } } }
最后在Startup類的Configure方法中使用自定義中間件:
// 使用自定義中間件 app.UseRequestIP();
運行程序,查看結(jié)果:
這樣就完成了一個自定義中間件。
當應(yīng)用程序在開發(fā)環(huán)境中運行時,開發(fā)人員異常頁中間件( UseDeveloperExceptionPage )報告應(yīng)用程序運行時的錯誤。
當應(yīng)用程序在生產(chǎn)環(huán)境中運行時,異常處理中間件( UseExceptionHandler )捕獲下面中間件中引發(fā)的異常。
HTTPS重定向中間件( UseHttpsRedirection )會將HTTP請求重定向到HTTPS。
靜態(tài)文件中間件( UseStaticFiles )返回靜態(tài)文件,并簡化進一步請求處理。
Cookie策略中間件( UseCookiePolicy )使應(yīng)用符合歐盟一般數(shù)據(jù)保護條例的規(guī)定。
路由中間件( UseRouting )用于路由的請求。
身份認證中間件( UseAuthentication )嘗試對用戶進行身份驗證,驗證通過之后才會允許用戶訪問安全資源。
授權(quán)中間件( UseAuthorization )用于授權(quán)驗證通過的用戶可以訪問哪些資源。
會話中間件( UseSession )建立和維護會話狀態(tài)。如果應(yīng)用程序使用會話狀態(tài),請在Cookie策略中間件之后和MVC中間件之前調(diào)用會話中間件。
終結(jié)點路由中間件( UseEndpoints )用于將 Razor Pages 終結(jié)點添加到請求管道。
看完了這篇文章,相信你對“ASP.NET Core中間件用法與官方常用中間件的示例分析”有了一定的了解,如果想了解更多相關(guān)知識,歡迎關(guān)注億速云行業(yè)資訊頻道,感謝各位的閱讀!
免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。