• asp.net core mvc 管道之中间件


    asp.net core mvc 管道之中间件

    • http请求处理管道通过注册中间件来实现各种功能,松耦合并且很灵活
    • 此文简单介绍asp.net core mvc中间件的注册以及运行过程
    • 通过理解中间件,将asp.net core mvc分解,以便更好地学习

    中间件写法

    • 先看一个简单的中间件,next是下一个委托方法,在本中间件的Invoke方法里面需要执行它,否则处理就会终止,消息处理到此中间件就会返回了
    • 因此,根据这个约定,一个中间生成一个委托方法,需要把所有的委托方法处理成嵌套的委托,即每个中间件里面执行下一个委托,这样处理过程就像管道一样连接起来,每个中间件就是管道处理的节点
    • 至于为什么要这样写中间件,这是约定好的,还有注意点,下面将会讲到
        public class Middleware
        {
            private readonly RequestDelegate _next;
    
            public RouterMiddleware(RequestDelegate next)
            {
                _next = next;
            }
    
            public async Task Invoke(HttpContext httpContext)
            {
                // do something
                await _next.Invoke(httpContext);
                // do something
            }
        }
    

    中间件管道生成

    • 以上中间件会通过方法生成一个委托,并添加到委托集合,中间生成委托的过程后面讲
    namespace Microsoft.AspNetCore.Builder.Internal
    {
        public class ApplicationBuilder : IApplicationBuilder
        {
            private readonly IList<Func<RequestDelegate, RequestDelegate>> _components = new List<Func<RequestDelegate, RequestDelegate>>();
            public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware)
            {
                _components.Add(middleware);
                return this;
            }
        }
    }
    
    • 最后的ApplicationBuilder.Build方法会处理所有注册的中间件生成的委托集合,将所有中间件生成的委托集合,处理成嵌套的形式,最终得到一个委托,连成一段管道。
    • 以下方法首先声明一个响应404的委托方法,把它当成所有中间件的最后一个,当然它不一定会被执行到,因为某个中间件可能不会调用它
    • 然后将这个委托作为参数,传入并执行_components这个委托集合里面的每一个委托,_components就是所有注册的中间件生成的委托集合,Reverse方法将集合反转,从最后注册的中间件对应的委托开始处理
    • 所以呢中间件的注册是有顺序的,也就是Startup.cs类里面的Configure方法,里面的每个Use开头的方法都对应一个中间件注册,代码的顺序就是注册的顺序,也是执行的顺序,千万不能写错了。因为MVC处于处理流程的最后面,因此UseMvc方法总是位于最后
    • 在看component,是从_components委托集合里面取出来的,执行后又得到一个RequestDelegate类型的委托,因此由中间件生成的委托的类型应该是Func<RequestDelegate, RequestDelegate>
            public RequestDelegate Build()
            {
                RequestDelegate app = context =>
                {
                    context.Response.StatusCode = 404;
                    return Task.CompletedTask;
                };
    
                foreach (var component in _components.Reverse())
                {
                    app = component(app);
                }
    
                return app;
            }
    

    中间件生成委托

    • 以下是中间件注册方法,实际是调用ApplicationBuilder.Use方法,将中间件生成的委托加入委托集合,完成中间件注册
    • app.Use方法参数,就是上面需要的类型Func<RequestDelegate, RequestDelegate>的委托,该委托的参数next 就是下一个中间件对应的委托,返回值就是中间件的Invoke方法对应的委托,该方法用到了next
    • 源码位于Microsoft.AspNetCore.Builder.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)
            {
    
                // 省略部分代码
    
                var applicationServices = app.ApplicationServices;
                return app.Use(next =>
                {             
    
                    // 省略部分代码
    
                    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 =>
                    {
                        var serviceProvider = context.RequestServices ?? applicationServices;
                        if (serviceProvider == null)
                        {
                            throw new InvalidOperationException(Resources.FormatException_UseMiddlewareIServiceProviderNotAvailable(nameof(IServiceProvider)));
                        }
    
                        return factory(instance, context, serviceProvider);
                    };
                });
            }
    

    中间件写法约定

    • 看以上代码,第一种写法,首先如果中间继承自IMiddleware接口,则调用UseMiddlewareInterface方法。使用了接口规范,那么你也不能乱写了,只需要注意在Invoke方法调用next即可
            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);
                        }
                    };
                });
            }
    
    • 第二种是开头举的例子,不继承自接口
      • 至少要有名为InvokeInvokeAsync的一个方法
        public static class UseMiddlewareExtensions
        {
            internal const string InvokeMethodName = "Invoke";
            internal const string InvokeAsyncMethodName = "InvokeAsync";
        }
    
        var invokeMethods = methods.Where(m =>
            string.Equals(m.Name, InvokeMethodName, StringComparison.Ordinal)
            || string.Equals(m.Name, InvokeAsyncMethodName, StringComparison.Ordinal)
            ).ToArray();
    
    • 类的方法只能有一个
                    if (invokeMethods.Length > 1)
                    {
                        throw new InvalidOperationException(Resources.FormatException_UseMiddleMutlipleInvokes(InvokeMethodName, InvokeAsyncMethodName));
                    }
    
                    if (invokeMethods.Length == 0)
                    {
                        throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoInvokeMethod(InvokeMethodName, InvokeAsyncMethodName, middleware));
                    }
    
    • 类的返回值是Task
                    var methodinfo = invokeMethods[0];
                    if (!typeof(Task).IsAssignableFrom(methodinfo.ReturnType))
                    {
                        throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNonTaskReturnType(InvokeMethodName, InvokeAsyncMethodName, nameof(Task)));
                    }
    
    • 方法的参数至少有一个,且第一个参数必须为是HttpContext
                    var parameters = methodinfo.GetParameters();
                    if (parameters.Length == 0 || parameters[0].ParameterType != typeof(HttpContext))
                    {
                        throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoParameters(InvokeMethodName, InvokeAsyncMethodName, nameof(HttpContext)));
                    }
    
    • 方法的参数如果只有一个,则将UseMiddleware方法传入的自定义参数args加上下一个委托next,得到新的参数数组,然后创建中间件实例,生成Invoke方法对应委托。此处注意,如果中间件的构造函数中有其它参数,但是未注册到ApplicationServices的话,需要在UseMiddleware方法中传入
                    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);
                    }
    
    • 方法的参数如果多于一个,则调用Compile方法,生成一个委托,该委托从IServiceProvider中获取需要的参数的实例,再调用Invoke方法,相比上面的情况,多了一步从IServiceProvider获取实例,注入到Invoke而已。
    • Compile方法使用了Linq表达式树,源码位于Microsoft.AspNetCore.Builder.UseMiddlewareExtensions,此处不作讲解,因为我也不太懂
                    var factory = Compile<object>(methodinfo, parameters);
    
                    return context =>
                    {
                        var serviceProvider = context.RequestServices ?? applicationServices;
                        if (serviceProvider == null)
                        {
                            throw new InvalidOperationException(Resources.FormatException_UseMiddlewareIServiceProviderNotAvailable(nameof(IServiceProvider)));
                        }
    
                        return factory(instance, context, serviceProvider);
                    };
    

    总结

    • 以上就是通过调试和阅读源码分析得到的结果,写出来之后阅读可能有偏差,但这是为了方便大家理解,感觉这个顺序介绍会好理解点,反正我是理解了,介绍顺序对我影响不大
    • 通过动手记录的过程,把之前调试阅读的时候没发现或者没理解的点都找到弄明白了,整明白了中间件的注册过程以及需要注意的书写规范,收获显而易见,所以源码才是最好的文档,而且文档未必有这么详细。通过记录,可以把细节补全甚至弄明白,这一点至关重要,再次体会到其重要性
    • 另外,千万不要在大晚上写技术博文啊,总结之类的东西,切记

    最后,文章可能有更新,请阅读原文获得更好的体验哦 https://www.cnblogs.com/xxred/p/9576622.html

  • 相关阅读:
    WCF服务自我寄宿 Windows服务
    客户端调用 WCF 的几种方式
    SQL Server 2005 数据库 可疑状态
    mysql server 自动断开的问题
    mysql数据表简单拷贝及重命名
    Mac提示App已损坏 你应该将它移到废纸篓的解决方案
    Mac系统下安装Tomcat,以及终端出现No such file or directory的错误提示解决方案
    md1
    转-SourceTree注册atlassian账号SIGUP按钮灰色无法注册的问题
    mysql my.cnf优化
  • 原文地址:https://www.cnblogs.com/xxred/p/9576622.html
Copyright © 2020-2023  润新知