Я привязываюсь к разделу конфигурации приложений в моей сборке хоста, используя следующее: -
services.Configure<MySettings1>(hostContext.Configuration.GetSection("TheSection"));
Я хочу привязать только один раздел, но фактический тип, к которому я хочу привязать, зависит от строковой переменной myTypeName и может быть одним из трех типов — MySettings1, MySettings2 или MySettings3.
Итак, что я хочу сделать (что, я знаю, невозможно): -
var myTypeName = Environment.GetEnvironmentVariable("MY_TYPE_NAME");
services.Configure<myTypeName>(hostContext.Configuration.GetSection("TheSection"));
Раздел может иметь разную структуру для каждого из трех типов и может называться TheSection или как-то иначе для каждого типа (этот бит в порядке, поскольку он уже является строкой).
Я не видел никаких перегрузок для метода Configure в документах MS, которым не требуется тип, но я не эксперт в дженериках, поэтому надеялся, что кто-нибудь укажет мне правильное направление (или, альтернативно, скажите мне, что я должен т сделать это из-за xyz :).
Вы говорите, что раздел конфигурации «TheSection» может иметь разные «формы» и что вы хотите привязать этот раздел к разным типам?
привет @Nkosi, я добавил, как заполнить myTypeName, это помогает? Сами разделы настроек имеют разную структуру, каждая из которых соответствует MySettings1, 2 и 3 соответственно.
привет @DavidOsborne да вот и все, MySettings1/2/3 имеют разные формы, я обновил вопрос, чтобы сделать его более понятным, спасибо :)
Похоже, это проблема XY. Я бы предложил пересмотреть текущий дизайн рассматриваемой системы.
@Nkosi я добавил описание своего дизайна ниже, пожалуйста, дайте мне знать, если вы считаете, что дизайн плохой
Это далеко не идеально, но это должно помочь вам двигаться вперед и дать вам что-то, что вы могли бы улучшить.
Вы можете определить типы для различных настроек «фигур» примерно так:
abstract class SettingsBase { }
class DerivedSettingsOne : SettingsBase { }
class DerivedSettingsTwo : SettingsBase { }
Затем создайте фабрику, которая знает, как преобразовать строку в тип. Однако он может возвращать только абстрактную базу, поскольку должен поддерживать все производные типы:
static class SettingsFactory {
SettingsBase GetSettings(string typeName, IConfigurationSection configSection) {
return
typeName switch {
"DerivedSettingsOne" => new DerivedSettingsOne { // props here },
"DerivedSettingsTwo" => new DerivedSettingsTwo { // props here },
_ => throw new ArgumentOutOfRangeException(nameof(direction)
};
}
}
Затем вы можете зарегистрировать фабрику, захватив значение из переменной среды и раздела конфигурации, которая будет разрешать запросы на SettingsBase
.
services.AddSingleton(s => {
return SettingsFactory.GetSettings(
Environment.GetEnvironmentVariable("MY_TYPE_NAME"),
hostContext.Configuration.GetSection("TheSection")
});
Теперь вы можете объявить типы, которым нужны настройки с зависимостью от SettingsBase
, которую разрешит контейнер IoC. Единственная проблема заключается в том, что потребителям SettingsBase
нужно будет привести его к производному типу, с которым им нужно работать:
class MyClass {
MyClass(SettingsBase settings) {
var expectedSettings = settings as DerivedSettingsTwo;
}
}
Имхо, это плохой дизайн. Хотя вы правильно сказали, что он далек от совершенства, он поощряет тесную связь с деталями реализации.
Конечно. Как это можно улучшить?
Ну, основная проблема до сих пор не прояснена ОП. Вот что привело вас на этот неблагоразумный путь.
ОП спрашивает, как сделать X, когда фактическая проблема может быть Y. У нас есть 3 отдельных типа настроек, которые необходимо динамически настроить для внедрения во время выполнения. Что по моему опыту поднимает флаги дизайна для меня.
я не думаю, что это проблема дизайна, у меня довольно простой дизайн, но вот он - у меня есть хост с несколькими рабочими классами, и я хочу запускать только одного рабочего в каждом из моих развертываний, потому что каждый рабочий могут иметь разные характеристики времени выполнения. поэтому я могу либо иметь хост-решение для каждого работника, либо иметь более 20 решений с дублированием основной части кода, связанного с хостом, или я могу иметь одно решение, содержащее хост, и загружать рабочего, который мне нужен, динамически на основе того, который я хочу запустить . или у меня может быть большой оператор switch для загрузки точно нужного работника, использующего реальный тип.
привет @DavidOsborne, большое спасибо за ваш ответ! я понимаю, что вы и nkosi чувствовали, что это может быть не идеально из-за тесной связи, но мне все равно было полезно увидеть технику :)
Без проблем. Мы все учимся!
Из-за использования здесь строки необходимо использовать отражение, чтобы получить желаемое поведение во время выполнения.
//...
string myTypeName = Environment.GetEnvironmentVariable("MY_TYPE_NAME");
//Gets the Type with the specified name
Type myType = Type.GetType(myTypeName, throwOnError: false, ignoreCase: true);
if (myType == null) {
//...fail early? throw meaningful exception?
} else {
IConfiguration section = hostContext.Configuration.GetSection("TheSection");
if (section == null) {
//...fail early? throw exception.
}
//using reflection to invoke the Configure<TOption> extension
Type extensionClass = typeof(OptionsConfigurationServiceCollectionExtensions);
//get the desired extension method by name and using the expected arguments
Type[] parameterTypes = new[] { typeof(IServiceCollection), typeof(IConfiguration) };
string extensionName = nameof(OptionsConfigurationServiceCollectionExtensions.Configure);
MethodInfo configureExtension = extensionClass.GetMethod(extensionName, parameterTypes);
//make the closed generic extension using the run time type from environment
MethodInfo extensionMethod = configureExtension.MakeGenericMethod(myType);
//invoke the extension "services.Configure<myType>(section);"
extensionMethod.Invoke(services, section);
}
//...
Основные части вышеперечисленного могут быть извлечены и превращены в расширение.
Необходимость прибегнуть к конфигурации этого уровня может указывать на проблемы проектирования рассматриваемой системы, которые потенциально могут привести к проблемам с ремонтопригодностью в будущем.
Это также предполагает, что фактический тип фактически используется в зависимых классах.
//ctor
public MyClass(IOptions<myType> options);
привет @nkosi, большое спасибо, я отмечу это как ответ, так как раньше я не использовал отражение, и поэтому я думаю, что это лучший ответ, особенно потому, что он дает мне больше понимания того, как я мог бы использовать отражение в будущем! я также буду помнить, чтобы быть осторожным, когда его использовать, чтобы избежать любых потенциальных проблем с дизайном :)
Можете ли вы показать больше кода, чтобы лучше представить, что вы на самом деле пытаетесь сделать. Я запутался в переменной myTypeName и в том, откуда она берется, поскольку она связана с настройкой конфигурации. Укажите, как может выглядеть раздел