IOptions注入

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

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

services.AddTransient(x => { var appSettings = x.GetService<IOptions>(); return new ConnectionResolver(appSettings.Value); }); 

甚至使用这种机制:

  AppSettings appSettings = new AppSettings(); Configuration.GetSection("AppSettings").Bind(appSettings); services.AddTransient(x => { return new ConnectionResolver(appSettings.SomeValue); }); 

使用设置:

 public class MyConnectionResolver { // Why this? public MyConnectionResolver(IOptions appSettings) { ... } // Why not this? public MyConnectionResolver(AppSettings appSettings) { ... } // Or this public MyConnectionResolver(IAppSettings appSettings) { ... } } 

为什么附加依赖? IOptions给我的是什么而不是旧式的注入方式?

从技术上讲,没有什么可以阻止您使用ASP.NET Core的dependency injection注册POCO类,或者创建一个包装类并IOption.Value返回IOption.Value

但是您将丢失Options包的高级function,即在源更改时自动更新它们,如此处的源代码所示。

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

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

Microsoft.Extension.Options包足够小,只包含抽象和具体services.Configure 。配置IConfiguration重载(与获取配置的方式,命令行,json,环境,azure密钥库等更紧密相关)。 )是一个单独的包。

总而言之,它对“基础设施”的依赖性非常有限。

虽然使用IOption是官方的做事方式,但我似乎无法超越这样一个事实:我们的外部库不需要知道有关DI容器或其实现方式的任何信息。 IOption似乎违反了这个概念,因为我们现在告诉我们的类库有关DI容器注入设置的方式 – 我们应该注入一个由该类定义的POCO或接口。

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

 public static class ConfigurationHelper { public static T GetObjectFromConfigSection( 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($"{configSection}:{propInfo.Name}"), propInfo.PropertyType); propInfo.SetValue(result, value, null); } } return result; } } 

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

 services.AddSingleton((_) => Configuration.GetObjectFromConfigSection("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一些好处看起来像过度工程。 例如,它允许我动态更改配置并跟踪更改 – 我使用了其他三个包含此function的DI容器,我从未使用过一次……同时,我几乎可以保证团队会想要将POCO类或接口注入库以获取其设置以替换ConfigurationManager ,经验丰富的开发人员不会对无关的包装器接口感到高兴。 我希望类似于我在这里描述的实用程序包含在ASP.NET Core的未来版本中,或者有人为我提供了一个令人信服的论据,说明为什么我错了。

我迟到了,我也无法忍受IOptions的要求。 这对开发人员来说是一个糟糕的设计。 IOptions应该是可选的,具有讽刺意味。

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

 var mySettings = new MySettings(); Configuration.GetSection("Key").Bind(mySettings); services.AddTransient(p => new MyService(mySettings)); 

您保留强类型,不需要在服务/库中使用IOptions。