.NET Worker 服务中的构造函数注入

问题描述

我花了几个月的时间用 ASP.NET Core MVC 编写代码。现在我正在尝试工作服务,但遇到了依赖注入的一些问题。

我可以在 Program.cs 中注册一个服务并在 Worker.cs 中使用它(见下文)。当您在类结构的深处需要服务时,我的问题就出现了:

  • 在 Worker.cs 中创建对象 1
    • 对象 1 将对象 2 作为属性
      • 对象 2 需要实例化服务

当然,我可以将服务向下传递到整个层次结构,但在这种情况下,我看不到服务的价值。 似乎我只能在工作服务级别使用依赖注入。在 MVC 中,您可以简单地注册一个服务并将其作为参数传递给控制器​​的构造函数。这里的优点是您不必像 Worker.cs 级别那样显式调用构造函数。 我在尝试访问连接字符串和设置时遇到了这个问题,但我想这同样适用于记录器。

Program.cs

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .UseWindowsService()
        .ConfigureServices((hostContext,services) =>
        {
            services.AddHostedService<Worker>();
            services.AddTransient<ITestService,TestService>();
        });

Worker.cs

public class Worker : BackgroundService
{
    private readonly ILogger<Worker> _logger;
    public readonly ITestService TestServ;

    public Worker(ILogger<Worker> logger,ITestService test)
    {
        _logger = logger;
        TestServ = test;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            //this works
            _logger.Log@R_192_4045@ion("TestService: " + TestServ.GetData());
            
            //is this necessary?
            TestClassServ test = new TestClassServ(TestServ);
            
            _logger.Log@R_192_4045@ion("TestService: " + test.GetData());

            await Task.Delay(1000,stoppingToken);
        }
    }
}

课程和服务

public interface ITestService
{
    string GetData();
}

public class TestService : ITestService
{
    public const string teststring = "service";

    public string GetData()
    {
        return teststring;
    }
}

public class TestClassServ
{
    public ITestService TestServ { get; set; }

    public TestClassServ(ITestService testServ)
    {
        TestServ = testServ;
    }

    public string GetData()
    {
        return TestServ.GetData();
    }
}

经过数周的谷歌搜索和尝试,我决定向您寻求帮助:

  1. 是否有可能在较低级别的类层次结构中注入服务而不将其从顶部向下传递?
  2. 你会怎么做?
  3. 我的问题是否源于程序的架构?

解决方法

似乎我只能在工作服务级别使用依赖注入。

该框架当然能够创建包含深层对象图的工作类,因此当然可以注入许多级别深度的依赖项;即使在使用工人类时也是如此。

但是这里有一个问题,即 Worker 类只被框架解析一次,基本上使它们成为单例。这意味着,即使您可以向它们注入依赖项,其直接和间接依赖项也不应该是 Scoped 依赖项。

我认为这就是您正在努力解决的问题,因为在大多数情况下,工作人员要做任何有用的事情,都需要与数据库对话。这通常意味着使用实体框架,并且其 DbContext 始终是 Scoped。将它注入到单例中是 a really bad ideaDbContext 变成 Captive Dependency

这意味着,您必须在应用程序的生命周期内从容器中解析它们,而不是将依赖项注入到工作程序的构造函数中。这将 Worker 与 DI Container 耦合,但当您将 Worker 设为 Composition Root 的一部分时,这不是问题。

不过,这确实需要您在解析服务之前创建一个新范围。举个例子:

public class Worker : BackgroundService
{
    // NOTE: Logger can be safely injected; loggers are always singletons
    private readonly ILogger<Worker> _logger;
    private readonly IServiceProvider _provider;

    public Worker(ILogger<Worker> logger,IServiceProvider provider)
    {
        _logger = logger;
        _provider = provider;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            await using (var scope = _provider.CreateScope())
            {
                var service = scope.GetRequiredService<ITestService>();
                service.GoDoSomethingUseful();
            }
        
            await Task.Delay(1000,stoppingToken);
        }
    }
}

我的问题是否源于程序的架构?

不,不像您的代码示例所示。只要您保持对 DI 容器(或其 IServiceProvider 抽象)内部的访问权限,您就可以了。