IOptions注入

时间:2016-12-14 19:01:06

标签: c# asp.net-core

在我看来,让域服务需要IOptions实例来传递配置是个坏主意。现在我已经将额外的(不必要的?)依赖项拉入库中。我已经看到很多在网络上注入IOptions的例子,但是我没有看到它带来的额外好处。

为什么不将实际的POCO注入服务?

    services.AddTransient<IConnectionResolver>(x =>
    {
        var appSettings = x.GetService<IOptions<AppSettings>>();

        return new ConnectionResolver(appSettings.Value);
    });

甚至使用这种机制:

        AppSettings appSettings = new AppSettings();

        Configuration.GetSection("AppSettings").Bind(appSettings);

        services.AddTransient<IConnectionResolver>(x =>
        {      
             return new ConnectionResolver(appSettings.SomeValue);
        });

使用设置:

public class MyConnectionResolver 
{
     // Why this?
     public MyConnectionResolver(IOptions<AppSettings> appSettings)
     {
           ... 
     }

     // Why not this?
     public MyConnectionResolver(AppSettings appSettings)
     {
           ... 
     }

     // Or this
     public MyConnectionResolver(IAppSettings appSettings)
     {
           ... 
     }
}

为什么还有其他依赖项? IOptions会给我带来什么,而不是旧式的注入方式?

5 个答案:

答案 0 :(得分:13)

从技术上讲,没有什么能阻止您使用ASP.NET Core的依赖注入注册POCO类,或者创建一个包装类并从中返回IOption<T>.Value

但是您将失去选项包的高级功能,即在源更改时自动更新它们,如您在源here中看到的那样。

正如您在该代码示例中所看到的,如果您通过services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));注册选项,它将读取appsettings.json中的设置并将其绑定到模型中,并另外跟踪它以进行更改。编辑appsettings.json时,将使用看到here的新值重新绑定模型。

当然,如果您想将一些基础设施泄漏到您的域中或传递Microsoft.Extension.Options包提供的额外功能,您需要自己决定。它是一个非常小的包,它与ASP.NET Core无关,所以可以独立使用它。

Microsoft.Extension.Options包足够小,只包含抽象和services.Configure的具体IConfiguration重载({1}}(与获取配置的关系更紧密,命令行,json) ,环境,天蓝色密钥保险库等)是一个单独的包。

总而言之,它依赖于&#34;基础设施&#34;非常有限。

答案 1 :(得分:3)

虽然使用IOption是正式的做事方式,但我似乎无法超越这样一个事实,即我们的外部图书馆不应该对DI容器或它的实施方式。 IOption似乎违反了这个概念,因为我们现在告诉我们的类库有关DI容器注入设置的方式 - 我们应该注入一个由该类定义的POCO或接口。

这让我非常恼火,因为我已经编写了一个实用程序来将POCO注入到我的类库中,该库中填充了appSettings.json部分的值。将以下类添加到应用程序项目中:

public static class ConfigurationHelper
{
    public static T GetObjectFromConfigSection<T>(
        this IConfigurationRoot configurationRoot,
        string configSection) where T : new()
    {
        var result = new T();

        foreach (var propInfo in typeof(T).GetProperties())
        {
            var propertyType = propInfo.PropertyType;
            if (propInfo?.CanWrite ?? false)
            {
                var value = Convert.ChangeType(configurationRoot.GetValue<string>($"{configSection}:{propInfo.Name}"), propInfo.PropertyType);
                propInfo.SetValue(result, value, null);
            }
        }

        return result;

    }
}

可能会有一些增强功能,但是当我使用简单的字符串和整数值进行测试时,它运行良好。以下是我在应用项目的Startup.cs中使用它的示例 - &gt;名为DataStoreConfiguration的设置类的ConfigureServices方法和相同名称的appSettings.json部分:

services.AddSingleton<DataStoreConfiguration>((_) =>
    Configuration.GetObjectFromConfigSection<DataStoreConfiguration>("DataStoreConfiguration"));

appSettings.json配置如下所示:

{
  "DataStoreConfiguration": {
    "ConnectionString": "Server=Server-goes-here;Database=My-database-name;Trusted_Connection=True;MultipleActiveResultSets=true",
    "MeaningOfLifeInt" : "42"
  },
 "AnotherSection" : {
   "Prop1" : "etc."
  }
}

我的库项目中定义了DataStoreConfiguration类,如下所示:

namespace MyLibrary.DataAccessors
{
    public class DataStoreConfiguration
    {
        public string ConnectionString { get; set; }
        public int MeaningOfLifeInt { get; set; }
    }
}

通过这个应用程序和库配置,我能够使用没有IOption包装器的构造函数注入将DataStoreConfiguration的具体实例直接注入到我的库中:

using System.Data.SqlClient;

namespace MyLibrary.DataAccessors
{
    public class DatabaseConnectionFactory : IDatabaseConnectionFactory
    {

        private readonly DataStoreConfiguration dataStoreConfiguration;

        public DatabaseConnectionFactory(
            DataStoreConfiguration dataStoreConfiguration)
        {
            // Here we inject a concrete instance of DataStoreConfiguration
            // without the `IOption` wrapper.
            this.dataStoreConfiguration = dataStoreConfiguration;
        }

        public SqlConnection NewConnection()
        {
            return new SqlConnection(dataStoreConfiguration.ConnectionString);
        }
    }
}

解耦是DI的一个重要考虑因素,所以我不确定为什么微软会让用户将他们的类库与IOptions之类的外部依赖关系联系起来,无论它看起来多么微不足道或者有什么好处呢?据说提供。我还建议IOptions的一些好处似乎过度工程化。例如,它允许我动态更改配置并跟踪更改 - 我已经使用了其他三个包含此功能的DI容器,而且我从未使用过一次......同时,我几乎可以保证你团队希望将POCO类或接口注入库以获取其设置以替换ConfigurationManager,并且经验丰富的开发人员不会对无关的包装器接口感到高兴。我希望类似于我在此描述的实用程序包含在ASP.NET Core的未来版本中,或者有人为我提供了一个令人信服的论据,说明我错误的原因。

答案 2 :(得分:1)

我也不能遵守IOptions建议。这对于开发人员来说是一个糟糕的设计。应该明确记录IOptions是可选的,讽刺的是。

这就是我为配置值所做的事情

var mySettings = new MySettings();
Configuration.GetSection("Key").Bind(mySettings);

services.AddTransient(p => new MyService(mySettings));

您保持强有力的输入,并且不需要在您的服务/库中使用IOptions。

答案 3 :(得分:0)

您可以执行以下操作:

services.AddTransient(
    o => ConfigurationBinder.Get<AppSettings>(Configuration.GetSection("AppSettings") 
);

使用Net.Core v.2.2,对我有用。

或者,然后使用IOption<T>.Value

看起来像这样

services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));

答案 4 :(得分:0)

credit

startup.csConfigureServices的这两行中,您可以像这样注入IOptions值:

public void ConfigureServices(IServiceCollection services)
{
    //...
    services.Configure<AppSettingsConfig>(Configuration.GetSection("AppSettings"));
    services.AddScoped(cfg => cfg.GetService<IOptions<AppSettingsConfig>>().Value);
}

然后用于:

 public MyConnectionResolver(AppSettings appSettings)
 {
       ... 
 }