日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > asp.net >内容正文

asp.net

跟我一起学.NetCore之中间件(Middleware)应用和自定义

發布時間:2023/12/4 asp.net 48 豆豆
生活随笔 收集整理的這篇文章主要介紹了 跟我一起学.NetCore之中间件(Middleware)应用和自定义 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前言

Asp.NetCore中的請求管道是通過一系列的中間件組成的,使得請求會根據需求進行對應的過濾和加工處理。在平時開發中會時常引用別人定義好的中間件,只需簡單進行app.Usexxx就能完成中間件的注冊,但是對于一些定制化需求還得自己進行處理和封裝,以下說說中間件的注冊應用和自定義中間件;

正文

在上一小節中有簡單提到,當注冊第三方封裝的中間件時,其實本質還是調用了IApplicationBuilder的Use方法;而在開發過程中,會使用以下三種方式進行中間件的注冊:

  • Use:通過Use的方式注冊中間件,可以控制是否將請求傳遞到下一個中間件;

  • Run:通過Run的方式注冊中間件,一般用于斷路或請求管道末尾,即不會將請求傳遞下去;

  • Map/MapWhen:請求管道中增加分支,條件滿足之后就由分支管道進行處理,而不會切換回主管道;Map用于請求路徑匹配,而MapWhen可以有更多的條件進行過濾;

  • UseMiddleWare :?一般用于注冊自定義封裝的中間件,內部其實是使用Use的方式進行中間件注冊;

相信都知道我的套路了,光說不練假把式,來一個Asp.NetCore API項目進行以上幾種中間件注冊方式演示:

圖中代碼部分將原先默認注冊的中間件刪除了,用Use和Run的方式分別注冊了兩個中間件(這里只是簡單的顯示文字,里面可以根據需求添加相關邏輯),其中用Use注冊的方式在上一節中已經提及到,直接將中間件添加鏈表中,這里就不再贅述了;

對于使用Run方式注冊中間,小伙伴們肯定不甘心止于此吧,所以這里直接看Run是如何實現:

namespace Microsoft.AspNetCore.Builder {public static class RunExtensions{//?也是一個擴展方法,但參數就是一個委托public static void Run(this IApplicationBuilder app, RequestDelegate handler){//?參數校驗,如果null就拋出異常if (app == null){throw new ArgumentNullException(nameof(app));}//?傳入的委托校驗,如果null也是拋出異常if (handler == null){throw new ArgumentNullException(nameof(handler));}//?這里其實只有一個?RequestDelegate執行邏輯,并沒有傳遞功能//?本質也是使用方法Useapp.Use(_ => handler);}} }

通過代碼可知,用Run方式只是將處理邏輯RequestDelegate傳入,并沒有傳遞的邏輯,所以Run注冊的中間件就會形成斷路,導致后面的中間件不能再執行了;

使用Map和MapWhen注冊的方式,其實是給管道開一個分支,就像高速公路一樣,有匝道,到了對應出口就進匝道了,就不能倒車回來了(倒回來扣你12分);同樣,請求管道也是,當條件滿足時,請求就走Map對應的分支管道,就不能重新返回主管道了;

代碼走一波,在注冊中間件的地方增加Map的使用:

Configure全部代碼如下:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {// 使用Use注冊中間 app.Use(async (context, next) => {await context.Response.WriteAsync("Hello Use1\r\n");// 將請求傳遞到下一個中間件await next();await context.Response.WriteAsync("Hello Use1 Response\r\n");});// 使用Use注冊中間 參數類型不一樣app.Use(requestDelegate =>{return async (context) =>{await context.Response.WriteAsync("Hello Use2\r\n");// 將請求傳遞到下一個中間件await requestDelegate(context);await context.Response.WriteAsync("Hello Use2 Response\r\n");};});// 分支管道,只有匹配到路徑才走分支管道app.Map("/Hello", builder =>{builder.Use(async (context, next) =>{await context.Response.WriteAsync("Hello MapUse\r\n");// 將請求傳遞到分支管道的下一個中間件await next();await context.Response.WriteAsync("Hello MapUse Response\r\n");});// 注冊分支管道中間件builder.Run(async context => {await context.Response.WriteAsync("Hello MapRun1~~~\r\n");});// 注冊分支管道中間件builder.Run(async context => {await context.Response.WriteAsync("Hello MapRun2~~~\r\n");});});// 使用Run app.Run(async context => {await context.Response.WriteAsync("Hello Run~~~\r\n");});//使用Run注冊app.Run(async context => {await context.Response.WriteAsync("Hello Code綜藝圈~~~\r\n");}); }

執行看效果:

Map方式注冊的分支管道只有路徑匹配了才走,否則都會走主管道;

仔細的小伙伴肯定會說,那是在分支管道上用了Run注冊中間件了,形成了斷路,所以導致不能執行主管道剩下的中間件,好,那我們稍微改改代碼:

這樣運行訪問分支管道時會報錯,因為分支管道中沒有下一個中間件了,還調用下一個中間件,那肯定有問題;

改了改,如下運行:

進入匝道還想倒回來,12分不要了嗎,哈哈哈;

MapWhen注冊的分支管道邏輯和Map差不多類似,只是匹配的條件更加靈活而已,可以根據自己需求進行調節匹配,如下:

看到這,小伙伴應該都知道,接下來肯定不會放過Map/MapWhen的實現:

  • Map

    public static IApplicationBuilder Map(this IApplicationBuilder app, PathString pathMatch, Action<IApplicationBuilder> configuration) {// 進行參數校驗 IApplicationBuilder對象不能為空if (app == null){throw new ArgumentNullException(nameof(app));}// 進行參數校驗 傳進的委托對象不能為空if (configuration == null){throw new ArgumentNullException(nameof(configuration));}// 匹配的路徑末尾不能有"/",否則就拋異常if (pathMatch.HasValue && pathMatch.Value.EndsWith("/", StringComparison.Ordinal)){throw new ArgumentException("The path must not end with a '/'", nameof(pathMatch));}// 克隆一個IApplicationBuilder,共用之前的屬性,這里其實創建了分支管道var branchBuilder = app.New();// 將創建出來的branchBuilder進行相關配置configuration(branchBuilder);// 構造出分支管道var branch = branchBuilder.Build();// 將構造出來的管道和匹配路徑進行封裝var options = new MapOptions{Branch = branch,PathMatch = pathMatch,};// 注冊中間件return app.Use(next => new MapMiddleware(next, options).Invoke); }//?MapMiddleware?的Invoke方法,及如何進入分支管道處理的 public async Task Invoke(HttpContext context) {// 參數判斷if (context == null){throw new ArgumentNullException(nameof(context));}PathString matchedPath;PathString remainingPath;// 判斷是否匹配路徑,如果匹配上就進入分支if (context.Request.Path.StartsWithSegments(_options.PathMatch, out matchedPath, out remainingPath)){// 更新請求地址var path = context.Request.Path;var pathBase = context.Request.PathBase;context.Request.PathBase = pathBase.Add(matchedPath);context.Request.Path = remainingPath;try{// 進入分支管道await _options.Branch(context);}finally{// 恢復原先請求地址,回到主管道之后,并沒有進行主管道也下一個中間件的傳遞,所以主管道后續不在執行context.Request.PathBase = pathBase;context.Request.Path = path;}}else{// 匹配不到路徑就繼續主管道執行await _next(context);} }
  • MapWhen:其實和Map差不多,只是傳入的匹配規則不一樣,比較靈活:

    public static IApplicationBuilder MapWhen(this IApplicationBuilder app, Predicate predicate, Action<IApplicationBuilder> configuration) {if (app == null){throw new ArgumentNullException(nameof(app));}if (predicate == null){throw new ArgumentNullException(nameof(predicate));}if (configuration == null){throw new ArgumentNullException(nameof(configuration));}// 構建分支管道,和Map一致var branchBuilder = app.New();configuration(branchBuilder);var branch = branchBuilder.Build();// 封裝匹配規則var options = new MapWhenOptions{Predicate = predicate,Branch = branch,};// 注冊中間件return app.Use(next => new MapWhenMiddleware(next, options).Invoke); } // MapWhenMiddleware 的Invoke方法 public async Task Invoke(HttpContext context) {// 參數校驗if (context == null){throw new ArgumentNullException(nameof(context));}// 判斷是否匹配規則,如果匹配就進入分支管道if (_options.Predicate(context)){await _options.Branch(context);}else{// 沒有匹配就繼續執行主管道await _next(context);} }

現在是不是清晰明了多了,不懵了吧;還沒完呢,繼續往下;

上面注冊中間件的方式是不是有點不那么好看,當中間件多了時候,可讀性很是頭疼,維護性也得花點功夫,所以微軟肯定想到這了,提供了類的方式進行中間件的封裝(但是要按照約定來),從而可以像使用第三方中間件那樣簡單,如下:

使用及運行:

是不是自定義也沒想象中那么難,其中注冊封裝的中間件時,在擴展方法中使用了app.UseMiddleware<T>()進行注冊,這個上一節中提到過,就是那段在上一節中有點嫌早的代碼,這里就拷過來了(偷個懶):

// 看著調用的方法 public static IApplicationBuilder UseMiddleware<TMiddleware>(this IApplicationBuilder app, params object[] args) {// 內部調用了以下方法return app.UseMiddleware(typeof(TMiddleware), args); } // 其實這里是對自定義中間件的注冊,這里可以不用太深入了解 public static IApplicationBuilder UseMiddleware(this IApplicationBuilder app, Type middleware, params object[] args) {if (typeof(IMiddleware).GetTypeInfo().IsAssignableFrom(middleware.GetTypeInfo())){// IMiddleware doesn't support passing args directly since it's// activated from the containerif (args.Length > 0){throw new NotSupportedException(Resources.FormatException_UseMiddlewareExplicitArgumentsNotSupported(typeof(IMiddleware)));}return UseMiddlewareInterface(app, middleware);}// 取得容器var applicationServices = app.ApplicationServices;// 反編譯進行包裝成注冊中間件的樣子(Func<ReuqestDelegate,RequestDelegate>),但可以看到本質使用IApplicationBuilder中Use方法return app.Use(next =>{// 獲取指定類型中的方法列表var methods = middleware.GetMethods(BindingFlags.Instance | BindingFlags.Public);// 找出名字是Invoke或是InvokeAsync的方法var invokeMethods = methods.Where(m =>string.Equals(m.Name, InvokeMethodName, StringComparison.Ordinal)|| string.Equals(m.Name, InvokeAsyncMethodName, StringComparison.Ordinal)).ToArray();// 如果有多個方法 ,就拋出異常,這里保證方法的唯一if (invokeMethods.Length > 1){throw new InvalidOperationException(Resources.FormatException_UseMiddleMutlipleInvokes(InvokeMethodName, InvokeAsyncMethodName));}// 如果沒有找到,也就拋出異常if (invokeMethods.Length == 0){throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoInvokeMethod(InvokeMethodName, InvokeAsyncMethodName, middleware));}// 取得唯一的方法Invoke或是InvokeAsync方法var methodInfo = invokeMethods[0];// 判斷類型是否返回Task,如果不是就拋出異常,要求返回Task的目的是為了后續包裝RequestDelegateif (!typeof(Task).IsAssignableFrom(methodInfo.ReturnType)){throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNonTaskReturnType(InvokeMethodName, InvokeAsyncMethodName, nameof(Task)));}// 判斷方法的參數,參數的第一個參數必須是HttpContext類型var parameters = methodInfo.GetParameters();if (parameters.Length == 0 || parameters[0].ParameterType != typeof(HttpContext)){throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoParameters(InvokeMethodName, InvokeAsyncMethodName, nameof(HttpContext)));}// 開始構造RequestDelegate對象var ctorArgs = new object[args.Length + 1];ctorArgs[0] = next;Array.Copy(args, 0, ctorArgs, 1, args.Length);//?這里找到參數匹配最多的構造函數進行實例創建var instance = ActivatorUtilities.CreateInstance(app.ApplicationServices, middleware, ctorArgs);// 如果參數只有一個HttpContext 就包裝成一個RequestDelegate返回if (parameters.Length == 1){return (RequestDelegate)methodInfo.CreateDelegate(typeof(RequestDelegate), instance);}// 如果參數有多個的情況就單獨處理,這里不詳細進去了var factory = Compile<object>(methodInfo, parameters);return context =>{var serviceProvider = context.RequestServices ?? applicationServices;if (serviceProvider == null){throw new InvalidOperationException(Resources.FormatException_UseMiddlewareIServiceProviderNotAvailable(nameof(IServiceProvider)));}return factory(instance, context, serviceProvider);};}); }

可以看出,框架將我們封裝的中間件類進行了反射獲取對應的方法和屬性,然后封裝成中間件(Func<RequestDelegate,RequestDelegate>)的樣子,從而是得編碼更加方便,中間件更容易分類管理了;通過以上代碼注釋也能看出在封裝中間件的時候對應的約定,哈哈哈,是不是得重新看一遍代碼(如果這樣,目標達到了);對了,框架提供了IMiddleware了接口,實現中間件的時候可以實現,但是約定還是一個不能少;

總結

我去,不能熬了,再熬明天起不來跑步了;這篇內容有點多,之所以沒分開,感覺關聯性比較強,一口氣看下來比較合適;下一節說說文件相關的點;

---------------------------------------------------

CSDN:Code綜藝圈

知乎:Code綜藝圈

掘金:Code綜藝圈

博客園:Code綜藝圈

bilibili:Code綜藝圈

---------------------------------------------------

一個被程序搞丑的帥小伙,關注"Code綜藝圈",識別關注跟我一起學~~~

擼文不易,莫要白瞟,三連走起~~~~

總結

以上是生活随笔為你收集整理的跟我一起学.NetCore之中间件(Middleware)应用和自定义的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。