ASP.NET Core中間件初始化的實(shí)現(xiàn)
前言
在日常使用ASP.NET Core開(kāi)發(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接口的方式定義中間件有何不同
- 再次,使用基于約定的方式自定義中間件的究竟是如何約束我們編寫(xiě)的類(lèi)和方法格式的
- 最后,使用約定的方式定義中間件,通過(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è)類(lèi),但是更好的符合符合面向?qū)ο蟮姆庋b思想,它的使用方式大致如下,首先定義一個(gè)Middleware的類(lèi)
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);
}
}
編寫(xiě)完成之后,需要手動(dòng)的將類(lèi)注冊(cè)到管道中才能生效,注冊(cè)方式如下所示
實(shí)現(xiàn)IMiddleware的方式
還有一種方式是實(shí)現(xiàn)IMiddleware接口的方式,這種方式比如前兩種方式常用,但是也確確實(shí)實(shí)的存在于ASP.NET Core中,既然存在也就有它存在的理由,我們也可以探究一下,它的使用方式也是需要自定義一個(gè)類(lèi)去實(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ò)注入的方式編寫(xiě)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)擊查看源碼👈]可以看到以下代碼
/// <summary> /// 將中間件委托添加到應(yīng)用程序的請(qǐng)求管道。 /// </summary> /// <param name="middleware">中間件委托</param> /// <returns>The <see cref="IApplicationBuilder"/>.</returns> IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware);
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ò)展類(lèi)的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ò)編寫(xiě)類(lèi)的形式定義中間件會(huì)是怎樣的轉(zhuǎn)換操作。找到UseMiddleware擴(kuò)展方法所在的地方,也就是UseMiddlewareExtensions擴(kuò)展類(lèi)里[點(diǎn)擊查看源碼👈],我們最常用的是UseMiddleware這個(gè)方法,而且這個(gè)方法是UseMiddlewareExtensions擴(kuò)展類(lèi)的入口方法[點(diǎn)擊查看源碼👈],說(shuō)白了就是它是完全調(diào)用別的方法沒(méi)有自己的實(shí)現(xiàn)邏輯
/// <summary>
/// 將中間件類(lèi)型添加到應(yīng)用程序的請(qǐng)求管道.
/// </summary>
/// <typeparam name="TMiddleware">中間件類(lèi)型</typeparam>
/// <param name="args">傳遞給中間件類(lèi)型實(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ò)展方法,在展示該方法之前我們先羅列一下該類(lèi)的常量屬性,因?yàn)轭?lèi)中的方法有用到,如下所示
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 =>
{
//獲取自定義中間件類(lèi)的非靜態(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));
}
//自定義的中間件類(lèi)中必須包含名為Invoke或InvokeAsync的方法,否則也會(huì)拋出異常
if (invokeMethods.Length == 0)
{
throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoInvokeMethod(InvokeMethodName, InvokeAsyncMethodName, middleware));
}
//名為Invoke或InvokeAsync的方法的返回值類(lèi)型必須是Task類(lèi)型,否則會(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類(lèi)型的實(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類(lèi)型的next
//也就是基于約定定義的中間件構(gòu)造函數(shù)的第一個(gè)參數(shù)是RequestDelegate類(lèi)型的實(shí)例
ctorArgs[0] = next;
Array.Copy(args, 0, ctorArgs, 1, args.Length);
//創(chuàng)建基于約定的中間件實(shí)例
//又看到ActivatorUtilities這個(gè)類(lèi)了,關(guān)于這個(gè)類(lèi)有興趣的可以研究一下,可以根據(jù)容器創(chuàng)建類(lèi)型實(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è)倏?
var 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ò)程。代碼中的注釋我寫(xiě)的比較詳細(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類(lèi)型
- ②查找方法名可以為Invoke或InvokeAsync,且存在而且只能存在一個(gè)
- ③Invoke或InvokeAsync方法返回值需為T(mén)ask,且方法的第一個(gè)參數(shù)必須為HttpContext類(lèi)型
- ④Invoke或InvokeAsync方法如果只包含HttpContext類(lèi)型參數(shù),則該方法直接轉(zhuǎn)換為RequestDelegate
- ⑤我們之所以可以通過(guò)構(gòu)造注入在中間件中獲取服務(wù)是因?yàn)榛诩s定的方式是通過(guò)ActivatorUtilities類(lèi)創(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
{
//釋放middleware
middlewareFactory.Release(middleware);
}
};
});
}
通過(guò)上面的代碼我們可以看到,IMiddleware實(shí)例是通過(guò)IMiddlewareFactory實(shí)例創(chuàng)建而來(lái),ASP.NET Core中IMiddlewareFactory默認(rèn)注冊(cè)的實(shí)現(xiàn)類(lèi)是MiddlewareFactory,接下來(lái)我們看下這個(gè)類(lèi)的實(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ù)類(lèi)型從容器中獲取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)類(lèi)到容器中,生命周期可自行約束,如果生命周期為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方法依賴(lài)的操作,首先反射是獲取UseMiddlewareExtensions類(lèi)的GetService方法操作
private static readonly MethodInfo GetServiceInfo = typeof(UseMiddlewareExtensions).GetMethod(nameof(GetService), BindingFlags.NonPublic | BindingFlags.Static)!;
其中GetService方法的實(shí)現(xiàn)如下所示,其實(shí)就是在容器ServiceProvider中獲取指定類(lèi)型實(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外部依賴(lài)已經(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、middleware
var httpContextArg = Expression.Parameter(typeof(HttpContext), "httpContext");
var providerArg = Expression.Parameter(typeof(IServiceProvider), "serviceProvider");
var instanceArg = Expression.Parameter(middleware, "middleware");
//穿件Expression數(shù)組,且數(shù)組第一個(gè)參數(shù)為httpContextArg
var 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類(lèi)型操作
if (parameterType.IsByRef)
{
throw new NotSupportedException(Resources.FormatException_InvokeDoesNotSupportRefOrOutParams(InvokeMethodName));
}
//構(gòu)建參數(shù)類(lèi)型表達(dá)式,即用戶(hù)構(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)換為parameterType
methodArguments[i] = Expression.Convert(getServiceCall, parameterType);
}
//獲取中間件類(lèi)型表達(dá)式
Expression middlewareInstanceArg = instanceArg;
if (methodInfo.DeclaringType != null && methodInfo.DeclaringType != typeof(T))
{
//轉(zhuǎn)換中間件類(lèi)型表達(dá)式類(lèi)型與聲明類(lèi)型一致
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)換為lambda
var lambda = Expression.Lambda<Func<T, HttpContext, IServiceProvider, Task>>(body, instanceArg, httpContextArg, providerArg);
return lambda.Compile();
}
上面的代碼比較抽象,其實(shí)主要是因?yàn)樗腔诒磉_(dá)式樹(shù)進(jìn)行各種操作的,如果對(duì)表達(dá)式樹(shù)比較熟悉的話,可能對(duì)上面的代碼理解起來(lái)還好一點(diǎn),如果不熟悉表達(dá)式樹(shù)的話,可能理解起來(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)化為我們比較容易理解的代碼的話就是下面所示的操作,如果我們編寫(xiě)了一個(gè)如下的中間件代碼
public class Middleware
{
public Task Invoke(HttpContext context, ILoggerFactory loggerFactory)
{
}
}
那么通過(guò)Compile方法將轉(zhuǎn)換為類(lèi)似以下形式的操作,這樣說(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類(lèi)創(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)類(lèi)ApplicationBuilder類(lèi)的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é)果即RequestDelegate
app = 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ú)立的類(lèi)有兩種方式,即基于約定的方式和實(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)于ASP.NET Core中間件初始化的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)ASP.NET Core中間件初始化內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
.Net?WebApi中實(shí)現(xiàn)自動(dòng)依賴(lài)注入的三種方法(最新推薦)
依賴(lài)關(guān)系注入 (DI) ,是一種軟件設(shè)計(jì)模式,這是一種在類(lèi)及其依賴(lài)項(xiàng)之間實(shí)現(xiàn)控制反轉(zhuǎn) (IoC)?的技術(shù), .NET 中的依賴(lài)關(guān)系注入是框架的內(nèi)置部分,與配置、日志記錄和選項(xiàng)模式一樣,這篇文章主要介紹了.Net?WebApi中實(shí)現(xiàn)自動(dòng)依賴(lài)注入的三種方法,需要的朋友可以參考下2024-03-03
asp.net DataGrid 中文字符排序的實(shí)現(xiàn)代碼
在論壇上看到有位朋友希望對(duì)中文按拼音進(jìn)行排序,剛好最近有點(diǎn)空,貼一份原來(lái)一個(gè)同事寫(xiě)的一個(gè)排序類(lèi),僅稍微改動(dòng)了下下,拿出來(lái)分享下.2009-09-09
ASP.NET MVC5網(wǎng)站開(kāi)發(fā)之實(shí)現(xiàn)數(shù)據(jù)存儲(chǔ)層功能(三)
這篇文章主要為大家詳細(xì)介紹了ASP.NET MVC5網(wǎng)站開(kāi)發(fā)之實(shí)現(xiàn)數(shù)據(jù)存儲(chǔ)層功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-08-08
Javascript 直接調(diào)用服務(wù)器C#代碼 ASP.NET Ajax實(shí)例
近來(lái)總有一些朋友會(huì)問(wèn)到一些入門(mén)的問(wèn)題,把這些問(wèn)題整理一下,寫(xiě)出來(lái)。在以前的文章里,曾經(jīng)利用純JS編寫(xiě)過(guò)Ajax引擎,在真正開(kāi)發(fā)的時(shí)候,大家都不喜歡以這種低效率的方式開(kāi)發(fā),利用MS Ajax的集成的引擎,可以簡(jiǎn)單不少工作。2010-03-03
ASP.NET GridView中文本內(nèi)容無(wú)法換行(自動(dòng)換行/正常換行)
用GridView來(lái)顯示課程表,每個(gè)單元格的內(nèi)容包括課程名、上課地點(diǎn)、教師姓名,然后我想讓它們分行顯示,感興趣的朋友可以了解下,或許對(duì)你有所幫助2013-02-02
asp.net顯示圖片到指定的Image控件中 具體實(shí)現(xiàn)
這篇文章介紹了asp.net顯示圖片到指定的Image控件中 具體實(shí)現(xiàn),有需要的朋友可以參考一下2013-11-11
VS2012/VS2013本地發(fā)布網(wǎng)站問(wèn)題集錦(HTTP錯(cuò)誤代碼)
這篇文章主要為大家詳細(xì)介紹了VS2012/VS2013本地發(fā)布網(wǎng)站遇到問(wèn)題,HTTP錯(cuò)誤代碼的解決方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-04-04
asp.net實(shí)現(xiàn)三層架構(gòu)的例子
這篇文章主要介紹了asp.net實(shí)現(xiàn)三層架構(gòu)的例子,十分的簡(jiǎn)單實(shí)用,有需要的小伙伴可以參考下。2015-07-07

