OwinBuilder源碼閱讀

源碼參見Microsoft.Owin.Host.SystemWeb.OwinBuilderweb

經過前文知道,Build方法將被調用,其作的第一件事兒就是尋找Startup方法app

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

GetAppStartup方法主要完成從當前Assembly中尋找AppStartup方法,這也是爲何申明Startup,使其被調用有兩種方法:ide

函數

[assembly: OwinStartup(typeof(XX.Startup))]  //利用OwinStartupAtrribute來指導Startup

 

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; }

 

上面源碼展現了調用DefaultLoaderLoad方法來搜索Startup,而Startup是一個Action<IAppBuilder>方法,即接受一個實現了IAppBuilder接口的實例做爲參數,返回值爲voidActionthis

    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;
        }

 

這裏默認將FriendlyNameMethodName設置爲空,即只記錄了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主要作的工做了。

相關文章
相關標籤/搜索