本文章将介绍 .NET Core 中 Host 的本质和一些特性并使用 Code 帮助理解
先决条件
.NET 8.0 SDK
介绍
ASP.NET Core 应用程序本质上是一个服务,这个服务启动了一个网络监听器,当监听器监听到 Http 请求以后,将请求传递给应用的管道进行处理,完成处理后生成 Http 响应
长时间运行的服务需要寄宿在托管进程中,提供了这个功能的系统称为 Host,Host 实现的功能将一个或多个长时间运行的服务给寄宿的托管进程中,并由 Host 来管理长时间运行的服务,这些服务被称为托管服务
任何需要在后台长时间运行的程序,我们都可以按照标准把他定义为托管服务将其寄宿在 Host 上面
将托管服务寄宿在 Host 上
托管服务需要实现 IHostedService,当程序启动时 Host 会自动找到所有注册的托管服务并将他们启动
需要引入相关的 NuGet 包: dotnet add package Microsoft.Extensions.Hosting -v 8.0.0
这里定义一个 SystemClock,用来打印当前时间
using Microsoft.Extensions.Hosting;
public class SystemClock : IHostedService
{
private Timer? _timer = null;
public Task StartAsync(CancellationToken cancellationToken)
{
_timer = new Timer(state =>
{
Console.WriteLine($"Current Time:{DateTime.Now:yyyy-MM-dd HH:mm:ss}");
}, null, 0, 1000);
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
_timer?.Dispose();
return Task.CompletedTask;
}
}
复制代码
然后实例化一个 HostBuilder(主机构建器),并使用 ConfigureServices 方法给 HostBuilder 配置服务,
HostBuilder 整合了依赖注入框架,这里的 collection 参数是 IServiceCollection(服务注册对象)
托管服务的容器最终也是依赖注入框架来提供的,托管服务自身所托管的服务也可以注册到依赖注入框架中
然后在服务注册对象中注册 SystemClock
最后通过 HostBuilder 创建 Host,并调用 Run 方法运行 Host,这时托管服务也会被启动
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
IHostBuilder hostBuilder = new HostBuilder()
.ConfigureServices(collection =>
{
collection.AddHostedService<SystemClock>();
// 下面注释的代码和上一行代码效果相同
// collection.AddSingleton<IHostedService, SystemClock>();
});
IHost host = hostBuilder.Build();
host.Run();
复制代码
在服务注册对象中注册服务
添加一个时间服务类
public class DateTimeService
{
public string GetDateTimeNow() => $"{DateTime.Now:yyyy-MM-dd HH:mm:ss}";
}
复制代码
然后将其注册到服务注册对象中
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
IHostBuilder hostBuilder = new HostBuilder()
.ConfigureServices(collection =>
{
collection.AddHostedService<SystemClock>();
// 下面注释的代码和上一行代码效果相同
// collection.AddSingleton<IHostedService, SystemClock>();
collection.AddSingleton<DateTimeService>();
});
IHost host = hostBuilder.Build();
host.Run();
复制代码
然后将 DateTimeService 通过 SystemClock 的构造函数注入到 SystemClock 对象中,
并更改打印时间的代码
using Microsoft.Extensions.Hosting;
public class SystemClock(DateTimeService dateTimeService) : IHostedService
{
private readonly DateTimeService _dateTimeService = dateTimeService;
private Timer? _timer = null;
public Task StartAsync(CancellationToken cancellationToken)
{
_timer = new Timer(state =>
{
Console.WriteLine($"Current Time:{_dateTimeService.GetDateTimeNow()}");
}, null, 0, 1000);
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
_timer?.Dispose();
return Task.CompletedTask;
}
}
复制代码
使用配置
在 Host 中有两种注册配置的函数,ConfigureHostConfiguration 和 ConfigureAppConfiguration,其中 ConfigureHostConfiguration 在 ConfigureAppConfiguration 之前执行
它们之间的关系是 ConfigureHostConfiguration 会初始化 IHostingEnvironment,然后 ConfigureAppConfiguration 的上下中能够拿到初始化过的配置,最终的配置是两个配置方法配置之和,当然,后者将会覆盖前者的相同配置
例如:在 ASP.NET Core 应用程序中,我们可以在系统的环境变量配置 ENVIRONMENT=Development, 然后在项目文件 ~\Properties\launchSettings.json 中配置 "ASPNETCORE_ENVIRONMENT": "Production",最后我们可以看到后者将前者值进行了覆盖,然后我们又可以通过我们设置的环境变量参数来加载争对不同环境的配置
在项目中增加四个 Json 文件,参数 Interval 配置打印的时间间隔
#region launchSettings.json 文件的内容
{ "ENVIRONMENT": "Development" }
#endregion
#region appsettings.json 文件的内容
{ "Interval": 1000 }
#endregion
#region appsettings.Development.json 文件的内容
{ "Interval": 2000 }
#endregion
#region appsettings.Production.json 文件的内容
{ "Interval": 3000 }
#endregion
复制代码
然后我们修改主机构建器的配置
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
IHostBuilder hostBuilder = new HostBuilder()
.ConfigureHostConfiguration(builder => builder
.AddJsonFile("launchSettings.json")
)
.ConfigureAppConfiguration((context, builder) => builder
.AddJsonFile("appsettings.json")
.AddJsonFile($"appsettings.{context.HostingEnvironment.EnvironmentName}.json")
// 下面注释的代码和上一行代码效果相同
// .AddJsonFile($"appsettings.{context.Configuration["ENVIRONMENT"]}.json")
)
.ConfigureServices(collection =>
{
collection.AddHostedService<SystemClock>();
// 下面注释的代码和上一行代码效果相同
// collection.AddSingleton<IHostedService, SystemClock>();
collection.AddSingleton<DateTimeService>();
});
复制代码
在上面代码中,使用 ConfigureHostConfiguration 方法添加当前环境变量为 Development,然后通过 ConfigureAppConfiguration 重载方法(这个重载的 context 可以拿到前面的配置)加载不同环境对应的配置
使用选项服务
增加一个配置对象的类
public class AppSettings
{
public string? Environment { get; set; }
public int Interval { get; set; }
}
复制代码
在服务注册对象中配置选项服务
IHostBuilder hostBuilder = new HostBuilder()
.ConfigureHostConfiguration(builder => builder
.AddJsonFile("Properties/launchSettings.json")
)
.ConfigureAppConfiguration((context, builder) => builder
.AddJsonFile("appsettings.json")
.AddJsonFile($"appsettings.{context.HostingEnvironment.EnvironmentName}.json")
// 上面一句配置与下面一句效果相同
//.AddJsonFile($"appsettings.{context.Configuration["ENVIRONMENT"]}.json")
)
.ConfigureServices((context, collection) => collection
.AddHostedService<SystemClock>()
// 下面注释的代码和上一行代码效果相同
// .AddSingleton<IHostedService, SystemClock>();
.AddSingleton<DateTimeService>()
.AddOptions()
.Configure<AppSettings>(context.Configuration)
);
复制代码
然后通过将 IOptions 或者 IConfiguration 将其注入到 SystemClock 对象中设置打印时间的时间间隔
现在 SystemClock 的全部代码应该为
public class SystemClock(
DateTimeService dateTimeService,
IConfiguration configuration,
IOptions<AppSettings> options
) : IHostedService
{
private readonly DateTimeService _dateTimeService = dateTimeService;
private readonly IConfiguration _configuration = configuration;
private readonly AppSettings _appSettings = options.Value;
private Timer? _timer = null;
public Task StartAsync(CancellationToken cancellationToken)
{
var interval = _configuration["Interval"]; // 2000
var environment = _configuration["ENVIRONMENT"]; // Development
_timer = new Timer(state =>
{
Console.WriteLine($"Current Time:{_dateTimeService.GetDateTimeNow()}");
}, null, 0, _appSettings.Interval);
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
_timer?.Dispose();
return Task.CompletedTask;
}
}
复制代码
启动项目,我们可以看到打印时间的时间间隔为两秒
集成日志功能
使用日志功能需要添加 NuGet,我这里用的是 Microsoft.Extensions.Logging.Console
使用命令 dotnet add package Microsoft.Extensions.Logging.Console -v 8.0.0 进行安装
在 主机构建器 中使用 ConfigureLogging 配置日志,该方法也有重载,能够拿到上下文中的配置
修改代码
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
IHostBuilder hostBuilder = new HostBuilder()
.ConfigureHostConfiguration(builder => builder
.AddJsonFile("Properties/launchSettings.json")
)
.ConfigureAppConfiguration((context, builder) => builder
.AddJsonFile("appsettings.json")
.AddJsonFile($"appsettings.{context.HostingEnvironment.EnvironmentName}.json")
// 上面一句配置与下面一句效果相同
//.AddJsonFile($"appsettings.{context.Configuration["ENVIRONMENT"]}.json")
)
.ConfigureServices((context, collection) => collection
.AddHostedService<SystemClock>()
// 下面注释的代码和上一行代码效果相同
// .AddSingleton<IHostedService, SystemClock>();
.AddSingleton<DateTimeService>()
.AddOptions()
.Configure<AppSettings>(context.Configuration)
)
.ConfigureLogging(builder => builder.AddConsole());
复制代码
然后将对象 ILogger<SystemClock> _logger 注入到 SystemClock 中
最后修改打印时间的代码为
_timer = new Timer(state =>
{
_logger.LogInformation($"Current Time:{_dateTimeService.GetDateTimeNow()}");
}, null, 0, _appSettings.Interval);
复制代码
启动项目,即可看到打印的日志系统
简单使用 HostApplicationLifetime
下面是一个托管服务
该服务注入了 IHostApplicationLifetime 对象,在服务的构造函数中设置了应用程序启动或停止进行打印
在托管服务启动以后,两秒后调用 IHostApplicationLifetime 对象的 StopApplication 方法将程序关闭
public class TestService : IHostedService
{
private readonly IHostApplicationLifetime _lifetime;
public TestService(IHostApplicationLifetime lifetime)
{
_lifetime = lifetime;
_lifetime.ApplicationStarted.Register(() => Console.WriteLine($"{DateTimeOffset.Now} 应用程序已启动"));
_lifetime.ApplicationStopping.Register(() => Console.WriteLine($"{DateTimeOffset.Now} 开始停止应用程序"));
_lifetime.ApplicationStopped.Register(() => Console.WriteLine($"{DateTimeOffset.Now} 应用程序已停止"));
}
public Task StartAsync(CancellationToken cancellationToken)
{
var task = Task.Delay(2000).ContinueWith(t => _lifetime.StopApplication());
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}
复制代码
该实例我们看到,我们可以通过 IHostApplicationLifetime 的 StopApplication 来关闭应用程序,
并且还利用 IHostApplicationLifetime 提供的三个令牌对象,向 IHostApplicationLifetime 的不同生命周期事件中注册我们自己的方法,来做一些我们希望做得事情
本文总结
Host 有三个主要对象,托管服务(IHostedService)、主机对象(IHost)、主机构建器(IHostBuilder),IHost 启动以后,将会一直等待应用程序关闭的通知,IHostBuilder 则是 IHost 的构建器,IHost 上运行一个或多个 IHostedService
当 IHost 启动时(执行 IHost.StartAsync 方法),利用依赖注入框架获取到所有注册的 IHostedService ,并通过 IHostedService.StartAsync 方法来启动它们,当应用程序被关闭的时候,IHost 也会被关闭(执行 IHost.StopAsync 方法),被 IHost 启动的所有 IHostedService 也都会调用 IHostedService.StopAsync 方法来停止服务(IHostedService 列表将会被反转,最后 Start 的 IHostedService 将最先 Stop)
由于 IHost 集成了 .NET Core 的依赖注入框架,托管服务所需的依赖服务也包括了 IHost 自身所依赖的服务,也都由这个框架提供,托管服务注册的本质就是将对应的 IHostedService 注册到依赖注入框架中,由于托管服务需要长时间运行,所以一般托管服务采用单例注册
评论