您好,登錄后才能下訂單哦!
這篇文章將為大家詳細講解有關ASP.NET Core中間件Middleware怎么用,小編覺得挺實用的,因此分享給大家做個參考,希望大家閱讀完這篇文章后可以有所收獲。
先借用微軟官方文檔的一張圖:
可以看到,中間件實際上是一種配置在HTTP請求管道中,用來處理請求和響應的組件。它可以:
決定是否將請求傳遞到管道中的下一個中間件
可以在管道中的下一個中間件處理之前和之后進行操作
此外,中間件的注冊是有順序的,書寫代碼時一定要注意!
該方法為HTTP請求管道添加一個中間件,并標識該中間件為管道終點,稱為終端中間件。也就是說,該中間件就是管道的末尾,在該中間件之后注冊的中間件將永遠都不會被執行。所以,該方法一般只會書寫在Configure
方法末尾。
public class Startup { public void Configure(IApplicationBuilder app) { app.Run(async context => { await context.Response.WriteAsync("Hello, World!"); }); } }
通過該方法快捷的注冊一個匿名的中間件
public class Startup { public void Configure(IApplicationBuilder app) { app.Use(async (context, next) => { // 下一個中間件處理之前的操作 Console.WriteLine("Use Begin"); await next(); // 下一個中間件處理完成后的操作 Console.WriteLine("Use End"); }); } }
注意:
1.如果要將請求發送到管道中的下一個中間件,一定要記得調用next.Invoke / next()
,否則會導致管道短路,后續的中間件將不會被執行
2.在中間件中,如果已經開始給客戶端發送Response
,請千萬不要調用next.Invoke / next()
,也不要對Response
進行任何更改,否則,將拋出異常。
3.可以通過context.Response.HasStarted
來判斷響應是否已開始。
以下都是錯誤的代碼寫法
錯誤1:
public class Startup { public void Configure(IApplicationBuilder app) { app.Use(async (context, next) => { await context.Response.WriteAsync("Use"); await next(); }); app.Run(context => { // 由于上方的中間件已經開始 Response,此處更改 Response Header 會拋出異常 context.Response.Headers.Add("test", "test"); return Task.CompletedTask; }); } }
錯誤2:
public class Startup { public void Configure(IApplicationBuilder app) { app.Use(async (context, next) => { await context.Response.WriteAsync("Use"); // 即使沒有調用 next.Invoke / next(),也不能在 Response 開始后對 Response 進行更改 context.Response.Headers.Add("test", "test"); }); } }
通過該方法針對不同的邏輯條件創建管道分支。需要注意的是:
進入了管道分支后,如果管道分支不存在管道短路或終端中間件,則會再次返回到主管道。
當使用PathString
時,路徑必須以“/”開頭,且允許只有一個'/'
字符
支持嵌套,即UseWhen中嵌套UseWhen等
支持同時匹配多個段,如 /get/user
public class Startup { public void Configure(IApplicationBuilder app) { // /get 或 /get/xxx 都會進入該管道分支 app.UseWhen(context => context.Request.Path.StartsWithSegments("/get"), app => { app.Use(async (context, next) => { Console.WriteLine("UseWhen:Use"); await next(); }); }); app.Use(async (context, next) => { Console.WriteLine("Use"); await next(); }); app.Run(async context => { Console.WriteLine("Run"); await context.Response.WriteAsync("Hello World!"); }); } }
當訪問 /get 時,輸出如下:
UseWhen:Use
Use
Run
如果你發現輸出了兩遍,別慌,看看是不是瀏覽器發送了兩次請求,分別是 /get 和 /favicon.ico
通過該方法針對不同的請求路徑創建管道分支。需要注意的是:
一旦進入了管道分支,則不會再回到主管道。
使用該方法時,會將匹配的路徑從HttpRequest.Path
中刪除,并將其追加到HttpRequest.PathBase
中。
路徑必須以“/”開頭,且不能只有一個'/'
字符
支持嵌套,即Map中嵌套Map、MapWhen(接下來會講)等
支持同時匹配多個段,如 /post/user
public class Startup { public void Configure(IApplicationBuilder app) { // 訪問 /get 時會進入該管道分支 // 訪問 /get/xxx 時會進入該管道分支 app.Map("/get", app => { app.Use(async (context, next) => { Console.WriteLine("Map get: Use"); Console.WriteLine($"Request Path: {context.Request.Path}"); Console.WriteLine($"Request PathBase: {context.Request.PathBase}"); await next(); }); app.Run(async context => { Console.WriteLine("Map get: Run"); await context.Response.WriteAsync("Hello World!"); }); }); // 訪問 /post/user 時會進入該管道分支 // 訪問 /post/user/xxx 時會進入該管道分支 app.Map("/post/user", app => { // 訪問 /post/user/student 時會進入該管道分支 // 訪問 /post/user/student/1 時會進入該管道分支 app.Map("/student", app => { app.Run(async context => { Console.WriteLine("Map /post/user/student: Run"); Console.WriteLine($"Request Path: {context.Request.Path}"); Console.WriteLine($"Request PathBase: {context.Request.PathBase}"); await context.Response.WriteAsync("Hello World!"); }); }); app.Use(async (context, next) => { Console.WriteLine("Map post/user: Use"); Console.WriteLine($"Request Path: {context.Request.Path}"); Console.WriteLine($"Request PathBase: {context.Request.PathBase}"); await next(); }); app.Run(async context => { Console.WriteLine("Map post/user: Run"); await context.Response.WriteAsync("Hello World!"); }); }); } }
當你訪問 /get/user 時,輸出如下:
Map get: Use
Request Path: /user
Request PathBase: /get
Map get: Run
當你訪問 /post/user/student/1 時,輸出如下:
Map /post/user/student: Run
Request Path: /1
Request PathBase: /post/user/student
其他情況交給你自己去嘗試啦!
與Map
類似,只不過MapWhen
不是基于路徑,而是基于邏輯條件創建管道分支。注意事項如下:
一旦進入了管道分支,則不會再回到主管道。
當使用PathString
時,路徑必須以“/”開頭,且允許只有一個'/'
字符
HttpRequest.Path
和HttpRequest.PathBase
不會像Map
那樣進行特別處理
支持嵌套,即MapWhen中嵌套MapWhen、Map等
支持同時匹配多個段,如 /get/user
public class Startup { public void Configure(IApplicationBuilder app) { // /get 或 /get/xxx 都會進入該管道分支 app.MapWhen(context => context.Request.Path.StartsWithSegments("/get"), app => { app.MapWhen(context => context.Request.Path.ToString().Contains("user"), app => { app.Use(async (context, next) => { Console.WriteLine("MapWhen get user: Use"); await next(); }); }); app.Use(async (context, next) => { Console.WriteLine("MapWhen get: Use"); await next(); }); app.Run(async context => { Console.WriteLine("MapWhen get: Run"); await context.Response.WriteAsync("Hello World!"); }); }); } }
當你訪問 /get/user 時,輸出如下:
MapWhen get user: Use
可以看到,即使該管道分支沒有終端中間件,也不會回到主管道。
一下子接觸了4個命名相似的、與中間件管道有關的API,不知道你有沒有暈倒,沒關系,我來幫大家總結一下:
Run
用于注冊終端中間件,Use
用來注冊匿名中間件,UseWhen
、Map
、MapWhen
用于創建管道分支。
UseWhen
進入管道分支后,如果管道分支中不存在短路或終端中間件,則會返回到主管道。Map
和MapWhen
進入管道分支后,無論如何,都不會再返回到主管道。
UseWhen
和MapWhen
基于邏輯條件來創建管道分支,而Map
基于請求路徑來創建管道分支,且會對HttpRequest.Path
和HttpRequest.PathBase
進行處理。
上面已經提到過的Run
和Use
就不再贅述了。
“約定大于配置”,先來個約法三章:
1.擁有公共(public)構造函數,且該構造函數至少包含一個類型為RequestDelegate
的參數
2.擁有名為Invoke
或InvokeAsync
的公共(public)方法,必須包含一個類型為HttpContext
的方法參數,且該參數必須位于第一個參數的位置,另外該方法必須返回Task
類型。
3.構造函數中的其他參數可以通過依賴注入(DI)填充,也可以通過UseMiddleware
傳參進行填充。
通過DI填充時,只能接收 Transient 和 Singleton 的DI參數。這是由于中間件是在應用啟動時構造的(而不是按請求構造),所以當出現 Scoped 參數時,構造函數內的DI參數生命周期與其他不共享,如果想要共享,則必須將Scoped DI參數添加到Invoke/InvokeAsync
來進行使用。
通過UseMiddleware
傳參時,構造函數內的DI參數和非DI參數順序沒有要求,傳入UseMiddleware
內的參數順序也沒有要求,但是我建議將非DI參數放到前面,DI參數放到后面。(這一塊感覺微軟做的好牛皮)
4.Invoke/InvokeAsync
的其他參數也能夠通過依賴注入(DI)填充,可以接收 Transient、Scoped 和 Singleton 的DI參數。
一個簡單的中間件如下:
public class MyMiddleware { // 用于調用管道中的下一個中間件 private readonly RequestDelegate _next; public MyMiddleware( RequestDelegate next, ITransientService transientService, ISingletonService singletonService) { _next = next; } public async Task InvokeAsync( HttpContext context, ITransientService transientService, IScopedService scopedService, ISingletonService singletonService) { // 下一個中間件處理之前的操作 Console.WriteLine("MyMiddleware Begin"); await _next(context); // 下一個中間件處理完成后的操作 Console.WriteLine("MyMiddleware End"); } }
然后,你可以通過UseMiddleware
方法將其添加到管道中
public class Startup { public void Configure(IApplicationBuilder app) { app.UseMiddleware<MyMiddleware>(); } }
不過,一般不推薦直接使用UseMiddleware
,而是將其封裝到擴展方法中
public static class AppMiddlewareApplicationBuilderExtensions { public static IApplicationBuilder UseMy(this IApplicationBuilder app) => app.UseMiddleware<MyMiddleware>(); } public class Startup { public void Configure(IApplicationBuilder app) { app.UseMy(); } }
優勢:
按照請求進行激活。這個就是說,上面基于約定的中間件實例是單例的,但是基于工廠的中間件,可以在依賴注入時設置中間件實例的生命周期。
使中間件強類型化(因為其實現了接口IMiddleware
)
該方式的實現基于IMiddlewareFactory
和IMiddleware
。先來看一下接口定義:
public interface IMiddlewareFactory { IMiddleware? Create(Type middlewareType); void Release(IMiddleware middleware); } public interface IMiddleware { Task InvokeAsync(HttpContext context, RequestDelegate next); }
你有沒有想過當我們調用UseMiddleware
時,它是如何工作的呢?事實上,UseMiddleware
擴展方法會先檢查中間件是否實現了IMiddleware
接口。 如果實現了,則使用容器中注冊的IMiddlewareFactory
實例來解析該IMiddleware
的實例(這下你知道為什么稱為“基于工廠的中間件”了吧)。如果沒實現,那么就使用基于約定的中間件邏輯來激活中間件。
注意,基于工廠的中間件,在應用的服務容器中一般注冊為 Scoped 或 Transient 服務。
這樣的話,咱們就可以放心的將 Scoped 服務注入到中間件的構造函數中了。
接下來,咱們就來實現一個基于工廠的中間件:
public class YourMiddleware : IMiddleware { public async Task InvokeAsync(HttpContext context, RequestDelegate next) { // 下一個中間件處理之前的操作 Console.WriteLine("YourMiddleware Begin"); await next(context); // 下一個中間件處理完成后的操作 Console.WriteLine("YourMiddleware End"); } } public static class AppMiddlewareApplicationBuilderExtensions { public static IApplicationBuilder UseYour(this IApplicationBuilder app) => app.UseMiddleware<YourMiddleware>(); }
然后,在ConfigureServices
中添加中間件依賴注入
public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddTransient<YourMiddleware>(); } }
最后,在Configure
中使用中間件
public class Startup { public void Configure(IApplicationBuilder app) { app.UseYour(); } }
微軟提供了IMiddlewareFactory
的默認實現:
public class MiddlewareFactory : IMiddlewareFactory { // The default middleware factory is just an IServiceProvider proxy. // This should be registered as a scoped service so that the middleware instances // don't end up being singletons. // 默認的中間件工廠僅僅是一個 IServiceProvider 的代理 // 該工廠應該注冊為 Scoped 服務,這樣中間件實例就不會成為單例 private readonly IServiceProvider _serviceProvider; public MiddlewareFactory(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } public IMiddleware? Create(Type middlewareType) { return _serviceProvider.GetRequiredService(middlewareType) as IMiddleware; } public void Release(IMiddleware middleware) { // The container owns the lifetime of the service // DI容器來管理服務的生命周期 } }
可以看到,該工廠使用過DI容器來解析出服務實例的。因此,當使用基于工廠的中間件時,是無法通過UseMiddleware
向中間件的構造函數傳參的。
基于約定的中間件實例都是 Singleton;而基于工廠的中間件實例可以是 Singleton、Scoped 和 Transient(當然,不建議注冊為 Singleton)
基于約定的中間件實例構造函數中可以通過依賴注入傳參,也可以用過UseMiddleware
傳參;而基于工廠的中間件只能通過依賴注入傳參
基于約定的中間件實例可以在Invoke/InvokeAsync
中添加更多的依賴注入參數;而基于工廠的中間件只能按照IMiddleware
的接口定義進行實現。
關于“ASP.NET Core中間件Middleware怎么用”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,使各位可以學到更多知識,如果覺得文章不錯,請把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。