I have an environment with 4 identical devices that I have to connect to and request some parameters via TCP connection (each device with its IP address). I've implemented a class for a single device that needs some parameters (like IP address, port, polling intervals etc...)
The class implements BackgroundService
interface and has a constructor like this one below:
public RemoteDevice(RemoteDeviceConfig config, ILogger<RemoteDevice> logger)
Inside the class there is an implementation of the ExecuteAsync
method with a while{}
loop inside, that does all the logic.
I want to build a Worker Service that handles multiple instances of that class, based on a configuration file (eg. config file with an array of devices)
In Program.cs I would do:
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
...
Without the RemoteDeviceConfig
constructor parameter, the ILogger
gets injected, but if I add it, I don't know how to inject my RemoteDeviceConfig
class
What am I getting wrong?
I'm using .NET Core 3.0 with VS2019 and Worker Service Template project.
UPDATE - ACCEPTED ANSWER
I've accepted @gldraphael answer , and I would add some details about the logging part specified in the answer's comments.
Problem: use ILogger implementation in classes instantiated in main Worker class.
Solution: inject ILoggerFactory in Worker class and use it to create loggers for subclasses
DeviceMainClass
and DeviceConfig
are respectively a generic device manager and its configuration.
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;
...
}
You wanna do something like this:
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>();
});
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);
}
}
}
If your current RemoteDevice
class has state information, create a List<RemoteDevice>
in the worker and initialize from configuration.
As I have faced similar problem recently - I am bumping this old question.
Consider using any mature DI container instead of default of default one. Say with Autofac it will look like:
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));
}
}
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.