.NET Core: как добавить параметры конфигурации в ServiceCollection, когда тип TOptions зависит от строкового значения?

Я привязываюсь к разделу конфигурации приложений в моей сборке хоста, используя следующее: -

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 :).

Можете ли вы показать больше кода, чтобы лучше представить, что вы на самом деле пытаетесь сделать. Я запутался в переменной myTypeName и в том, откуда она берется, поскольку она связана с настройкой конфигурации. Укажите, как может выглядеть раздел

Nkosi 10.11.2022 13:05

Вы говорите, что раздел конфигурации «TheSection» может иметь разные «формы» и что вы хотите привязать этот раздел к разным типам?

David Osborne 10.11.2022 13:11

привет @Nkosi, я добавил, как заполнить myTypeName, это помогает? Сами разделы настроек имеют разную структуру, каждая из которых соответствует MySettings1, 2 и 3 соответственно.

danrockcoll 10.11.2022 13:43

привет @DavidOsborne да вот и все, MySettings1/2/3 имеют разные формы, я обновил вопрос, чтобы сделать его более понятным, спасибо :)

danrockcoll 10.11.2022 13:44

Похоже, это проблема XY. Я бы предложил пересмотреть текущий дизайн рассматриваемой системы.

Nkosi 10.11.2022 15:13

@Nkosi я добавил описание своего дизайна ниже, пожалуйста, дайте мне знать, если вы считаете, что дизайн плохой

danrockcoll 10.11.2022 17:50
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
6
202
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Это далеко не идеально, но это должно помочь вам двигаться вперед и дать вам что-то, что вы могли бы улучшить.

Вы можете определить типы для различных настроек «фигур» примерно так:

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;
   }
}

Имхо, это плохой дизайн. Хотя вы правильно сказали, что он далек от совершенства, он поощряет тесную связь с деталями реализации.

Nkosi 10.11.2022 15:11

Конечно. Как это можно улучшить?

David Osborne 10.11.2022 15:18

Ну, основная проблема до сих пор не прояснена ОП. Вот что привело вас на этот неблагоразумный путь.

Nkosi 10.11.2022 15:20

ОП спрашивает, как сделать X, когда фактическая проблема может быть Y. У нас есть 3 отдельных типа настроек, которые необходимо динамически настроить для внедрения во время выполнения. Что по моему опыту поднимает флаги дизайна для меня.

Nkosi 10.11.2022 15:21

я не думаю, что это проблема дизайна, у меня довольно простой дизайн, но вот он - у меня есть хост с несколькими рабочими классами, и я хочу запускать только одного рабочего в каждом из моих развертываний, потому что каждый рабочий могут иметь разные характеристики времени выполнения. поэтому я могу либо иметь хост-решение для каждого работника, либо иметь более 20 решений с дублированием основной части кода, связанного с хостом, или я могу иметь одно решение, содержащее хост, и загружать рабочего, который мне нужен, динамически на основе того, который я хочу запустить . или у меня может быть большой оператор switch для загрузки точно нужного работника, использующего реальный тип.

danrockcoll 10.11.2022 17:48

привет @DavidOsborne, большое спасибо за ваш ответ! я понимаю, что вы и nkosi чувствовали, что это может быть не идеально из-за тесной связи, но мне все равно было полезно увидеть технику :)

danrockcoll 11.11.2022 09:34

Без проблем. Мы все учимся!

David Osborne 11.11.2022 10:08
Ответ принят как подходящий

Из-за использования здесь строки необходимо использовать отражение, чтобы получить желаемое поведение во время выполнения.

//...

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, большое спасибо, я отмечу это как ответ, так как раньше я не использовал отражение, и поэтому я думаю, что это лучший ответ, особенно потому, что он дает мне больше понимания того, как я мог бы использовать отражение в будущем! я также буду помнить, чтобы быть осторожным, когда его использовать, чтобы избежать любых потенциальных проблем с дизайном :)

danrockcoll 11.11.2022 09:39

Другие вопросы по теме