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請求過來後,就會按照這個順序依次執行對應的中間件邏輯。

  

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章