AppBuilder(一)【Use彙總】

風靈使發表於2018-07-09

原始碼參見Microsoft.Owin.Host.SystemWeb.OwinBuilder

Microsoft.Owin.Builder.AppBuilder

前文講到

internal static OwinAppContext Build()
        {
            Action<IAppBuilder> startup = GetAppStartup();
            return Build(startup);
        }

GetAppStartup()已經尋找到Startup類,並封裝了其中的Configuration方法,接下來就會呼叫Build(startup)方法。

internal static OwinAppContext Build(Action<IAppBuilder> startup)
        {
            if (startup == null)
            {
                throw new ArgumentNullException("startup");
            }

            var appContext = new OwinAppContext();    //例項化OwinAppContext,主要獲得網站名稱和程式名稱
            appContext.Initialize(startup);    //進一步初始化OwinAppContext
            return appContext;
        }

來看看OwinAppContext.Initialize(IAppBuilder)做了什麼

internal void Initialize(Action<IAppBuilder> startup)
        {
            Capabilities = new ConcurrentDictionary<string, object>();    //初始化為多執行緒安全的Dictionary,說明為多執行緒共享資源

            var builder = new AppBuilder();    //初始化AppBuilder,向Properties中填入一些基本資訊,這構成了Server環境的快照
            builder.Properties[Constants.OwinVersionKey] = Constants.OwinVersion;
            builder.Properties[Constants.HostTraceOutputKey] = TraceTextWriter.Instance;
            builder.Properties[Constants.HostAppNameKey] = AppName;
            builder.Properties[Constants.HostOnAppDisposingKey] = OwinApplication.ShutdownToken;
            builder.Properties[Constants.HostReferencedAssemblies] = new ReferencedAssembliesWrapper();
            builder.Properties[Constants.ServerCapabilitiesKey] = Capabilities;
            builder.Properties[Constants.SecurityDataProtectionProvider] = new MachineKeyDataProtectionProvider().ToOwinFunction();
            builder.SetLoggerFactory(new DiagnosticsLoggerFactory());

            Capabilities[Constants.SendFileVersionKey] = Constants.SendFileVersion;

            CompilationSection compilationSection = (CompilationSection)System.Configuration.ConfigurationManager.GetSection(@"system.web/compilation");
            bool isDebugEnabled = compilationSection.Debug;
            if (isDebugEnabled)
            {
                builder.Properties[Constants.HostAppModeKey] = Constants.AppModeDevelopment;
            }

            DetectWebSocketSupportStageOne();

            try
            {
                startup(builder);    //真正的由使用者定義的middleware注入開始了,這將完成pipeline的構造
            }
            catch (Exception ex)
            {
                _trace.WriteError(Resources.Trace_EntryPointException, ex);
                throw;
            }

            AppFunc = (AppFunc)builder.Build(typeof(AppFunc));//記錄pipeline的第一個middleware,這將是整個pipeline的入口
        }

先從AppBuilder的例項化開始

public AppBuilder()
        {
            _properties = new Dictionary<string, object>();    //初始化environment資源
            _conversions = new Dictionary<Tuple<Type, Type>, Delegate>();    //初始化轉換器,這將不是本文重點,後期再涉及
            _middleware = new List<Tuple<Type, Delegate, object[]>>();    //初始化middleware列表

            _properties[Constants.BuilderAddConversion] = new Action<Delegate>(AddSignatureConversion);    //記錄Conversion
            _properties[Constants.BuilderDefaultApp] = NotFound;

            SignatureConversions.AddConversions(this);
        }

上面的原始碼展示了AppBuilder初始化pipeline流動所需的envrionmentmiddleware,這也是OWIN最重要的兩個資料結構。

builder完成例項化之後會嘗試startup(builder)這即是呼叫使用者定義的Configuration(IAppBuilder)方法。

public partial class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            ConfigureAuth(app);
        }
    }
public partial class Startup
    {
        public void ConfigureAuth(IAppBuilder app)
        {

            app.CreatePerOwinContext<BlogContext>(BlogContext.Create);    //將資料庫上下文注入envrionment中
            app.CreatePerOwinContext<AppUserManager>(AppUserManager.Create);    //將AppUserManager注入environment中
            app.CreatePerOwinContext<AppSigninManager>(AppSigninManager.Create);    //將AppSigninManager注入environment中

            app.UseCookieAuthentication(new CookieAuthenticationOptions    //標記CookeAuthentication這個middleware在pipeline中的stage,並配置引數
            {
                AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
                LoginPath = new PathString("/Account/Login"),
                Provider = new CookieAuthenticationProvider
                {
                    // Enables the application to validate the security stamp when the user logs in.
                    // This is a security feature which is used when you change a password or add an external login to your account. 
                    OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<AppUserManager, AppUser>(
                        validateInterval: TimeSpan.FromMinutes(30),
                        regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
                }
            });

            // Enables the application to remember the second login verification factor such as phone or email.
            // Once you check this option, your second step of verification during the login process will be remembered on the device where you logged in from.
            // This is similar to the RememberMe option when you log in.
            app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);    //標記stage
        }

    }

以新建MVC工程預設生成的Starup類為例。目前我們可以大致猜測一下這份配置方法中只進行了兩個stage的註冊,而pipeline有11個stage之多,具體如下

private static readonly IList<string> StageNames = new[]
        {
            Constants.StageAuthenticate,
            Constants.StagePostAuthenticate,
            Constants.StageAuthorize,
            Constants.StagePostAuthorize,
            Constants.StageResolveCache,
            Constants.StagePostResolveCache,
            Constants.StageMapHandler,
            Constants.StagePostMapHandler,
            Constants.StageAcquireState,
            Constants.StagePostAcquireState,
            Constants.StagePreHandlerExecute,
        };

接下來我們通過對app.UseCookieAuthentication(new CookieAuthenticationOptions)的追蹤來了解如何進行middleware的註冊。

public static class CookieAuthenticationExtensions
    {
        public static IAppBuilder UseCookieAuthentication(this IAppBuilder app, CookieAuthenticationOptions options)
        {
            return app.UseCookieAuthentication(options, PipelineStage.Authenticate);    //在PipelineStage.Authenticate階段執行
        }

        public static IAppBuilder UseCookieAuthentication(this IAppBuilder app, CookieAuthenticationOptions options, PipelineStage stage)
        {
            if (app == null)
            {
                throw new ArgumentNullException("app");
            }

            app.Use(typeof(CookieAuthenticationMiddleware), app, options);    //app.Use實際上就是將middleware壓入middleware的List中
            app.UseStageMarker(stage);    //標記middleware所在stage
            return app;
        }
    }

這裡涉及到AppBuilder的兩個核心操作app.Useapp.UseStageMarker

先來看看app.Use,在AppBuilder中定義了Use(object middleware, params object[] args),而在AppBuilderUseExtensions還有兩種定義,我們逐個來解釋。

  public IAppBuilder Use(object middleware, params object[] args)
        {
            _middleware.Add(ToMiddlewareFactory(middleware, args));
            return this;
        }

可見這個方法主要做的操作是將middleware進行轉換並壓入middleware的List中,轉換過程實際上對middleware進行引數檢查和簽名,這將是一個非常複雜的過程,一步一步地來。

先看看ToMiddlewareFactory方法。

private static Tuple<Type, Delegate, object[]> ToMiddlewareFactory(object middlewareObject, object[] args)    //接受兩個引數,返回一個三元組
        {
            if (middlewareObject == null)
            {
                throw new ArgumentNullException("middlewareObject");
            }

            var middlewareDelegate = middlewareObject as Delegate;    //嘗試將middlewareObject型別轉換為Delegate,而我們上面傳入的是一個Type,將會轉換失敗
            if (middlewareDelegate != null)    //如果轉換成功
            {
                return Tuple.Create(GetParameterType(middlewareDelegate), middlewareDelegate, args);    //直接返回三元組,這似乎是最理想的情況
            }

            Tuple<Type, Delegate, object[]> factory = ToInstanceMiddlewareFactory(middlewareObject, args);    //嘗試在middlewareObject類中尋找Initialize方法,並檢查args參
    //數是否符合Initialize方法的引數
            if (factory != null)
            {
                return factory;
            }

            factory = ToGeneratorMiddlewareFactory(middlewareObject, args);    //嘗試在middlewareObject類中尋找Invoke方法,並作引數檢查

            if (factory != null)
            {
                return factory;
            }

            if (middlewareObject is Type)    //這是本文的app.Use方法傳入的引數型別,所以會進入這一流程中
            {
                return ToConstructorMiddlewareFactory(middlewareObject, args, ref middlewareDelegate);
            }

            throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture,
                Resources.Exception_MiddlewareNotSupported, middlewareObject.GetType().FullName));

        }

接下來將分別對ToInstanceMiddlewareFactoryToGeneratorMiddlewareFactoryToConstructorMiddlewareFactory進行介紹。

private static Tuple<Type, Delegate, object[]> ToInstanceMiddlewareFactory(object middlewareObject, object[] args)
        {
            MethodInfo[] methods = middlewareObject.GetType().GetMethods();    //將middleware當作一個例項處理,獲取其Type,再獲得類中所有方法
            foreach (var method in methods)    //遍歷方法
            {
                if (method.Name != Constants.Initialize)    //尋找Initialize方法
                {
                    continue;
                }
                ParameterInfo[] parameters = method.GetParameters();    //獲取Initialize的參數列
                Type[] parameterTypes = parameters.Select(p => p.ParameterType).ToArray();    //獲取參數列的Type型別

                if (parameterTypes.Length != args.Length + 1)    //Initialize的引數應該比args的引數多一個,因為第一個引數應該是下一個middleware
                {
                    continue;
                }
                if (!parameterTypes
                    .Skip(1)
                    .Zip(args, TestArgForParameter)
                    .All(x => x))
                {    //對Initialize第一個以後的引數與args進行一對一驗證,args應該為對應項的例項
                    continue;
                }

                // DynamicInvoke can't handle a middleware with multiple args, just push the args in via closure.
               //開發者在此處註釋動態Invoke無法處理middleware有多個引數的情況,所以進行了一次封裝,簡化參數列
                Func<object, object> func = app =>
                {
                    object[] invokeParameters = new[] { app }.Concat(args).ToArray();    //將app(實際上為envrionment)引數與args合併
                    method.Invoke(middlewareObject, invokeParameters);    //真正呼叫middleware的Initialize方法
                    return middlewareObject;    //返回middlewareObject例項
                };

                return Tuple.Create<Type, Delegate, object[]>(parameters[0].ParameterType, func, new object[0]);
            }
            return null;
        }

ToInstanceMiddlewareFactory返回的是一個具體的例項,似乎不滿足Func<IDictionary<string, object>, Task>型別,這也是目前沒弄明白的地方

private static Tuple<Type, Delegate, object[]> ToGeneratorMiddlewareFactory(object middlewareObject, object[] args)
        {
            MethodInfo[] methods = middlewareObject.GetType().GetMethods();    //將middlewareObject當作一個例項,獲取Type並獲取所有方法
            foreach (var method in methods)
            {
                if (method.Name != Constants.Invoke)    //尋找Invoke方法
                {
                    continue;
                }
                ParameterInfo[] parameters = method.GetParameters();    //獲取Invoke方法的參數列
                Type[] parameterTypes = parameters.Select(p => p.ParameterType).ToArray();    //獲取引數的Type表

                if (parameterTypes.Length != args.Length + 1)
                {
                    continue;
                }
                if (!parameterTypes
                    .Skip(1)
                    .Zip(args, TestArgForParameter)
                    .All(x => x))
                {    //將Type表與args進行一對一驗證
                    continue;
                }
                IEnumerable<Type> genericFuncTypes = parameterTypes.Concat(new[] { method.ReturnType });    //將引數Type表與返回Type合併
                Type funcType = Expression.GetFuncType(genericFuncTypes.ToArray());    //獲取方法簽名Type
                Delegate middlewareDelegate = Delegate.CreateDelegate(funcType, middlewareObject, method);    //對例項的Invoke方法進行delegate封裝
                return Tuple.Create(parameters[0].ParameterType, middlewareDelegate, args);
            }
            return null;
        }

ToGeneratorMiddlewareFactory返回型別由Invoke方法決定,通常會是一個Task

private static Tuple<Type, Delegate, object[]> ToConstructorMiddlewareFactory(object middlewareObject, object[] args, ref Delegate middlewareDelegate)
        {
            var middlewareType = middlewareObject as Type;    //嘗試將middlwareObject轉換為Type
            ConstructorInfo[] constructors = middlewareType.GetConstructors();    //獲取類的構造方法
            foreach (var constructor in constructors)    //遍歷構造方法
            {
                ParameterInfo[] parameters = constructor.GetParameters();    //獲取建構函式的參數列
                Type[] parameterTypes = parameters.Select(p => p.ParameterType).ToArray();    //獲取引數的Type表
                if (parameterTypes.Length != args.Length + 1)    //參數列應該比args多一項,第一項應該為下一個middleware
                {
                    continue;
                }
                if (!parameterTypes
                    .Skip(1)
                    .Zip(args, TestArgForParameter)
                    .All(x => x))
                {    //對參數列的第一項之後的與args一對一校驗
                    continue;
                }

                ParameterExpression[] parameterExpressions = parameters.Select(p => Expression.Parameter(p.ParameterType, p.Name)).ToArray();    //提取參數列的Type和Name
                NewExpression callConstructor = Expression.New(constructor, parameterExpressions);    //建立建構函式
                middlewareDelegate = Expression.Lambda(callConstructor, parameterExpressions).Compile();    //對建構函式進行Lambda封裝
                return Tuple.Create(parameters[0].ParameterType, middlewareDelegate, args);
            }

            throw new MissingMethodException(string.Format(CultureInfo.CurrentCulture,
                Resources.Exception_NoConstructorFound, middlewareType.FullName, args.Length + 1));
        }

上面三種方法讓我們看見了微軟工程師的花樣使用委託、LambdaFunc,如果不是資深C#程式設計師,誰知道這語言還有這些特性,但能感覺到主要是想將原來類中的初始化方法進行歸一化,封裝在一個匿名delegate中,併合成一個三元組,後面採用統一的方法進行呼叫,這個三元組的Item1就是共用的下一個middleware也是當前middleware所在pipelineStage的簽名。

再來看看AppBuilderUseExtension擴充套件的兩個Use方法和一個Run方法。

public static IAppBuilder Use<T>(this IAppBuilder app, params object[] args)
        {
            if (app == null)
            {
                throw new ArgumentNullException("app");
            }

            return app.Use(typeof(T), args);

        }

這個Use方法將middlewareType放在Use<T>中,做為一個模板方法,實際上是對Use(typeof(T), args)的一個封裝。

public static IAppBuilder Use(this IAppBuilder app, Func<IOwinContext, Func<Task> /*next*/, Task> handler)
        {
            if (app == null)
            {
                throw new ArgumentNullException("app");
            }
            if (handler == null)
            {
                throw new ArgumentNullException("handler");
            }

            return app.Use<UseHandlerMiddleware>(handler);
        }

這個Use方法接受一個Func<IOwinContext, Func<Task> , Task>的委託,並對Use<UseHandlerMiddleware>進行封裝,為了後文引述方便我們將其重新命名為UseExtensionOne

public static void Run(this IAppBuilder app, Func<IOwinContext, Task> handler)
        {
            if (app == null)
            {
                throw new ArgumentNullException("app");
            }
            if (handler == null)
            {
                throw new ArgumentNullException("handler");
            }

            app.Use<UseHandlerMiddleware>(handler);
        }

這個Run方法接受一個 Func<IOwinContext, Task> 的委託,並對Use<UseHandlerMiddleware>進行封裝,將其重新命名為RunExtensionTwo方法。

現在檢視微軟對於Middleware的使用舉例頁面 http://www.asp.net/aspnet/overview/owin-and-katana/owin-middleware-in-the-iis-integrated-pipeline

網頁中黃色部分對middleware的注入有具體舉例,如下

//這是對UseExtensionOne的實際應用
            app.Use((context, next) =>
            {
                PrintCurrentIntegratedPipelineStage(context, "Middleware 1");
                return next.Invoke();
            });
            //這是對UseExtensionOne的實際應用
            app.Use((context, next) =>
            {
                PrintCurrentIntegratedPipelineStage(context, "2nd MW");
                return next.Invoke();
            }); 
            //這是對RunExtentionTwo的實際應用
            app.Run(context =>
            {
                PrintCurrentIntegratedPipelineStage(context, "3rd MW");
                return context.Response.WriteAsync("Hello world");
            });

三個很簡單的middleware被注入到pipeline中,但因為實際上是對Use<UseHandlerMiddleware>的封裝,也就是對Use(typeof(T), args)的封裝,所以實際上與 app.Use(typeof(CookieAuthenticationMiddleware), app, options)差不多,最終都會存成一樣的三元組壓進middlewareList中,所以他們都會呼叫UseHandlerMiddleware的建構函式,而檢視UseHandlerMiddleware發現其建構函式的第一項就是一個AppFunc,這與middlewareStage的切換和pipeline的流動息息相關,後面將詳細介紹其原理。

總結,本文主要講了AppBuilderUse方法的具體流程與擴充套件,三種擴充套件方法實際上都是對基礎Use的封裝,而基礎Use方法總的來說可以接受四種middlewareObject

序號 說明
1 Delegate是最簡單的,直接可以封裝成三元組壓入List
2 有Initialize方法的類的例項,參數列第一項為一個AppFunc或者OwinMiddleware,只要其Invoke之後能返回一個Task就行,為了避免DynamicInvoke的弊端進行了一次封裝,
3 有Invoke方法的類的例項,參數列也需要匯聚到一個object[]中,這兩種設計應該是有不同需求背景的,目前不知道究竟有什麼不同
4 Type,這需要對這個類的構造方法進行封裝,參考UseHandlerMiddleware的建構函式,第一個引數應該是一個AppFunc

需要理解這四種Use方法的不同,還需要了解pipeline重構是如何做的,這就是下一節AppBuilder.Build中的具體內容了,裡面有很多細節的東西,這將直接構建整個pipeline,也是整個OWIN最核心的地方。