【问题标题】:C# Dependency Injection - injecting interface that takes the same interface as a parameterC# Dependency Injection - 注入接口,将相同的接口作为参数
【发布时间】:2020-09-01 18:54:45
【问题描述】:

我在尝试使用依赖注入时遇到了一些麻烦,需要帮助。

我有一个服务IService 在我的应用程序中以几种不同的方式实现。

ServiceA : IService { public ServiceA(IDependencyA A, IDependency B) {...} }
ServiceB : IService { public ServiceB(IDependencyA A, IDependency B) {...} }
ServiceC : IService { public ServiceC(IService serviceA, IService serviceB) {...} }

在我的startup.cs 文件中,我将根据我的配置文件中的参数选择使用哪一个。像这样的:

var service = Configuration["AppConfig:Service:ID"];
switch(service) {
    case "A":
        services.AddTransient<IService, ServiceA>();
    case "B":
        services.AddTransient<IService, ServiceB>();
    case "C":
        // ??
}

我能够非常轻松地创建和使用服务 A 和服务 B。我事先注入了它们的依赖项 A 和 B 并且它们被创建得很好。问题在于第三项服务。我需要将另外两个服务注入其中。我的问题是:最好的方法是什么?

有没有一种方法可以创建服务 A 和 B 的具体实现,但以某种方式在其构造函数中使用注入的依赖项 A 和 B?我是否必须弄乱接口才能使其正常工作?也许我必须更改 ServiceC 的构造函数以接受 A 和 B 的具体实现?

更新:不知道这是否是最好的解决方案,但我最终做了以下事情来让它工作。

...
case "C":
    services.AddTransient<ServiceA>();
    services.AddTransient<ServiceB>();
    services.AddTransient<IService>(s => 
        new ServiceC(
            s.GetService<ServiceA>(),
            s.GetService<ServiceB>()
    ));

【问题讨论】:

  • 添加更多关于服务C的细节。如果它确实需要这2个具体实现,那么你应该改变构造函数。否则会破坏 Liskov 替换原则。修复开关,没有中断,服务A注册了2次。
  • 服务 C 的目标是使用服务 A 的实现,但是如果服务 A 不能提供正确的结果,则回退到服务 B 的结果。
  • 为所需服务创建工厂模式,并在需要时实例化它们。并非所有事情都需要由 DI() 处理
  • 根据您在此处的最后一条评论,基于结果的服务使用情况,您可以使用类似于Strategy Pattern 的概念以更简洁的方式实现此目的。如果您能预测结果是否会成功,那当然会更好。
  • @mymemesarespiciest 即使您不直接使用 Polly,您也应该了解它是如何工作的,它如何解决您可能遇到的相同问题

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


【解决方案1】:

另一种选择。引入另一个接口,这样你就可以同时注册ServiceC和它的依赖,没有任何歧义或循环引用。

interface IService {}
interface IServiceImpl : IService {}

ServiceA : IServiceImpl { public ServiceA(IDependencyA A, IDependency B) {...} }
ServiceB : IServiceImpl { public ServiceB(IDependencyA A, IDependency B) {...} }
ServiceC : IService { public ServiceC(IEnumerable<IServiceImpl> services) {...} }

services.AddTransient<IServiceImpl, ServiceA>();
services.AddTransient<IServiceImpl, ServiceB>();
services.AddTransient<IService, ServiceC>();

【讨论】:

  • 会的。 DI 通常会注入第一个注册的类型。请注意,我将 ServiceC 的构造函数更改为 IEnumerable,因此它将接收所有服务实现。
  • 我明白为什么 ServiceC 没问题。我需要在这个周末自己测试 ServiceA 和 ServiceB 的设置。
  • 顺便说一句:测试时,我应该如何解决例如 ServiceA 和 ServiceB控制器?通过注入IServiceImpl?
  • 这个想法是将IService 保留为“公共API”,并且仅将IServiceImpl 引用为“内部”作为实现细节......
  • 我看到 OP 通过使用 services.AddTransient(); 解决了它services.AddTransient(); ,也许现在没有必要进一步深入研究。无论如何,现在我确实了解您的答案背后的逻辑,为此 +1 :-) 感谢您抽出时间杰里米。 BR
【解决方案2】:

必须有更好的解释来解释这种依赖关系。

Microsoft.Extensions.DependencyInjection 不像其他 IoC 容器(即 Castle.Windsor)那样提供命名注册。

有一种方法仍然可以使用 A 和 B 的实例注册 ServiceC

            services.AddTransient<ServiceA>();
            services.AddTransient<ServiceB>();
            services.AddTransient<IService>(serviceProvider => {
                return new ServiceC((IService)serviceProvider.GetRequiredService(typeof(ServiceA)), (IService)serviceProvider.GetRequiredService(typeof(ServiceB)));
            });

【讨论】:

  • 你发布这个很有趣,因为它与我刚刚发现的最终工作的解决方案几乎相同。唯一的区别是您不需要强制转换为 IService,因为这些服务已经实现了该接口。
  • 啊哈不错!顺便提一句。我写的时候不刷新。和“有趣”你这么想:) 铸造的唯一原因是GetRequiredService 确实需要类型转换
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-04-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多