Как реализовать ConfigurationSection с помощью ConfigurationElementCollection

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

У меня есть 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, но обнаружил две вещи:

  • он не работал.
  • он устарел.

Теперь я полностью потерял, что делать, чтобы читать свои данные из config. Любая помощь пожалуйста!

Ответ 1

Предыдущий ответ верен, но я также дам вам весь код.

Ваш 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];

Ответ 2

Если вы ищете настраиваемый раздел конфигурации, например, следующий

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

PrimaryAgent и SecondaryAgent

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

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, поскольку в нем нет никакой разницы. Вам просто нужно создать один класс, как указано выше, только с одним свойством. посмотрим, как реализовать коллекцию Lanes

он разбивается на две части, сначала вам нужно создать класс реализации элемента, тогда вам нужно создать класс элемента коллекции

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 - это перечисление, и если вы пытаетесь использовать любое другое значение в конфигурации, которое не определено в приложении Enumeration, то при запуске будет запускаться System.Configuration.ConfigurationErrorsException. Ok позволяет перейти к определению коллекции

[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(может быть сразу после начала корневого тега).

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

для людей, которые все еще путаются о том, как доход действительно работает, прочитайте эту приятную статью

Два ключевых пункта, взятых из вышеприведенной статьи,

он действительно не заканчивает выполнение методов. yield возвращает паузу и в следующий раз, когда вы его назовете (для следующего значение перечисления), метод будет продолжать выполняться с последнего return return call. Это звучит немного запутанно, я думаю... (Шей Фридман)

Доходность не является особенностью .Net runtime. Это всего лишь язык С# которая компилируется в простой код IL компилятором С#. (Lars Corneliussen)

Ответ 3

Это общий код для сбора конфигурации:

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

Конфигурируемый элемент конфигурации Здесь:

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>

Надеюсь, что это поможет!

Ответ 4

Более простая альтернатива для тех, кто предпочел бы не писать все эти шаблоны конфигурации вручную...

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

Попробуйте наследовать от ConfigurationSection. В этом блоге от Фила Хаака есть пример.

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

В .NET Framework версии 2.0 и выше вы должны вместо этого вывести из класса ConfigurationSection реализацию соответствующего обработчика раздела конфигурации.