源碼參見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流動所需的envrionment和middleware,這也是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的註冊,而pipeline有11個stage之多,具體以下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.Use和app.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方法。
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 }
上面三種方法讓咱們看見了微軟工程師的花樣使用委託、Lambda、Func,若是不是資深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方法將middleware的Type放在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)差很少,最終都會存成同樣的三元組壓進middleware的List中,因此他們都會調用UseHandlerMiddleware的構造函數,而查看UseHandlerMiddleware發現其構造函數的第一項就是一個AppFunc,這與middlewareStage的切換和pipeline的流動息息相關,後面將詳細介紹其原理。
總結,本文主要講了AppBuilder的Use方法的具體流程與擴展,三種擴展方法實際上都是對基礎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最核心的地方。