浅谈 asp.net core 程序启动的时候做了什么
本篇已收录至 asp.net core 随笔系列
通过阅读本文, 希望能够对以下问题有一些思路:
- ASP.Net Core web 应用程序的启动方式?
- 程序如果是通过命令行启动的, 可以添加 args 参数, 这些参数是如何传递到底层的?
- Host 是做什么用的?
- 在自动生成的 code 中可以看到 appSettings.json 以及 appSettings.Development.Json, 我们知道是对不同的模式的 settings. 那么底层是怎么实现读取的?
- 相应的, 不同的环境mode, 的log的配置也是不一样的. 底层是如何做到区分的? 默认 asp.net core 3.0 有哪些提供的 logger?
- asp.net core web server 使用的是什么? 什么时候配置的?
- 有哪些中间件是在 web Host 构建时就配置了?
- Startup.cs 文件的使用原理?
- 底层的IOC的容器是如何初始化的
程序启动的入口
使用vs创建 asp.net core(以下简称anc)的web项目里面有个 program.cs 文件. 文件内容很简单:
anc web app 项目实际是一个 console app, 所以有 main 函数, 程序的入口就是 main 函数
main 函数就做了一些"简单微小"的工作:
- 创建HostBuilder
- 'HostBuilder'在真正'Build'出Host之前还做了什么?
- HostBuilder执行Build()将Host构建出来
- IHostBuilder.Build()
- Host.Run()
构建 HostBuilder
这个小节主要是围绕这段代码展开讨论:
Host.CreateDefaultBuilder(args)
Host 是一个静态类, 用其 CreateDefaultBuilder
方法可以创建一个 HostBuilder 实例 (IHostBuilder).
3.0 以前是 WebHostBuilder, 3.0 以后给原来的 WebHostBuilder 外面包裹了一层就叫 HostBuilder. 可能微软希望将 Host 这个概念抽象出来, 以后可能不止是 web host, 还有 XX host.
CreateDefaultBuilder(), 微软给提供的说明中写道: Initializes a new instance of the Microsoft.Extensions.Hosting.HostBuilder class with pre-configured defaults.
, 这里需要了解一下配置的方式, 因为在 Server 真正启动之前的所有的操作其实都是在各种配置与嵌套配置当中徘徊. 下图是方法的底层源码大致的内容:
方法的返回值是 HostBuilder, 在返回之前进行了一系列的配置, 咱们单独拿出一个举例, 比如 ConfigureHostConfiguration, 看名字可以知道是配置Host的一些Configuration的:
参数是委托类型的实例, 代码内部将委托实例添加到一个泛型的类型也是委托类型的集合当中:
_configureHostConfigActions 这个字段是 HostBuilder 的实例的众多字段的一个, 有朋友要问, 都放到集合里, 啥时候执行这个委托里面的代码真正的进行配置呢? 这个问题先放在这, 先看一下除了这个集合, HostBuilder中还有什么集合:
这些集合都是通过 builder 调用不同的 Configure 方法, 或者其他的方法, 将想要配置的委托实例作为参数传递, 再添加到集合当中的. 然后我们回答上面提出的问题, 看看是什么时候将这些集合中的委托实例取出执行每个委托进来的代码的:
集合的委托实例遍历执行后, 得到的 configBuilder 再次 Build 后赋值给 _hostConfiguration. 同样也是 HostBuilder 类中的一个字段, 类似的字段还有好几个如图:
我们分别介绍这几个字段的初始化方式以及对应的作用.
-
_hostingEnvironment 与 _hostConfiguration 会被用于初始化 _hostBuilderContext:
-
_hostBuilderContext 的实例最后会被添加到 anc 内置的容器中, 开发者可以随时获取到这个实例, 进而获取各项配置.
-
HostingEnvironment 也是同理, 最后会被添加到容器中.
-
_appServices 就是我们熟知的容器了. 创建容器也是 HostBuilder.Build() 方法中最后执行的一步:
这种先添加到缓存再进行配置的意义在于, 对于我们开发人员, 可以使用HostBuilder在真正调用Build()以前, 调用想用的任何配置对其进行配置, 即使配置重复了, 因为是向集合中添加委托实例, 最后执行代码的时候, 相同的配置项, 后加入的也会覆盖前面的.
总的说来 CreateDefaultBuilder() 创建 HostBuilder 做了这么几件事儿:
- 设置 IHostEnvironment.ContentRootPath
- 从环境变量中获取前缀为
DOTNET_
的配置项配置 IConfiguration - 从命令行参数 args 中获取配置项配置 IConfiguration
- 从配置文件 'appsettings.json'以及'appsettings.[Microsoft.Extensions.Hosting.IHostEnvironment.EnvironmentName].json'中获取配置项配置 IConfiguration
- 配置 log provider 默认为 console, debug,以及 event source output
- ... 省略我不会的
HostBuilder在真正Build出Host之前还做了什么?
没错, 在上一小节, 其实我们已经创建出 HostBuilder, 还扩展透露了一些 HostBuilder 调用 Build() 做的事情. 但是 anc 框架自动为我们生成的代码中不是马上就执行了Build方法, 而是继续进行额外的配置, 这个配置又起到了什么作用呢? 不配置的话, anc的项目还能够正确运行吗? 带着这个疑问, 继续阅读吧.
微软对于这个方法的说明已经解释了为什么一定要继续配置一下这个WebHostBuilder, 因为在这里才是真正加载内置的Web server. 而且能够跨平台也是因为有这个web server.
下面是这个小节的重点 !!! 接下来是详细解析这张图的环节
-
第一步, HostBuilder 调用 ConfigureWebHost.
-
第二步, ConfigureWebHost 方法内, 首先初始化一个 GenericWebHostBuilder (IWebHostBuilder), 初始化这个 web host builder 很重要. 因为webhost是真正拥有startupLoader的类.
-
第三步, 将第二步初始化出的 webHostBuilder 传入委托方法中执行委托代码.
-
第四步, WebHost.ConfigureWebDefaults(webHostBuilder), 其实就是配置 Kestrel 作为 web server等等
-
第五步, 继续将 webHostBuilder 传入委托方法中执行委托代码, 此时的委托代码为 Program.cs 文件中传进来的委托代码.
-
最后是将 GenericWebHostService 的实例添加到容器中. 然后返回经过配置了WebHostBuilder 的 HostBuilder
builder.ConfigureServices((context, services) => services.AddHostedService<GenericWebHostService>());
IHostBuilder.Build();
这部分没什么好说的, 就是依次执行之前配置了那么多的委托代码, 上文中我们已经解析过. 最后Build完返回的是
return _appServices.GetRequiredService<IHost>();
从 service collection 的容器内获取 IHost 对应的实例返回.
这个实例是在CreateServiceProvider时添加进去的:
services.AddSingleton<IHost, Internal.Host>();
Host.Run()
值得注意的是这里有段代码是将 webHost 启动:
_hostedServices = Services.GetService<IEnumerable<IHostedService>>();
foreach (var hostedService in _hostedServices)
{
// Fire IHostedService.Start
await hostedService.StartAsync(combinedCancellationToken).ConfigureAwait(false);
}
这里的最终目的是将 web server 启动. 最终能够接收 http request 和 返回 http response.
总结
asp.net core web 启动过程涉及到的底层部分主要分为几个步骤:
创建 HostBuilder > 配置 HostBuilder > 配置 webHostBuilder > 创建 webHost > 开始 build host > host 启动. 我画了一张流程图, 方便记忆: