溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

ASP.NET Core中間件初始化的實(shí)現(xiàn)方法

發(fā)布時(shí)間:2021-05-28 12:59:19 來源:億速云 閱讀:392 作者:小新 欄目:開發(fā)技術(shù)

小編給大家分享一下ASP.NET Core中間件初始化的實(shí)現(xiàn)方法,希望大家閱讀完這篇文章之后都有所收獲,下面讓我們一起去探討吧!

前言 

 在日常使用ASP.NET Core開發(fā)的過程中我們多多少少會(huì)設(shè)計(jì)到使用中間件的場景,ASP.NET Core默認(rèn)也為我們內(nèi)置了許多的中間件,甚至有時(shí)候我們需要自定義中間件來幫我們處理一些請(qǐng)求管道過程中的處理。接下來,我們將圍繞著以下幾個(gè)問題來簡單探究一下,關(guān)于ASP.NET Core中間件是如何初始化的

  • 首先,使用UseMiddleware注冊自定義中間件和直接Use的方式有何不同

  • 其次,使用基于約定的方式定義中間件和使用實(shí)現(xiàn)IMiddleware接口的方式定義中間件有何不同

  • 再次,使用基于約定的方式自定義中間件的究竟是如何約束我們編寫的類和方法格式的

  • 最后,使用約定的方式定義中間件,通過構(gòu)造注入和通過Invoke方法注入的方式有何不同

接下來我們將圍繞這幾個(gè)核心點(diǎn)來逐步探究關(guān)于ASP.NET Core關(guān)于中間件初始化的神秘面紗,來指導(dǎo)我們以后使用它的時(shí)候需要有注意點(diǎn),來減少踩坑的次數(shù)。

自定義的方式

使用自定義中間件的方式有好幾種,咱們簡單來演示一下三種比較常用方式。

Use方式

首先,也是最直接最簡單的使用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也是我們比較常用的一種方式,這種方式使用起來相對(duì)于第一種來說,雖然使用起來可能會(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)的將類注冊到管道中才能生效,注冊方式如下所示

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)將中間件注冊到容器中,至于聲明周期也沒做特殊要求,可以直接注冊為單例模式

services.AddSingleton<IMiddleware,RequestCultureOtherMiddleware>();

完成上步操作之后,同樣也需要將其注冊到管道中去

app.UseMiddleware<RequestCultureOtherMiddleware>();

這種方式相對(duì)于第二種方式的主要區(qū)別在于靈活性方面的差異,它實(shí)現(xiàn)了IMiddleware接口,那就要受到IMiddleware接口的約束,也就是我們常說的里氏代換原則,首先我們可以先來看下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);
}

通過這個(gè)接口也就看出來InvokeAsync只能接受HttpContext和RequestDelegate參數(shù),無法定義其他形式的參數(shù),也沒辦法通過注入的方式編寫InvokeAsync方法參數(shù),說白了就是沒有第二種方式靈活,受限較大。
關(guān)于常用的自定義中間件的方式,我們就先說到這里,我們也知道了如何定義使用中間件。接下來我們就來探討一下,這么多種方式之間到底存在怎樣的聯(lián)系。

源碼探究

上面我們已經(jīng)演示了關(guān)于使用中間件的幾種方式,那么這么幾種使用方式之間有啥聯(lián)系或區(qū)別,我們只看到了表面的,接下來我們來看一下關(guān)于中間件初始化的源碼來一探究竟。
首先,無論那種形式都是基于IApplicationBuilder這個(gè)接口擴(kuò)展而來的,所以我們先從這里下手,找到源碼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è),而是來自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í)行。接下來我們來看下通過編寫類的形式定義中間件會(huì)是怎樣的轉(zhuǎn)換操作。找到UseMiddleware擴(kuò)展方法所在的地方,也就是UseMiddlewareExtensions擴(kuò)展類里[點(diǎn)擊查看源碼?],我們最常用的是UseMiddleware這個(gè)方法,而且這個(gè)方法是UseMiddlewareExtensions擴(kuò)展類的入口方法[點(diǎn)擊查看源碼?],說白了就是它是完全調(diào)用別的方法沒有自己的實(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)轭愔械姆椒ㄓ杏玫?,如下所?/p>

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)樗亲缘饺萜髦械?,所以不能通過構(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ù)長度多一個(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ì)咱們再看
        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不能為空,否則沒法玩了
            if (serviceProvider == null)
            {
                throw new InvalidOperationException(Resources.FormatException_UseMiddlewareIServiceProviderNotAvailable(nameof(IServiceProvider)));
            }
            //返回委托執(zhí)行結(jié)果
            return factory(instance, context, serviceProvider);
        };
    });
}

這個(gè)方法其實(shí)是工作的核心方法,通過這里可以看出來,自定義中間件的大致執(zhí)行過程。代碼中的注釋我寫的比較詳細(xì),有興趣的可以仔細(xì)了解一下,如果懶得看我們就大致總結(jié)一下大致的核心點(diǎn)

  • 首先UseMiddleware的本質(zhì)確實(shí)還是執(zhí)行的Use方法

  • 實(shí)現(xiàn)IMiddleware接口的中間件走的是獨(dú)立的處理邏輯,而且構(gòu)造函數(shù)傳遞自定義的參數(shù),因?yàn)樗臄?shù)據(jù)來自于容器的注入。

  • 基于約定定義中間件的情況,即不實(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

    • ⑤我們之所以可以通過構(gòu)造注入在中間件中獲取服務(wù)是因?yàn)榛诩s定的方式是通過ActivatorUtilities類創(chuàng)建的實(shí)例

通過上面的源碼我們了解到了實(shí)現(xiàn)IMiddleware接口的方式自定義中間件的方式是單獨(dú)處理的即在UseMiddlewareInterface方法中[點(diǎn)擊查看源碼?],接下來我們查看一下該方法的代碼

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)
            {
                // 沒有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);
            }
        };
    });
}

通過上面的代碼我們可以看到,IMiddleware實(shí)例是通過IMiddlewareFactory實(shí)例創(chuàng)建而來,ASP.NET Core中IMiddlewareFactory默認(rèn)注冊的實(shí)現(xiàn)類是MiddlewareFactory,接下來我們看下這個(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ì)象的生命周期,所以這里啥也沒有
    }
}

好吧,其實(shí)就是在容器中獲取的IMiddleware實(shí)例,通過這個(gè)我們就可以總結(jié)出來實(shí)現(xiàn)IMiddleware接口的形式創(chuàng)建中間件的操作

  • 需要實(shí)現(xiàn)IMiddleware接口,來約束中間件的行為,方法名只能為InvokeAsync

  • 需要手動(dòng)注冊IMiddleware和實(shí)現(xiàn)類到容器中,生命周期可自行約束,如果生命周期為Scope或瞬時(shí),那么每次請(qǐng)求都會(huì)創(chuàng)建新的中間件實(shí)例

  • 沒辦法通過InvokeAsync方法注入服務(wù),因?yàn)槭艿搅薎Middleware接口的約束

上面我們看到了實(shí)現(xiàn)IMiddleware接口的方式中間件是如何被初始化的,接下來我們繼續(xù)來看,基于約定的方式定義的中間件是如何被初始化的。通過上面我們展示的源碼可知,實(shí)現(xiàn)邏輯在Compile方法中,該方法整體實(shí)現(xiàn)方式就是基于Expression,主要原因個(gè)人猜測有兩點(diǎn),一個(gè)是形式比較靈活能應(yīng)對(duì)的場景較多,二是性能稍微比反射好一點(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)展示出來了,接下來我們就可以繼續(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ù)占位,所以跳過第一個(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)換為parameterType
        methodArguments[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)換為lambda
    var lambda = Expression.Lambda<Func<T, HttpContext, IServiceProvider, Task>>(body, instanceArg, httpContextArg, providerArg);
    return lambda.Compile();
}

上面的代碼比較抽象,其實(shí)主要是因?yàn)樗腔诒磉_(dá)式樹進(jìn)行各種操作的,如果對(duì)表達(dá)式樹比較熟悉的話,可能對(duì)上面的代碼理解起來還好一點(diǎn),如果不熟悉表達(dá)式樹的話,可能理解起來比較困難,不過還是建議簡單學(xué)習(xí)一下Expression相關(guān)的操作,慢慢的發(fā)現(xiàn)還是挺有意思的,它的性能整體來說比傳統(tǒng)的反射性能也會(huì)更好一點(diǎn)。其實(shí)Compile主要實(shí)現(xiàn)的操作轉(zhuǎn)化為我們比較容易理解的代碼的話就是下面所示的操作,如果我們編寫了一個(gè)如下的中間件代碼

public class Middleware
{
    public Task Invoke(HttpContext context, ILoggerFactory loggerFactory)
    {
    }
}

那么通過Compile方法將轉(zhuǎn)換為類似以下形式的操作,這樣說的話可能會(huì)好理解一點(diǎn)

Task Invoke(Middleware instance, HttpContext httpContext, IServiceProvider provider)
{
    return instance.Invoke(httpContext, (ILoggerFactory)UseMiddlewareExtensions.GetService(provider, typeof(ILoggerFactory));
}

通過上面的源碼分析我們了解到,基于約定的方式定義的中間件實(shí)例是通過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即我們通過Use添加的中間件
    foreach (var component in _components.Reverse())
    {
       //得到執(zhí)行結(jié)果即RequestDelegate
        app = component(app);
    }
    //返回第一個(gè)管道中間件
    return app;
}

通過上面的代碼我們可以清楚的看到,管道最終執(zhí)行的就是執(zhí)行Func<RequestDelegate, RequestDelegate>這個(gè)委托的返回結(jié)果RequestDelegate。

由此得到結(jié)論,基于約定的中間件形式,通構(gòu)造函數(shù)注入的服務(wù)實(shí)例,是和應(yīng)用程序的生命周期一致的。通過Invoke或InvokeAsync方法注入的服務(wù)實(shí)例每次請(qǐng)求都會(huì)被執(zhí)行到,即生命周期是Scope的。

總結(jié)

    通過本次對(duì)源碼的研究,我們認(rèn)識(shí)到了自定義的ASP.NET Core中間件是如何被初始化的。雖然自定義的中間件的形式有許多種方式,但是最終還都是轉(zhuǎn)換為IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware)這種方式。將中間件抽離為獨(dú)立的類有兩種方式,即基于約定的方式和實(shí)現(xiàn)IMiddleware接口的形式,通過分析源碼我們也更深刻的了解兩種方式的不同之處。基于約定的方式更靈活,它的聲明周期是單例的,但是通過它的Invoke或InvokeAsync方法注入的服務(wù)實(shí)例生命周期是Scope的。實(shí)現(xiàn)IMiddleware接口的方式生命周期取決于自己注冊服務(wù)實(shí)例時(shí)候聲明的周期,而且這種方式?jīng)]辦法通過方法注入服務(wù),因?yàn)橛蠭Middleware接口InvokeAsync方法的約束。

    當(dāng)然不僅僅是我們在總結(jié)中說的的這些,還存在更多的細(xì)節(jié),這些我們在分析源碼的時(shí)候都有涉及,相信閱讀文章比較仔細(xì)的同學(xué)肯定會(huì)注意到這些。閱讀源碼收獲正是這些,解決心中的疑問,了解更多的細(xì)節(jié),有助于在實(shí)際使用中避免一些不必要的麻煩。本次講解就到這里,愿各位能有所收獲。

看完了這篇文章,相信你對(duì)“ASP.NET Core中間件初始化的實(shí)現(xiàn)方法”有了一定的了解,如果想了解更多相關(guān)知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道,感謝各位的閱讀!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI