Как реализовать 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, несмотря на использование кода из принятого ответа.
Сирданк
Сначала я тоже это пропустил - эта часть: [ConfigurationCollection (typeof (ServiceCollection), AddItemName = "add", ClearItemsName = "clear", RemoveItemName = "remove")] 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атрибута не являются необходимыми, поскольку «add» / «clear» / «remove» уже являются именами по умолчанию элементов XML.
Вим Коенен
2
Как сделать так, чтобы теги не добавлялись? Это только кажется, что работает, если они добавить. Это не сработало бы, если бы это было <Service Port = "6996" ReportType = "File" /> или <Service Port = "7001" ReportType = "Other" />
JonathanWolfson
7
@JonathanWolfson: просто измените AddItemName = "add" на AddItemName = "Service"
Мубашар
Это все еще подход для .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ссылку на сборку в ваш проект

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

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

Удачного кодирования :)

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

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

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

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

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

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

MZF
источник
Прохладно! Думал о том же и обнаружил, что я не одинок. Желаю MS реализовать это для всех
конфигов
Любое предложение о том, как сделать это с BasicMap для предметов? Я не хочу реализовывать Add, если я могу избежать этого.
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>();
fearofawhackplanet
источник
5
Слава Богу за этот ответ
Свен
Для людей, которые просто хотят сделать это и не обязательно создавать все с нуля, это реальный ответ :)
CodeThief
5

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

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

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

Джефф Огата
источник