zoukankan      html  css  js  c++  java
  • asp.net core mvc剖析:处理管道构建

    在启动流程文章中提到,在WebHost类中,通过BuildApplication完成http请求处理管道的构建。在来看一下代码:

     。。。。。。
     //这个调用的就是Startup.cs类中的Configure方法
     configure(builder);
     //生成中间件链式结构
     return builder.Build();
    

    在框架中,一个中间件处理逻辑是使用一个RequestDelegate委托类型来表示的,定义:delegate Task RequestDelegate(HttpContext context)

    那是不是我们直接创建委托方法就可以了?答案是否定的,为了形成一个链式结构,中间定义跟注册都有一定的要求。

    首先先介绍下如何定义一个中间件。定义中间件只需要定义一个类即可,但是这个类并不是随意写,里面的结构由一定的要求:

    1,类必须包含一个构造方法,这个构造方法的第一个参数必须是一个RequestDelegate类型,这个参数表达的就是当前定义中间件的下一个中间件。

    2,必须包含一个Invoke方法,方法的第一个参数必须是HttpContext类型,返回值类型必须是Task,Invoke方法中实现中间件逻辑

    下面是一个中间件定义实例代码:

    class MiddlewareSample
    {
          private RequestDelegate _next;
          public MiddlewareSample(RequesetDelegate next)
          {
               _next=next;
          }     
          public Task Invoke(HttpContext context)
          {
               //中间件逻辑代码
               ......
    
              //调用下一个中间件,实现链式调用,当然可以根据业务场景去确定是否继续往下调用
              _next(context); 
          } 
    }
    

      

      

    再来看下,如何把定义好的中间件注册到管道中。IApplicationBuilder提供了UseMiddleware的扩展方法,通过这个方法就可以把一个中间件类型注册到管道中。那我们来看一下,它里面到底做了什么?

    public static IApplicationBuilder UseMiddleware(this IApplicationBuilder app, Type middleware, params object[] args)
            {
                var applicationServices = app.ApplicationServices;
                return app.Use(next =>
                {
                    var methods = middleware.GetMethods(BindingFlags.Instance | BindingFlags.Public);
                    //通过反射获取Invoke方法信息
                    var invokeMethods = methods.Where(m => string.Equals(m.Name, InvokeMethodName, StringComparison.Ordinal)).ToArray();
                    if (invokeMethods.Length > 1)
                    {
                        throw new InvalidOperationException(Resources.FormatException_UseMiddleMutlipleInvokes(InvokeMethodName));
                    }
    
                    if (invokeMethods.Length == 0)
                    {
                        throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoInvokeMethod(InvokeMethodName));
                    }
    
                    var methodinfo = invokeMethods[0];
                    if (!typeof(Task).IsAssignableFrom(methodinfo.ReturnType))
                    {
                        throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNonTaskReturnType(InvokeMethodName, nameof(Task)));
                    }
    
                    var parameters = methodinfo.GetParameters();
                    //判断Invoke方法是否包含HttpContext参数,并且要求是第一个参数
                    if (parameters.Length == 0 || parameters[0].ParameterType != typeof(HttpContext))
                    {
                        throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoParameters(InvokeMethodName, nameof(HttpContext)));
                    }
    
                    var ctorArgs = new object[args.Length + 1];
                    ctorArgs[0] = next;
                    Array.Copy(args, 0, ctorArgs, 1, args.Length);
    
                    //实例化中间件类型,并把next最为构造方法的第一个参数传递进去
                    var instance = ActivatorUtilities.CreateInstance(app.ApplicationServices, middleware, ctorArgs);
                    if (parameters.Length == 1)
                    {
                        //如果invoke方法只包含一个httpcontext参数,直接创建RequestDelegate
                        return (RequestDelegate)methodinfo.CreateDelegate(typeof(RequestDelegate), instance);
                    }
                    //这里我把它理解成一个RequestDelegate代理委托
                    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);
                    };
                });
            }  
    

      

      

      在UseMiddleware方法中,其实是通过反射的方式,解析中间件定义的类型,最后创建了一个中间件工厂委托对象,工厂就是一个Func<RequestDelegate, RequestDelegate>委托类型,它接收一个RequestDelegate中间件,然后返回一个新的中间件,接收的这个中间件参数是新生成的这个中间件的下一个中间件。创建好工厂后,调用IApplicationBuilder.Use方法,把这个工厂加入到中间件工厂列表中,Use方法实现如下:

         public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware)
            {
                _components.Add(middleware);
                return this;
            }

      到此只是有了一个中间件工厂集合,最后通过调用builder.Build()方法中完成的中间件链式结构的生成。

         public RequestDelegate Build()
            {
                RequestDelegate app = context =>
                {
                    context.Response.StatusCode = 404;
                    return TaskCache.CompletedTask;
                };
    
                foreach (var component in _components.Reverse())
                {
                    app = component(app);
                }
    
                return app;
            }
    

      首先定义了一个返回404错误异常的一个RequestDelegate,这个是作为中间件的然后把中间件集合反转,依次调用中间件工厂来完成中间件的初始化。这里为什么要反转,我们来分析下。

      假如我们注册中间件顺序为1->2->3->4,那_components中工厂的顺序就是工厂1->工厂2->工厂3->工厂4,反转后就成了工厂4->工厂3->工厂2->工厂1,然后进入for循环,首先调用工厂4,用返回404错误中间件作为参数,最后返回一个中间件4,然后调用工厂3,把中间件4传递进去,再生成一个中间件,最后调用工厂1,这样就形成了 中间件1->中间件2->中间件3->中间件4->404错误中间件 这样的链式结构。当一个http请求过来后,就会按照这个顺序依次执行对应的中间件逻辑。

      另外框架还提供了分支特性,就是根据条件不同,可以选择不同的管道分支来处理,如下图:

      HttpAbstractions项目中提供了IApplicationBuilder.MapWhen扩展方法,方法如下:

    public static IApplicationBuilder MapWhen(this IApplicationBuilder app, Predicate predicate, Action<IApplicationBuilder> configuration)
            {
                if (app == null)
                {
                    throw new ArgumentNullException(nameof(app));
                }
    
                if (predicate == null)
                {
                    throw new ArgumentNullException(nameof(predicate));
                }
    
                if (configuration == null)
                {
                    throw new ArgumentNullException(nameof(configuration));
                }
    
                // 创建了一个新的分支ApplicationBulider
                var branchBuilder = app.New();
                //configuration类似Startup.cs中的Configuration方法,目的是配置分支的处理管道
                configuration(branchBuilder);
                var branch = branchBuilder.Build();
    
                // predicate就是分支进入的条件,branch就是创建好的分支管道
                var options = new MapWhenOptions
                {
                    Predicate = predicate,
                    Branch = branch,
                };
                //借助MapWhenMiddleware封装了分支处理管道,然后加入到当前的主管道中
                return app.Use(next => new MapWhenMiddleware(next, options).Invoke);
            }
    

      

      来看一下MapWhenMiddleware中间件的Invoke方法实现

    public async Task Invoke(HttpContext context)
            {
                if (context == null)
                {
                    throw new ArgumentNullException(nameof(context));
                }
             //判断当前条件是否符合配置的条件
                if (_options.Predicate(context))
                {
                    //符合条件,直接进入分支的处理管道
                    await _options.Branch(context);
                }
                else
                {
                    //不符合条件的话,直接进入主管道的下一个中间件
                    await _next(context);
                }
            }
    

      

      从上面的逻辑我们可以看出来,如果进入了分支管道,那主管道中的后续中间件都执行不了了,我们可以这么理解,一旦使用了MapWhen,创建好分支后,主管道后续注册中间件也可以看作一个分支,只有当其他分支管道都不符合条件的时候,再进入这个分支,如下图:

    分支3并不是通过MapWhen注册的分支,这个只有在分支1,分支2都不符合条件的时候,才进入。

      

  • 相关阅读:
    PHP识别验证码-image-ocr
    Session的一些小疑问
    PHP-webdriver自动化测试完成登录
    大文件日志快速解析入库
    Linux权限说明
    使用python的selenium自动化登录获取cookie
    PHP编码的注释规范
    MySQL主主架构、读写分离搭建学习
    用docker尝试nginx的负载均衡
    lua require
  • 原文地址:https://www.cnblogs.com/dxp909/p/6400804.html
Copyright © 2011-2022 走看看