ASP.NET Core 运行原理解剖[3]:Middleware-请求管道的构成
在 ASP.NET 中,我們知道,它有一個面向切面的請求管道,有19個主要的事件構(gòu)成,能夠讓我們進行靈活的擴展。通常是在?web.config?中通過注冊?HttpModule?來實現(xiàn)對請求管道事件監(jiān)聽,并通過?HttpHandler?進入到我們的應(yīng)用程序中。而在 ASP.NET Core 中,對請求管道進行了重新設(shè)計,通過使用一種稱為中間件的方式來進行管道的注冊,同時也變得更加簡潔和強大。
IApplicationBuilder
在第一章中,我們就介紹過?IApplicationBuilder,在我們熟悉的 Startup 類的Configure方法中,通常第一個參數(shù)便是IApplicationBuilder,對它應(yīng)該是非常熟悉了,而在這里,就再徹底的解剖一下?IApplicationBuilder?對象。
首先,IApplicationBuilder?是用來構(gòu)建請求管道的,而所謂請求管道,本質(zhì)上就是對?HttpContext?的一系列操作,即通過對?Request?的處理,來生成?Reponse。因此,在 ASP.NET Core 中定義了一個?RequestDelegate?委托,來表示請求管道中的一個步驟,它有如下定義:
public delegate Task RequestDelegate(HttpContext context);而對請求管道的注冊是通過?Func<RequestDelegate, RequestDelegate>?類型的委托(也就是中間件)來實現(xiàn)的。
為什么要設(shè)計一個這樣的委托呢?讓我們來分析一下,它接收一個?RequestDelegate?類型的參數(shù),并返回一個?RequestDelegate?類型,也就是說前一個中間件的輸出會成為下一個中間件的輸入,這樣把他們串聯(lián)起來,形成了一個完整的管道。那么第一個中間件的輸入是什么,最后一個中間件的輸出又是如何處理的呢?帶著這個疑惑,我們慢慢往下看。
IApplicationBuilder 的默認(rèn)實現(xiàn)是 ApplicationBuilder,它的定義在?HttpAbstractions?項目中 :
public interface IApplicationBuilder{IServiceProvider ApplicationServices { get; set; }IFeatureCollection ServerFeatures { get; }IDictionary<string, object> Properties { get; } ??IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware); ?
??IApplicationBuilder New(); ?
?
? ?RequestDelegate Build(); }
? ?public class ApplicationBuilder : IApplicationBuilder{ ?
? ?
? ??private readonly IList<Func<RequestDelegate, RequestDelegate>> _components = new List<Func<RequestDelegate, RequestDelegate>>();... }
它有一個內(nèi)部的?Func<RequestDelegate, RequestDelegate>?類型的集合(用來保存我們注冊的中間件)和三個核心方法:
Use
Use是我們非常熟悉的注冊中間件的方法,其實現(xiàn)非常簡單,就是將注冊的中間件保存到其內(nèi)部屬性?_components?中。
public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware){_components.Add(middleware); ? ?return this; }我們使用Use注冊兩個簡單的中間件:
public void Configure(IApplicationBuilder app){app.Use(next =>{Console.WriteLine("A"); ? ? ??return async (context) =>{ ? ? ? ? ? ?// 1. 對Request做一些處理// TODO// 2. 調(diào)用下一個中間件Console.WriteLine("A-BeginNext"); ? ? ? ? ?
? ?await next(context);Console.WriteLine("A-EndNext"); ? ? ? ? ? ?// 3. 生成 Response//TODO};});app.Use(next =>{Console.WriteLine("B"); ? ?
? ? ? ?return async (context) =>{ ? ? ? ? ? ?// 1. 對Request做一些處理// TODO// 2. 調(diào)用下一個中間件Console.WriteLine("B-BeginNext"); ? ? ?
? ? ? ? ? ?await next(context);Console.WriteLine("B-EndNext"); ? ? ? ? ?
? ? ? ? ? ??// 3. 生成 Response//TODO};}); }
如上,注冊了A和B兩個中間件,通常每一個中間件有如上所示三個處理步驟,也就是圍繞著Next分別對Request和Respone做出相應(yīng)的處理,而B的執(zhí)行會嵌套在A的里面,因此A是第一個處理Request,并且最后一個收到Respone,這樣就構(gòu)成一個經(jīng)典的的U型管道。
而上面所示代碼的執(zhí)行結(jié)算如下:
非常符合我們的預(yù)期,但是最終返回的結(jié)果是一個?404 HttpNotFound,這又是為什么呢?讓我們再看一下它的?Build?方法。
Build
第一章中,我們介紹到,在?Hosting?的啟動中,便是通過該?Build?方法創(chuàng)建一個?RequestDelegate?類型的委托,Http Server 通過該委托來完成整個請求的響應(yīng),它有如下定義:
public RequestDelegate Build(){RequestDelegate app = context =>{context.Response.StatusCode = 404; ? ? ?? ? ? ?return Task.CompletedTask;}; ?
? ?foreach (var component in _components.Reverse()){app = component(app);} ? ?return app; }
可以看到首先定義了一個?404?的中間件,然后使用了Reverse函數(shù)將注冊的中間件列表進行反轉(zhuǎn),因此首先執(zhí)行我們所注冊的最后一個中間件,輸入?yún)?shù)便是一個?404?,依次執(zhí)行到第一個中間件,將它的輸出傳遞給?HostingApplication?再由?IServer?來執(zhí)行。整個構(gòu)建過程是類似于俄羅斯套娃,按我們的注冊順序從里到外,一層套一層。
最后,再解釋一下,上面的代碼返回404的原因。RequestDelegate的執(zhí)行是從俄羅斯套娃的最外層開始,也就是從我們注冊的第一個中間件A開始執(zhí)行,A調(diào)用B,B則調(diào)用前面介紹的404?的中間件,最終也就返回了一個?404,那如何避免返回404呢,這時候就要用到 IApplicationBuilder 的擴展方法Run了。
Run
對于上面?404?的問題,我們只需要對中間件A做如下修改即可:
app.Use(next => {Console.WriteLine("B"); ? ?return async (context) =>{ ? ? ? ?// 1. 對Request做一些處理// TODO// 2. 調(diào)用下一個中間件Console.WriteLine("B-BeginNext"); ? ?? ? ? ?await context.Response.WriteAsync("Hello ASP.NET Core!");Console.WriteLine("B-EndNext"); ? ? ?
? ? ? ??// 3. 生成 Response//TODO}; });
將之前的?await next(context);?替換成了?await context.Response.WriteAsync("Hello ASP.NET Core!");,自然也就將404替換成了返回一個?"Hello ASP.NET Core!"?字符串。
在我們注冊的中間件中,是通過?Next?委托 來串連起來的,如果在某一個中間件中沒有調(diào)用?Next?委托,則該中間件將做為管道的終點,因此,我們在最后一個中間件不應(yīng)該再調(diào)用?Next?委托,而?Run?擴展方法,通常用來注冊最后一個中間件,有如下定義:
public static class RunExtensions{ ??public static void Run(this IApplicationBuilder app, RequestDelegate handler) ? ?
{ ? ? ? ?
? ? ? ?if (app == null){ ? ? ? ? ?
? ? ? ? ? ? ? ?throw new ArgumentNullException(nameof(app));} ? ? ?
?? ? ? ?if (handler == null){ ? ? ? ?
?? ? ? ?? ?throw new ArgumentNullException(nameof(handler));}app.Use(_ => handler);} }
可以看到,Run?方法接收的只有一個?RequestDelegate?委托,沒有了?Next?委托,進而保證了它不會再調(diào)用下一個中間件,即使我們在它之后注冊了其它中間件,也不會被執(zhí)行。因此建議,我們最終處理?Response?的中間件使用?Run?來注冊,類似于 ASP.NET 4.x 中的?HttpHandler。
New
而?IApplicationBuilder?還有一個常用的?New?方法,通常用來創(chuàng)建分支:
public class ApplicationBuilder : IApplicationBuilder{ ??private ApplicationBuilder(ApplicationBuilder builder) ? ?{Properties = new CopyOnWriteDictionary<string, object>(builder.Properties, StringComparer.Ordinal);} ?
?
??public IApplicationBuilder New() ? ?{ ? ?
??? ?return new ApplicationBuilder(this);} }
New 方法根據(jù)自身來“克隆”了一個新的 ApplicationBuilder 對象,而新的 ApplicationBuilder 可以訪問到創(chuàng)建它的對象的?Properties?屬性,但是對自身?Properties?屬性的修改,卻不到影響到它的創(chuàng)建者,這是通過?CopyOnWriteDictionary?來實現(xiàn)的:
internal class CopyOnWriteDictionary<TKey, TValue> : IDictionary<TKey, TValue> { ??private readonly IDictionary<TKey, TValue> _sourceDictionary; ?
?public CopyOnWriteDictionary(IDictionary<TKey, TValue> sourceDictionary, IEqualityComparer<TKey> comparer) ? ?{_sourceDictionary = sourceDictionary;_comparer = comparer;} ?
?
? ?private IDictionary<TKey, TValue> ReadDictionary => _innerDictionary ?? _sourceDictionary; ? ?private IDictionary<TKey, TValue> WriteDictionary => { ? ? ? ?if (_innerDictionary == null){_innerDictionary = new Dictionary<TKey, TValue>(_sourceDictionary, _comparer);} ? ? ?
? ??return _innerDictionary;}; }
最后再放一張網(wǎng)上經(jīng)典的 ASP.NET Core 請求管道圖:
IMiddleware
通過上面的介紹,我們知道,中間件本質(zhì)上就是一個類型為?Func<RequestDelegate, RequestDelegate>?的委托對象,但是直接使用這個委托對象還是多有不便,因此 ASP.NET Core 提供了一個更加具體的中間件的概念,我們在大部分情況下都會將中間件定義成一個單獨的類型,使代碼更加清晰。
首先看一下?IMiddleware?接口定義:
public interface IMiddleware{ ?? ??Task InvokeAsync(HttpContext context, RequestDelegate next); }
IMiddleware?中只有一個方法:InvokeAsync,它接收一個?HttpContext?參數(shù),用來處理HTTP請求,和一個?RequestDelegate?參數(shù),代表下一個中間件。當(dāng)然, ASP.NET Core 并沒有要求我們必須實現(xiàn)?IMiddleware?接口,我們也可以像?Startup?類的實現(xiàn)方式一樣,通過遵循一些約定來更加靈活的定義我們的中間件。
UseMiddleware
對于?IMiddleware?類型的中間件的注冊,使用?UseMiddleware?擴展方法,定義如下:
public static class UseMiddlewareExtensions{ ??
? ?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())){ ? ? ? ?
? ? ? ?? ?return UseMiddlewareInterface(app, middleware);}...} }
泛型的注冊方法,在 ASP.NET Core 中比較常見,比如日志,依賴注入中都有類似的方法,它只是一種簡寫形式,最終都是將泛型轉(zhuǎn)換為Type類型進行注冊。
如上代碼,首先通過通過?IsAssignableFrom?方法來判斷是否實現(xiàn)?IMiddleware?接口,從而分為了兩種方式實現(xiàn)方式,我們先看一下實現(xiàn)了?IMiddleware?接口的中間件的執(zhí)行過程:
private static IApplicationBuilder UseMiddlewareInterface(IApplicationBuilder app, Type middlewareType){? ?return app.Use(next =>{ ? ? ?
? ? ?return async context =>{ ? ? ? ?
? ? ?? ?var middlewareFactory = (IMiddlewareFactory)context.RequestServices.GetService(typeof(IMiddlewareFactory)); ? ? ? ? ? ?var middleware = middlewareFactory.Create(middlewareType); ? ? ? ? ? ?try{ ? ? ? ? ?
? ? ?? ? ? ? ?await middleware.InvokeAsync(context, next);} ? ? ? ? ?
? ? ?? ? ? ?finally{middlewareFactory.Release(middleware);}};}); }
如上,創(chuàng)建了一個?Func<RequestDelegate, RequestDelegate>?委托,在返回的?RequestDelegate?委托中調(diào)用我們的 IMiddleware 中間件的?InvokeAsync?方法。其實也只是簡單的對?Use?方法的一種封裝。而 IMiddleware 實例的創(chuàng)建則使用 IMiddlewareFactory 來實現(xiàn)的:
public class MiddlewareFactory : IMiddlewareFactory{ ??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) ? ?{} }
通過如上代碼,可以發(fā)現(xiàn)一個坑,因為 IMiddleware 實例的創(chuàng)建是直接從 DI 容器中來獲取的,也就是說,如果我們沒有將我們實現(xiàn)了?IMiddleware?接口的中間件注冊到DI中,而直接使用?UseMiddleware?來注冊時,會報錯:“`InvalidOperationException: No service for type 'MiddlewareXX' has been registered.”。
不過通常我們并不會去實現(xiàn)?IMiddleware?接口,而是采用基于約定的,更加靈活的方式來定義中間件,而此時,UseMiddleware?方法會通過反射來創(chuàng)建中間件的實例:
public static IApplicationBuilder UseMiddleware(this IApplicationBuilder app, Type middleware, params object[] args){ ??// 未實例 IMiddleware 時的注冊方式return app.Use(next =>{ ? ? ?
? ?var methods = middleware.GetMethods(BindingFlags.Instance | BindingFlags.Public); ? ? ?
? ? ?var invokeMethods = methods.Where(m => ? ? ?
? ? ?? ? ?string.Equals(m.Name, InvokeMethodName, StringComparison.Ordinal)|| string.Equals(m.Name, InvokeAsyncMethodName, StringComparison.Ordinal)).ToArray();... ? ? ?
? ? ?? ? ? ?var methodinfo = invokeMethods[0]; ? ?
? ? ?? ? ? ?var parameters = methodinfo.GetParameters();
? ? ?? ? ? ?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); ? ? ? ?
? ? ?? ? ? ?if (parameters.Length == 1){ ? ? ? ? ?
? ? ?? ? ? ??return (RequestDelegate)methodinfo.CreateDelegate(typeof(RequestDelegate), instance);} ? ? ?
? ? ?? ? ? ???var factory = Compile<object>(methodinfo, parameters); ? ? ? ?return context =>{ ? ? ? ? ? ?
? ? ?? ? ? ???return factory(instance, context, serviceProvider);};}); }
首先是根據(jù)命名約定來判斷我們的注冊的 Middleware 類是否符合要求,然后使用ActivatorUtilities.CreateInstance調(diào)用構(gòu)造函數(shù),創(chuàng)建實例。而在調(diào)用構(gòu)造函數(shù)時需要的碼數(shù),會先在傳入到?UseMiddleware?方法中的參數(shù)?args?中來查找 ,如果找不到則再去DI中查找,再找不到,將會拋出一個異常。實例創(chuàng)建成功后,調(diào)用Invoke/InvokeAsync方法,不過針對Invoke方法的調(diào)用并沒有直接使用反射來實現(xiàn),而是采用表了達式,后者具有更好的性能,感興趣的可以去看完整代碼?UseMiddlewareExtensions?中的?Compile?方法。
通過以上代碼,我們也可以看出?IMiddleware?的命名約定:
必須要有一個?Invoke?或?InvokeAsync?方法,兩者也只能存在一個。
返回類型必須是?Task?或者繼承自?Task。
Invoke?或?InvokeAsync?方法必須要有一個?HttpContext 類型的參數(shù)。
不過,需要注意的是,Next?委托必須放在構(gòu)造函數(shù)中,而不能放在?InvokeAsync?方法參數(shù)中,這是因為?Next?并不在DI系統(tǒng)中,而?ActivatorUtilities.CreateInstance?創(chuàng)建實例時,也會檢查構(gòu)造中是否具有?RequestDelegate?類型的?Next?參數(shù),如果沒有,則會拋出一個異常:“A suitable constructor for type '{instanceType}' could not be located. Ensure the type is concrete and services are registered for all parameters of a public constructor.”。
UseWhen
在有些場景下,我們可能需要針對某些請求,做一些特定的操作。當(dāng)然,我們可以定義一個中間件,在中間件中判斷該請求是否符合我們的預(yù)期,進而選擇是否執(zhí)行該操作。但是有一種更好的方式?UseWhen?來實現(xiàn)這樣的需求。從名字我們可以猜出,它提供了一種基于條件來注冊中間件的方式,有如下定義:
using Predicate = Func<HttpContext, bool>;public static IApplicationBuilder UseWhen(this IApplicationBuilder app, Predicate predicate, Action<IApplicationBuilder> configuration){
? ?var branchBuilder = app.New();configuration(branchBuilder); ?
? ? ?return app.Use(main =>{branchBuilder.Run(main); ? ? ?
? ? ? ?var branch = branchBuilder.Build(); ?
? ? ? ??return context =>{ ? ? ? ? ?
? ? ? ???if (predicate(context)){ ? ? ? ? ?
? ? ? ???? ? ?return branch(context);} ? ? ? ? ? ?
? ? ? ???? ? ?else{ ? ? ? ? ? ? ?
? ? ? ???? ? ? ?return main(context);}};}); }
首先使用上面介紹過的?New?方法創(chuàng)建一個管道分支,將我們傳入的?configuration?委托注冊到該分支中,然后再將?Main?也就是后續(xù)的中間件也注冊到該分支中,最后通過我們指定的?Predicate?來判斷是執(zhí)行新分支,還是繼續(xù)在之前的管道中執(zhí)行。
它的使用方式如下:
public void Configure(IApplicationBuilder app){app.UseMiddlewareA();app.UseWhen(context => context.Request.Path.StartsWithSegments("/api"), appBuilder =>{appBuilder.UseMiddlewareB();});app.UseMiddlewareC); }我們注冊了三個中間件:A, B, C 。中間件 A 和 C 會一直執(zhí)行(除了短路的情況), 而 B 只有在符合預(yù)期時,也就是當(dāng)請求路徑以?/api?開頭時,才會執(zhí)行。
UseWhen是非常強大和有用的,建議當(dāng)我們想要針對某些請求做一些特定的處理時,我們應(yīng)該只為這些請求注冊特定的中間件,而不是在中間件中去判斷請求是否符合預(yù)期來選擇執(zhí)行某些操作,這樣能有更好的性能。
以下是?UseWhen?的一些使用場景:
分別對MVC和WebAPI做出不同的錯誤響應(yīng)。
為特定的IP添加診斷響應(yīng)頭。
只對匿名用戶使用輸出緩存。
針對某些請求進行統(tǒng)計。
MapWhen
MapWhen 與 UseWhen 非常相似,但是他們有著本質(zhì)的區(qū)別,先看一下?MapWhen?的定義:
using Predicate = Func<HttpContext, bool>;public static IApplicationBuilder MapWhen(this IApplicationBuilder app, Predicate predicate, Action<IApplicationBuilder> configuration){
? ?var branchBuilder = app.New();configuration(branchBuilder); ?
? ??var branch = branchBuilder.Build(); ?
? ?? ?// put middleware in pipelinevar options = new MapWhenOptions{Predicate = predicate,Branch = branch,}; ? ?return app.Use(next => new MapWhenMiddleware(next, options).Invoke); }
如上,可以看出他們的區(qū)別:MapWhen?并沒有將父分支中的后續(xù)中間件注冊進來,而是一個獨立的分支,而在?MapWhenMiddleware?中只是簡單的判斷是執(zhí)行新分支還是舊分支:
public class MapWhenMiddleware{... ? ?public async Task Invoke(HttpContext context) ? ?{ ? ?? ?if (_options.Predicate(context)){ ? ? ? ?
? ?? ?await _options.Branch(context);} ? ? ? ?
? ?? ?else{ ? ? ? ?
? ?? ?? ?await _next(context);}} }
再看一下?MapWhen?的運行效果:
public void Configure(IApplicationBuilder app){app.UseMiddlewareA();app.MapWhen(context => context.Request.Path.StartsWithSegments("/api"), appBuilder =>{appBuilder.UseMiddlewareB();});app.UseMiddlewareC(); }如上,中間件A將一直執(zhí)行,之后如果請求路徑以?/api?開頭,則會執(zhí)行?B?,并到此結(jié)束,不會再執(zhí)行?C?,反之,不執(zhí)行?B?,而執(zhí)行?C?以及后續(xù)的其它的中間件。
當(dāng)我們希望某些請求使用完全獨立的處理方式時,MapWhen?就非常有用,如?UseStaticFiles?:
public void Configure(IApplicationBuilder app){app.MapWhen(context => context.Request.Path.Value.StartsWithSegments("/assets"), appBuilder => appBuilder.UseStaticFiles()); }如上,只有以?/assets?開頭的請求,才會執(zhí)行?StaticFiles?中間件,而其它請求則不會執(zhí)行?StaticFiles?中間件,這樣可以帶來稍微的性能提升。
UsePathBase
UsePathBase用于拆分請求路徑,類似于 MVC 中?Area?的效果,它不會創(chuàng)建請求管道分支,不影響管道的流程,僅僅是設(shè)置?Request?的?Path?和?PathBase?屬性:
public static IApplicationBuilder UsePathBase(this IApplicationBuilder app, PathString pathBase){pathBase = pathBase.Value?.TrimEnd('/'); ??if (!pathBase.HasValue){ ? ? ? ?return app;} ? ?
?
?return app.UseMiddleware<UsePathBaseMiddleware>(pathBase); }
?
?public class UsePathBaseMiddleware{ ?
?
??public async Task Invoke(HttpContext context) ? ?{ ? ? ?
??
?? ?if (context.Request.Path.StartsWithSegments(_pathBase, out matchedPath, out remainingPath)){ ? ? ? ?
?? ? ? ?? ?var originalPath = context.Request.Path; ? ?
?? ? ? ? ? ?var originalPathBase = context.Request.PathBase;context.Request.Path = remainingPath;context.Request.PathBase = originalPathBase.Add(matchedPath); ? ? ? ? ? ?try{ ? ? ? ? ? ?
?? ? ? ? ? ? ? ?await _next(context);} ? ? ? ? ?
?? ? ? ? ? ?finally{context.Request.Path = originalPath;context.Request.PathBase = originalPathBase;}} ? ?
?? ? ? ??else{ ? ? ? ? ?
?? ? ? ?? ?await _next(context);}} }
如上,當(dāng)請求路徑以我們指定的?PathString?開頭時,則將請求的 PathBase 設(shè)置為 傳入的?pathBase,Path 則為剩下的部分。
PathString 用來表示請求路徑的一個片段,它可以從字符串隱式轉(zhuǎn)換,但是要求必須以?/?開頭,并且不以?/?結(jié)尾。
Map
Map 包含?UsePathBase?的功能,并且創(chuàng)建一個獨立的分支來完成請求的處理,類似于?MapWhen:
public static class MapExtensions{ ??public static IApplicationBuilder Map(this IApplicationBuilder app, PathString pathMatch, Action<IApplicationBuilder> configuration) ? ?{... ? ? ?
??return app.Use(next => new MapMiddleware(next, options).Invoke);} }
以上方法中與?MapWhen?一樣,不同的只是?Map?調(diào)用了?MapMiddleware?中間件:
public class MapMiddleware{... ??public async Task Invoke(HttpContext 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);}} }
如上,可以看出?Map?擴展方法比?MapWhen?多了對?Request.PathBase?和?Request.Path?的處理,最后演示一下?Map?的用例:
public void Configure(IApplicationBuilder app){app.Map("/account", builder =>{builder.Run(async context =>{Console.WriteLine($"PathBase: {context.Request.PathBase}, Path: {context.Request.Path}"); ? ? ? ?? ?await context.Response.WriteAsync("This is from account");});});app.Run(async context =>{Console.WriteLine($"PathBase: {context.Request.PathBase}, Path: {context.Request.Path}"); ? ?
? ?? ?await context.Response.WriteAsync("This is default");}); }
如上,我們?yōu)?/account?定義了一個分支,當(dāng)我們?/account/user?的時候,將返回?This is from account?,并且會將 Request.PathBase 設(shè)置為?/account?,將 Request.Path 設(shè)置為?/user。
總結(jié)
本文詳細介紹了 ASP.NET Core 請求管道的構(gòu)建過程,以及一些幫助我們更加方便的來配置請求管道的擴展方法。在 ASP.NET Core 中,至少要有一個中間件來響應(yīng)請求,而我們的應(yīng)用程序?qū)嶋H上只是中間件的集合,MVC 也只是其中的一個中間件而已。簡單來說,中間件就是一個處理http請求和響應(yīng)的組件,多個中間件構(gòu)成了請求處理管道,每個中間件都可以選擇處理結(jié)束,還是繼續(xù)傳遞給管道中的下一個中間件,以此串聯(lián)形成請求管道。通常,我們注冊的每個中間件,每次請求和響應(yīng)均會被調(diào)用,但也可以使用?Map?,?MapWhen?,UseWhen?等擴展方法對中間件進行過濾。
參考資料:
conditional-middleware-based-on-request
asp-net-core-and-the-enterprise-part-3-middleware
相關(guān)文章:?
.NET Core 2.0 正式發(fā)布信息匯總
.NET Standard 2.0 特性介紹和使用指南
.NET Core 2.0 的dll實時更新、https、依賴包變更問題及解決
.NET Core 2.0 特性介紹和使用指南
Entity Framework Core 2.0 新特性
體驗 PHP under .NET Core
.NET Core 2.0使用NLog
升級項目到.NET Core 2.0,在Linux上安裝Docker,并成功部署
解決Visual Studio For Mac Restore失敗的問題
ASP.NET Core 2.0 特性介紹和使用指南
.Net Core下通過Proxy 模式 使用 WCF
.NET Core 2.0 開源Office組件 NPOI
ASP.NET Core Razor頁面 vs MVC
Razor Page–Asp.Net Core 2.0新功能 ?Razor Page介紹
MySql 使用 EF Core 2.0 CodeFirst、DbFirst、數(shù)據(jù)庫遷移(Migration)介紹及示例
.NET Core 2.0遷移技巧之web.config配置文件
asp.net core MVC 過濾器之ExceptionFilter過濾器(一)
ASP.NET Core 使用Cookie驗證身份
ASP.NET Core MVC – Tag Helpers 介紹
ASP.NET Core MVC – Caching Tag Helpers
ASP.NET Core MVC – Form Tag Helpers
ASP.NET Core MVC – 自定義 Tag Helpers
ASP.NET Core MVC – Tag Helper 組件
ASP.NET Core 運行原理解剖[1]:Hosting
ASP.NET Core 運行原理解剖[2]:Hosting補充之配置介紹
原文地址:http://www.cnblogs.com/RainingNight/p/middleware-in-asp-net-core.html
.NET社區(qū)新聞,深度好文,微信中搜索dotNET跨平臺或掃描二維碼關(guān)注
總結(jié)
以上是生活随笔為你收集整理的ASP.NET Core 运行原理解剖[3]:Middleware-请求管道的构成的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 发达国家与发展中国家编程语言技术的分布差
- 下一篇: 2017(深圳) .NET技术分享交流会