【问题标题】:.NET Core Dependency Injection how to handle multiple objects.NET Core 依赖注入如何处理多个对象
【发布时间】:2022-01-27 00:04:11
【问题描述】:

正如标题所说,我有一个 .NET Core 应用程序,我正在尝试将其转换为并利用内置的 Microsoft Dependency Injection。

我有一个对象和该对象的基类,称之为CommunicationBaseCommunicator。当我的应用启动并读取配置文件时,我可以有 N 个对象来实例化。

以前,在切换到依赖注入之前,在我的启动例程中读取配置文件的某个位置,我将拥有一个 List<CommunicationBase> 变量,我将实例化并添加 Communicator 对象,同时设置一些基本属性,根据我的配置中的数量和配置中的每个属性而改变。

我将如何使用 DI 实现这一目标?

我知道在我的服务中,我会注册类型,以便可以将其注入到其他类构造函数中。

例如,services.AddTransient<CommunicationBase, Communicator>();,但据我了解,这只是向 DI 注册类型。我可以将它注入到一个类中并拥有其中一个的随机实例。

然后我将如何拥有 N 个实例并能够在创建实例时设置每个实例的属性?

或者,在这种情况下,DI 是没有必要或不起作用的,我需要像以前那样做吗?

谢谢!

【问题讨论】:

  • 通常在简单的应用程序中你会这样做:读取设置,初始化记录器/主机/任何对设置至关重要的东西,使用设置配置 DI(这里是你的 N),构建 DI 容器,执行启动操作(长时间运行的,此时他们可以从 DI 获取设置),完成(主应用程序周期已启动并运行)。您实际上可以将所有内容放在 DI 中并在某个类上调用 START,但它会变得笨拙/异常处理不当/等等,为此您需要很多垃圾工厂。我通常会创建几个样板项目:用于控制台、用于 webapp
  • 您如何使用这些Communicator 实例?它们是否被其他服务使用?还是它们应该是长时间运行的后台实例?换句话说,当这些类“通信”时,哪个对象负责发起和驱动这种通信?

标签: c# .net .net-core dependency-injection


【解决方案1】:

我会稍微修改here 显示的方法。所以我会定义一些枚举,然后用来决定返回什么实例。

示例类设置和枚举:

public enum CommuniationType
{
    False, True, Other,
}

public abstract class CommunicationBase
{
    public CommunicationBase(CommuniationType communiationType)
    {
        CommuniationType = communiationType;
    }

    public bool IsConnected { get; set; }
    
    public CommuniationType CommuniationType { get; protected set; }
}

public class Communicator : CommunicationBase
{
    public Communicator(CommuniationType communiationType) : base(communiationType) { }
}

现在,在您可以访问服务集合的地方(例如,在 ASP.NET 中,该地方将是 Stratup.RegisterServices 方法)定义具体类的对象并注册它们,如下面的示例代码所示(在底部,也有使用CommunicationBase对象的测试类来测试puproses):

public class Program
{
    static void Main(string[] args)
    {
        var serviceCollection = new ServiceCollection();
        
        SetupNObjects(serviceCollection);

        serviceCollection.AddTransient<CommunicationBaseServiceResolver>(serviceProvider => communicationType =>
        {
            var implementations = serviceProvider.GetServices<CommunicationBase>();
            return implementations.First(x => x.CommuniationType == communicationType);
        });

        serviceCollection.AddScoped<FalseTestClass>();
        serviceCollection.AddScoped<TrueTestClass>();

        var serviceProvider = serviceCollection.BuildServiceProvider();

        var f = serviceProvider.GetService<FalseTestClass>();
        var t = serviceProvider.GetService<TrueTestClass>();
    }
    
    // Here you should take care of registering objects, after reading config.
    // That would be best place to do that.
    static void SetupNObjects(ServiceCollection serviceCollection)
    {
        var comFalse = new Communicator(CommuniationType.False);
        comFalse.IsConnected = false;

        var comTrue = new Communicator(CommuniationType.True);
        comTrue.IsConnected = true;

        serviceCollection.AddScoped<CommunicationBase>((serviceProvider) => comFalse);
        serviceCollection.AddScoped<CommunicationBase>((serviceProvider) => comTrue);
    }
}

public class FalseTestClass
{
    private readonly CommunicationBase communication;

    public FalseTestClass(CommunicationBaseServiceResolver resolver)
    {
        communication = resolver(CommuniationType.False);
    }
}

public class TrueTestClass
{
    private readonly CommunicationBase communication;
    
    public TrueTestClass(CommunicationBaseServiceResolver resolver)
    {
        communication = resolver(CommuniationType.True);
    }
}

【讨论】:

  • 感谢您的详细回复。只有一个问题/后续问题。你在这里展示的方式,我有两个测试类,False 和 True,我必须编写代码。如果我需要 N 个 TestClasses 并且该数量可以根据配置更改怎么办?我可以为配置中的每个实例共享同一个类。所以,就叫它“TestClass”吧。我将如何实例化我需要的许多测试类,所有类型的 CommunicationBase,并将它们全部添加到 DI?这就是我要解决的用例。谢谢!
  • @MichaelBedford 我稍微重构了代码并提取了单独的方法来处理这个逻辑。在那里你应该阅读配置并创建对象。还需要枚举来扩展,因此您可以区分 N 个不同的对象。
【解决方案2】:

首先你需要清楚 Transient、Scoped、Singleton 生命周期之间的区别。了解如何使用将从您的配置文件中读取的 Communicator 对象列表。

解决您的问题的一种方法是

  1. 使用一种方法创建一个接口 ICommunicatorList 以获取列表,我的意思是您可以包含通信器列表。
  2. 创建一个继承自 ICommunicatorList 的类(例如,称为 CommunicatorList),并为您的 Communicator 列表提供一个私有字段。在构造函数方法中,使用通信器列表设置您的私有字段,o 在这里您可以从配置文件部分接收类似参数,以迭代并填充您的私有字段。
  3. 在此类上实现您的代码以返回通信器列表。
  4. 现在,您可以在启动文件中创建服务 services.AddTransient(x => new CommunicatorList(parameters));

【讨论】:

  • 抱歉耽搁了,是的。瞬态每次都会创建一个新的服务实例,即使是在同一个请求中。例如,如果您调用控制器操作并注入服务,这里将创建该服务的一个新实例,如果您在同一个请求中调用另一个使用该服务的方法,则会创建一个新实例。使用 Scoped 会为请求创建一个新的服务实例,并在该请求上重用该实例,因此在前面的示例中只会创建一个实例。最后,对于 Singleton,实例对于所有应用程序都是唯一的。
【解决方案3】:

我会这样做。

首先你有通信器和设置类:

namespace WebApiApp
{
    public abstract class CommunicationBase
    {
        public abstract string Communicate();
    }

    public class Communicator1Settings
    {
        public string Parameter { get; set; }
    }

    public class Communicator1 : CommunicationBase
    {
        private readonly string parameter;
        public Communicator1(string parameter)
        {
            this.parameter = parameter;
        }

        public override string Communicate()
        {
            return $"Type: {nameof(Communicator1)}, parameter: {this.parameter}";
        }
    }

    public class Communicator2Settings
    {
        public string Parameter1 { get; set; }
        public string Parameter2 { get; set; }
    }


    public class Communicator2 : CommunicationBase
    {
        private readonly string parameter1;
        private readonly string parameter2;
        public Communicator2(string parameter1, string parameter2)
        {
            this.parameter1 = parameter1;
            this.parameter2 = parameter2;
        }

        public override string Communicate()
        {
            return $"Type: {nameof(Communicator1)}, parameter1: {this.parameter1}, parameter2: {this.parameter2}";
        }
    }

    public class CommunicatorsSettings
    {
        public List<Communicator1Settings> Communicators1 { get; set; }
        public List<Communicator2Settings> Communicators2 { get; set; }
    }

}

在 appsettings.json 你有通信器的配置:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",

  "Communicators": {
    "Communicators1": [
      {
        "Parameter": "First communicator1 parameter"
      },
      {
        "Parameter": "Second communicator1 parameter"
      }
    ],
    "Communicators2": [
      {
        "Parameter1": "First communicator2 parameter1",
        "Parameter2": "First communicator2 parameter2"
      },
      {
        "Parameter1": "Second communicator2 parameter1",
        "Parameter2": "Second communicator2 parameter2"
      }
    ]
  }
}

因此,您有两个具有不同参数的Communicator1 实例和两个具有不同参数的Communicator2 实例。

然后,您配置容器。以下是.net 6program.cs的内容:

using WebApiApp;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

AddCommunicators();

builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

void AddCommunicators()
{
    var settings = new CommunicatorsSettings();
    builder.Configuration.Bind("Communicators", settings);
    foreach (var communicatorSettings in settings.Communicators1)
    {
        builder.Services.AddScoped<CommunicationBase>(
            _ => new Communicator1(communicatorSettings.Parameter));
    }
    foreach (var communicatorSettings in settings.Communicators2)
    {
        builder.Services.AddScoped<CommunicationBase>(
            _ => new Communicator2(communicatorSettings.Parameter1, communicatorSettings.Parameter2));
    }
}

现在您可以将IEnumerable&lt;CommunicationBase&gt; 注入您的控制器:

using Microsoft.AspNetCore.Mvc;

namespace WebApiApp.Controllers
{

    [ApiController]
    [Route("[controller]")]
    public class CommunicatorsController : Controller
    {
        private readonly IEnumerable<CommunicationBase> communicators;

        public CommunicatorsController(IEnumerable<CommunicationBase> communicators)
        {
            this.communicators = communicators;
        }
        public IActionResult Get()
        {
            var result = this.communicators.Select(x => x.Communicate());
            return this.Json(result);
        }
    }
}

这是/communicators Web API 的结果:

[
    "Type: Communicator1, parameter: First communicator1 parameter",
    "Type: Communicator1, parameter: Second communicator1 parameter",
    "Type: Communicator1, parameter1: First communicator2 parameter1, parameter2: First communicator2 parameter2",
    "Type: Communicator1, parameter1: Second communicator2 parameter1, parameter2: Second communicator2 parameter2"
]

【讨论】:

  • 感谢您的详细回复。只有一个问题/后续问题。按照您在此处显示的方式,我必须编写两个测试类 Communicator1 和 2。如果我需要 N 个 Communicator 类并且该数量可以根据配置进行更改怎么办?我可以为配置中的每个实例共享同一个类。所以,就叫它“通讯器”吧。我将如何实例化我需要的许多类,所有类型都是 CommunicationBase,并将它们全部添加到 DI?这就是我要解决的用例。谢谢!
  • @MichaelBedford 好吧,就像你可以创建两个类的实例一样,你也可以创建 n 个类的实例。
猜你喜欢
  • 2020-12-20
  • 2017-07-23
  • 2016-05-22
  • 1970-01-01
  • 1970-01-01
  • 2017-04-12
  • 2022-01-01
  • 2021-10-23
  • 1970-01-01
相关资源
最近更新 更多