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);
                };

總結

  • 以上就是經過調試和閱讀源碼分析獲得的結果,寫出來以後閱讀可能有誤差,但這是爲了方便你們理解,感受這個順序介紹會好理解點,反正我是理解了,介紹順序對我影響不大
  • 經過動手記錄的過程,把以前調試閱讀的時候沒發現或者沒理解的點都找到弄明白了,整明白了中間件的註冊過程以及須要注意的書寫規範,收穫顯而易見,因此源碼纔是最好的文檔,並且文檔未必有這麼詳細。經過記錄,能夠把細節補全甚至弄明白,這一點相當重要,再次體會到其重要性
  • 另外,千萬不要在大晚上寫技術博文啊,總結之類的東西,切記

最後,文章可能有更新,請閱讀原文得到更好的體驗哦 http://www.javashuo.com/article/p-uggknsul-hn.htmlhtml

相關文章
相關標籤/搜索