Инъекции IOptions
Мне кажется, что плохой идеей иметь службу домена требуется экземпляр 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 покупают меня вместо старого школьного способа инъекции?
Ответы
Ответ 1
Технически ничто не мешает вам регистрировать свои классы POCO с помощью ASP.NET Core Dependency Injection или создавать класс-оболочку и возвращать из IOption<T>.Value
.
Но вы потеряете дополнительные возможности пакета опций, а именно, чтобы они автоматически обновляются при изменении источника, как вы можете видеть в источнике здесь.
Как вы можете видеть в этом примере кода, если вы регистрируете свои параметры через services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));
он будет читать и связывать настройки из appsettings.json в модель и дополнительно отслеживать ее для изменений. Когда appsettings.json будет отредактирован и будет пересобирать модель с новыми значениями, как показано здесь.
Конечно, вам нужно решить для себя, если вы хотите протечь немного инфраструктуры в свой домен или передать дополнительные функции, предлагаемые пакетом Microsoft.Extension.Options
. Это довольно маленький пакет, который не привязан к ASP.NET Core, поэтому его можно использовать независимо от него.
Пакет Microsoft.Extension.Options
достаточно мал, что он содержит только абстракции и конкретные services.Configure
IConfiguration
перегрузку, которая для IConfiguration
(которая ближе связана с получением конфигурации, командной строки, json, environment, azure key vault и т.д. ) представляет собой отдельный пакет.
Так что в целом, зависимости от "инфраструктуры" довольно ограничены.
Ответ 2
При использовании 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 → ConfigureServices для класса настроек с именем DataStoreConfiguration
и секции 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; }
}
}
С помощью этой конфигурации приложений и библиотек мне удалось вставить конкретный экземпляр DataStoreConfiguration непосредственно в мою библиотеку, используя инъекцию конструктора без оболочки IOption
:
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, поэтому я не уверен, почему Microsoft подключила пользователей к связям своих библиотек классов с внешней зависимостью, например IOptions
, независимо от того, насколько тривиальна она кажется или какие преимущества она якобы предоставляет. Я также хотел бы предположить, что некоторые из преимуществ IOptions
кажутся чрезмерными. Например, это позволяет мне динамически изменять конфигурацию и отслеживать изменения - я использовал три других контейнера DI, которые включали эту функцию, и я никогда не использовал ее один раз... Между тем, я могу фактически гарантировать вам, что команды захотят ввести классы или интерфейсы POCO в библиотеки, чтобы их параметры заменили ConfigurationManager
, и опытные разработчики не будут довольны внешним интерфейсом обертки. Я надеюсь, что утилита, аналогичная тому, что я описал здесь, включена в будущие версии ASP.NET Core OR, и кто-то дает мне убедительный аргумент в пользу того, почему я ошибаюсь.
Ответ 3
Я не выношу рекомендации IOptions. Это дрянной дизайн, чтобы заставить это на разработчиков. IOptions должны быть четко документированы как необязательные, о иронии.
Это то, что я делаю для своих значений конфигурации
var mySettings = new MySettings();
Configuration.GetSection("Key").Bind(mySettings);
services.AddTransient(p => new MyService(mySettings));
Вы сохраняете сильную типизацию и не нуждаетесь в использовании IOptions в своих службах/библиотеках.