asp.net core mvc 管道之中介軟體

笑笑?發表於2018-09-03

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

相關文章