环境:
- window 10
- .netcore 3.1
- vs2019 16.5.1
- dnspy v6.1.4
参照:
.netcore入门22:使用dnSpy调试asp.net core源码
asp.net core 系列 17 通用主机 IHostBuilder
官方文档:.NET 通用主机
一、什么是通用主机?
通用主机是微软抽象出来的一个资源容器,它里面包括了配置模型、日志框架、依赖容器等。它里面还包括一些主机服务(IHostedService:存放在依赖注入容器中),这样当通用主机启动或停止的时候实际上就是针对这些主机服务进行开启和停止操作。
二、为什么要有通用主机?
在.net core 2.x以前我们创建aspnetcore应用是使用:
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
}
但是在.net core 3.x的时候我们使用如下:
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
2.x的那种方式就把主机和web服务捆绑到了一起(其实通过名字IWebHost也能看的出来:主机即web服务),到了3.x微软就把主机(IHost)和web服务剥离开来:主机里面可以包含多个主机服务(IHostedService),而web服务只是其中可选的一个服务类型而已。它们的关系看下图:
此时,我们再来看一下3.x版本中生成的方法,其中Host.CreateDefaultBuilder()…Build().Run()都是针对通用主机的,而web服务是在.ConfigureWebHostDefaults()方法中注入的。
那么,为什么要将主机和web服务剥离开来呢?因为微软不想这么一个框架只能用在web服务上,其他的比如:定时服务,消息队列,tcp服务器等等,下面就看一个具体的定时服务示例:
三、测试非web服务的通用主机
新建一个空的web项目,将Program中的代码替换成如下:
public class Program
{
public static void Main(string[] args)
{
new HostBuilder()
.ConfigureServices(services =>
{
services.AddLogging();
services.AddSingleton<IHostedService, MyHostService>();
})
.ConfigureLogging(loggingBuilder =>
{
loggingBuilder.AddConsole();
})
.Build()
.Run();
Console.WriteLine("ok");
Console.ReadLine();
}
}
public class MyHostService : IHostedService
{
private readonly ILogger<MyHostService> logger;
public MyHostService(ILogger<MyHostService> logger)
{
this.logger = logger;
}
public Task StartAsync(CancellationToken cancellationToken)
{
return Task.Factory.StartNew(() =>
{
while (true)
{
if (cancellationToken.IsCancellationRequested) break;
logger.LogInformation("MyHostService is Comming...");
Thread.Sleep(5000);
}
});
}
public Task StopAsync(CancellationToken cancellationToken)
{
logger.LogInformation("MyHostService is stop!");
return Task.CompletedTask;
}
}
直接运行,看控制台输出:
可以看到,我们通过asp.net core 3.x的这种设计,成功的启动了一个定时服务。
四、通过源码查看通用主机的构建过程
注意: 这里说的是通用主机,暂不涉及到web服务(我们在asp.net core中使用的web服务是:GenericWebHostService : IHostedService
)。
4.1 代码执行过程说明
通用主机的使用流程大概如下:
- 创建“构建者对象”(通用主机的构建者对象:HostBuilder)
- 向“构建者对象”中注入构建逻辑
- 使用“构建者对象”建造“通用主机”(HostBuilder.Build())
- 运行“通用主机” (Host.Run())
- 对于控制台工程:Ctrl+C 通用主机结束运行,程序结束
通用主机的构建、启动流程图:
通过上面的图,我们可以看到:主机的构建主要是由HostBuilder主导的,那么我们先来看看HostBuilder
的源码:
public class HostBuilder : IHostBuilder
{
//属性堆
public IDictionary<object, object> Properties { get; } = new Dictionary<object, object>();
//主机配置
private IConfiguration _hostConfiguration;
private List<Action<IConfigurationBuilder>> _configureHostConfigActions = new List<Action<IConfigurationBuilder>>();
public IHostBuilder ConfigureHostConfiguration(Action<IConfigurationBuilder> configureDelegate){...}
//应用配置
private IConfiguration _appConfiguration;
private List<Action<HostBuilderContext, IConfigurationBuilder>> _configureAppConfigActions = new List<Action<HostBuilderContext, IConfigurationBuilder>>();
public IHostBuilder ConfigureAppConfiguration(Action<HostBuilderContext, IConfigurationBuilder> configureDelegate){...}
//依赖注入配置
private IServiceProvider _appServices;
private List<Action<HostBuilderContext, IServiceCollection>> _configureServicesActions = new List<Action<HostBuilderContext, IServiceCollection>>();
public IHostBuilder ConfigureServices(Action<HostBuilderContext, IServiceCollection> configureDelegate){...}
//主机构建上下文
private HostBuilderContext _hostBuilderContext;
//主机环境
private HostingEnvironment _hostingEnvironment;
......
}
上面是简化后的代码,上面的ConfigureHostConfiguration
以及其他的两个方法都只是将传进来的委托存放到了对应的集合上(_configureHostConfigActions)。
当程序启动的时候,代码通过HostBuilder的Config…方法将一系列的处理逻辑分别存储了起来,当进行Build()的时候,代码就会将HostBuilder存储的这些逻辑执行并从依赖容器中解析出注解对象Host
,最后启动这个Host(就是启动Host中的IHostedService)。
4.2 调试代码,查看执行过程
接下来使用dnSpy调试看一下源码:
从上图的Host.CreateDefaultBuilder(args)
调试进去,会看到下图所示:
上图中显示了HostBuidler的配置过程(配置了日志、依赖容器、json配置等),其实针对通用主机的配置就这么多。然后看一下,HostBuilder的Build方法:
调试进入Build方法后,我们看到下图所示:
从上面代码中我们可以看到,在Build主机的时候我们调用各种构建逻辑(包括日志、json配置等等,这些逻辑都是在配置的时候由我们或asp.net core框架注入进去的,其中包括将主机服务注入到依赖容器),然后创建出依赖容器,最后从容器中获取到了主机对象(Host)。
最后我们再看一下主机的启动过程:
调试进去后(多按几个F11),我们会看到下图所示:
从上图我们可以看到:主机启动过程就是将主机中的一系列主机服务启动了。
最后再来看一下HostBuilder的主要内容以及Build过程分析: