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都不符合条件的时候,才进入。

      

  • 相关阅读:
    LeetCode 977 有序数组的平方
    LeetCode 24 两两交换链表中的节点
    LeetCode 416 分割等和子集
    LeetCode 142 环形链表II
    LeetCode 106 从中序与后序遍历序列构造二叉树
    LeetCode 637 二叉树的层平均值
    LeetCode 117 填充每个节点的下一个右侧节点
    LeetCode 75 颜色分类
    redhat 7.4 挂载ntfs格式的u盘并且使用
    redhat 查看CPU frequency scaling(CPU频率缩放)
  • 原文地址:https://www.cnblogs.com/dxp909/p/6400804.html
Copyright © 2011-2022 走看看