• AspNetCore7.0源码解读之UseMiddleware


    Use​Middleware​Extensions

    前言

    本文编写时源码参考github仓库主分支。

    aspnetcore提供了Use方法供开发者自定义中间件,该方法接收一个委托对象,该委托接收一个RequestDelegate对象,并返回一个RequestDelegate对象,方法定义如下:

    IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware);
    

    委托RequestDelegate的定义

    /// <summary>
    /// A function that can process an HTTP request.
    /// </summary>
    /// <param name="context">The <see cref="HttpContext"/> for the request.</param>
    /// <returns>A task that represents the completion of request processing.</returns>
    public delegate Task RequestDelegate(HttpContext context);
    

    如果我们直接使用IApplicationBuilder.Use来写中间件逻辑,可以使用lamda表达式来简化代码,如下:

    app.Use((RequestDelegate next) =>
    {
        return (HttpContext ctx) =>
        {
            // do your logic
            return next(ctx);
        };
    });
    

    如果写一些简单的逻辑,这种方式最为方便,问题是如果需要写的中间件代码比较多,依然这样去写,会导致我们Program.cs文件代码非常多,如果有多个中间件,那么最后我们的的Program.cs文件包含多个中间件代码,看上去十分混乱。

    将中间件逻辑独立出来

    为了解决我们上面的代码不优雅,我们希望能将每个中间件业务独立成一个文件,多个中间件代码不混乱的搞到一起。我们需要这样做。

    单独的中间件文件

    // Middleware1.cs
    public class Middleware1
    {
        public static RequestDelegate Logic(RequestDelegate requestDelegate)
        {
            return (HttpContext ctx) =>
            {
                // do your logic
                return requestDelegate(ctx);
            };
        }
    }
    

    调用中间件

    app.Use(Middleware1.Logic);
    // 以下是其他中间件示例
    app.Use(Middleware2.Logic);
    app.Use(Middleware3.Logic);
    app.Use(Middleware4.Logic);
    

    这种方式可以很好的将各个中间件逻辑独立出来,Program.cs此时变得十分简洁,然而我们还不满足这样,因为我们的Logic方法中直接返回一个lamada表达式(RequestDelegate对象),代码层级深了一层,每个中间件都多写这一层壳似乎不太优雅,能不能去掉这层lamada表达式呢?

    UseMiddlewareExtensions

    为了解决上面提到的痛点,UseMiddlewareExtensions扩展类应运而生,它在Aspnetcore底层大量使用,它主要提供一个泛型UseMiddleware<T>方法用来方便我们注册中间件,下面是该方法的定义

    public static IApplicationBuilder UseMiddleware<TMiddleware>(this IApplicationBuilder app, params object?[] args)
    

    如果只看这个方法的声明,估计没人知道如何使用,因为该方法接收的泛型参数TMiddleware没有添加任何限制,而另一个args参数也是object类型,而且是可以不传的,也就是它只需要传任意一个类型都不会在编译时报错。
    比如这样,完全不会报错:

    image.png
    当然,如果你这样就运行程序,一定会收到下面的异常

    System.InvalidOperationException:“No public 'Invoke' or 'InvokeAsync' method found for middleware of type 'System.String'.”
    

    提示我们传的类型没有InvokeInvokeAsync公共方法,这里大概能猜到,底层应该是通过反射进行动态调用InvokeInvokeAsync公共方法的。

    源码分析

    想要知道其本质,唯有查看源码,以下源码来自UseMiddlewareExtensions

    如下,该扩展类一共提供两个并且是重载的公共方法UseMiddleware,一般都只会使用第一个UseMiddleware,第一个UseMiddleware方法内部再去调用第二个UseMiddleware方法,源码中对类型前面添加的[DynamicallyAccessedMembers(MiddlewareAccessibility)]属性可以忽略,它的作用是为了告诉编译器我们通过反射访问的范围,以防止对程序集对我们可能调用的方法或属性等进行裁剪。

    
    internal const string InvokeMethodName = "Invoke";
    internal const string InvokeAsyncMethodName = "InvokeAsync";
    
    /// <summary>
    /// Adds a middleware type to the application's request pipeline.
    /// </summary>
    /// <typeparam name="TMiddleware">The middleware type.</typeparam>
    /// <param name="app">The <see cref="IApplicationBuilder"/> instance.</param>
    /// <param name="args">The arguments to pass to the middleware type instance's constructor.</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);
    }
     
    /// <summary>
    /// Adds a middleware type to the application's request pipeline.
    /// </summary>
    /// <param name="app">The <see cref="IApplicationBuilder"/> instance.</param>
    /// <param name="middleware">The middleware type.</param>
    /// <param name="args">The arguments to pass to the middleware type instance's constructor.</param>
    /// <returns>The <see cref="IApplicationBuilder"/> instance.</returns>
    public static IApplicationBuilder UseMiddleware(
        this IApplicationBuilder app,
        [DynamicallyAccessedMembers(MiddlewareAccessibility)] Type middleware,
        params object?[] args)
    {
        if (typeof(IMiddleware).IsAssignableFrom(middleware))
        {
            // IMiddleware doesn't support passing args directly since it's
            // activated from the container
            if (args.Length > 0)
            {
                throw new NotSupportedException(Resources.FormatException_UseMiddlewareExplicitArgumentsNotSupported(typeof(IMiddleware)));
            }
     
            return UseMiddlewareInterface(app, middleware);
        }
     
        var applicationServices = app.ApplicationServices;
        var methods = middleware.GetMethods(BindingFlags.Instance | BindingFlags.Public);
        MethodInfo? invokeMethod = null;
        foreach (var method in methods)
        {
            if (string.Equals(method.Name, InvokeMethodName, StringComparison.Ordinal) || string.Equals(method.Name, InvokeAsyncMethodName, StringComparison.Ordinal))
            {
                if (invokeMethod is not null)
                {
                    throw new InvalidOperationException(Resources.FormatException_UseMiddleMutlipleInvokes(InvokeMethodName, InvokeAsyncMethodName));
                }
     
                invokeMethod = method;
            }
        }
     
        if (invokeMethod is null)
        {
            throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoInvokeMethod(InvokeMethodName, InvokeAsyncMethodName, middleware));
        }
     
        if (!typeof(Task).IsAssignableFrom(invokeMethod.ReturnType))
        {
            throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNonTaskReturnType(InvokeMethodName, InvokeAsyncMethodName, nameof(Task)));
        }
     
        var parameters = invokeMethod.GetParameters();
        if (parameters.Length == 0 || parameters[0].ParameterType != typeof(HttpContext))
        {
            throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoParameters(InvokeMethodName, InvokeAsyncMethodName, nameof(HttpContext)));
        }
     
        var state = new InvokeMiddlewareState(middleware);
     
        return app.Use(next =>
        {
            var middleware = state.Middleware;
     
            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)invokeMethod.CreateDelegate(typeof(RequestDelegate), instance);
            }
     
            var factory = Compile<object>(invokeMethod, parameters);
     
            return context =>
            {
                var serviceProvider = context.RequestServices ?? applicationServices;
                if (serviceProvider == null)
                {
                    throw new InvalidOperationException(Resources.FormatException_UseMiddlewareIServiceProviderNotAvailable(nameof(IServiceProvider)));
                }
     
                return factory(instance, context, serviceProvider);
            };
        });
    }
    

    第一个UseMiddleware可以直接跳过,看第二个UseMiddleware方法,该方法一上来就先判断我们传的泛型类型是不是IMiddleware接口的派生类,如果是,直接交给UseMiddlewareInterface方法。

    if (typeof(IMiddleware).IsAssignableFrom(middleware))
     {
         // IMiddleware doesn't support passing args directly since it's
         // activated from the container
         if (args.Length > 0)
         {
             throw new NotSupportedException(Resources.FormatException_UseMiddlewareExplicitArgumentsNotSupported(typeof(IMiddleware)));
         }
     
         return UseMiddlewareInterface(app, middleware);
     }
    

    这里总算看到应该有的东西了,如果声明UseMiddleware<T>方法时,对泛型T添加IMiddleware限制,我们不看源码就知道如何编写我们的中间件逻辑了,只需要写一个类,继承IMiddleware并实现InvokeAsync方法即可, UseMiddlewareInterface方法的实现比较简单,因为我们继承了接口,逻辑相对会简单点。

    private static IApplicationBuilder UseMiddlewareInterface(
        IApplicationBuilder app,
        Type middlewareType)
    {
        return app.Use(next =>
        {
            return async context =>
            {
                var middlewareFactory = (IMiddlewareFactory?)context.RequestServices.GetService(typeof(IMiddlewareFactory));
                if (middlewareFactory == null)
                {
                    // No middleware factory
                    throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoMiddlewareFactory(typeof(IMiddlewareFactory)));
                }
     
                var middleware = middlewareFactory.Create(middlewareType);
                if (middleware == null)
                {
                    // The factory returned null, it's a broken implementation
                    throw new InvalidOperationException(Resources.FormatException_UseMiddlewareUnableToCreateMiddleware(middlewareFactory.GetType(), middlewareType));
                }
     
                try
                {
                    await middleware.InvokeAsync(context, next);
                }
                finally
                {
                    middlewareFactory.Release(middleware);
                }
            };
        });
    }
    
    public interface IMiddleware
    {
        /// <summary>
        /// Request handling method.
        /// </summary>
        /// <param name="context">The <see cref="HttpContext"/> for the current request.</param>
        /// <param name="next">The delegate representing the remaining middleware in the request pipeline.</param>
        /// <returns>A <see cref="Task"/> that represents the execution of this middleware.</returns>
        Task InvokeAsync(HttpContext context, RequestDelegate next);
    }
    

    如果我们的类不满足IMiddleware,继续往下看

    通过反射查找泛型类中InvokeInvokeAsync方法

    var applicationServices = app.ApplicationServices;
    var methods = middleware.GetMethods(BindingFlags.Instance | BindingFlags.Public);
    MethodInfo? invokeMethod = null;
    foreach (var method in methods)
    {
        if (string.Equals(method.Name, InvokeMethodName, StringComparison.Ordinal) || string.Equals(method.Name, InvokeAsyncMethodName, StringComparison.Ordinal))
        {
            // 如果Invoke和InvokeAsync同时存在,则抛出异常,也就是,我们只能二选一
            if (invokeMethod is not null)
            {
                throw new InvalidOperationException(Resources.FormatException_UseMiddleMutlipleInvokes(InvokeMethodName, InvokeAsyncMethodName));
            }
     
            invokeMethod = method;
        }
    }
    
    // 如果找不到Invoke和InvokeAsync则抛出异常,上文提到的那个异常。
    if (invokeMethod is null)
    {
        throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoInvokeMethod(InvokeMethodName, InvokeAsyncMethodName, middleware));
    }
    
    // 如果Invoke和InvokeAsync方法的返回值不是Task或Task的派生类,则抛出异常
    if (!typeof(Task).IsAssignableFrom(invokeMethod.ReturnType))
    {
        throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNonTaskReturnType(InvokeMethodName, InvokeAsyncMethodName, nameof(Task)));
    }
    Snippet
    
    // 如果Invoke和InvokeAsync方法没有参数,或第一个参数不是HttpContext,抛异常
    var parameters = invokeMethod.GetParameters();
    if (parameters.Length == 0 || parameters[0].ParameterType != typeof(HttpContext))
    {
        throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoParameters(InvokeMethodName, InvokeAsyncMethodName, nameof(HttpContext)));
    }
    
    

    上面一堆逻辑主要就是检查我们的InvokeInvokeAsync方法是否符合要求,即:必须是接收HttpContext参数,返回Task对象,这恰好就是委托RequestDelegate的定义。

    构造RequestDelegate
    这部分源码的解读都注释到相应的位置了,如下

    var state = new InvokeMiddlewareState(middleware);
    // 调用Use函数,向管道中注册中间件
    return app.Use(next =>
    {
        var middleware = state.Middleware;
    
        var ctorArgs = new object[args.Length + 1];
        // next是RequestDelegate对象,作为构造函数的第一个参数传入
        ctorArgs[0] = next;
        Array.Copy(args, 0, ctorArgs, 1, args.Length);
        // 反射实例化我们传入的泛型类,并把next和args作为构造函数的参数传入
        var instance = ActivatorUtilities.CreateInstance(app.ApplicationServices, middleware, ctorArgs);
        // 如果我们的Invoke方法只有一个参数,则直接创建该方法的委托
        if (parameters.Length == 1)
        {
            return (RequestDelegate)invokeMethod.CreateDelegate(typeof(RequestDelegate), instance);
        }
        
        // 当Invoke方法不止一个参数HttpContext,通过Compile函数创建动态表达式目录树,
        // 表达式目录树的构造此处略过,其目的是实现将除第一个参数的其他参数通过IOC注入
        var factory = Compile<object>(invokeMethod, parameters);
    
        return context =>
        {
            // 获取serviceProvider用于在上面构造的表达式目录树中实现依赖注入
            var serviceProvider = context.RequestServices ?? applicationServices;
            if (serviceProvider == null)
            {
                throw new InvalidOperationException(Resources.FormatException_UseMiddlewareIServiceProviderNotAvailable(nameof(IServiceProvider)));
            }
            // 将所需的参数传入构造的表达式目录树工厂
            return factory(instance, context, serviceProvider);
        };
    });
    

    至此,整个扩展类的源码就解读完了。

    通过UseMiddleware注入自定义中间件

    通过上面的源码解读,我们知道了其实我们传入的泛型类型是有严格的要求的,主要有两种

    通过继承IMiddleware

    继承IMiddleware并实现该接口的InvokeAsync函数

    public class Middleware1 : IMiddleware
    {
        public async Task InvokeAsync(HttpContext context, RequestDelegate next)
        {
            // do your logic
            await next(context);
        }
    }
    

    通过反射

    我们知道,在不继承IMiddleware的情况下,底层会通过反射实例化泛型类型,并通过构造函数传入RequestDelegate,而且要有一个公共函数InvokeInvokeAsync,并且接收的第一个参数是HttpContext,返回Task,根据要求我们将Middleware1.cs改造如下

    public class Middleware1
    {
        RequestDelegate next;
     
        public Middleware1(RequestDelegate next)
        {
            this.next = next;
        }
     
        public async Task Invoke(HttpContext httpContext)
        {
            // do your logic
            await this.next(httpContext);
        }
    }
    

    总结

    通过源码的学习,我们弄清楚底层注册中间件的来龙去脉,两种方式根据自己习惯进行使用,笔者认为通过接口的方式更加简洁直观简单,并且省去了反射带来的性能损失,推荐使用。既然通过继承接口那么爽,为啥还费那么大劲实现反射的方式呢?由源码可知,如果继承接口的话,就不能进行动态传参了。

    if (typeof(IMiddleware).IsAssignableFrom(middleware))
            {
                // IMiddleware doesn't support passing args directly since it's
                // activated from the container
                if (args.Length > 0)
                {
                    throw new NotSupportedException(Resources.FormatException_UseMiddlewareExplicitArgumentsNotSupported(typeof(IMiddleware)));
                }
     
                return UseMiddlewareInterface(app, middleware);
            }
    

    所以在需要传参的场景,则必须使用反射的方式,所以两种方式都有其存在的必要。

    如果本文对您有帮助,还请点赞转发关注一波支持作者。


    __EOF__

  • 本文作者: Gui.H
  • 本文链接: https://www.cnblogs.com/springhgui/p/16244129.html
  • 关于博主: 评论和私信会在第一时间回复。或者直接私信我。
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
  • 声援博主: 如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。
  • 相关阅读:
    代码随想录训练营二刷第四十八天 | 139.单词拆分 背包问题总结
    Arduino 电机测速
    day42 62.不同路径 63. 不同路径 II
    Mybatis参数传递方式
    iOS Autolayout 约束设置【顺序】的重要性!
    Command CodeSign failed with a nonzero exit code
    spring boot 自定义注解封装(@RequestLimit注解)
    设计模式- 单例模式(Singleton)结构|实现方式|优缺点|场景
    语料库数据处理个案实例(词性赋码、词性还原)
    Stream流源码解析调用过程
  • 原文地址:https://www.cnblogs.com/springhgui/p/16244129.html