简体   繁体   English

在 .NET Core 中使用不同的构造函数参数(DI 和非 DI)注册多个 IHostedService 实例

[英]Register multiple instances of IHostedService with different constructor parameters (DI and non-DI) in .NET Core

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).我有一个包含 4 个相同设备的环境,我必须连接到这些设备并通过 TCP 连接请求一些参数(每个设备都有其 IP 地址)。 I've implemented a class for a single device that needs some parameters (like IP address, port, polling intervals etc...)我已经为需要一些参数(如 IP 地址、端口、轮询间隔等)的单个设备实现了 class

The class implements BackgroundService interface and has a constructor like this one below: class 实现了BackgroundService接口,并具有如下构造函数:

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.在 class 内部有一个ExecuteAsync方法的实现,里面有一个while{}循环,它完成了所有的逻辑。

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)我想基于配置文件(例如,带有设备数组的配置文件)构建一个 Worker 服务来处理 class 的多个实例

In Program.cs I would do:在 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 
...

Without the RemoteDeviceConfig constructor parameter, the ILogger gets injected, but if I add it, I don't know how to inject my RemoteDeviceConfig class如果没有RemoteDeviceConfig构造函数参数,则会注入ILogger ,但如果我添加它,我不知道如何注入RemoteDeviceConfig class

What am I getting wrong?我怎么了?

I'm using .NET Core 3.0 with VS2019 and Worker Service Template project.我将 .NET Core 3.0 与 VS2019 和 Worker Service Template 项目一起使用。

UPDATE - ACCEPTED ANSWER更新 - 接受的答案

I've accepted @gldraphael answer , and I would add some details about the logging part specified in the answer's comments.我已经接受了@gldraphael answer ,并且我会添加一些有关答案评论中指定的日志记录部分的详细信息。

Problem: use ILogger implementation in classes instantiated in main Worker class.问题:在主 Worker class 中实例化的类中使用 ILogger 实现。

Solution: inject ILoggerFactory in Worker class and use it to create loggers for subclasses解决方案:在 Worker class 中注入 ILoggerFactory 并使用它为子类创建记录器

DeviceMainClass and DeviceConfig are respectively a generic device manager and its configuration. DeviceMainClassDeviceConfig分别是通用设备管理器及其配置。

Worker.cs工人.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 appsettings.json

{
  "Devices": [
    { "Name": "D1" },
    { "Name": "D2" },
    { "Name": "D3" },
    { "Name": "D4" }
  ]
}

CreateHostBuilder() : 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.如果您当前的RemoteDevice class 有 state 信息,请在 worker 中创建List<RemoteDevice>并从配置中初始化。

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.考虑使用任何成熟的 DI 容器,而不是默认的默认容器。 Say with Autofac it will look like: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)); 
        }

    }
}

Check for more details here and here . 在此处此处查看更多详细信息。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM