Як реалізувати ConfigurationSection з ConfigurationElementCollection


166

Я намагаюся реалізувати в проекті розділ налаштування, і я продовжую працювати над винятками, які я не розумію. Я сподіваюся, що хтось може заповнити заготовки тут.

У мене App.configце виглядає приблизно так:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <configSections>
        <section name="ServicesSection" type="RT.Core.Config.ServicesConfigurationSectionHandler, RT.Core"/>
    </configSections>
    <ServicesSection type="RT.Core.Config.ServicesSection, RT.Core">
            <Services>
                <AddService Port="6996" ReportType="File" />
                <AddService Port="7001" ReportType="Other" />
            </Services>
        </ServicesSection>
</configuration>

У мене ServiceConfigелемент визначений так:

public class ServiceConfig : ConfigurationElement
  {
    public ServiceConfig() {}

    public ServiceConfig(int port, string reportType)
    {
      Port = port;
      ReportType = reportType;
    }

    [ConfigurationProperty("Port", DefaultValue = 0, IsRequired = true, IsKey = true)]
    public int Port 
    {
      get { return (int) this["Port"]; }
      set { this["Port"] = value; }
    }

    [ConfigurationProperty("ReportType", DefaultValue = "File", IsRequired = true, IsKey = false)]
    public string ReportType
    {
      get { return (string) this["ReportType"]; }
      set { this["ReportType"] = value; }
    }
  }

І у мене є таке ServiceCollectionвизначення:

public class ServiceCollection : ConfigurationElementCollection
  {
    public ServiceCollection()
    {
      Console.WriteLine("ServiceCollection Constructor");
    }

    public ServiceConfig this[int index]
    {
      get { return (ServiceConfig)BaseGet(index); }
      set
      {
        if (BaseGet(index) != null)
        {
          BaseRemoveAt(index);
        }
        BaseAdd(index, value);
      }
    }

    public void Add(ServiceConfig serviceConfig)
    {
      BaseAdd(serviceConfig);
    }

    public void Clear()
    {
      BaseClear();
    }

    protected override ConfigurationElement CreateNewElement()
    {
      return new ServiceConfig();
    }

    protected override object GetElementKey(ConfigurationElement element)
    {
      return ((ServiceConfig) element).Port;
    }

    public void Remove(ServiceConfig serviceConfig)
    {
      BaseRemove(serviceConfig.Port);
    }

    public void RemoveAt(int index)
    {
      BaseRemoveAt(index);
    }

    public void Remove(string name)
    {
      BaseRemove(name);
    }
  }

Частина, яка мені не вистачає, - що робити для обробника. Спочатку я намагався реалізувати, IConfigurationSectionHandlerале знайшов дві речі:

  1. це не спрацювало
  2. вона застаріла.

Я повністю втрачений зараз, що робити, щоб я міг прочитати свої дані з конфігурації. Будь-яка допомога, будь ласка!


Я не можу це працювати. Я хотів би побачити RT.Core.Config.ServicesSection. Я просто отримую нерозпізнаний елемент 'AddService', незважаючи на використання коду з прийнятої відповіді.
sirdank

Я також спочатку пропустив це - цю частину: [ConfigurationCollection (typeof (ServiceCollection), AddItemName = "додати", ClearItemsName = "очистити", RemoveItemName = "видалити")] AddItemName має відповідати, якщо ви змінили "add" в "addService" це спрацювало
HeatherD

Відповіді:


188

Попередня відповідь правильна, але я вам також дам весь код.

Ваш app.config повинен виглядати так:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
   <configSections>
      <section name="ServicesSection" type="RT.Core.Config.ServiceConfigurationSection, RT.Core"/>
   </configSections>
   <ServicesSection>
      <Services>
         <add Port="6996" ReportType="File" />
         <add Port="7001" ReportType="Other" />
      </Services>
   </ServicesSection>
</configuration>

Ваші ServiceConfigта ServiceCollectionзаняття залишаються незмінними.

Вам потрібен новий клас:

public class ServiceConfigurationSection : ConfigurationSection
{
   [ConfigurationProperty("Services", IsDefaultCollection = false)]
   [ConfigurationCollection(typeof(ServiceCollection),
       AddItemName = "add",
       ClearItemsName = "clear",
       RemoveItemName = "remove")]
   public ServiceCollection Services
   {
      get
      {
         return (ServiceCollection)base["Services"];
      }
   }
}

І це повинно зробити трюк. Для його споживання ви можете використовувати:

ServiceConfigurationSection serviceConfigSection =
   ConfigurationManager.GetSection("ServicesSection") as ServiceConfigurationSection;

ServiceConfig serviceConfig = serviceConfigSection.Services[0];

10
У [Add|Remove|Clear]ItemNameвластивостях на ConfigurationCollectionатрибут не дійсно необхідні в даному випадку, так як «додати» / «ясно» / «видалити» вже імена за замовчуванням елементів XML.
Вім Коен

2
Як змусити його працювати, щоб теги не додавались? Це, здається, працює, лише якщо вони додаються Не буде працювати, якби це було <Service Port = "6996" ReportType = "Файл" /> або <Service Port = "7001" ReportType = "Інше" />
JonathanWolfson

7
@JonathanWolfson: просто змінити AddItemName = "додати" до AddItemName = "Сервіс"
Mubashar

Це все-таки підхід для .NET 4.5?
розчавити

6
@crush: так, у цьому запиленому куточку .NET не так багато змін.
Рассел МакКлур

84

Якщо ви шукаєте спеціальний розділ конфігурації, як описано нижче

<CustomApplicationConfig>
        <Credentials Username="itsme" Password="mypassword"/>
        <PrimaryAgent Address="10.5.64.26" Port="3560"/>
        <SecondaryAgent Address="10.5.64.7" Port="3570"/>
        <Site Id="123" />
        <Lanes>
          <Lane Id="1" PointId="north" Direction="Entry"/>
          <Lane Id="2" PointId="south" Direction="Exit"/>
        </Lanes> 
</CustomApplicationConfig>

тоді ви можете використовувати мою реалізацію розділу конфігурації, щоб почати додавати System.Configurationпосилання на проект до свого проекту

Подивіться на всі вкладені елементи, які я використав. Перший - це Повноважні дані з двома атрибутами, тому давайте змогу додати їх першими

Елемент облікових даних

public class CredentialsConfigElement : System.Configuration.ConfigurationElement
    {
        [ConfigurationProperty("Username")]
        public string Username
        {
            get 
            {
                return base["Username"] as string;
            }
        }

        [ConfigurationProperty("Password")]
        public string Password
        {
            get
            {
                return base["Password"] as string;
            }
        }
    }

Первинний агент та вторинний агент

Обидва мають однакові атрибути і схожі на Адреса до набору серверів для первинного та перехідного режиму, тому вам просто потрібно створити один клас елементів для обох таких, як наступний

public class ServerInfoConfigElement : ConfigurationElement
    {
        [ConfigurationProperty("Address")]
        public string Address
        {
            get
            {
                return base["Address"] as string;
            }
        }

        [ConfigurationProperty("Port")]
        public int? Port
        {
            get
            {
                return base["Port"] as int?;
            }
        }
    }

Я поясню, як використовувати два різних елемента з одним класом пізніше в цій публікації, пропустимо SiteId, оскільки в ньому немає різниці. Вам просто потрібно створити один клас, як і вище, лише з одним властивістю. давайте подивимося, як реалізувати колекцію доріжок

він розділений на дві частини. Спочатку потрібно створити клас реалізації елементів, а потім створити клас елементів колекції

LaneConfigElement

public class LaneConfigElement : ConfigurationElement
    {
        [ConfigurationProperty("Id")]
        public string Id
        {
            get
            {
                return base["Id"] as string;
            }
        }

        [ConfigurationProperty("PointId")]
        public string PointId
        {
            get
            {
                return base["PointId"] as string;
            }
        }

        [ConfigurationProperty("Direction")]
        public Direction? Direction
        {
            get
            {
                return base["Direction"] as Direction?;
            }
        }
    }

    public enum Direction
    { 
        Entry,
        Exit
    }

Ви можете помітити, що один атрибут LanElement- це Перерахування, і якщо ви спробуєте використати будь-яке інше значення в конфігурації, яке не визначено в додатку Перерахування, буде System.Configuration.ConfigurationErrorsExceptionзапускатися при запуску. Гаразд, перейдемо до визначення колекції

[ConfigurationCollection(typeof(LaneConfigElement), AddItemName = "Lane", CollectionType = ConfigurationElementCollectionType.BasicMap)]
    public class LaneConfigCollection : ConfigurationElementCollection
    {
        public LaneConfigElement this[int index]
        {
            get { return (LaneConfigElement)BaseGet(index); }
            set
            {
                if (BaseGet(index) != null)
                {
                    BaseRemoveAt(index);
                }
                BaseAdd(index, value);
            }
        }

        public void Add(LaneConfigElement serviceConfig)
        {
            BaseAdd(serviceConfig);
        }

        public void Clear()
        {
            BaseClear();
        }

        protected override ConfigurationElement CreateNewElement()
        {
            return new LaneConfigElement();
        }

        protected override object GetElementKey(ConfigurationElement element)
        {
            return ((LaneConfigElement)element).Id;
        }

        public void Remove(LaneConfigElement serviceConfig)
        {
            BaseRemove(serviceConfig.Id);
        }

        public void RemoveAt(int index)
        {
            BaseRemoveAt(index);
        }

        public void Remove(String name)
        {
            BaseRemove(name);
        }

    }

ви можете помітити, що я встановив, що AddItemName = "Lane"ви можете вибрати все, що вам подобається, для вашого пункту запису колекції, я вважаю за краще використовувати "додати" за замовчуванням, але я змінив це лише заради цієї публікації.

Тепер усі наші вкладені Елементи були реалізовані, ми повинні об'єднати всі класи в класі, який повинен реалізувати System.Configuration.ConfigurationSection

CustomApplicationConfigSection

public class CustomApplicationConfigSection : System.Configuration.ConfigurationSection
    {
        private static readonly ILog log = LogManager.GetLogger(typeof(CustomApplicationConfigSection));
        public const string SECTION_NAME = "CustomApplicationConfig";

        [ConfigurationProperty("Credentials")]
        public CredentialsConfigElement Credentials
        {
            get
            {
                return base["Credentials"] as CredentialsConfigElement;
            }
        }

        [ConfigurationProperty("PrimaryAgent")]
        public ServerInfoConfigElement PrimaryAgent
        {
            get
            {
                return base["PrimaryAgent"] as ServerInfoConfigElement;
            }
        }

        [ConfigurationProperty("SecondaryAgent")]
        public ServerInfoConfigElement SecondaryAgent
        {
            get
            {
                return base["SecondaryAgent"] as ServerInfoConfigElement;
            }
        }

        [ConfigurationProperty("Site")]
        public SiteConfigElement Site
        {
            get
            {
                return base["Site"] as SiteConfigElement;
            }
        }

        [ConfigurationProperty("Lanes")]
        public LaneConfigCollection Lanes
        {
            get { return base["Lanes"] as LaneConfigCollection; }
        }
    }

Тепер ви бачите, що у нас є два властивості з ім'ям, PrimaryAgentі SecondaryAgentобидва мають один і той же тип, тепер ви легко зрозумієте, чому у нас був лише один клас реалізації проти цих двох елементів.

Перш ніж ви зможете використовувати цей нещодавно придуманий розділ конфігурації у вашому app.config (або web.config), вам просто потрібно повідомити програмі, що ви винайшли свій власний розділ конфігурації та приділити йому певну повагу, для цього вам потрібно додати наступні рядки у app.config (можливо, одразу після початку тегу root).

<configSections>
    <section name="CustomApplicationConfig" type="MyNameSpace.CustomApplicationConfigSection, MyAssemblyName" />
  </configSections>

ПРИМІТКА: MyAssemblyName має бути без .dll, наприклад, якщо ви збираєте ім'я файлу myDll.dll, тоді використовуйте myDll замість myDll.dll

для отримання цієї конфігурації використовуйте наступний рядок коду будь-де у вашій програмі

CustomApplicationConfigSection config = System.Configuration.ConfigurationManager.GetSection(CustomApplicationConfigSection.SECTION_NAME) as CustomApplicationConfigSection;

Я сподіваюсь, що вищезгаданий пост допоможе вам розпочати роботу зі складним видом спеціальних розділів конфігурації.

Щасливе кодування :)

**** Редагувати **** Щоб увімкнути LINQ, LaneConfigCollectionвам потрібно реалізуватиIEnumerable<LaneConfigElement>

І Додайте наступну реалізацію GetEnumerator

public new IEnumerator<LaneConfigElement> GetEnumerator()
        {
            int count = base.Count;
            for (int i = 0; i < count; i++)
            {
                yield return base.BaseGet(i) as LaneConfigElement;
            }
        }

для людей, які все ще плутаються з приводу того, як справді працює врожай, прочитайте цю приємну статтю

Дві ключові моменти, взяті з наведеної статті

це насправді не закінчує виконання методу. return return призупиняє виконання методу, і наступного разу, коли ви викликаєте його (для наступного значення перерахунку), метод буде продовжувати виконуватись з останнього виклику зворотного виходу. Думаю, це звучить трохи заплутано ... (Шей Фрідман)

Вихід не є особливістю виконання .Net. Це лише функція мови C #, яка компілюється компілятором C # у простий код ІЛ. (Ларс Корнеліуссен)


3
Дякуємо за наданий повний приклад, це дійсно дуже допомагає!
Джон Лейдегрен

46

Це загальний код для збирання конфігурації:

public class GenericConfigurationElementCollection<T> :   ConfigurationElementCollection, IEnumerable<T> where T : ConfigurationElement, new()
{
    List<T> _elements = new List<T>();

    protected override ConfigurationElement CreateNewElement()
    {
        T newElement = new T();
        _elements.Add(newElement);
        return newElement;
    }

    protected override object GetElementKey(ConfigurationElement element)
    {
        return _elements.Find(e => e.Equals(element));
    }

    public new IEnumerator<T> GetEnumerator()
    {
        return _elements.GetEnumerator();
    }
}

Після цього GenericConfigurationElementCollectionви можете просто використовувати його в розділі конфігурацій (це приклад з мого диспетчера):

public class  DispatcherConfigurationSection: ConfigurationSection
{
    [ConfigurationProperty("maxRetry", IsRequired = false, DefaultValue = 5)]
    public int MaxRetry
    {
        get
        {
            return (int)this["maxRetry"];
        }
        set
        {
            this["maxRetry"] = value;
        }
    }

    [ConfigurationProperty("eventsDispatches", IsRequired = true)]
    [ConfigurationCollection(typeof(EventsDispatchConfigurationElement), AddItemName = "add", ClearItemsName = "clear", RemoveItemName = "remove")]
    public GenericConfigurationElementCollection<EventsDispatchConfigurationElement> EventsDispatches
    {
        get { return (GenericConfigurationElementCollection<EventsDispatchConfigurationElement>)this["eventsDispatches"]; }
    }
}

Тут налаштовано елемент Config:

public class EventsDispatchConfigurationElement : ConfigurationElement
{
    [ConfigurationProperty("name", IsRequired = true)]
    public string Name
    {
        get
        {
            return (string) this["name"];
        }
        set
        {
            this["name"] = value;
        }
    }
}

Конфігураційний файл виглядатиме так:

<?xml version="1.0" encoding="utf-8" ?>
  <dispatcherConfigurationSection>
    <eventsDispatches>
      <add name="Log" ></add>
      <add name="Notification" ></add>
      <add name="tester" ></add>
    </eventsDispatches>
  </dispatcherConfigurationSection>

Сподіваюся, це допоможе!


Класно! Думав про те саме і виявив, що я не один. Бажаю MS реалізувати це для всіх
конфігурацій

Будь-яка пропозиція щодо того, як це зробити з BasicMap для елементів? Я не хочу впроваджувати Додати, якщо я можу цього уникати.
SpaceCowboy74

28

Простіша альтернатива для тих, хто вважає за краще не писати всю конфігураційну плиту вручну ...

1) Встановіть Nerdle.AutoConfig від NuGet

2) Визначте тип ServiceConfig (або конкретний клас, або просто інтерфейс, або зробимо)

public interface IServiceConfiguration
{
    int Port { get; }
    ReportType ReportType { get; }
}

3) Вам потрібен тип для зберігання колекції, наприклад

public interface IServiceCollectionConfiguration
{
    IEnumerable<IServiceConfiguration> Services { get; } 
}

4) Додайте розділ конфігурації так (зверніть увагу на іменування camelCase)

<configSections>
  <section name="serviceCollection" type="Nerdle.AutoConfig.Section, Nerdle.AutoConfig"/>
</configSections>

<serviceCollection>
  <services>
    <service port="6996" reportType="File" />
    <service port="7001" reportType="Other" />
  </services>
</serviceCollection>

5) Карта з AutoConfig

var services = AutoConfig.Map<IServiceCollectionConfiguration>();

5
Дякую Богові за цю відповідь
Svend

Для людей, які просто хочуть це зробити, а не обов’язково створювати все з нуля, це справжня відповідь :)
CodeTentist

5

Спробуйте успадкувати від ConfigurationSection . Цей приклад у блозі Філа Хака є прикладом.

Підтверджено відповідно до документації для IConfigurationSectionHandler :

У .NET Framework версії 2.0 і вище вам потрібно замість цього походити з класу ConfigurationSection для реалізації відповідного обробника розділу конфігурації.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.