AppBuilder(一)Use彙總

源碼參見Microsoft.Owin.Host.SystemWeb.OwinBuilder程序員

   Microsoft.Owin.Builder.AppBuilderweb

前文講到數據庫

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

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

 1 internal static OwinAppContext Build(Action<IAppBuilder> startup)
 2         {
 3             if (startup == null)
 4             {
 5                 throw new ArgumentNullException("startup");
 6             }
 7 
 8             var appContext = new OwinAppContext();    //實例化OwinAppContext,主要得到網站名稱和程序名稱
 9             appContext.Initialize(startup);    //進一步初始化OwinAppContext
10             return appContext;
11         }

來看看OwinAppContext.Initialize(IAppBuilder)作了什麼數據結構

 1 internal void Initialize(Action<IAppBuilder> startup)
 2         {
 3             Capabilities = new ConcurrentDictionary<string, object>();    //初始化爲多線程安全的Dictionary,說明爲多線程共享資源
 4 
 5             var builder = new AppBuilder();    //初始化AppBuilder,向Properties中填入一些基本信息,這構成了Server環境的快照
 6             builder.Properties[Constants.OwinVersionKey] = Constants.OwinVersion;
 7             builder.Properties[Constants.HostTraceOutputKey] = TraceTextWriter.Instance;
 8             builder.Properties[Constants.HostAppNameKey] = AppName;
 9             builder.Properties[Constants.HostOnAppDisposingKey] = OwinApplication.ShutdownToken;
10             builder.Properties[Constants.HostReferencedAssemblies] = new ReferencedAssembliesWrapper();
11             builder.Properties[Constants.ServerCapabilitiesKey] = Capabilities;
12             builder.Properties[Constants.SecurityDataProtectionProvider] = new MachineKeyDataProtectionProvider().ToOwinFunction();
13             builder.SetLoggerFactory(new DiagnosticsLoggerFactory());
14 
15             Capabilities[Constants.SendFileVersionKey] = Constants.SendFileVersion;
16 
17             CompilationSection compilationSection = (CompilationSection)System.Configuration.ConfigurationManager.GetSection(@"system.web/compilation");
18             bool isDebugEnabled = compilationSection.Debug;
19             if (isDebugEnabled)
20             {
21                 builder.Properties[Constants.HostAppModeKey] = Constants.AppModeDevelopment;
22             }
23 
24             DetectWebSocketSupportStageOne();
25 
26             try
27             {
28                 startup(builder);    //真正的由用戶定義的middleware注入開始了,這將完成pipeline的構造
29             }
30             catch (Exception ex)
31             {
32                 _trace.WriteError(Resources.Trace_EntryPointException, ex);
33                 throw;
34             }
35 
36             AppFunc = (AppFunc)builder.Build(typeof(AppFunc));//記錄pipeline的第一個middleware,這將是整個pipeline的入口
37         }

先從AppBuilder的實例化開始多線程

 1 public AppBuilder()
 2         {
 3             _properties = new Dictionary<string, object>();    //初始化environment資源
 4             _conversions = new Dictionary<Tuple<Type, Type>, Delegate>();    //初始化轉換器,這將不是本文重點,後期再涉及
 5             _middleware = new List<Tuple<Type, Delegate, object[]>>();    //初始化middleware列表
 6 
 7             _properties[Constants.BuilderAddConversion] = new Action<Delegate>(AddSignatureConversion);    //記錄Conversion
 8             _properties[Constants.BuilderDefaultApp] = NotFound;
 9 
10             SignatureConversions.AddConversions(this);
11         }

上面的源碼展現了AppBuilder初始化pipeline流動所需的envrionmentmiddleware,這也是OWIN最重要的兩個數據結構。app

builder完成實例化以後會嘗試startup(builder)這便是調用用戶定義的Configuration(IAppBuilder)方法。asp.net

 1 public partial class Startup
 2     {
 3         public void Configuration(IAppBuilder app)
 4         {
 5             ConfigureAuth(app);
 6         }
 7     }
 8 public partial class Startup
 9     {
10         public void ConfigureAuth(IAppBuilder app)
11         {
12 
13             app.CreatePerOwinContext<BlogContext>(BlogContext.Create);    //將數據庫上下文注入envrionment中
14             app.CreatePerOwinContext<AppUserManager>(AppUserManager.Create);    //將AppUserManager注入environment中
15             app.CreatePerOwinContext<AppSigninManager>(AppSigninManager.Create);    //將AppSigninManager注入environment中
16 
17             app.UseCookieAuthentication(new CookieAuthenticationOptions    //標記CookeAuthentication這個middleware在pipeline中的stage,並配置參數
18             {
19                 AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
20                 LoginPath = new PathString("/Account/Login"),
21                 Provider = new CookieAuthenticationProvider
22                 {
23                     // Enables the application to validate the security stamp when the user logs in.
24                     // This is a security feature which is used when you change a password or add an external login to your account. 
25                     OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<AppUserManager, AppUser>(
26                         validateInterval: TimeSpan.FromMinutes(30),
27                         regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
28                 }
29             });
30 
31             // Enables the application to remember the second login verification factor such as phone or email.
32             // 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.
33             // This is similar to the RememberMe option when you log in.
34             app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);    //標記stage
35         }
36 
37     }

 

以新建MVC工程默認生成的Starup類爲例。目前咱們能夠大體猜想一下這份配置方法中只進行了兩個stage的註冊,而pipeline11stage之多,具體以下ide

 

 1 private static readonly IList<string> StageNames = new[]
 2         {
 3             Constants.StageAuthenticate,
 4             Constants.StagePostAuthenticate,
 5             Constants.StageAuthorize,
 6             Constants.StagePostAuthorize,
 7             Constants.StageResolveCache,
 8             Constants.StagePostResolveCache,
 9             Constants.StageMapHandler,
10             Constants.StagePostMapHandler,
11             Constants.StageAcquireState,
12             Constants.StagePostAcquireState,
13             Constants.StagePreHandlerExecute,
14         };

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

 1 public static class CookieAuthenticationExtensions
 2     {
 3         public static IAppBuilder UseCookieAuthentication(this IAppBuilder app, CookieAuthenticationOptions options)
 4         {
 5             return app.UseCookieAuthentication(options, PipelineStage.Authenticate);    //在PipelineStage.Authenticate階段執行
 6         }
 7 
 8         public static IAppBuilder UseCookieAuthentication(this IAppBuilder app, CookieAuthenticationOptions options, PipelineStage stage)
 9         {
10             if (app == null)
11             {
12                 throw new ArgumentNullException("app");
13             }
14 
15             app.Use(typeof(CookieAuthenticationMiddleware), app, options);    //app.Use實際上就是將middleware壓入middleware的List中
16             app.UseStageMarker(stage);    //標記middleware所在stage
17             return app;
18         }
19     }

這裏涉及到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進行轉換並壓入middlewareList中,轉換過程實際上對middleware進行參數檢查和簽名,這將是一個很是複雜的過程,一步一步地來。

先看看ToMiddlewareFactory方法。

 1 private static Tuple<Type, Delegate, object[]> ToMiddlewareFactory(object middlewareObject, object[] args)    //接受兩個參數,返回一個三元組
 2         {
 3             if (middlewareObject == null)
 4             {
 5                 throw new ArgumentNullException("middlewareObject");
 6             }
 7 
 8             var middlewareDelegate = middlewareObject as Delegate;    //嘗試將middlewareObject類型轉換爲Delegate,而咱們上面傳入的是一個Type,將會轉換失敗
 9             if (middlewareDelegate != null)    //若是轉換成功
10             {
11                 return Tuple.Create(GetParameterType(middlewareDelegate), middlewareDelegate, args);    //直接返回三元組,這彷佛是最理想的狀況
12             }
13 
14             Tuple<Type, Delegate, object[]> factory = ToInstanceMiddlewareFactory(middlewareObject, args);    //嘗試在middlewareObject類中尋找Initialize方法,並檢查args參
15     //數是否符合Initialize方法的參數
16             if (factory != null)
17             {
18                 return factory;
19             }
20 
21             factory = ToGeneratorMiddlewareFactory(middlewareObject, args);    //嘗試在middlewareObject類中尋找Invoke方法,並做參數檢查
22 
23             if (factory != null)
24             {
25                 return factory;
26             }
27 
28             if (middlewareObject is Type)    //這是本文的app.Use方法傳入的參數類型,因此會進入這一流程中
29             {
30                 return ToConstructorMiddlewareFactory(middlewareObject, args, ref middlewareDelegate);
31             }
32 
33             throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture,
34                 Resources.Exception_MiddlewareNotSupported, middlewareObject.GetType().FullName));
35 
36         }

接下來將分別對ToInstanceMiddlewareFactory,ToGeneratorMiddlewareFactory,ToConstructorMiddlewareFactory進行介紹。

 1 private static Tuple<Type, Delegate, object[]> ToInstanceMiddlewareFactory(object middlewareObject, object[] args)
 2         {
 3             MethodInfo[] methods = middlewareObject.GetType().GetMethods();    //將middleware看成一個實例處理,獲取其Type,再得到類中全部方法
 4             foreach (var method in methods)    //遍歷方法
 5             {
 6                 if (method.Name != Constants.Initialize)    //尋找Initialize方法
 7                 {
 8                     continue;
 9                 }
10                 ParameterInfo[] parameters = method.GetParameters();    //獲取Initialize的參數表
11                 Type[] parameterTypes = parameters.Select(p => p.ParameterType).ToArray();    //獲取參數表的Type類型
12 
13                 if (parameterTypes.Length != args.Length + 1)    //Initialize的參數應該比args的參數多一個,由於第一個參數應該是下一個middleware
14                 {
15                     continue;
16                 }
17                 if (!parameterTypes
18                     .Skip(1)
19                     .Zip(args, TestArgForParameter)
20                     .All(x => x))
21                 {    //對Initialize第一個之後的參數與args進行一對一驗證,args應該爲對應項的實例
22                     continue;
23                 }
24          
25                 // DynamicInvoke can't handle a middleware with multiple args, just push the args in via closure.
26                //開發者在此處註釋動態Invoke沒法處理middleware有多個參數的狀況,因此進行了一次封裝,簡化參數表
27                 Func<object, object> func = app =>
28                 {
29                     object[] invokeParameters = new[] { app }.Concat(args).ToArray();    //將app(實際上爲envrionment)參數與args合併
30                     method.Invoke(middlewareObject, invokeParameters);    //真正調用middleware的Initialize方法
31                     return middlewareObject;    //返回middlewareObject實例
32                 };
33 
34                 return Tuple.Create<Type, Delegate, object[]>(parameters[0].ParameterType, func, new object[0]);
35             }
36             return null;
37         }

ToInstanceMiddlewareFactory返回的是一個具體的實例,彷佛不知足Func<IDictionary<string, object>, Task>類型,這也是目前沒弄明白的地方

 1 private static Tuple<Type, Delegate, object[]> ToGeneratorMiddlewareFactory(object middlewareObject, object[] args)
 2         {
 3             MethodInfo[] methods = middlewareObject.GetType().GetMethods();    //將middlewareObject看成一個實例,獲取Type並獲取全部方法
 4             foreach (var method in methods)
 5             {
 6                 if (method.Name != Constants.Invoke)    //尋找Invoke方法
 7                 {
 8                     continue;
 9                 }
10                 ParameterInfo[] parameters = method.GetParameters();    //獲取Invoke方法的參數表
11                 Type[] parameterTypes = parameters.Select(p => p.ParameterType).ToArray();    //獲取參數的Type表
12 
13                 if (parameterTypes.Length != args.Length + 1)
14                 {
15                     continue;
16                 }
17                 if (!parameterTypes
18                     .Skip(1)
19                     .Zip(args, TestArgForParameter)
20                     .All(x => x))
21                 {    //將Type表與args進行一對一驗證
22                     continue;
23                 }
24                 IEnumerable<Type> genericFuncTypes = parameterTypes.Concat(new[] { method.ReturnType });    //將參數Type表與返回Type合併
25                 Type funcType = Expression.GetFuncType(genericFuncTypes.ToArray());    //獲取方法簽名Type
26                 Delegate middlewareDelegate = Delegate.CreateDelegate(funcType, middlewareObject, method);    //對實例的Invoke方法進行delegate封裝
27                 return Tuple.Create(parameters[0].ParameterType, middlewareDelegate, args);
28             }
29             return null;
30         }

ToGeneratorMiddlewareFactory返回類型由Invoke方法決定,一般會是一個Task

 1 private static Tuple<Type, Delegate, object[]> ToConstructorMiddlewareFactory(object middlewareObject, object[] args, ref Delegate middlewareDelegate)
 2         {
 3             var middlewareType = middlewareObject as Type;    //嘗試將middlwareObject轉換爲Type
 4             ConstructorInfo[] constructors = middlewareType.GetConstructors();    //獲取類的構造方法
 5             foreach (var constructor in constructors)    //遍歷構造方法
 6             {
 7                 ParameterInfo[] parameters = constructor.GetParameters();    //獲取構造函數的參數表
 8                 Type[] parameterTypes = parameters.Select(p => p.ParameterType).ToArray();    //獲取參數的Type表
 9                 if (parameterTypes.Length != args.Length + 1)    //參數表應該比args多一項,第一項應該爲下一個middleware
10                 {
11                     continue;
12                 }
13                 if (!parameterTypes
14                     .Skip(1)
15                     .Zip(args, TestArgForParameter)
16                     .All(x => x))
17                 {    //對參數表的第一項以後的與args一對一校驗
18                     continue;
19                 }
20 
21                 ParameterExpression[] parameterExpressions = parameters.Select(p => Expression.Parameter(p.ParameterType, p.Name)).ToArray();    //提取參數表的Type和Name
22                 NewExpression callConstructor = Expression.New(constructor, parameterExpressions);    //建立構造函數
23                 middlewareDelegate = Expression.Lambda(callConstructor, parameterExpressions).Compile();    //對構造函數進行Lambda封裝
24                 return Tuple.Create(parameters[0].ParameterType, middlewareDelegate, args);
25             }
26 
27             throw new MissingMethodException(string.Format(CultureInfo.CurrentCulture,
28                 Resources.Exception_NoConstructorFound, middlewareType.FullName, args.Length + 1));
29         }

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

再來看看AppBuilderUseExtension擴展的兩個Use方法和一個Run方法。

 1 public static IAppBuilder Use<T>(this IAppBuilder app, params object[] args)
 2         {
 3             if (app == null)
 4             {
 5                 throw new ArgumentNullException("app");
 6             }
 7 
 8             return app.Use(typeof(T), args);
 9 
10         }

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

 1 public static IAppBuilder Use(this IAppBuilder app, Func<IOwinContext, Func<Task> /*next*/, Task> handler)
 2         {
 3             if (app == null)
 4             {
 5                 throw new ArgumentNullException("app");
 6             }
 7             if (handler == null)
 8             {
 9                 throw new ArgumentNullException("handler");
10             }
11 
12             return app.Use<UseHandlerMiddleware>(handler);
13         }

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

 1 public static void Run(this IAppBuilder app, Func<IOwinContext, Task> handler)
 2         {
 3             if (app == null)
 4             {
 5                 throw new ArgumentNullException("app");
 6             }
 7             if (handler == null)
 8             {
 9                 throw new ArgumentNullException("handler");
10             }
11 
12             app.Use<UseHandlerMiddleware>(handler);
13         }

這個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的注入有具體舉例,以下

 1 //這是對UseExtensionOne的實際應用
 2             app.Use((context, next) =>
 3             {
 4                 PrintCurrentIntegratedPipelineStage(context, "Middleware 1");
 5                 return next.Invoke();
 6             });
 7             //這是對UseExtensionOne的實際應用
 8             app.Use((context, next) =>
 9             {
10                 PrintCurrentIntegratedPipelineStage(context, "2nd MW");
11                 return next.Invoke();
12             }); 
13             //這是對RunExtentionTwo的實際應用
14             app.Run(context =>
15             {
16                 PrintCurrentIntegratedPipelineStage(context, "3rd MW");
17                 return context.Response.WriteAsync("Hello world");
18             });     

三個很簡單的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最核心的地方。

相關文章
相關標籤/搜索