在ASP.NET Core - 從Program和Startup開始這篇文章裏面,咱們知道了Startup這個類的重要性,它主要負責了:html
在源碼分析以前補充一點,雖然咱們通常是按約定把這個類名定義成了Startup,可是在真正應用中,咱們不是必需要命名爲Startup的,這只是一個抽象概念,咱們能夠命名其餘的類名,只須要在UseStartup/UseStartup<TStartup>中顯式註冊這個啓動類便可,系統會把這個啓動類註冊爲單例,例如: web
public class Program { public static void Main(string[] args) { BuildWebHost(args).Run(); } public static IWebHost BuildWebHost(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup<YourStartupClass>() .Build(); } public class YourStartupClass { public void ConfigureService(IServiceCollection services) { } public void Configure(IApplicationBuilder app) { } }
從前面咱們能夠看到Startup是在UseStartup方法裏面被引用進來,咱們先看一下UseStartup是如何把Startup類註冊進來的 app
/// <summary>Specify the startup type to be used by the web host.</summary> /// <param name="hostBuilder">The <see cref="T:Microsoft.AspNetCore.Hosting.IWebHostBuilder" /> to configure.</param> /// <param name="startupType">The <see cref="T:System.Type" /> to be used.</param> /// <returns>The <see cref="T:Microsoft.AspNetCore.Hosting.IWebHostBuilder" />.</returns> public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder,Type startupType) { string name = startupType.GetTypeInfo().Assembly.GetName().Name; return hostBuilder.UseSetting(WebHostDefaults.ApplicationKey, name).ConfigureServices((Action<IServiceCollection>) (services => { if (typeof (IStartup).GetTypeInfo().IsAssignableFrom(startupType.GetTypeInfo())) ServiceCollectionServiceExtensions.AddSingleton(services, typeof (IStartup), startupType); else ServiceCollectionServiceExtensions.AddSingleton(services, typeof (IStartup), (Func<IServiceProvider, object>) (sp => { IHostingEnvironment requiredService = sp.GetRequiredService<IHostingEnvironment>(); return (object) new ConventionBasedStartup(StartupLoader.LoadMethods(sp, startupType, requiredService.EnvironmentName)); })); })); } /// <summary>Specify the startup type to be used by the web host.</summary> /// <param name="hostBuilder">The <see cref="T:Microsoft.AspNetCore.Hosting.IWebHostBuilder" /> to configure.</param> /// <typeparam name="TStartup">The type containing the startup methods for the application.</typeparam> /// <returns>The <see cref="T:Microsoft.AspNetCore.Hosting.IWebHostBuilder" />.</returns> public static IWebHostBuilder UseStartup<TStartup>(this IWebHostBuilder hostBuilder) where TStartup : class { return hostBuilder.UseStartup(typeof (TStartup)); }
從上面代碼咱們能夠看出,這裏主要是調用了WebHostBuilder的ConfigureServices方法,咱們看一下ConfigureServices作了什麼ide
public IWebHostBuilder ConfigureServices( Action<IServiceCollection> configureServices) { if (configureServices == null) throw new ArgumentNullException(nameof (configureServices)); return this.ConfigureServices((Action<WebHostBuilderContext, IServiceCollection>) ((_, services) => configureServices(services))); } /// <summary> /// Adds a delegate for configuring additional services for the host or web application. This may be called /// multiple times. /// </summary> /// <param name="configureServices">A delegate for configuring the <see cref="T:Microsoft.Extensions.DependencyInjection.IServiceCollection" />.</param> /// <returns>The <see cref="T:Microsoft.AspNetCore.Hosting.IWebHostBuilder" />.</returns> public IWebHostBuilder ConfigureServices( Action<WebHostBuilderContext, IServiceCollection> configureServices) { if (configureServices == null) throw new ArgumentNullException(nameof (configureServices)); this._configureServicesDelegates.Add(configureServices); return (IWebHostBuilder) this; }
這裏主要是把委託添加到_configureServicesDelegates列表裏面,這個_configureServicesDelegates有什麼用呢?這個屬性是一個很是重要的承載角色,在後面的WebHost真正調用Build方法時,咱們再詳細講解。 源碼分析
再次看回UseStartup,這裏調用了WebHostBuilder的ConfigureServices並向_configureServicesDelegates註冊了一個委託,咱們看一下這個委託的實體,這裏面有兩個分支:ui
1. Startup實現IStartup接口this
直接註冊該Startup爲單例。從這裏看出,其實咱們的Startup類還有另外一種方式實現的,就是直接實現IStartup接口。(其實還有一種是繼承StartupBase)spa
2. Startup沒實現IStartup接口code
註冊類型爲ConventionBasedStartup的Startup類型orm
public class ConventionBasedStartup : IStartup { private readonly StartupMethods _methods; public ConventionBasedStartup(StartupMethods methods) { this._methods = methods; } public void Configure(IApplicationBuilder app) { try { this._methods.ConfigureDelegate(app); } catch (Exception ex) { if (ex is TargetInvocationException) ExceptionDispatchInfo.Capture(ex.InnerException).Throw(); throw; } } public IServiceProvider ConfigureServices(IServiceCollection services) { try { return this._methods.ConfigureServicesDelegate(services); } catch (Exception ex) { if (ex is TargetInvocationException) ExceptionDispatchInfo.Capture(ex.InnerException).Throw(); throw; } } }
注意這個ConventionBasedStartup其實也是實現了IStartup接口,ConventionBasedStartup對象是根據一個StartupMethods對象建立的,咱們來看一下這個StartupMethods類型的定義
public class StartupMethods { public StartupMethods(object instance,Action<IApplicationBuilder> configure, Func<IServiceCollection, IServiceProvider> configureServices) { this.StartupInstance = instance; this.ConfigureDelegate = configure; this.ConfigureServicesDelegate = configureServices; } public object StartupInstance { get; } public Func<IServiceCollection, IServiceProvider> ConfigureServicesDelegate { get; } public Action<IApplicationBuilder> ConfigureDelegate { get; } }
StartupMethods只提供兩個註冊服務和中間件的方法,這兩個方法體如今由它的兩個屬性(ConfigureServicesDelegate和ConfigureDelegate)提供的兩個委託對象。
在咱們UseStartup代碼裏面,是經過StartupLoader.LoadMethods基於Startup類型獲取到一個StartupMethods
public class StartupLoader { public static StartupMethods LoadMethods(IServiceProvider hostingServiceProvider,Type startupType,string environmentName) { ConfigureBuilder configureDelegate = StartupLoader.FindConfigureDelegate(startupType, environmentName); ConfigureServicesBuilder servicesDelegate = StartupLoader.FindConfigureServicesDelegate(startupType, environmentName); ConfigureContainerBuilder containerDelegate = StartupLoader.FindConfigureContainerDelegate(startupType, environmentName); object instance1 = (object) null; if (!configureDelegate.MethodInfo.IsStatic || servicesDelegate != null && !servicesDelegate.MethodInfo.IsStatic) instance1 = ActivatorUtilities.GetServiceOrCreateInstance(hostingServiceProvider, startupType); StartupLoader.ConfigureServicesDelegateBuilder instance2 = (StartupLoader.ConfigureServicesDelegateBuilder) Activator.CreateInstance(typeof (StartupLoader.ConfigureServicesDelegateBuilder<>).MakeGenericType(containerDelegate.MethodInfo != (MethodInfo) null ? containerDelegate.GetContainerType() : typeof (object)), (object) hostingServiceProvider, (object) servicesDelegate, (object) containerDelegate, instance1); return new StartupMethods(instance1, configureDelegate.Build(instance1), instance2.Build()); } private static ConfigureBuilder FindConfigureDelegate(Type startupType, string environmentName) { return new ConfigureBuilder(StartupLoader.FindMethod(startupType, "Configure{0}", environmentName, typeof (void), true)); } private static ConfigureContainerBuilder FindConfigureContainerDelegate(Type startupType, string environmentName) { return new ConfigureContainerBuilder(StartupLoader.FindMethod(startupType, "Configure{0}Container", environmentName, typeof (void), false)); } private static ConfigureServicesBuilder FindConfigureServicesDelegate( Type startupType,string environmentName) { MethodInfo method = StartupLoader.FindMethod(startupType, "Configure{0}Services", environmentName, typeof (IServiceProvider), false); if ((object) method == null) method = StartupLoader.FindMethod(startupType, "Configure{0}Services", environmentName, typeof (void), false); return new ConfigureServicesBuilder(method); } private static MethodInfo FindMethod( Type startupType,string methodName,string environmentName,Type returnType = null, bool required = true) { string methodNameWithEnv = string.Format((IFormatProvider) CultureInfo.InvariantCulture, methodName, (object) environmentName); string methodNameWithNoEnv = string.Format((IFormatProvider) CultureInfo.InvariantCulture, methodName, (object) ""); MethodInfo[] methods = startupType.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public); List<MethodInfo> list = ((IEnumerable<MethodInfo>) methods).Where<MethodInfo>((Func<MethodInfo, bool>) (method => method.Name.Equals(methodNameWithEnv, StringComparison.OrdinalIgnoreCase))).ToList<MethodInfo>(); if (list.Count > 1) throw new InvalidOperationException(string.Format("Having multiple overloads of method '{0}' is not supported.", (object) methodNameWithEnv)); if (list.Count == 0) { list = ((IEnumerable<MethodInfo>) methods).Where<MethodInfo>((Func<MethodInfo, bool>) (method => method.Name.Equals(methodNameWithNoEnv, StringComparison.OrdinalIgnoreCase))).ToList<MethodInfo>(); if (list.Count > 1) throw new InvalidOperationException(string.Format("Having multiple overloads of method '{0}' is not supported.", (object) methodNameWithNoEnv)); } MethodInfo methodInfo = list.FirstOrDefault<MethodInfo>(); if (methodInfo == (MethodInfo) null) { if (required) throw new InvalidOperationException(string.Format("A public method named '{0}' or '{1}' could not be found in the '{2}' type.", (object) methodNameWithEnv, (object) methodNameWithNoEnv, (object) startupType.FullName)); return (MethodInfo) null; } if (!(returnType != (Type) null) || !(methodInfo.ReturnType != returnType)) return methodInfo; if (required) throw new InvalidOperationException(string.Format("The '{0}' method in the type '{1}' must have a return type of '{2}'.", (object) methodInfo.Name, (object) startupType.FullName, (object) returnType.Name)); return (MethodInfo) null; } }
這裏面主要是經過反射獲取startupType裏面的Configure和ConfigureServices做爲參數賦值給StartupMethods的ConfigureDelegate和ConfigureServicesDelegate,這樣一個完整的ConventionBasedStartup類型的startup就被註冊爲單例了。
比較有意思的是,咱們能夠看到,Startup類中的這兩個方法除了能夠命名爲ConfigureServices和Configure以外,它們還能夠攜帶運行環境名稱,具體採用的格式分別爲Configure{EnvironmentName}Services和Configure{EnvironmentName},後者具備更高的選擇優先級。
注意到FindConfigureServicesDelegate這個方法的實現,通常來講,ConfigureServices/Configure{EnvironmentName}Services這個方法不具備返回值(返回類型爲void),可是它也能夠定義成一個返回類型爲IServiceProvider的方法。若是這個方法返回一個ServiceProvider對象,後續過程當中獲取的全部服務將從這個ServiceProvider中提取。這個返回的ServiceProvider對咱們後續的一個註冊是很是有用的,具體在【ASP.NET Core - 利用Windsor Castle實現通用註冊】這篇文章中提到,咱們基於返回的IServiceProvider進行容器替換進而實現通用註冊,對於沒有返回值的狀況,系統會根據當前註冊的服務建立一個ServiceProvider。
注意到此爲止,程序仍是沒有執行作一個真正的註冊,由於咱們只是往_configureServicesDelegates添加了委託而已,並無執行,這個的執行是在WebHost真正調用Build方法時。
其實CreateDefaultBuilder方法中的其餘幾個UseXXX(UserUrl,UseKestrel等)擴展方法也是一樣的道理,把對應須要註冊的Action委託一樣寫入了configureServicesDelegates
上面的這個過程,咱們看到其實包含不少隱含的邏輯,這些邏輯的理解,能夠提供多種選項讓咱們在後面的真正開發中可以根據本身項目的一個實際狀況進行選擇,到此爲止,咱們如今能看到的最重要的一個點是:_configureServicesDelegates的一個承載做用。