源码参见Microsoft.Owin.Host.SystemWeb.OwinBuilder
通过前文知道,Build方法将被调用,其做的第一件事儿就是寻找Startup方法
internal static OwinAppContext Build() { Action<IAppBuilder> startup = GetAppStartup(); return Build(startup); }
而GetAppStartup方法主要完成从当前Assembly中寻找AppStartup方法,这也是为什么申明Startup,使其被调用有两种方法:
1
[assembly: OwinStartup(typeof(XX.Startup))] //利用OwinStartupAtrribute来指导Startup
2
<appSettings> <add key="owin:appStartup" value="StartupDemo.ProductionStartup" /> </appSettings> //在webconfig中定义owin:appStartup键值对,以下将对负责搜索Startup方法的DefaultLoader的源码进行分析,来了解如何定位Startup方法的
internal static Action<IAppBuilder> GetAppStartup() { string appStartup = ConfigurationManager.AppSettings[Constants.OwinAppStartup]; var loader = new DefaultLoader(new ReferencedAssembliesWrapper()); IList<string> errors = new List<string>(); Action<IAppBuilder> startup = loader.Load(appStartup ?? string.Empty, errors);
if (startup == null) { throw new EntryPointNotFoundException(Resources.Exception_AppLoderFailure + Environment.NewLine + " - " + string.Join(Environment.NewLine + " - ", errors) + (IsAutomaticAppStartupEnabled ? Environment.NewLine + Resources.Exception_HowToDisableAutoAppStartup : string.Empty) + Environment.NewLine + Resources.Exception_HowToSpecifyAppStartup); } return startup; }
上面源码展示了调用DefaultLoader的Load方法来搜索Startup,而Startup是一个Action<IAppBuilder>方法,即接受一个实现了IAppBuilder接口的实例作为参数,返回值为void的Action
public Action<IAppBuilder> Load(string startupName, IList<string> errorDetails) { return LoadImplementation(startupName, errorDetails) ?? _next(startupName, errorDetails); }
Load方法实际上是对LoadImplementation的一个封装,如果寻找失败则使用_next进行寻找(实际上这会返回null,这不是重点)
1 private Action<IAppBuilder> LoadImplementation(string startupName, IList<string> errorDetails) 2 { 3 Tuple<Type, string> typeAndMethod = null; 4 startupName = startupName ?? string.Empty; 5 // Auto-discovery or Friendly name? 6 if (!startupName.Contains(',')) 7 { 8 typeAndMethod = GetDefaultConfiguration(startupName, errorDetails); //通常会进入这一流程,如果startupName中包含逗号,则对应另一种申明方式 9 } 10 11 if (typeAndMethod == null && !string.IsNullOrWhiteSpace(startupName)) //这种申明方式为StartupName = “startupName,assemblyName” 12 { 13 typeAndMethod = GetTypeAndMethodNameForConfigurationString(startupName, errorDetails); //对startupName和assemblyName进行分离,并找到对应的assembly加载 14 //其中的startupName 15 } 16 17 if (typeAndMethod == null) 18 { 19 return null; 20 } 21 22 Type type = typeAndMethod.Item1; 23 // default to the "Configuration" method if only the type name was provided //如果只提供了startup的type,则默认调用其中的Configuration方法 24 string methodName = !string.IsNullOrWhiteSpace(typeAndMethod.Item2) ? typeAndMethod.Item2 : Constants.Configuration; 25 26 Action<IAppBuilder> startup = MakeDelegate(type, methodName, errorDetails); //直接调用startup方法或者做为一个middleware压入List中,后文会讲到具体实现 27 if (startup == null) 28 { 29 return null; 30 } 31 32 return builder => //再对startup进行一次delegate封装,传入参数为builder,供上层调用 33 { 34 if (builder == null) 35 { 36 throw new ArgumentNullException("builder"); 37 } 38 39 object value; 40 if (!builder.Properties.TryGetValue(Constants.HostAppName, out value) || 41 String.IsNullOrWhiteSpace(Convert.ToString(value, CultureInfo.InvariantCulture))) 42 { 43 builder.Properties[Constants.HostAppName] = type.FullName; //获取并记录HostAppName 44 } 45 startup(builder); //开始构造 46 }; 47 }
由于参数startupName为最初定义的常量,其值为Constants.OwinAppStartup = "owin:AppStartup";所以很明显会调用GetDefaultConfiguration(startupName, errorDetails)方法进一步处理。
private Tuple<Type, string> GetDefaultConfiguration(string friendlyName, IList<string> errors) { friendlyName = friendlyName ?? string.Empty; bool conflict = false; Tuple<Type, string> result = SearchForStartupAttribute(friendlyName, errors, ref conflict); if (result == null && !conflict && string.IsNullOrEmpty(friendlyName)) { result = SearchForStartupConvention(errors); } return result; }
这个方法又是对SearchForStartupAttribute的一个封装
先了解一下OwinStartupAttribute
看上文使用到的构造函数
public OwinStartupAttribute(Type startupType) : this(string.Empty, startupType, string.Empty) { } public OwinStartupAttribute(string friendlyName, Type startupType, string methodName) { if (friendlyName == null) { throw new ArgumentNullException("friendlyName"); } if (startupType == null) { throw new ArgumentNullException("startupType"); } if (methodName == null) { throw new ArgumentNullException("methodName"); } FriendlyName = friendlyName; StartupType = startupType; MethodName = methodName; }
这里默认将FriendlyName和MethodName设置为空,即只记录了Startup类的Type,下面的SearchForStartupAttribute主要也是通过寻找OwinStartupAttribute中的StartupType 来获取Startup的。
1 private Tuple<Type, string> SearchForStartupAttribute(string friendlyName, IList<string> errors, ref bool conflict) 2 { 3 friendlyName = friendlyName ?? string.Empty; 4 bool foundAnyInstances = false; 5 Tuple<Type, string> fullMatch = null; 6 Assembly matchedAssembly = null; 7 foreach (var assembly in _referencedAssemblies) // 遍历程序集 8 { 9 object[] attributes; 10 try 11 { 12 attributes = assembly.GetCustomAttributes(inherit: false); // 获取程序集的所有自定义Attribute 13 } 14 catch (CustomAttributeFormatException) 15 { 16 continue; 17 } 18 19 foreach (var owinStartupAttribute in attributes.Where(attribute => attribute.GetType().Name.Equals(Constants.OwinStartupAttribute, StringComparison.Ordinal))) // 对获取到的Attribute进行过滤,只遍历OwinStartupAttribute,即是优先会 //对上文所说的第一种 Startup申明进行调用 20 { 21 Type attributeType = owinStartupAttribute.GetType(); //采用反射机制,先获取Type 22 foundAnyInstances = true; 23 24 // Find the StartupType property. 25 PropertyInfo startupTypeProperty = attributeType.GetProperty(Constants.StartupType, typeof(Type)); //寻找属性名是StartupType,属性类型是Type的属性 26 if (startupTypeProperty == null) //寻找失败,记录错误 27 { 28 errors.Add(string.Format(CultureInfo.CurrentCulture, LoaderResources.StartupTypePropertyMissing, 29 attributeType.AssemblyQualifiedName, assembly.FullName)); 30 continue; 31 } 32 33 var startupType = startupTypeProperty.GetValue(owinStartupAttribute, null) as Type; //获取StartupType属性的值,并转换为Type,为反射做准备 34 if (startupType == null) //获取或者转换失败,记录错误 35 { 36 errors.Add(string.Format(CultureInfo.CurrentCulture, LoaderResources.StartupTypePropertyEmpty, assembly.FullName)); 37 continue; 38 } 39 40 // FriendlyName is an optional property. 41 string friendlyNameValue = string.Empty; //FriendlyName是可选项,作为对Startup类的别称,不是重点 42 PropertyInfo friendlyNameProperty = attributeType.GetProperty(Constants.FriendlyName, typeof(string)); 43 if (friendlyNameProperty != null) 44 { 45 friendlyNameValue = friendlyNameProperty.GetValue(owinStartupAttribute, null) as string ?? string.Empty; 46 } 47 48 if (!string.Equals(friendlyName, friendlyNameValue, StringComparison.OrdinalIgnoreCase)) //如果未定义FriendlyName则默认是Empty,否则记录错误 49 { 50 errors.Add(string.Format(CultureInfo.CurrentCulture, LoaderResources.FriendlyNameMismatch, 51 friendlyNameValue, friendlyName, assembly.FullName)); 52 continue; 53 } 54 55 // MethodName is an optional property. 56 string methodName = string.Empty; 同理MethodName也是可选项,如果为定义默认是Empty 57 PropertyInfo methodNameProperty = attributeType.GetProperty(Constants.MethodName, typeof(string)); 58 if (methodNameProperty != null) 59 { 60 methodName = methodNameProperty.GetValue(owinStartupAttribute, null) as string ?? string.Empty; 61 } 62 63 if (fullMatch != null) //表明已经寻找到一个Startup类,则冲突了,说明有重复申明Startup类 64 { 65 conflict = true; 66 errors.Add(string.Format(CultureInfo.CurrentCulture, 67 LoaderResources.Exception_AttributeNameConflict, 68 matchedAssembly.GetName().Name, fullMatch.Item1, assembly.GetName().Name, startupType, friendlyName)); 69 } 70 else //尚未寻找到Startup类,将StartupType和MethodName存为二元组,记录程序集 71 { 72 fullMatch = new Tuple<Type, string>(startupType, methodName); 73 matchedAssembly = assembly; 74 } 75 } 76 } 77 78 if (!foundAnyInstances) //未寻找到申明Startup的程序集,记录错误 79 { 80 errors.Add(LoaderResources.NoOwinStartupAttribute); 81 } 82 if (conflict) //如果有冲突,返回null 83 { 84 return null; 85 } 86 return fullMatch; //返回结果 87 }
前文讲到MakeDelegate(Type type, string methodName, IList<string> errors)主要作用是将寻找到的startup方法作为一个middleware压入List中,看其源码
1 private Action<IAppBuilder> MakeDelegate(Type type, string methodName, IList<string> errors) 2 { 3 MethodInfo partialMatch = null; 4 foreach (var methodInfo in type.GetMethods()) 5 { 6 if (!methodInfo.Name.Equals(methodName)) 7 { 8 continue; 9 } 10 11 // void Configuration(IAppBuilder app) //检测Startup类中的Configuration方法的参数和返回值,这种为默认的方法,也是新建MVC时默认的方法 12 if (Matches(methodInfo, false, typeof(IAppBuilder))) //方法无返回值(void),参数为(IAppBuilder) 13 { 14 object instance = methodInfo.IsStatic ? null : _activator(type); //如果为静态方法,则不需要实例,否则实例化一个Startup对象 15 return builder => methodInfo.Invoke(instance, new[] { builder }); //返回一个Lambda形式的delegate,实际上就是调用Startup的Configuration(IAppBuilder)方法 16 } 17 18 // object Configuration(IDictionary<string, object> appProperties) //另一种Configuration方法,参数为Environment,返回object 19 if (Matches(methodInfo, true, typeof(IDictionary<string, object>))) 20 { 21 object instance = methodInfo.IsStatic ? null : _activator(type); //由于传入参数为Dictionary,所以将这个Configuration方法压入middleware的List中 22 return builder => builder.Use(new Func<object, object>(_ => methodInfo.Invoke(instance, new object[] { builder.Properties }))); 23 } //builder.Use传入参数是一个Func<object,object>的delegate,实际上就是一个middleware,不过因为在初始化阶段,所以不需要进入下一个stage 24 25 // object Configuration() //无参数,返回object 26 if (Matches(methodInfo, true)) 27 { 28 object instance = methodInfo.IsStatic ? null : _activator(type); 29 return builder => builder.Use(new Func<object, object>(_ => methodInfo.Invoke(instance, new object[0]))); 30 } 31 32 partialMatch = partialMatch ?? methodInfo; //记录找到但不符合三种定义的Configuration方法 33 } 34 35 if (partialMatch == null) //未找到的Configuration,记录错误 36 { 37 errors.Add(string.Format(CultureInfo.CurrentCulture, 38 LoaderResources.MethodNotFoundInClass, methodName, type.AssemblyQualifiedName)); 39 } 40 else 找到Configuration,但不符合三种定义,记录错误 41 { 42 errors.Add(string.Format(CultureInfo.CurrentCulture, LoaderResources.UnexpectedMethodSignature, 43 methodName, type.AssemblyQualifiedName)); 44 } 45 return null; 46 }
总结:OwinBuilder主要完成对Startup类的寻找,并调用其中的Configuration方法,Configuration有三种签名(传入参数与返回结果),将其封装成一个方法返回给上层,供上层调用。接下来就是最重要的工作,调用Startup中的Configuration具体做了什么,每个middleware是如何注入到pipeline中的,这就是AppBuilder主要做的工作了。