在 .NET Core 中使用不同的构造函数参数(DI 和非 DI)注册多个 IHostedService 实例
Register multiple instances of IHostedService with different constructor parameters (DI and non-DI) in .NET Core
我的环境有 4 个相同的设备,我必须连接到这些设备并通过 TCP 连接请求一些参数(每个设备都有其 IP 地址)。
我已经为需要一些参数(如 IP 地址、端口、轮询间隔等)的单个设备实现了 class
class 实现了 BackgroundService
接口并且有一个像下面这样的构造函数:
public RemoteDevice(RemoteDeviceConfig config, ILogger<RemoteDevice> logger)
在 class 中有一个 ExecuteAsync
方法的实现,里面有一个 while{}
循环,它完成了所有的逻辑。
我想构建一个 Worker Service 来处理 class 的多个实例,基于配置文件(例如,带有设备数组的配置文件)
在Program.cs我会做:
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseSystemd()
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<RemoteDevice>(); //First device
services.AddHostedService<RemoteDevice>(); //Second device
...
services.AddHostedService<RemoteDevice>(); //n-th device
});
...
//Methods to load configuration
...
没有 RemoteDeviceConfig
构造函数参数,ILogger
被注入,但是如果我添加它,我不知道如何注入我的 RemoteDeviceConfig
class
我哪里错了?
我将 .NET Core 3.0 与 VS2019 和 Worker 服务模板项目一起使用。
更新 - 已接受的答案
我已经接受 @gldraphael ,我会添加一些关于答案评论中指定的日志记录部分的详细信息。
问题:在主 Worker class.
中实例化的 classes 中使用 ILogger 实现
解决方案:在 Worker class 中注入 ILoggerFactory 并使用它为 subclasses
创建记录器
DeviceMainClass
和DeviceConfig
分别是通用设备管理器及其配置。
Worker.cs
List<DeviceMainClass> devices;
//Pass ILoggerFactory to worker constructor for Dependency Injection
//On worker start, instantiate all subclasses and create logger for each class
public Worker(IOptions<List<DeviceConfig>> options, ILogger<Worker> logger, ILoggerFactory loggerFactory)
{
devices = new List<DeviceMainClass>();
foreach(var device in _config)
{
devices.Add(new DeviceMainClass(_loggerFactory.CreateLogger<DeviceMainClass>(),...other parameters));
}
_logger = logger;
_loggerFactory = loggerFactory;
_config = options.Value;
}
...
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
//***for testing purposes only, repeats every second***
_logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
await Task.Delay(1000, stoppingToken);
}
}
DeviceMainClass
private readonly ILogger<DeviceMainClass> _logger;
//Use
public DeviceMainClass(ILogger<DeviceMainClass> logger, ...other parameters)
{
_logger = logger;
...
}
您想做这样的事情:
appsettings.json
{
"Devices": [
{ "Name": "D1" },
{ "Name": "D2" },
{ "Name": "D3" },
{ "Name": "D4" }
]
}
CreateHostBuilder()
:
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services.Configure<List<DeviceConfig>>(hostContext.Configuration.GetSection("Devices"));
services.AddHostedService<Worker>();
});
工人:
public class Worker : BackgroundService
{
private readonly ILogger<Worker> _logger;
private readonly List<DeviceConfig> config;
public Worker(IOptions<List<DeviceConfig>> options, ILogger<Worker> logger)
{
config = options.Value;
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
foreach(var device in config)
{
// TODO: Process each device here
_logger.LogInformation("Processing device {name}", device.Name);
}
await Task.Delay(1000, stoppingToken);
}
}
}
如果您当前的 RemoteDevice
class 有状态信息,请在 worker 中创建一个 List<RemoteDevice>
并从配置中初始化。
因为我最近遇到了类似的问题 - 我遇到了这个老问题。
考虑使用任何成熟的 DI 容器而不是默认的默认容器。
说 Autofac 它看起来像:
public class Program {
public static void Main(string[] args) {
Host.CreateDefaultBuilder(args)
.UseServiceProviderFactory(new AutofacServiceProviderFactory())
.ConfigureContainer<ContainerBuilder>(builder => { builder.RegisterType<DeviceMainClass>(); })
.ConfigureServices(services => { services.AddHostedService<Worker>(); })
//...
.Build()
.Run();
}
}
public class DeviceConfig {
//...
}
public class DeviceMainClass {
private readonly ILogger<DeviceMainClass> _logger;
private readonly DeviceConfig _deviceConfig;
public DeviceMainClass(ILogger<DeviceMainClass> logger, DeviceConfig deviceConfig) {
_logger = logger;
_deviceConfig = deviceConfig;
}
}
public class Worker : BackgroundService {
List<DeviceMainClass> devices;
private readonly ILogger<Worker> _logger;
private readonly List<DeviceConfig> _config;
public Worker(IOptions<List<DeviceConfig>> options, Func<DeviceConfig, DeviceMainClass> deviceFactory, ILogger<Worker> logger) {
_logger = logger;
_config = options.Value;
devices = new List<DeviceMainClass>();
foreach (var deviceConfig in _config) {
//autofac automatically adds appropriate logger when calling DeviceMainClass constructor
devices.Add(deviceFactory(deviceConfig));
}
}
}
我的环境有 4 个相同的设备,我必须连接到这些设备并通过 TCP 连接请求一些参数(每个设备都有其 IP 地址)。 我已经为需要一些参数(如 IP 地址、端口、轮询间隔等)的单个设备实现了 class
class 实现了 BackgroundService
接口并且有一个像下面这样的构造函数:
public RemoteDevice(RemoteDeviceConfig config, ILogger<RemoteDevice> logger)
在 class 中有一个 ExecuteAsync
方法的实现,里面有一个 while{}
循环,它完成了所有的逻辑。
我想构建一个 Worker Service 来处理 class 的多个实例,基于配置文件(例如,带有设备数组的配置文件)
在Program.cs我会做:
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseSystemd()
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<RemoteDevice>(); //First device
services.AddHostedService<RemoteDevice>(); //Second device
...
services.AddHostedService<RemoteDevice>(); //n-th device
});
...
//Methods to load configuration
...
没有 RemoteDeviceConfig
构造函数参数,ILogger
被注入,但是如果我添加它,我不知道如何注入我的 RemoteDeviceConfig
class
我哪里错了?
我将 .NET Core 3.0 与 VS2019 和 Worker 服务模板项目一起使用。
更新 - 已接受的答案
我已经接受 @gldraphael
问题:在主 Worker class.
中实例化的 classes 中使用 ILogger 实现解决方案:在 Worker class 中注入 ILoggerFactory 并使用它为 subclasses
创建记录器DeviceMainClass
和DeviceConfig
分别是通用设备管理器及其配置。
Worker.cs
List<DeviceMainClass> devices;
//Pass ILoggerFactory to worker constructor for Dependency Injection
//On worker start, instantiate all subclasses and create logger for each class
public Worker(IOptions<List<DeviceConfig>> options, ILogger<Worker> logger, ILoggerFactory loggerFactory)
{
devices = new List<DeviceMainClass>();
foreach(var device in _config)
{
devices.Add(new DeviceMainClass(_loggerFactory.CreateLogger<DeviceMainClass>(),...other parameters));
}
_logger = logger;
_loggerFactory = loggerFactory;
_config = options.Value;
}
...
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
//***for testing purposes only, repeats every second***
_logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
await Task.Delay(1000, stoppingToken);
}
}
DeviceMainClass
private readonly ILogger<DeviceMainClass> _logger;
//Use
public DeviceMainClass(ILogger<DeviceMainClass> logger, ...other parameters)
{
_logger = logger;
...
}
您想做这样的事情:
appsettings.json
{
"Devices": [
{ "Name": "D1" },
{ "Name": "D2" },
{ "Name": "D3" },
{ "Name": "D4" }
]
}
CreateHostBuilder()
:
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services.Configure<List<DeviceConfig>>(hostContext.Configuration.GetSection("Devices"));
services.AddHostedService<Worker>();
});
工人:
public class Worker : BackgroundService
{
private readonly ILogger<Worker> _logger;
private readonly List<DeviceConfig> config;
public Worker(IOptions<List<DeviceConfig>> options, ILogger<Worker> logger)
{
config = options.Value;
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
foreach(var device in config)
{
// TODO: Process each device here
_logger.LogInformation("Processing device {name}", device.Name);
}
await Task.Delay(1000, stoppingToken);
}
}
}
如果您当前的 RemoteDevice
class 有状态信息,请在 worker 中创建一个 List<RemoteDevice>
并从配置中初始化。
因为我最近遇到了类似的问题 - 我遇到了这个老问题。
考虑使用任何成熟的 DI 容器而不是默认的默认容器。 说 Autofac 它看起来像:
public class Program {
public static void Main(string[] args) {
Host.CreateDefaultBuilder(args)
.UseServiceProviderFactory(new AutofacServiceProviderFactory())
.ConfigureContainer<ContainerBuilder>(builder => { builder.RegisterType<DeviceMainClass>(); })
.ConfigureServices(services => { services.AddHostedService<Worker>(); })
//...
.Build()
.Run();
}
}
public class DeviceConfig {
//...
}
public class DeviceMainClass {
private readonly ILogger<DeviceMainClass> _logger;
private readonly DeviceConfig _deviceConfig;
public DeviceMainClass(ILogger<DeviceMainClass> logger, DeviceConfig deviceConfig) {
_logger = logger;
_deviceConfig = deviceConfig;
}
}
public class Worker : BackgroundService {
List<DeviceMainClass> devices;
private readonly ILogger<Worker> _logger;
private readonly List<DeviceConfig> _config;
public Worker(IOptions<List<DeviceConfig>> options, Func<DeviceConfig, DeviceMainClass> deviceFactory, ILogger<Worker> logger) {
_logger = logger;
_config = options.Value;
devices = new List<DeviceMainClass>();
foreach (var deviceConfig in _config) {
//autofac automatically adds appropriate logger when calling DeviceMainClass constructor
devices.Add(deviceFactory(deviceConfig));
}
}
}