如何使用 .NET 内置 DI 容器配置依赖注入 "many levels down"

How to configure dependency injection "many levels down" using .NET built-in DI Container

我有一个控制台 .NET 核心应用程序,它使用 Microsoft.Extensions.DependencyInjection 库作为依赖注入框架。

我想使用这个框架来注入一个“向下”两层的依赖,而不必在中间层冗余地提到这个依赖。我该怎么做?

目前,我可以注入依赖项的唯一方法是将它向下传递,直到需要它为止。这是我的独立控制台应用程序,它演示了要求。它是一个简单的程序,可以根据示例资产和负债金额计算一个人的净资产。 (它实际上只是减去这两个数额)。

Program.cs 文件包含程序 Main 入口点并注册依赖项。

Program.cs:

public class Program
{
    private static IServiceProvider _serviceProvider;
    public static void Main(string[] args)
    {
        RegisterServices();
        IServiceScope scope = _serviceProvider.CreateScope();
        scope.ServiceProvider.GetRequiredService<ConsoleApplication>().Run();
        DisposeServices();
    }

    private static void RegisterServices()
    {
        var services = new ServiceCollection();
        services.AddSingleton<ICalculator, Calculator>();
        services.AddSingleton<ConsoleApplication>();
        _serviceProvider = services.BuildServiceProvider(true);
    }

    private static void DisposeServices()
    {
        if (_serviceProvider == null)
        {
            return;
        }
        if (_serviceProvider is IDisposable)
        {
            ((IDisposable)_serviceProvider).Dispose();
        }
    }
}

设置依赖项注入后,Program.cs 运行 ConsoleApplication.cs Run 方法。

ConsoleApplication.cs:

internal class ConsoleApplication
{
    private readonly ICalculator _calculator;
    public ConsoleApplication(ICalculator calculator)
    {
        _calculator = calculator;
    }

    public void Run()
    {
        Person person = new Person(_calculator)
        {
            Assets = 1000m,
            Liabilities = 300m
        };
        Console.WriteLine(person.GetNetWorth());
    }
}

上面的代码实例化了一个例子Person并调用了GetNetWorth方法。此人 class 如下所示。

Person.cs:

public class Person
{
    private readonly ICalculator _calculator;

    public Person(ICalculator calculator)
    {
        _calculator = calculator;
    }

    public decimal Assets {get; set;}
    public decimal Liabilities {get; set;}

    public decimal GetNetWorth()
    {
        decimal netWorth = _calculator.Subtract(Assets, Liabilities);
        return netWorth;
    }
}

Person class 对 Calculator 有依赖关系,如下所示:

Calculator.cs:

public interface ICalculator
{
    decimal Add(decimal num1, decimal num2);
    decimal Subtract(decimal num1, decimal num2);
    decimal Multiply(decimal num1, decimal num2);
    decimal Divide(decimal num1, decimal num2);
}

public class Calculator : ICalculator
{
    public decimal Add(decimal num1, decimal num2) => num1 + num2;
    public decimal Subtract(decimal num1, decimal num2) => num1 - num2;
    public decimal Multiply(decimal num1, decimal num2) => num1 * num2;
    public decimal Divide(decimal num1, decimal num2) => num1 / num2;
}

鉴于上面的程序,您可以看到这里的问题是 class ConsoleApplication.cs 有这行代码:

private readonly ICalculator _calculator;
public ConsoleApplication(ICalculator calculator)
{
    _calculator = calculator;
}

该代码是多余的,应该避免,因为 ConsoleApplication.cs 没有 这种依赖性,因此不应该知道任何关于它的信息。我被迫包含它的唯一原因是将它传递到下一个级别 Person 需要依赖项。

对于上面的例子,我如何调整program.cs以避免传递依赖?我有一种感觉,我使用的 Microsoft.Extensions.DependencyInjection 框架完全错误。使用 DI 容器的全部意义在于规避这个问题。我也可能根本没有使用 DI 容器,代码会更简单。

我已经阅读了许多询问类似问题的 SO 帖子。但是,它们不适合我的控制台应用程序、.NET 和使用 Microsoft.Extensions.DependencyInjection; DI 框架的特定情况。

这是一个设计问题。 Person class 根据 运行 时间数据显示 SRP(单一职责原则)/SOC(关注点分离)违规和 DI 代码味道。 (基于提供的简化示例)。

Injecting runtime data into your application components is a code smell. Runtime data should flow through the method calls of already-constructed object graphs.

引用Dependency Injection Code Smell: Injecting runtime data into components

注意这个 class 是如何用 run-time 数据构造的。

public class Person {
    private readonly ICalculator _calculator;

    public Person(ICalculator calculator) {
        _calculator = calculator;
    }

    public decimal Assets {get; set;} //<--Run-time data
    public decimal Liabilities {get; set;} //<--Run-time data

    public decimal GetNetWorth() {
        decimal netWorth = _calculator.Subtract(Assets, Liabilities);
        return netWorth;
    }
}

创建一个单独的服务,其职责是在 run-time 找到一个人并计算他们的净资产

//Run-time data
public class Person {        
    public decimal Assets {get; set;}
    public decimal Liabilities {get; set;}
}

//Service abstraction
public interface IPersonNetWorthService {
    decimal GetNetWorth(Person person);
}

//Service implementation
public class PersonNetWorthService : IPersonNetWorthService {
    private readonly ICalculator _calculator;

    public PersonNetWorthService(ICalculator calculator) {
        _calculator = calculator;
    }

    public decimal GetNetWorth(Person person) {
        decimal netWorth = _calculator.Subtract(person.Assets, person.Liabilities);
        return netWorth;
    }
}

控制台应用程序现在可以干净地执行其功能,没有任何违规和代码味道

internal class ConsoleApplication
{
    private readonly IPersonNetWorthService calculator;
    public ConsoleApplication(IPersonNetWorthService calculator) {
        this.calculator = calculator;
    }

    public void Run() {
        Person person = new Person() {
            Assets = 1000m,
            Liabilities = 300m
        };
        Console.WriteLine(calculator.GetNetWorth(person));
    }
}

记得注册所有依赖项

    private static void RegisterServices() {
        IServiceCollection services = new ServiceCollection();
        services.AddSingleton<IPersonNetWorthService, PersonNetWorthService>();
        services.AddSingleton<ICalculator, Calculator>();
        services.AddSingleton<ConsoleApplication>();
        _serviceProvider = services.BuildServiceProvider(true);
    }