源碼參見Microsoft.Owin.Host.SystemWeb.OwinBuilderweb
經過前文知道,Build方法將被調用,其作的第一件事兒就是尋找Startup方法app
internal static OwinAppContext Build() { Action<IAppBuilder> startup = GetAppStartup(); return Build(startup); }
而GetAppStartup方法主要完成從當前Assembly中尋找AppStartup方法,這也是爲何申明Startup,使其被調用有兩種方法:ide
1 函數
[assembly: OwinStartup(typeof(XX.Startup))] //利用OwinStartupAtrribute來指導Startup
2 ui
<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的Actionthis
public Action<IAppBuilder> Load(string startupName, IList<string> errorDetails) { return LoadImplementation(startupName, errorDetails) ?? _next(startupName, errorDetails); }
Load方法其實是對LoadImplementation的一個封裝,若是尋找失敗則使用_next進行尋找(實際上這會返回null,這不是重點)spa
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)方法進一步處理。code
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的一個封裝orm
先了解一下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主要作的工做了。