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