【问题标题】:Dependency injection : static parameters and services依赖注入:静态参数和服务
【发布时间】:2018-10-11 15:37:47
【问题描述】:

我有这个服务将被注入到其他一些控制器。

它需要一个服务,以及一个从配置文件中获取的连接字符串。

public class MyService : IMyService
{
  public MyService(IService1 service1, IService2 service2, string connectionString){
     //...
  }
}

我希望注入 IService1IService2,但要手动指定 connectionString。我无法解决这个问题,我看到的例子要么非常复杂,要么不是我想要实现的。

public void ConfigureServices(IServiceCollection services)
{
    var cfg = new MyConfiguration();   
    Configuration.Bind("config", cfg);
    var connectionString = cfg["myConnectionString"];
    services.AddSingleton<IMyService, MyService>(/*what can I do here?*/)
}

这可以简单实现吗?

【问题讨论】:

  • 我通常只注册配置对象,这样我就可以将它注入到任何需要它的构造函数中。
  • 这确实是一个解决方案,但我很确定会有办法实现这个特定的事情
  • @Karl-JohanSjögren 虽然这可行,但了解框架的人倾向于建议不要将IConfiguration 直接注入到类中。
  • 可能是这样,我倾向于将其封装在我自己的配置对象中,例如 IDatabaseConfiguration 和 ISmtpConfiguration 以避免让其他实现知道配置键。
  • 哪个好。框架中还内置了IOptions&lt;T&gt; 模式。参考Options pattern in ASP.NET Core

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


【解决方案1】:

您可以访问工厂委托中的服务提供者。

在初始化服务时解析其他依赖并注入静态变量。

//...
var connectionString = cfg["myConnectionString"];
services.AddSingleton<IMyService>(_ => 
    new MyService(_.GetService<IService1>(), _.GetService<IService2>(), connectionString));

在上面的示例中,工厂委托中的_IServiceProvider,而GetService&lt;T&gt; 扩展方法用于解析其他服务,前提是它们也注册到服务集合中。

第一次请求 IMyService 时将调用工厂委托。

作为替代方案,引用Options pattern in ASP.NET Core

并假设例如一个 settings.json 文件

{
  "myConnectionString": "value1_from_json",
}

考虑使用配置扩展提供的IOptions&lt;T&gt;

创建一个类来存储所需的配置。

public class MyConnections {
    public string MyConnectionString { get; set; }
}

重构类以依赖IOption&lt;MyConnections&gt;

public class MyService : IMyService {
    private string connectionString;
    public MyService(IService1 service1, IService2 service2, IOptions<MyConnections> options){
        connectionString = options.Value.MyConnectionString;
        //...
    }

    //...
}

并在启动时配置它

//...

services.Configure<MyConnections>(Configuration);
services.AddSingleton<IMyService, MyService>();

【讨论】:

  • 我可以使用这种方法注册具有循环依赖的服务吗?例如,我可以用_.GetService&lt;IService2&gt; 注册IService1,然后用_.GetService&lt;IService1&gt; 注册IService2 吗?
  • 循环依赖往往会破坏系统,所以我不明白你为什么要这样做
  • 因为这也是我的情况,我应该更具体一些。我的IService1 在它的构造函数中需要IService2,反之亦然
  • @ArthurAttout 这是一个设计问题,注定要失败。你需要在这件事上审查你的设计选择。这也是与最初提出的问题完全不同的问题。
  • @ArthurAttout 我建议您查看什么是循环依赖以及它为什么不好。 DI 并不是为了解决这个问题。它可以帮助您发现它(当事情破裂时),但它不是解决方案。好的设计是解决循环依赖问题的唯一方法。
【解决方案2】:

您可以按照 Nkosi 的建议进行操作,并使用创建服务的工厂。然而,这有一个缺点,即这是非常明确的,并且当您的构造函数签名发生变化时(例如,当您需要不同的依赖项时),您总是需要调整对象的创建。

在 ASP.NET Core 世界中更合适的解决方案是使用选项模式。您所做的基本上不是要求将连接字符串传递给构造函数,而是创建一个新的配置类型来配置您的服务。然后,您可以使用选项模式配置此对象。

看起来像这样:

public class MyService : IMyService
{
    public MyService(IService1 service1, IService2 service2, IOptions<MyServiceOptions> serviceOptions)
    {
        var connectionString = serviceOptions.Value.ConnectionString;

        //...
    }
}

public class MyServiceOptions
{
    public string ConnectionString
    { get; set; }
}

然后在您的启动中,只需注册您的类型并配置选项:

services.AddSingleton<IMyService, MyService>();
services.Configure<MyServiceOptions>(options => {
    options.ConnectionString = "connection string";
});

如果您将连接字符串的配置放入单独的配置部分,您甚至可以这样做:

services.Configure<MyServiceOption>(configuration.GetSection("MyService"));

假设您的配置如下所示:

{
    "MyService": {
        "ConnectionString": "…"
    },
    // …
}

【讨论】:

  • 这仍然是一个好习惯吗,因为@Steven 说注入IOptions&lt;T&gt; 不是首选?
  • @ArthurAttout 我不太同意链接文章的内容。本文假定选项始终是从组合根目录中的单个配置源创建的。这完全是错误的,并且忽略了选项模式的大部分。此外,关于验证:即使您跳过选项模式并直接使用配置对象,您仍然会遇到未配置设置的相同问题。您应该在构造函数中验证它们,然后使用选项没有真正的不同。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-12-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多