源码参见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)方法。
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最重要的两个数据结构。
builder完成实例化之后会尝试startup(builder)这即是调用用户定义的Configuration(IAppBuilder)方法。
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之多,具体如下
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最核心的地方。