为什么我们可以在Startup这个 “孤零零的” 类中配置依赖注入和管道?
它是什么时候被实例化并且调用的?
参数中的IServiceCollection services是怎么来的?
处理管道是怎么构建起来的?
启动过程中,系统“默默的”做了哪些准备工作?
上一篇文章讲了ASP.NET Core中的依赖注入, 而它的配置是在Startup这个文件中的 ConfigureServices(IServiceCollection services) 方法,而且Startup这个类也没有继承任何类或者接口。 深入的想一想,可能会冒出类似上面列出的好多问题,下面用一幅图来看透它。
一、整体流程图
先上图, 觉得看不清可以点击看大图或者下载后放大查看。
二、WebHostBuilder
应用程序在Main方法之后通过调用CreateDefaultBuilder方法创建并配置WebHostBuilder,
1 public class WebHostBuilder : IWebHostBuilder 2 { 3 private readonly List<Action<WebHostBuilderContext, IServiceCollection>> _configureServicesDelegates; 4 5 private IConfiguration _config; 6 public IWebHostBuilder UseSetting(string key, string value) 7 { 8 _config[key] = value; 9 return this; 10 } 22 public IWebHostBuilder ConfigureServices(Action<WebHostBuilderContext, IServiceCollection> configureServices) 23 { 24 if (configureServices == null) 25 { 26 throw new ArgumentNullException(nameof(configureServices)); 27 } 29 _configureServicesDelegates.Add(configureServices); 30 return this; 31 } 32 }
WebHostBuilder存在一个重要的集合① private readonly List> _configureServicesDelegates; , 通过 ConfigureServices 方法将需要的Action加入进来。
UseSetting是一个用于设置Key-Value的方法, 一些常用的配置均会通过此方法写入_config中。
三、UseStartup()
CreateDefaultBuilder之后调用UseStartup(),指定Startup为启动类。
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)); }); } }); }
首先获取Startup类对应的AssemblyName, 调用UseSetting方法将其设置为WebHostDefaults.ApplicationKey(“applicationName”)的值。
然后调用WebHostBuilder的②ConfigureServices方法,将一个Action写入WebHostBuilder 的 configureServicesDelegates中。
这个Action的意思就是说,如果这个被指定的类startupType是一个实现了IStartup的类, 那么将其通过AddSingleton注册到services 这个ServiceCollection中, 如果不是, 那么将其“转换”成 ConventionBasedStartup 这个实现了 IStartup的类后再进行注册。这里涉及到一个StartupLoader的LoadMethods()方法,会通过字符串的方式查找“ConfigureServices”、“Configure{ environmentName}Services”这样的方法。
注意:这里只是将一个Action写入了configureServicesDelegates, 而不是已经执行了对IStartup的注册, 因为这个Action尚未执行,services也还不存在。就像菩萨对八戒说: 八戒(Startup)你先在高老庄等着吧, 将来有个和尚带领一个取经小分队(ServiceCollection services )过来的时候你加入他们。
其实在CreateDefaultBuilder方法中的几个UseXXX的方法也是这样通过ConfigureServices将对应的Action写入了configureServicesDelegates, 等待唐僧的到来。
四、WebHostBuilder.Build()
创建并配置好的WebHostBuilder开始通过Build方法创建WebHost了, 首先是BuildCommonServices,
1 private IServiceCollection BuildCommonServices(out AggregateException hostingStartupErrors) 2 { 3 //...省略... 4 var services = new ServiceCollection(); 5 services.AddSingleton(_hostingEnvironment); 6 services.AddSingleton(_context); 7 //....各种Add.... 9 foreach (var configureServices in _configureServicesDelegates) 10 { 11 configureServices(_context, services); 12 } 14 return services; 15 }
在这个方法里创建了ServiceCollection services(以唐僧为首的取经小分队), 然后通过各种Add方法注册了好多内容进去(收了悟空),然后③foreach 之前暂存在configureServicesDelegates中的各个Action,传入services逐一执行, 将之前需要注册的内容注册到services中, 这里就包括Startup(八戒),注意这里仅是进行了注册,而未执行Startup的方法。
处理好的这个services被BuildCommonServices返回后赋值给 hostingServices,然后 hostingServices经过Clone()生成 applicationServices,再由这个 applicationServices进行 GetProviderFromFactory(hostingServices)生成一个 IServiceProvider hostingServiceProvider.经过一系列的处理后,可以创建WebHost了。
var host = new WebHost( applicationServices, hostingServiceProvider, _options, _config, hostingStartupErrors); host.Initialize();
将生成的applicationServices 和 hostingServiceProvider作为参数传递给新生成的WebHost。接下来就是这个WebHost的 Initialize()。