Jak zaimplementować ConfigurationSection z ConfigurationElementCollection

166

Próbuję zaimplementować sekcję konfiguracji niestandardowej w projekcie i ciągle napotykam na wyjątki, których nie rozumiem. Mam nadzieję, że ktoś może tutaj wypełnić puste miejsca.

Mam App.configto tak:

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

Mam taki ServiceConfigelement zdefiniowany:

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

I mam takie ServiceCollectionzdefiniowane:

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

Brakuje mi tego, co zrobić dla przewodnika. Początkowo próbowałem zaimplementować, IConfigurationSectionHandlerale znalazłem dwie rzeczy:

  1. to nie zadziałało
  2. jest przestarzały.

Całkowicie zagubiłem się teraz w tym, co mam zrobić, aby móc odczytać moje dane z konfiguracji. Proszę o pomoc!

Chris Holmes
źródło
Nie mogę sprawić, żeby to zadziałało. Bardzo chciałbym zobaczyć RT.Core.Config.ServicesSection. Po prostu otrzymuję Nierozpoznany element „AddService” pomimo użycia kodu z zaakceptowanej odpowiedzi.
sirdank
Na początku też to przegapiłem - ta część: [ConfigurationCollection (typeof (ServiceCollection), AddItemName = "add", ClearItemsName = "clear", RemoveItemName = "remove")] AddItemName musi się zgadzać, więc jeśli zmienisz "add" na „addService” to zadziała
HeatherD

Odpowiedzi:

188

Poprzednia odpowiedź jest prawidłowa, ale podam Ci również cały kod.

Twój plik app.config powinien wyglądać tak:

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

Twoje ServiceConfigi ServiceCollectionklasy pozostają niezmienione.

Potrzebujesz nowej klasy:

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

I to powinno załatwić sprawę. Aby go skonsumować, możesz użyć:

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

ServiceConfig serviceConfig = serviceConfigSection.Services[0];
Russell McClure
źródło
10
Te [Add|Remove|Clear]ItemNamewłaściwości na ConfigurationCollectionatrybucie nie są naprawdę konieczne w tym przypadku, ponieważ „dodać” / „clear” / „Usuń” są już nazwy domyślnych elementów XML.
Wim Coenen
2
Jak mogę sprawić, by działało, aby tagi nie były dodawane? Wydaje się, że działa tylko wtedy, gdy są dodawane. Nie działałoby, gdyby był to <Service Port = "6996" ReportType = "File" /> lub <Service Port = "7001" ReportType = "Other" />
JonathanWolfson
7
@JonathanWolfson: po prostu zmień AddItemName = "add" na AddItemName = "Service"
Mubashar
Czy jest to nadal podejście do .NET 4.5?
zmiażdżyć
6
@crush: tak, niewiele zmian w tym zakurzonym zakątku .NET.
Russell McClure
84

Jeśli szukasz sekcji konfiguracji niestandardowej, takiej jak poniżej

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

następnie możesz użyć mojej implementacji sekcji konfiguracji, aby rozpocząć dodawanie System.Configurationodwołania do zestawu do projektu

Spójrz na każdy zagnieżdżony element, którego użyłem. Pierwszy to Credentials z dwoma atrybutami, więc dodajmy go najpierw

Element poświadczeń

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 i SecondaryAgent

Oba mają te same atrybuty i wyglądają jak adres do zestawu serwerów dla serwera podstawowego i przełączania awaryjnego, więc wystarczy utworzyć jedną klasę elementów dla obu, takich jak następujące

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

W dalszej części tego postu wyjaśnię, jak używać dwóch różnych elementów z jedną klasą, pomińmy SiteId, ponieważ nie ma w nim różnicy. Musisz tylko utworzyć jedną klasę, taką samą jak powyżej, z tylko jedną właściwością. zobaczmy, jak wdrożyć kolekcję Lanes

jest podzielony na dwie części, najpierw należy utworzyć klasę implementacji elementu, a następnie utworzyć klasę elementu kolekcji

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
    }

możesz zauważyć, że jeden atrybut LanElementjest wyliczeniem i jeśli spróbujesz użyć jakiejkolwiek innej wartości w konfiguracji, która nie jest zdefiniowana w aplikacji wyliczającej, System.Configuration.ConfigurationErrorsExceptionprzy uruchomieniu zostanie wyrzucona . Ok, przejdźmy do definicji zbioru

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

    }

możesz zauważyć, że ustawiłem, AddItemName = "Lane"że możesz wybrać, co chcesz dla elementu wpisu do kolekcji, wolę użyć opcji „dodaj” jako domyślną, ale zmieniłem to tylko ze względu na ten post.

Teraz wszystkie nasze zagnieżdżone elementy zostały zaimplementowane, teraz powinniśmy zagregować je w klasie, która ma zostać zaimplementowana 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; }
        }
    }

Teraz możesz zobaczyć, że mamy dwie właściwości o nazwie PrimaryAgenti SecondaryAgentobie mają ten sam typ, teraz możesz łatwo zrozumieć, dlaczego mieliśmy tylko jedną klasę implementacji dla tych dwóch elementów.

Zanim będziesz mógł użyć tej nowo wynalezionej sekcji konfiguracji w swoim app.config (lub web.config), musisz tylko powiedzieć aplikacji, że stworzyłeś własną sekcję konfiguracji i dać jej trochę szacunku, aby to zrobić musisz dodać następujące wiersze w app.config (może znajdować się zaraz po rozpoczęciu tagu głównego).

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

UWAGA: MyAssemblyName powinno być bez .dll, np. Jeśli nazwa pliku zestawu to myDll.dll, użyj myDll zamiast myDll.dll

aby pobrać tę konfigurację, użyj następującego wiersza kodu w dowolnym miejscu aplikacji

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

Mam nadzieję, że powyższy post pomoże ci zacząć od nieco skomplikowanych niestandardowych sekcji konfiguracyjnych.

Miłego kodowania :)

**** Edytuj **** Aby włączyć LINQ LaneConfigCollection, musisz zaimplementowaćIEnumerable<LaneConfigElement>

I dodaj następującą implementację GetEnumerator

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

dla ludzi, którzy wciąż nie wiedzą, jak naprawdę działa wydajność, przeczytaj ten fajny artykuł

Dwie kluczowe kwestie zaczerpnięte z powyższego artykułu to

tak naprawdę nie kończy wykonywania metody. yield return wstrzymuje wykonywanie metody, a przy następnym wywołaniu jej (dla następnej wartości wyliczenia) metoda będzie nadal wykonywana od ostatniego wywołania funkcji yield return. Wydaje mi się, że to trochę zagmatwane… (Shay Friedman)

Wydajność nie jest cechą środowiska wykonawczego .Net. Jest to po prostu funkcja języka C #, która jest kompilowana do prostego kodu IL przez kompilator C #. (Lars Corneliussen)

Mubaszar
źródło
3
Dzięki za dostarczenie pełnego przykładu, to naprawdę bardzo pomaga!
John Leidegren
46

Oto ogólny kod do zbierania konfiguracji:

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

Gdy już to zrobisz GenericConfigurationElementCollection, możesz po prostu użyć go w sekcji konfiguracji (to jest przykład z mojego Dispatchera):

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

Element konfiguracyjny to config tutaj:

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

Plik konfiguracyjny wyglądałby tak:

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

Mam nadzieję, że to pomoże!

Mzf
źródło
Chłodny! Myślałem o tym samym i stwierdziłem, że nie jestem sam. Życzę MS wdrożenia tego dla wszystkich konfiguracji FCL
abatishchev
Jakieś sugestie, jak to zrobić z podstawową mapą elementów? Nie chcę wdrażać Dodaj, jeśli mogę tego uniknąć.
SpaceCowboy74
28

Łatwiejsza alternatywa dla tych, którzy woleliby nie pisać ręcznie wszystkich tych schematów konfiguracji ...

1) Zainstaluj Nerdle.AutoConfig z NuGet

2) Zdefiniuj typ ServiceConfig (albo konkretna klasa, albo tylko interfejs)

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

3) Będziesz potrzebować typu do przechowywania kolekcji, np

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

4) Dodaj sekcję konfiguracji w ten sposób (zwróć uwagę na nazewnictwo 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) Mapa z funkcją AutoConfig

var services = AutoConfig.Map<IServiceCollectionConfiguration>();
strachofawhackplanet
źródło
5
Dzięki Bogu za tę odpowiedź
Svend
Dla ludzi, którzy chcą to zrobić i niekoniecznie tworzyć wszystko od zera, to jest prawdziwa odpowiedź :)
CodeThief
5

Spróbuj dziedziczyć po ConfigurationSection . Ten post na blogu autorstwa Phila Haacka zawiera przykład.

Potwierdzono, zgodnie z dokumentacją dla IConfigurationSectionHandler :

W programie .NET Framework w wersji 2.0 i nowszych należy zamiast tego wywodzić się z klasy ConfigurationSection, aby zaimplementować powiązaną procedurę obsługi sekcji konfiguracji.

Jeff Ogata
źródło