ASP.NET Core中间件初始化探究
前言
????在日常使用ASP.NET Core開發(fā)的過(guò)程中我們多多少少會(huì)設(shè)計(jì)到使用中間件的場(chǎng)景,ASP.NET Core默認(rèn)也為我們內(nèi)置了許多的中間件,甚至有時(shí)候我們需要自定義中間件來(lái)幫我們處理一些請(qǐng)求管道過(guò)程中的處理。接下來(lái),我們將圍繞著以下幾個(gè)問(wèn)題來(lái)簡(jiǎn)單探究一下,關(guān)于ASP.NET Core中間件是如何初始化的
首先,使用UseMiddleware注冊(cè)自定義中間件和直接Use的方式有何不同
其次,使用基于約定的方式定義中間件和使用實(shí)現(xiàn)IMiddleware接口的方式定義中間件有何不同
再次,使用基于約定的方式自定義中間件的究竟是如何約束我們編寫的類和方法格式的
最后,使用約定的方式定義中間件,通過(guò)構(gòu)造注入和通過(guò)Invoke方法注入的方式有何不同
接下來(lái)我們將圍繞這幾個(gè)核心點(diǎn)來(lái)逐步探究關(guān)于ASP.NET Core關(guān)于中間件初始化的神秘面紗,來(lái)指導(dǎo)我們以后使用它的時(shí)候需要有注意點(diǎn),來(lái)減少踩坑的次數(shù)。
自定義的方式
使用自定義中間件的方式有好幾種,咱們簡(jiǎn)單來(lái)演示一下三種比較常用方式。
Use方式
首先,也是最直接最簡(jiǎn)單的使用Use的方式,比如
app.Use(async (context, next) => {var endpoint = context.Features.Get<IEndpointFeature>()?.Endpoint;if (endpoint != null){ResponseCacheAttribute responseCache = endpoint.Metadata.GetMetadata<ResponseCacheAttribute>();if (responseCache != null){//做一些事情}}await next(); });基于約定的方式
然后使用UseMiddleware也是我們比較常用的一種方式,這種方式使用起來(lái)相對(duì)于第一種來(lái)說(shuō),雖然使用起來(lái)可能會(huì)稍微繁瑣一點(diǎn),畢竟需要定義一個(gè)類,但是更好的符合符合面向?qū)ο蟮姆庋b思想,它的使用方式大致如下,首先定義一個(gè)Middleware的類
public class RequestCultureMiddleware {private readonly RequestDelegate _next;public RequestCultureMiddleware(RequestDelegate next){_next = next;}public async Task InvokeAsync(HttpContext context){var cultureQuery = context.Request.Query["culture"];if (!string.IsNullOrWhiteSpace(cultureQuery)){var culture = new CultureInfo(cultureQuery);CultureInfo.CurrentCulture = culture;CultureInfo.CurrentUICulture = culture;}await _next(context);} }編寫完成之后,需要手動(dòng)的將類注冊(cè)到管道中才能生效,注冊(cè)方式如下所示
app.UseMiddleware<RequestCultureMiddleware>();實(shí)現(xiàn)IMiddleware的方式
還有一種方式是實(shí)現(xiàn)IMiddleware接口的方式,這種方式比如前兩種方式常用,但是也確確實(shí)實(shí)的存在于ASP.NET Core中,既然存在也就有它存在的理由,我們也可以探究一下,它的使用方式也是需要自定義一個(gè)類去實(shí)現(xiàn)IMiddleware接口,如下所示
public class RequestCultureOtherMiddleware:IMiddleware {public async Task InvokeAsync(HttpContext context, RequestDelegate next){var cultureQuery = context.Request.Query["culture"];if (!string.IsNullOrWhiteSpace(cultureQuery)){var culture = new CultureInfo(cultureQuery);CultureInfo.CurrentCulture = culture;CultureInfo.CurrentUICulture = culture;}await next(context);} }這種方式和第二種方式略有不同,需要手動(dòng)將中間件注冊(cè)到容器中,至于聲明周期也沒(méi)做特殊要求,可以直接注冊(cè)為單例模式
services.AddSingleton<IMiddleware,RequestCultureOtherMiddleware>();完成上步操作之后,同樣也需要將其注冊(cè)到管道中去
app.UseMiddleware<RequestCultureOtherMiddleware>();這種方式相對(duì)于第二種方式的主要區(qū)別在于靈活性方面的差異,它實(shí)現(xiàn)了IMiddleware接口,那就要受到IMiddleware接口的約束,也就是我們常說(shuō)的里氏代換原則,首先我們可以先來(lái)看下IMiddleware接口的定義[點(diǎn)擊查看源碼????]
public interface IMiddleware {/// <summary>/// 請(qǐng)求處理方法/// </summary>/// <param name="context">當(dāng)前請(qǐng)求上下文</param>/// <param name="next">請(qǐng)求管道中下一個(gè)中間件的委托</param>Task InvokeAsync (HttpContext context, RequestDelegate next); }通過(guò)這個(gè)接口也就看出來(lái)InvokeAsync只能接受HttpContext和RequestDelegate參數(shù),無(wú)法定義其他形式的參數(shù),也沒(méi)辦法通過(guò)注入的方式編寫InvokeAsync方法參數(shù),說(shuō)白了就是沒(méi)有第二種方式靈活,受限較大。
關(guān)于常用的自定義中間件的方式,我們就先說(shuō)到這里,我們也知道了如何定義使用中間件。接下來(lái)我們就來(lái)探討一下,這么多種方式之間到底存在怎樣的聯(lián)系。
源碼探究
上面我們已經(jīng)演示了關(guān)于使用中間件的幾種方式,那么這么幾種使用方式之間有啥聯(lián)系或區(qū)別,我們只看到了表面的,接下來(lái)我們來(lái)看一下關(guān)于中間件初始化的源碼來(lái)一探究竟。
首先,無(wú)論那種形式都是基于IApplicationBuilder這個(gè)接口擴(kuò)展而來(lái)的,所以我們先從這里下手,找到源碼IApplicationBuilder位置[點(diǎn)擊查看源碼????]可以看到以下代碼
IApplicationBuilder接口里只有Use的方式可以添加中間件,由此我們可以大致猜到兩點(diǎn)信息
其它添加中間件的方式,都是在擴(kuò)展自IApplicationBuilder,并不是IApplicationBuilder本身的方法。
其它添加中間件的形式,最終都會(huì)轉(zhuǎn)換為Use的方式。
Use擴(kuò)展方法
上面我們看到了IApplicationBuilder只包含了一個(gè)Use方法,但是我們?nèi)粘>幊讨凶畛J褂玫降膮s并不是這一個(gè),而是來(lái)自UseExtensions擴(kuò)展類的Use擴(kuò)展方法,實(shí)現(xiàn)如下所示[點(diǎn)擊查看源碼????]
public static IApplicationBuilder Use(this IApplicationBuilder app, Func<HttpContext, Func<Task>, Task> middleware) {//將middleware轉(zhuǎn)換為Use(Func<RequestDelegate, RequestDelegate> middleware)的形式return app.Use(next =>{return context =>{Func<Task> simpleNext = () => next(context);return middleware(context, simpleNext);};}); }如預(yù)料的那樣,Use的擴(kuò)展方法最終都會(huì)轉(zhuǎn)換為Use(Func<RequestDelegate, RequestDelegate> middleware)的形式去執(zhí)行。Use擴(kuò)展方法的形式還是比較清晰的,畢竟也是基于委托的形式,而且參數(shù)是固定的。
UseMiddleware
上面我們看到了Use的擴(kuò)展方法,它最終還是轉(zhuǎn)換為Use(Func<RequestDelegate, RequestDelegate> middleware)的形式去執(zhí)行。接下來(lái)我們來(lái)看下通過(guò)編寫類的形式定義中間件會(huì)是怎樣的轉(zhuǎn)換操作。找到UseMiddleware擴(kuò)展方法所在的地方,也就是UseMiddlewareExtensions擴(kuò)展類里[點(diǎn)擊查看源碼????],我們最常用的是UseMiddleware這個(gè)方法,而且這個(gè)方法是UseMiddlewareExtensions擴(kuò)展類的入口方法[點(diǎn)擊查看源碼????],說(shuō)白了就是它是完全調(diào)用別的方法沒(méi)有自己的實(shí)現(xiàn)邏輯
/// <summary> /// 將中間件類型添加到應(yīng)用程序的請(qǐng)求管道. /// </summary> /// <typeparam name="TMiddleware">中間件類型</typeparam> /// <param name="args">傳遞給中間件類型實(shí)例的構(gòu)造函數(shù)的參數(shù).</param> /// <returns>The <see cref="IApplicationBuilder"/> instance.</returns> public static IApplicationBuilder UseMiddleware<[DynamicallyAccessedMembers(MiddlewareAccessibility)]TMiddleware>(this IApplicationBuilder app, params object[] args) { return app.UseMiddleware(typeof(TMiddleware), args); }繼續(xù)向下看找到它調(diào)用的擴(kuò)展方法,在展示該方法之前我們先羅列一下該類的常量屬性,因?yàn)轭愔械姆椒ㄓ杏玫?#xff0c;如下所示
internal const string InvokeMethodName = "Invoke"; internal const string InvokeAsyncMethodName = "InvokeAsync";從這里我們可以得到一個(gè)信息,基于約定的形式自定義的中間件觸發(fā)方法名可以是Invoke或InvokeAsync,繼續(xù)看執(zhí)行方法的實(shí)現(xiàn)代碼
public static IApplicationBuilder UseMiddleware(this IApplicationBuilder app, [DynamicallyAccessedMembers(MiddlewareAccessibility)] Type middleware, params object[] args) {//判斷自定義的中間件是否是實(shí)現(xiàn)了IMiddleware接口if (typeof(IMiddleware).GetTypeInfo().IsAssignableFrom(middleware.GetTypeInfo())){//Middleware不支持直接傳遞參數(shù)//因?yàn)樗亲?cè)到容器中的,所以不能通過(guò)構(gòu)造函數(shù)傳遞自定義的參數(shù),否則拋出異常if (args.Length > 0){throw new NotSupportedException(Resources.FormatException_UseMiddlewareExplicitArgumentsNotSupported(typeof(IMiddleware)));}//實(shí)現(xiàn)IMiddleware接口的中間件走的是這個(gè)邏輯,咱們待會(huì)看return UseMiddlewareInterface(app, middleware);}var applicationServices = app.ApplicationServices;return app.Use(next =>{//獲取自定義中間件類的非靜態(tài)public方法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();//方法名為Invoke或InvokeAsync的方法只能有有一個(gè),存在多個(gè)話會(huì)拋出異常if (invokeMethods.Length > 1){throw new InvalidOperationException(Resources.FormatException_UseMiddleMutlipleInvokes(InvokeMethodName, InvokeAsyncMethodName));}//自定義的中間件類中必須包含名為Invoke或InvokeAsync的方法,否則也會(huì)拋出異常if (invokeMethods.Length == 0){throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoInvokeMethod(InvokeMethodName, InvokeAsyncMethodName, middleware));}//名為Invoke或InvokeAsync的方法的返回值類型必須是Task類型,否則會(huì)拋出異常var methodInfo = invokeMethods[0];if (!typeof(Task).IsAssignableFrom(methodInfo.ReturnType)){throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNonTaskReturnType(InvokeMethodName, InvokeAsyncMethodName, nameof(Task)));}//獲取Invoke或InvokeAsync方法的參數(shù)var parameters = methodInfo.GetParameters();//如果該方法不存在參數(shù)或方法的第一個(gè)參數(shù)不是HttpContext類型的實(shí)例,會(huì)拋出異常if (parameters.Length == 0 || parameters[0].ParameterType != typeof(HttpContext)){throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoParameters(InvokeMethodName, InvokeAsyncMethodName, nameof(HttpContext)));}//定義新的數(shù)組比傳遞的參數(shù)長(zhǎng)度多一個(gè),為啥呢?往下看。var ctorArgs = new object[args.Length + 1];//因?yàn)榉椒〝?shù)組的首元素是RequestDelegate類型的next//也就是基于約定定義的中間件構(gòu)造函數(shù)的第一個(gè)參數(shù)是RequestDelegate類型的實(shí)例ctorArgs[0] = next;Array.Copy(args, 0, ctorArgs, 1, args.Length);//創(chuàng)建基于約定的中間件實(shí)例//又看到ActivatorUtilities這個(gè)類了,關(guān)于這個(gè)類有興趣的可以研究一下,可以根據(jù)容器創(chuàng)建類型實(shí)例,非常好用var instance = ActivatorUtilities.CreateInstance(app.ApplicationServices, middleware, ctorArgs);//如果Invoke或InvokeAsync方法只有一個(gè)參數(shù),則直接創(chuàng)建RequestDelegate委托返回if (parameters.Length == 1){//RequestDelegate其實(shí)就是public delegate Task RequestDelegate(HttpContext context);return (RequestDelegate)methodInfo.CreateDelegate(typeof(RequestDelegate), instance);}//編譯Invoke或InvokeAsync方法,關(guān)于Compile的實(shí)現(xiàn)等會(huì)咱們?cè)倏磛ar factory = Compile<object>(methodInfo, parameters);//返回這個(gè)委托//看著這個(gè)委托的格式有點(diǎn)眼熟,其實(shí)就是RequestDelegate即public delegate Task RequestDelegate(HttpContext context);return context =>{var serviceProvider = context.RequestServices ?? applicationServices;//serviceProvider不能為空,否則沒(méi)法玩了if (serviceProvider == null){throw new InvalidOperationException(Resources.FormatException_UseMiddlewareIServiceProviderNotAvailable(nameof(IServiceProvider)));}//返回委托執(zhí)行結(jié)果return factory(instance, context, serviceProvider);};}); }這個(gè)方法其實(shí)是工作的核心方法,通過(guò)這里可以看出來(lái),自定義中間件的大致執(zhí)行過(guò)程。代碼中的注釋我寫的比較詳細(xì),有興趣的可以仔細(xì)了解一下,如果懶得看我們就大致總結(jié)一下大致的核心點(diǎn)
首先UseMiddleware的本質(zhì)確實(shí)還是執(zhí)行的Use方法
實(shí)現(xiàn)IMiddleware接口的中間件走的是獨(dú)立的處理邏輯,而且構(gòu)造函數(shù)傳遞自定義的參數(shù),因?yàn)樗臄?shù)據(jù)來(lái)自于容器的注入。
基于約定定義中間件的情況,即不實(shí)現(xiàn)IMiddleware的情況下。
①基于約定定義的中間件,構(gòu)造函數(shù)的第一個(gè)參數(shù)需要是RequestDelegate類型
②查找方法名可以為Invoke或InvokeAsync,且存在而且只能存在一個(gè)
③Invoke或InvokeAsync方法返回值需為Task,且方法的第一個(gè)參數(shù)必須為HttpContext類型
④Invoke或InvokeAsync方法如果只包含HttpContext類型參數(shù),則該方法直接轉(zhuǎn)換為RequestDelegate
⑤我們之所以可以通過(guò)構(gòu)造注入在中間件中獲取服務(wù)是因?yàn)榛诩s定的方式是通過(guò)ActivatorUtilities類創(chuàng)建的實(shí)例
通過(guò)上面的源碼我們了解到了實(shí)現(xiàn)IMiddleware接口的方式自定義中間件的方式是單獨(dú)處理的即在UseMiddlewareInterface方法中[點(diǎn)擊查看源碼????],接下來(lái)我們查看一下該方法的代碼
private static IApplicationBuilder UseMiddlewareInterface(IApplicationBuilder app, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type middlewareType) {return app.Use(next =>{return async context =>{var middlewareFactory = (IMiddlewareFactory?)context.RequestServices.GetService(typeof(IMiddlewareFactory));if (middlewareFactory == null){// 沒(méi)有middlewarefactory直接拋出異常throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoMiddlewareFactory(typeof(IMiddlewareFactory)));}//創(chuàng)建middleware實(shí)例var middleware = middlewareFactory.Create(middlewareType);if (middleware == null){throw new InvalidOperationException(Resources.FormatException_UseMiddlewareUnableToCreateMiddleware(middlewareFactory.GetType(), middlewareType));}try{//執(zhí)行middleware的InvokeAsync方法await middleware.InvokeAsync(context, next);}finally{//釋放middlewaremiddlewareFactory.Release(middleware);}};}); }通過(guò)上面的代碼我們可以看到,IMiddleware實(shí)例是通過(guò)IMiddlewareFactory實(shí)例創(chuàng)建而來(lái),ASP.NET Core中IMiddlewareFactory默認(rèn)注冊(cè)的實(shí)現(xiàn)類是MiddlewareFactory,接下來(lái)我們看下這個(gè)類的實(shí)現(xiàn)[點(diǎn)擊查看源碼????]
public class MiddlewareFactory : IMiddlewareFactory{ private readonly IServiceProvider _serviceProvider;public MiddlewareFactory(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; }public IMiddleware? Create(Type middlewareType) { //根據(jù)類型從容器中獲取IMiddleware實(shí)例 return _serviceProvider.GetRequiredService(middlewareType) as IMiddleware; }public void Release(IMiddleware middleware) { //因?yàn)槿萜骺刂屏藢?duì)象的生命周期,所以這里啥也沒(méi)有 }} 好吧,其實(shí)就是在容器中獲取的IMiddleware實(shí)例,通過(guò)這個(gè)我們就可以總結(jié)出來(lái)實(shí)現(xiàn)IMiddleware接口的形式創(chuàng)建中間件的操作需要實(shí)現(xiàn)IMiddleware接口,來(lái)約束中間件的行為,方法名只能為InvokeAsync
需要手動(dòng)注冊(cè)IMiddleware和實(shí)現(xiàn)類到容器中,生命周期可自行約束,如果生命周期為Scope或瞬時(shí),那么每次請(qǐng)求都會(huì)創(chuàng)建新的中間件實(shí)例
沒(méi)辦法通過(guò)InvokeAsync方法注入服務(wù),因?yàn)槭艿搅薎Middleware接口的約束
上面我們看到了實(shí)現(xiàn)IMiddleware接口的方式中間件是如何被初始化的,接下來(lái)我們繼續(xù)來(lái)看,基于約定的方式定義的中間件是如何被初始化的。通過(guò)上面我們展示的源碼可知,實(shí)現(xiàn)邏輯在Compile方法中,該方法整體實(shí)現(xiàn)方式就是基于Expression,主要原因個(gè)人猜測(cè)有兩點(diǎn),一個(gè)是形式比較靈活能應(yīng)對(duì)的場(chǎng)景較多,二是性能稍微比反射好一點(diǎn)。在此之前,我們先展示一下Compile方法依賴的操作,首先反射是獲取UseMiddlewareExtensions類的GetService方法操作
private?static?readonly?MethodInfo?GetServiceInfo?=?typeof(UseMiddlewareExtensions).GetMethod(nameof(GetService),?BindingFlags.NonPublic?|?BindingFlags.Static)!;其中GetService方法的實(shí)現(xiàn)如下所示,其實(shí)就是在容器ServiceProvider中獲取指定類型實(shí)例
private static object GetService(IServiceProvider sp, Type type, Type middleware) {var service = sp.GetService(type);if (service == null){throw new InvalidOperationException(Resources.FormatException_InvokeMiddlewareNoService(type, middleware));}return service; }好了上面已將Compile外部依賴已經(jīng)展示出來(lái)了,接下來(lái)我們就可以繼續(xù)探究Compile方法了[點(diǎn)擊查看源碼????]
private static Func<T, HttpContext, IServiceProvider, Task> Compile<T>(MethodInfo methodInfo, ParameterInfo[] parameters) {var middleware = typeof(T);//構(gòu)建三個(gè)Parameter名為httpContext、serviceProvider、middlewarevar httpContextArg = Expression.Parameter(typeof(HttpContext), "httpContext");var providerArg = Expression.Parameter(typeof(IServiceProvider), "serviceProvider");var instanceArg = Expression.Parameter(middleware, "middleware");//穿件Expression數(shù)組,且數(shù)組第一個(gè)參數(shù)為httpContextArgvar methodArguments = new Expression[parameters.Length];methodArguments[0] = httpContextArg;//因?yàn)镮nvoke或InvokeAsync方法第一個(gè)參數(shù)為HttpContext,且methodArguments第一個(gè)參數(shù)占位,所以跳過(guò)第一個(gè)參數(shù)for (int i = 1; i < parameters.Length; i++){//獲取方法參數(shù)var parameterType = parameters[i].ParameterType;//不支持ref類型操作if (parameterType.IsByRef){throw new NotSupportedException(Resources.FormatException_InvokeDoesNotSupportRefOrOutParams(InvokeMethodName));}//構(gòu)建參數(shù)類型表達(dá)式,即用戶構(gòu)建方法參數(shù)的操作var parameterTypeExpression = new Expression[]{providerArg,Expression.Constant(parameterType, typeof(Type)),Expression.Constant(methodInfo.DeclaringType, typeof(Type))};//聲明調(diào)用GetServiceInfo的表達(dá)式var getServiceCall = Expression.Call(GetServiceInfo, parameterTypeExpression);//將getServiceCall操作轉(zhuǎn)換為parameterTypemethodArguments[i] = Expression.Convert(getServiceCall, parameterType);}//獲取中間件類型表達(dá)式Expression middlewareInstanceArg = instanceArg;if (methodInfo.DeclaringType != null && methodInfo.DeclaringType != typeof(T)){//轉(zhuǎn)換中間件類型表達(dá)式類型與聲明類型一致middlewareInstanceArg = Expression.Convert(middlewareInstanceArg, methodInfo.DeclaringType);}//調(diào)用middlewareInstanceArg(即當(dāng)前中間件)的methodInfo(即獲取Invoke或InvokeAsync)方法參數(shù)(methodArguments)var body = Expression.Call(middlewareInstanceArg, methodInfo, methodArguments);//轉(zhuǎn)換為lambdavar lambda = Expression.Lambda<Func<T, HttpContext, IServiceProvider, Task>>(body, instanceArg, httpContextArg, providerArg);return lambda.Compile(); }上面的代碼比較抽象,其實(shí)主要是因?yàn)樗腔诒磉_(dá)式樹進(jìn)行各種操作的,如果對(duì)表達(dá)式樹比較熟悉的話,可能對(duì)上面的代碼理解起來(lái)還好一點(diǎn),如果不熟悉表達(dá)式樹的話,可能理解起來(lái)比較困難,不過(guò)還是建議簡(jiǎn)單學(xué)習(xí)一下Expression相關(guān)的操作,慢慢的發(fā)現(xiàn)還是挺有意思的,它的性能整體來(lái)說(shuō)比傳統(tǒng)的反射性能也會(huì)更好一點(diǎn)。其實(shí)Compile主要實(shí)現(xiàn)的操作轉(zhuǎn)化為我們比較容易理解的代碼的話就是下面所示的操作,如果我們編寫了一個(gè)如下的中間件代碼
public class Middleware {public Task Invoke(HttpContext context, ILoggerFactory loggerFactory){} }那么通過(guò)Compile方法將轉(zhuǎn)換為類似以下形式的操作,這樣說(shuō)的話可能會(huì)好理解一點(diǎn)
Task Invoke(Middleware instance, HttpContext httpContext, IServiceProvider provider) {return instance.Invoke(httpContext, (ILoggerFactory)UseMiddlewareExtensions.GetService(provider, typeof(ILoggerFactory)); }通過(guò)上面的源碼分析我們了解到,基于約定的方式定義的中間件實(shí)例是通過(guò)ActivatorUtilities類創(chuàng)建的,而且創(chuàng)建實(shí)例是在返回RequestDelegate委托之前,IApplicationBuilder的Use方法只會(huì)在首次運(yùn)行的時(shí)候執(zhí)行,后續(xù)管道串聯(lián)執(zhí)行的其實(shí)正是它返回的結(jié)果RequestDelegate這個(gè)委托。但是執(zhí)行轉(zhuǎn)換Invoke或InvokeAsync方法為執(zhí)行委托的操作卻是在返回的RequestDelegate委托當(dāng)中,也就是我們每次請(qǐng)求管道會(huì)處理的邏輯中。這個(gè)邏輯可以在IApplicationBuilder默認(rèn)的實(shí)現(xiàn)類ApplicationBuilder類的Build方法中可以得知[點(diǎn)擊查看源碼????],它的實(shí)現(xiàn)邏輯如下所示
public RequestDelegate Build() {//最后的管道處理,即請(qǐng)求未能匹配到任何終結(jié)點(diǎn)的情況RequestDelegate app = context =>{var endpoint = context.GetEndpoint();var endpointRequestDelegate = endpoint?.RequestDelegate;if (endpointRequestDelegate != null){var message =$"The request reached the end of the pipeline without executing the endpoint: '{endpoint!.DisplayName}'. " +$"Please register the EndpointMiddleware using '{nameof(IApplicationBuilder)}.UseEndpoints(...)' if using " +$"routing.";throw new InvalidOperationException(message);}//執(zhí)行管道的重點(diǎn)是404,只有未命中任何終結(jié)點(diǎn)的情況下才會(huì)走到這里context.Response.StatusCode = StatusCodes.Status404NotFound;return Task.CompletedTask;};//_components即我們通過(guò)Use添加的中間件foreach (var component in _components.Reverse()){//得到執(zhí)行結(jié)果即RequestDelegateapp = component(app);}//返回第一個(gè)管道中間件return app; }通過(guò)上面的代碼我們可以清楚的看到,管道最終執(zhí)行的就是執(zhí)行Func<RequestDelegate, RequestDelegate>這個(gè)委托的返回結(jié)果RequestDelegate。由此得到結(jié)論,基于約定的中間件形式,通構(gòu)造函數(shù)注入的服務(wù)實(shí)例,是和應(yīng)用程序的生命周期一致的。通過(guò)Invoke或InvokeAsync方法注入的服務(wù)實(shí)例每次請(qǐng)求都會(huì)被執(zhí)行到,即生命周期是Scope的。
總結(jié)
????通過(guò)本次對(duì)源碼的研究,我們認(rèn)識(shí)到了自定義的ASP.NET Core中間件是如何被初始化的。雖然自定義的中間件的形式有許多種方式,但是最終還都是轉(zhuǎn)換為IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware)這種方式。將中間件抽離為獨(dú)立的類有兩種方式,即基于約定的方式和實(shí)現(xiàn)IMiddleware接口的形式,通過(guò)分析源碼我們也更深刻的了解兩種方式的不同之處?;诩s定的方式更靈活,它的聲明周期是單例的,但是通過(guò)它的Invoke或InvokeAsync方法注入的服務(wù)實(shí)例生命周期是Scope的。實(shí)現(xiàn)IMiddleware接口的方式生命周期取決于自己注冊(cè)服務(wù)實(shí)例時(shí)候聲明的周期,而且這種方式?jīng)]辦法通過(guò)方法注入服務(wù),因?yàn)橛蠭Middleware接口InvokeAsync方法的約束。
????當(dāng)然不僅僅是我們?cè)诳偨Y(jié)中說(shuō)的的這些,還存在更多的細(xì)節(jié),這些我們?cè)诜治鲈创a的時(shí)候都有涉及,相信閱讀文章比較仔細(xì)的同學(xué)肯定會(huì)注意到這些。閱讀源碼收獲正是這些,解決心中的疑問(wèn),了解更多的細(xì)節(jié),有助于在實(shí)際使用中避免一些不必要的麻煩。本次講解就到這里,愿各位能有所收獲。
????歡迎掃碼關(guān)注我的公眾號(hào)????
總結(jié)
以上是生活随笔為你收集整理的ASP.NET Core中间件初始化探究的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: NLog-ASP.NET Core 5入
- 下一篇: 如何在 ASP.NET Core 中使用