新建的.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的思想!