新建的.net core 程序啟動本質上是一個控制台應用程序,所以它的入口在Main方法中,所以啟動的開始時從Main方法開始。
1 public class Program 2 { 3 public static void Main(string[] args) 4 { 5 BuildWebHost(args).Run(); 6 } 7 8 public static IWebHost BuildWebHost(string[] args)=> 9 WebHost.CreateDefaultBuilder(args).UseStartup<Startup>().Build(); 10 11 }
WebHost的CreateDefaultBuilder方法負責創建WebHostBuilder,最后調用WebHostBuilder的build方法創建一個WebHost,這個流程是現在Core里面流行的創建方式,類似讀取Config的流程。
IConfiguration configuration = new ConfigurationBuilder().Add(new MemoryConfigurationSource { InitialData = source }).Build(); configuration.GetSection("Format");//調用Config的獲得數據的方法。
我們看到的第一個方法:WebHost.CreateDefaultBuilder(args)。
1 public static IWebHostBuilder CreateDefaultBuilder(string[] args) 2 { 3 var builder = new WebHostBuilder() 4 .UseKestrel()//用Kestreal作為服務器 5 .UseContentRoot(Directory.GetCurrentDirectory())//設置程序的根目錄 6 .ConfigureAppConfiguration((hostingContext, config) => 7 { 8 //根據環境把對應的appsetting配置文件加入到config容器中 9 var env = hostingContext.HostingEnvironment; 10 11 config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) 12 .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); 13 14 if (env.IsDevelopment()) 15 { 16 var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName)); 17 if (appAssembly != null) 18 { 19 //添加用戶機密數據 20 config.AddUserSecrets(appAssembly, optional: true); 21 } 22 } 23 //添加環境變量 24 config.AddEnvironmentVariables(); 25 //這句加參數不知道有什么用 26 if (args != null) 27 { 28 config.AddCommandLine(args); 29 } 30 }) 31 .ConfigureLogging((hostingContext, logging) => 32 { 33 logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); 34 logging.AddConsole(); 35 logging.AddDebug(); 36 }) 37 .UseIISIntegration() 38 .UseDefaultServiceProvider((context, options) => 39 { 40 options.ValidateScopes = context.HostingEnvironment.IsDevelopment(); 41 }); 42 43 return builder; 44 }
添加用戶機密是干什么的可以詳細看ASP.NET Core 優雅的在開發環境保存機密(User Secrets),具體就是用來保存一些機密數據。
WebHostBuilder類
1 //環境變量的參數 2 private readonly IHostingEnvironment _hostingEnvironment; 3 //存儲添加的Service委托的一個集合 4 private readonly List<Action<WebHostBuilderContext, IServiceCollection>> _configureServicesDelegates; 5 //訪問配置文件 6 private IConfiguration _config; 7 //WebHost的一些配置選項,它里面其實都是存在config里面的 8 private WebHostOptions _options; 9 //webHostBuilder上下文,里面兩個屬性,一個是IHostingEnvironment,一個是IConfiguration 10 private WebHostBuilderContext _context; 11 //判斷是否調用了Build方法,調用以后為true,再次調用Build方法時會報錯 12 private bool _webHostBuilt; 13 //存儲添加配置的一個委托集合 14 private List<Action<WebHostBuilderContext, IConfigurationBuilder>> _configureAppConfigurationBuilderDelegates; 15 16 /// <summary> 17 /// Initializes a new instance of the <see cref="WebHostBuilder"/> class. 18 /// </summary> 19 public WebHostBuilder() 20 { 21 _hostingEnvironment = new HostingEnvironment(); 22 _configureServicesDelegates = new List<Action<WebHostBuilderContext, IServiceCollection>>(); 23 _configureAppConfigurationBuilderDelegates = new List<Action<WebHostBuilderContext, IConfigurationBuilder>>(); 24 25 _config = new ConfigurationBuilder() 26 .AddEnvironmentVariables(prefix: "ASPNETCORE_") 27 .Build(); 28 29 if (string.IsNullOrEmpty(GetSetting(WebHostDefaults.EnvironmentKey))) 30 { 31 // Try adding legacy environment keys, never remove these. 32 UseSetting(WebHostDefaults.EnvironmentKey, Environment.GetEnvironmentVariable("Hosting:Environment") 33 ?? Environment.GetEnvironmentVariable("ASPNET_ENV")); 34 } 35 36 if (string.IsNullOrEmpty(GetSetting(WebHostDefaults.ServerUrlsKey))) 37 { 38 // Try adding legacy url key, never remove this. 39 UseSetting(WebHostDefaults.ServerUrlsKey, Environment.GetEnvironmentVariable("ASPNETCORE_SERVER.URLS")); 40 } 41 42 _context = new WebHostBuilderContext 43 { 44 Configuration = _config 45 }; 46 }
ConfigureAppConfiguration方法就是把委托加到_configureAppConfigurationBuilderDelegates的集合當中去。
AddLogging就是把Service添加到_configureServicesDelegates中去。
UseStartUp方法
public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder, Type startupType) { var startupAssemblyName = startupType.GetTypeInfo().Assembly.GetName().Name; return hostBuilder .UseSetting(WebHostDefaults.ApplicationKey, startupAssemblyName) .ConfigureServices(services => { if (typeof(IStartup).GetTypeInfo().IsAssignableFrom(startupType.GetTypeInfo())) { services.AddSingleton(typeof(IStartup), startupType); } else { services.AddSingleton(typeof(IStartup), sp => { var hostingEnvironment = sp.GetRequiredService<IHostingEnvironment>(); return new ConventionBasedStartup(StartupLoader.LoadMethods(sp, startupType, hostingEnvironment.EnvironmentName)); }); } }); }
設置一下webhostBuilder的程序集名稱,把StartUp注冊到DI容器中去。目前還沒有注入到di容器中去,只是跟之前一樣,把這個委托加到了_configureServicesDelegates中去了,在循環執行委托的時候,才真正的注入到di容器中去,這個委托的用處就是把StartUp注入到DI去。
此時有兩個判斷,如果泛型傳進來的StartUp類是繼承了IStartup的話就直接注入到DI去的,如果不是的話,就裝載默認的StartUp類,也就是我們Core項目的新建時候的那個StartUp類。
看一下StartupLoader.LoadMethods(sp, startupType, hostingEnvironment.EnvironmentName)方法的代碼:
1 public static StartupMethods LoadMethods(IServiceProvider hostingServiceProvider, Type startupType, string environmentName) 2 { 3 //獲取StartUp類的Config方法(注:這個是可以根據環境來獲取不同的配置方法:方法名帶有環境變量類似於:Configure{0},占位符就是環境變量的值test,stage等) 4 var configureMethod = FindConfigureDelegate(startupType, environmentName); 5 //獲取StartUp類的ConfigService方法(跟Config獲取一樣可以分環境獲取方法,Configure{0}Service) 6 var servicesMethod = FindConfigureServicesDelegate(startupType, environmentName); 7 8 //獲取StartUp類的ConfigContainer方法(跟Config獲取一樣可以分環境獲取方法,Configure{0}Container) 9 var configureContainerMethod = FindConfigureContainerDelegate(startupType, environmentName); 10 11 object instance = null; 12 if (!configureMethod.MethodInfo.IsStatic || (servicesMethod != null && !servicesMethod.MethodInfo.IsStatic)) 13 { 14 instance = ActivatorUtilities.GetServiceOrCreateInstance(hostingServiceProvider, startupType); 15 } 16 //構建一個執行ConfigService的委托 17 var configureServicesCallback = servicesMethod.Build(instance); 18 var configureContainerCallback = configureContainerMethod.Build(instance); 19 20 Func<IServiceCollection, IServiceProvider> configureServices = services => 21 { 22 // Call ConfigureServices, if that returned an IServiceProvider, we're done,執行ConfigService,如果執行ConfigService方法返回的是一個IServiceProvider,則就直接返回,不執行下面的替換ConfigContainer的方法 23 IServiceProvider applicationServiceProvider = configureServicesCallback.Invoke(services); 24 25 if (applicationServiceProvider != null) 26 { 27 return applicationServiceProvider; 28 } 29 30 // If there's a ConfigureContainer method 31 if (configureContainerMethod.MethodInfo != null) 32 { 33 // We have a ConfigureContainer method, get the IServiceProviderFactory<TContainerBuilder> 34 var serviceProviderFactoryType = typeof(IServiceProviderFactory<>).MakeGenericType(configureContainerMethod.GetContainerType()); 35 var serviceProviderFactory = hostingServiceProvider.GetRequiredService(serviceProviderFactoryType); 36 // var builder = serviceProviderFactory.CreateBuilder(services); 37 var builder = serviceProviderFactoryType.GetMethod(nameof(DefaultServiceProviderFactory.CreateBuilder)).Invoke(serviceProviderFactory, new object[] { services }); 38 configureContainerCallback.Invoke(builder); 39 // applicationServiceProvider = serviceProviderFactory.CreateServiceProvider(builder); 40 applicationServiceProvider = (IServiceProvider)serviceProviderFactoryType.GetMethod(nameof(DefaultServiceProviderFactory.CreateServiceProvider)).Invoke(serviceProviderFactory, new object[] { builder }); 41 } 42 else 43 { 44 // Get the default factory 45 var serviceProviderFactory = hostingServiceProvider.GetRequiredService<IServiceProviderFactory<IServiceCollection>>(); 46 47 // Don't bother calling CreateBuilder since it just returns the default service collection 48 applicationServiceProvider = serviceProviderFactory.CreateServiceProvider(services); 49 } 50 51 return applicationServiceProvider ?? services.BuildServiceProvider(); 52 }; 53 54 return new StartupMethods(instance, configureMethod.Build(instance), configureServices); 55 }
ConventionBasedStartup這個類就是一個類似於StartUp的類,New得到時候,就是把StartupMethods傳進去,里面的話,就是有兩個方法Config指向StartUp的Config方法,ConfigService方法執行StartUp的ConfigService方法。
最后就是WebHostBuilder.Build()方法
1 public IWebHost Build() 2 { 3 if (_webHostBuilt) 4 { 5 throw new InvalidOperationException(Resources.WebHostBuilder_SingleInstance); 6 } 7 _webHostBuilt = true; 8 9 // Warn about deprecated environment variables 10 if (Environment.GetEnvironmentVariable("Hosting:Environment") != null) 11 { 12 Console.WriteLine("The environment variable 'Hosting:Environment' is obsolete and has been replaced with 'ASPNETCORE_ENVIRONMENT'"); 13 } 14 15 if (Environment.GetEnvironmentVariable("ASPNET_ENV") != null) 16 { 17 Console.WriteLine("The environment variable 'ASPNET_ENV' is obsolete and has been replaced with 'ASPNETCORE_ENVIRONMENT'"); 18 } 19 20 if (Environment.GetEnvironmentVariable("ASPNETCORE_SERVER.URLS") != null) 21 { 22 Console.WriteLine("The environment variable 'ASPNETCORE_SERVER.URLS' is obsolete and has been replaced with 'ASPNETCORE_URLS'"); 23 } 24 25 var hostingServices = BuildCommonServices(out var hostingStartupErrors); 26 var applicationServices = hostingServices.Clone(); 27 var hostingServiceProvider = hostingServices.BuildServiceProvider(); 28 29 AddApplicationServices(applicationServices, hostingServiceProvider); 30 31 var host = new WebHost( 32 applicationServices, 33 hostingServiceProvider, 34 _options, 35 _config, 36 hostingStartupErrors); 37 38 host.Initialize(); 39 40 return host; 41 }
里面的重點是在BuildCommonServices中,里面默認添加了一系列的Service。如下面的代碼:
1 private IServiceCollection BuildCommonServices(out AggregateException hostingStartupErrors) 2 { 3 hostingStartupErrors = null; 4 5 _options = new WebHostOptions(_config); 6 7 if (!_options.PreventHostingStartup) 8 { 9 var exceptions = new List<Exception>(); 10 11 // Execute the hosting startup assemblies 12 // HostingStartupAssemblies = $"{ApplicationName};{configuration[WebHostDefaults.HostingStartupAssembliesKey]}".Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries) ?? new string[0];這就是HostingStartupAssembly查找,當我們有類繼承與IHostingStartup是,他必須得在build的config里面添加一個key為WebHostDefaults.HostingStartupAssembliesKey的程序集名稱,多個以分號隔開。 13 foreach (var assemblyName in _options.HostingStartupAssemblies) 14 { 15 try 16 { 17 var assembly = Assembly.Load(new AssemblyName(assemblyName)); 18 19 //程序集上必須打上HostingStartupAttribute標簽 20 foreach (var attribute in assembly.GetCustomAttributes<HostingStartupAttribute>()) 21 { 22 var hostingStartup = (IHostingStartup)Activator.CreateInstance(attribute.HostingStartupType); 23 //執行hostingStartup的config方法。 24 hostingStartup.Configure(this); 25 } 26 } 27 catch (Exception ex) 28 { 29 // Capture any errors that happen during startup 30 exceptions.Add(new InvalidOperationException($"Startup assembly {assemblyName} failed to execute. See the inner exception for more details.", ex)); 31 } 32 } 33 34 if (exceptions.Count > 0) 35 { 36 hostingStartupErrors = new AggregateException(exceptions); 37 } 38 } 39 40 var contentRootPath = ResolveContentRootPath(_options.ContentRootPath, AppContext.BaseDirectory); 41 var applicationName = _options.ApplicationName; 42 43 // Initialize the hosting environment 44 _hostingEnvironment.Initialize(applicationName, contentRootPath, _options); 45 _context.HostingEnvironment = _hostingEnvironment; 46 47 var services = new ServiceCollection(); 48 services.AddSingleton(_hostingEnvironment); 49 services.AddSingleton(_context); 50 51 var builder = new ConfigurationBuilder() 52 .SetBasePath(_hostingEnvironment.ContentRootPath) 53 .AddInMemoryCollection(_config.AsEnumerable()); 54 55 //執行之前添加的配置文件的委托 56 foreach (var configureAppConfiguration in _configureAppConfigurationBuilderDelegates) 57 { 58 configureAppConfiguration(_context, builder); 59 } 60 61 var configuration = builder.Build(); 62 services.AddSingleton<IConfiguration>(configuration); 63 _context.Configuration = configuration; 64 65 var listener = new DiagnosticListener("Microsoft.AspNetCore"); 66 services.AddSingleton<DiagnosticListener>(listener); 67 services.AddSingleton<DiagnosticSource>(listener); 68 69 services.AddTransient<IApplicationBuilderFactory, ApplicationBuilderFactory>(); 70 services.AddTransient<IHttpContextFactory, HttpContextFactory>(); 71 services.AddScoped<IMiddlewareFactory, MiddlewareFactory>(); 72 services.AddOptions(); 73 services.AddLogging(); 74 75 // Conjure up a RequestServices 76 services.AddTransient<IStartupFilter, AutoRequestServicesStartupFilter>(); 77 services.AddTransient<IServiceProviderFactory<IServiceCollection>, DefaultServiceProviderFactory>(); 78 79 // Ensure object pooling is available everywhere. 80 services.AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>(); 81 //之前再UseStartUp(string assemblyName)設置的startup類的程序集,在這之前也執行了 一段這個代碼,在UseStartUp里面 82 //UseStartUp<T>(),這個重載的方法里面是沒有加入這個程序集名稱的,只是設置一下UseSetting(WebHostDefaults.ApplicationKey, startupAssemblyName) 83 if (!string.IsNullOrEmpty(_options.StartupAssembly)) 84 { 85 try 86 { 87 var startupType = StartupLoader.FindStartupType(_options.StartupAssembly, _hostingEnvironment.EnvironmentName); 88 89 if (typeof(IStartup).GetTypeInfo().IsAssignableFrom(startupType.GetTypeInfo())) 90 { 91 services.AddSingleton(typeof(IStartup), startupType); 92 } 93 else 94 { 95 services.AddSingleton(typeof(IStartup), sp => 96 { 97 var hostingEnvironment = sp.GetRequiredService<IHostingEnvironment>(); 98 var methods = StartupLoader.LoadMethods(sp, startupType, hostingEnvironment.EnvironmentName); 99 return new ConventionBasedStartup(methods); 100 }); 101 } 102 } 103 catch (Exception ex) 104 { 105 var capture = ExceptionDispatchInfo.Capture(ex); 106 services.AddSingleton<IStartup>(_ => 107 { 108 capture.Throw(); 109 return null; 110 }); 111 } 112 } 113 //到這里就已經把說有的Service加到ServiceCollection 114 foreach (var configureServices in _configureServicesDelegates) 115 { 116 configureServices(_context, services); 117 } 118 119 return services; 120 }
var applicationServices = hostingServices.Clone();
var hostingServiceProvider = hostingServices.BuildServiceProvider();
AddApplicationServices(applicationServices, hostingServiceProvider)
這里就是新建一個IServiceCollection,把hostingServices里面的service復制一份到applicationService里面去,同時里面有一個
var listener = new DiagnosticListener("Microsoft.AspNetCore"); services.AddSingleton<DiagnosticListener>(listener); services.AddSingleton<DiagnosticSource>(listener);
這個listener,要保證在兩個container里面都是同一個。
1 private void AddApplicationServices(IServiceCollection services, IServiceProvider hostingServiceProvider) 2 { 3 // We are forwarding services from hosting contrainer so hosting container 4 // can still manage their lifetime (disposal) shared instances with application services. 5 // NOTE: This code overrides original services lifetime. Instances would always be singleton in 6 // application container. 7 var listener = hostingServiceProvider.GetService<DiagnosticListener>(); 8 services.Replace(ServiceDescriptor.Singleton(typeof(DiagnosticListener), listener)); 9 services.Replace(ServiceDescriptor.Singleton(typeof(DiagnosticSource), listener)); 10 }
最后是新建一個WebHost,然后是調用Initialize方法進行初始化的一些工作,我們最重要的就是看里面的初始化干了什么東西?
//確保是否_applicationService是不是空的,空的話就調用IStartUp的ConfigServices方法獲得 EnsureApplicationServices(); //調用了UseUrls,則這里面把Server.Feature.Address的值賦值上去 EnsureServer(); var builderFactory = _applicationServices.GetRequiredService<IApplicationBuilderFactory>(); var builder = builderFactory.CreateBuilder(Server.Features); builder.ApplicationServices = _applicationServices; var startupFilters = _applicationServices.GetService<IEnumerable<IStartupFilter>>(); Action<IApplicationBuilder> configure = _startup.Configure; //查找是否 注冊了StartUpFilter,如果注冊了,則先調用過濾器 foreach (var filter in startupFilters.Reverse()) { configure = filter.Configure(configure); } //調用第一個注冊的config的方法然后接連調用鏈條 configure(builder); return builder.Build();
EnsureApplicationServices()的方法是最終調用StartUp類里面的ConfigService方法,把Service注入進去。
private void EnsureApplicationServices() { if (_applicationServices == null) { EnsureStartup();
//調用ConfigureServices方法把服務注冊進去(實際調用的是ConventionBasedStartup這個類的ConfigService方法。)
_applicationServices = _startup.ConfigureServices(_applicationServiceCollection);
}
}
至此,build方法弄完了。
最后是Run方法:
//這里時開始的方法。
await host.StartAsync(token); var hostingEnvironment = host.Services.GetService<IHostingEnvironment>(); var applicationLifetime = host.Services.GetService<IApplicationLifetime>(); Console.WriteLine($"Hosting environment: {hostingEnvironment.EnvironmentName}"); Console.WriteLine($"Content root path: {hostingEnvironment.ContentRootPath}");
//UseUrls時,傳入的url,這里回監聽這些地址。 var serverAddresses = host.ServerFeatures.Get<IServerAddressesFeature>()?.Addresses; if (serverAddresses != null) { foreach (var address in serverAddresses) { Console.WriteLine($"Now listening on: {address}"); } } if (!string.IsNullOrEmpty(shutdownMessage)) { Console.WriteLine(shutdownMessage); } await host.WaitForTokenShutdownAsync(token); }
StartAsync里面最終調用的就是IServer的Start方法。
最終的啟動流程就結束了!!!!。
最后感覺,這個啟動流程為了配合DI容器寫的好繞,里面很多的委托進行傳進傳出,看的人很暈!!
但是它帶來的靈活性也是巨大的,可以自定義很多地方,真正體現了萬物皆DI的思想!
