为什么我们可以在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<action<webhostbuildercontext, iservicecollection>> _configureservicesdelegates; , 通过 configureservices 方法将需要的action加入进来。
usesetting是一个用于设置key-value的方法, 一些常用的配置均会通过此方法写入_config中。
三、usestartup<startup>()
createdefaultbuilder之后调用usestartup<startup>(),指定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()。
五、webhost.initialize()
webhost的 initialize()的主要工作就是buildapplication()。
ensureapplicationservices(): 用来处理webhost的 private iserviceprovider _applicationservices ,④startup的configureservices方法在这里被调用。
_startup = _hostingserviceprovider.getrequiredservice<istartup>();
_applicationservices = _startup.configureservices(_applicationservicecollection);
通过 getrequiredservice<istartup>() 获取到我们的_startup, 然后调用这个_startup的 ⑤configureservices 方法,这就是我们用于依赖注入的startup类的configureservices方法了。
所以,_applicationservices是根据_applicationservicecollection 加上我们在_startup中注册的内容之后重新生成的 iserviceprovider。
ensureserver()⑥:通过 getrequiredservice<iserver>()获取server并配置监听地址。
var builderfactory = _applicationservices.getrequiredservice<iapplicationbuilderfactory>(); var builder = builderfactory.createbuilder(server.features); builder.applicationservices = _applicationservices;
获取到 iapplicationbuilderfactory并通过它⑦创建 iapplicationbuilder,并将上面创建的_applicationservices赋值给它的applicationservices,它还有个重要的集合_components
private readonly ilist<func<requestdelegate, requestdelegate>> _components = new list<func<requestdelegate, requestdelegate>>();
从_components的类型可以看出它其实是中间件的集合,是该调用我们的_startup的configure方法的时候了。
先获取定义的istartupfilter, ⑧foreach这些istartupfilter并与_startup的configure方法一起将配置的中间件写入_components,然后通过 build()创建requestdelegate _application,
在build()中对_components进行处理生成请求处理管道,关于istartupfilter和生成管道这部分将在下篇文章进行详细说明。
六、webhost.run()
webhost创建完毕, 最后一步就是run起来了,webhost的run()会调用它的方法startasync()
public virtual async task startasync(cancellationtoken cancellationtoken = default(cancellationtoken)) { //......var hostingapp = new hostingapplication(_application, _logger, diagnosticsource, httpcontextfactory); await server.startasync(hostingapp, cancellationtoken).configureawait(false); _hostedserviceexecutor.startasync(cancellationtoken).configureawait(false); //..... }
在之前的文章中我们知道,请求是经过 server监听=>处理成httpcontext=>application处理,所以这里首先传入上面创建的_application和一个httpcontextfactory来⑨生成一个hostingapplication,并将这个hostingapplication传入server的startasync(), 当server监听到请求之后, 后面的工作由hostingapplication来完成。
⑩hostedserviceexecutor.startasync()方法用来开启一个后台运行的服务,一些需要后台运行的操作比如定期刷新缓存等可以放到这里来。
七、更新
感谢dudu的留言,去github上看了一下webhost的最新源码,buildapplication()不再包含ensureapplicationservices()的调用,并且转移到了webhost.startasync() 中进行; webhost.initialize() 中由原本调用buildapplication()改为调用原本放在buildapplication()中调用的ensureapplicationservices()。
通过vs加载符号的方式调试获取到的webhost仍是原来的版本,即使删除下载的文件后再次重新获取也一样, 应该是和新建项目默认引用的依赖版本有关。