Как реализовать ConfigurationSection с помощью ConfigurationElementCollection - PullRequest
153 голосов
/ 14 октября 2010

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

У меня есть 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. устарело.

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

Ответы [ 5 ]

179 голосов
/ 14 октября 2010

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

Ваш 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];
75 голосов
/ 10 октября 2013

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

<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 ссылки на сборку для вашего проекта

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

Элемент учетных данных

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 является перечислением, и если вы попытаетесь использовать любое другое значение в конфигурации, которое не определено в приложении перечисления, при запуске будет выдано 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 (может быть сразу после запуска корневого тега).

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

Надеюсь, вышеприведенный пост поможет вам начать с немного сложного вида пользовательских разделов конфигурации.

Happy Coding:)

**** **** Редактировать Чтобы включить 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, прочитайте эту замечательную статью

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

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

Выход не является функцией среды выполнения .Net. Это просто язык C # функция, которая компилируется в простой код IL компилятором C #. (Ларс Корнелиусен)

46 голосов
/ 17 октября 2010

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

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, вы можете просто использовать его в разделе конфигурации (это пример из моего Dispatcher):

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>

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

22 голосов
/ 17 февраля 2017

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

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>();
4 голосов
/ 14 октября 2010

Попробуйте наследовать от ConfigurationSection . Это сообщение в блоге от Phil Haack содержит пример.

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

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

...