So implementieren Sie einen ConfigurationSection mit einer ConfigurationElementCollection

166

Ich versuche, einen benutzerdefinierten Konfigurationsabschnitt in einem Projekt zu implementieren, und stoße immer wieder auf Ausnahmen, die ich nicht verstehe. Ich hoffe, dass hier jemand die Lücken füllen kann.

Ich habe App.configdas sieht so aus:

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

Ich habe ein ServiceConfigElement wie folgt definiert:

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

Und ich habe so ServiceCollectiondefiniert:

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

Der Teil, den ich vermisse, ist, was für den Handler zu tun ist. Ursprünglich habe ich versucht, ein zu implementieren, IConfigurationSectionHandleraber zwei Dinge gefunden:

  1. es hat nicht funktioniert
  2. es ist veraltet.

Ich bin jetzt völlig verloren, was ich tun soll, damit ich meine Daten aus der Konfiguration lesen kann. Jede Hilfe bitte!

Chris Holmes
quelle
Ich kann das nicht zum Laufen bringen. Ich würde gerne RT.Core.Config.ServicesSection sehen. Ich erhalte nur das nicht erkannte Element 'AddService', obwohl ich auch den Code aus der akzeptierten Antwort verwende.
Sirdank
Das habe ich auch zuerst verpasst - dieser Teil: [ConfigurationCollection (typeof (ServiceCollection), AddItemName = "add", ClearItemsName = "clear", RemoveItemName = "remove")] Der AddItemName muss übereinstimmen, wenn Sie "add" in geändert haben "addService" würde es funktionieren
HeatherD

Antworten:

188

Die vorherige Antwort ist richtig, aber ich gebe Ihnen auch den gesamten Code.

Ihre app.config sollte folgendermaßen aussehen:

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

Ihre ServiceConfig und ServiceCollectionKlassen bleiben unverändert.

Du brauchst eine neue Klasse:

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

Und das sollte den Trick machen. Um es zu konsumieren, können Sie verwenden:

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

ServiceConfig serviceConfig = serviceConfigSection.Services[0];
Russell McClure
quelle
10
Die [Add|Remove|Clear]ItemNameEigenschaften des ConfigurationCollectionAttributs sind in diesem Fall nicht wirklich erforderlich, da "add" / "clear" / "remove" bereits die Standardnamen der XML-Elemente sind.
Wim Coenen
2
Wie kann ich dafür sorgen, dass die Tags nicht hinzugefügt werden? Es scheint nur zu funktionieren, wenn sie hinzugefügt werden. Es würde nicht funktionieren, wenn es <Service Port = "6996" ReportType = "File" /> oder <Service Port = "7001" ReportType = "Other" /> wäre
JonathanWolfson
7
@ JonathanWolfson: Ändern Sie einfach AddItemName = "add" in AddItemName = "Service"
Mubashar
Ist dies immer noch der Ansatz für .NET 4.5?
Crush
6
@crush: Ja, nicht viel ändert sich in dieser staubigen Ecke von .NET.
Russell McClure
84

Wenn Sie nach einem benutzerdefinierten Konfigurationsabschnitt wie dem folgenden suchen

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

Dann können Sie meinen Abschnitt zur Implementierung der Konfiguration verwenden, um loszulegen System.Configuration Assemblyverweis auf Ihr Projekt

Schauen Sie sich die einzelnen verschachtelten Elemente an, die ich verwendet habe. Das erste sind Anmeldeinformationen mit zwei Attributen. Fügen Sie sie also zuerst hinzu

Anmeldeinformationen Element

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

Beide haben dieselben Attribute und scheinen eine Adresse für eine Reihe von Servern für ein primäres und ein Failover zu sein. Sie müssen also nur eine Elementklasse für beide erstellen, wie die folgenden

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

Ich werde später in diesem Beitrag erklären, wie zwei verschiedene Elemente mit einer Klasse verwendet werden. Lassen Sie uns die SiteId überspringen, da es keinen Unterschied gibt. Sie müssen nur eine Klasse wie oben mit nur einer Eigenschaft erstellen. Lassen Sie uns sehen, wie die Lanes-Sammlung implementiert wird

Es ist in zwei Teile geteilt. Zuerst müssen Sie eine Elementimplementierungsklasse erstellen, dann müssen Sie eine Sammlungselementklasse erstellen

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
    }

Sie können feststellen, dass ein Attribut von LanElementeine Aufzählung ist. Wenn Sie versuchen, einen anderen Wert in der Konfiguration zu verwenden, der nicht in der Aufzählungsanwendung definiert ist, wird System.Configuration.ConfigurationErrorsExceptionbeim Start ein Wert ausgelöst. Ok, fahren wir mit der Sammlungsdefinition fort

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

    }

Sie können feststellen, dass ich festgelegt habe, dass AddItemName = "Lane"Sie für Ihr Sammlungseintragselement auswählen können, was Sie möchten. Ich bevorzuge die Verwendung des Standardelements "Hinzufügen", aber ich habe es nur für diesen Beitrag geändert.

Jetzt sind alle unsere verschachtelten Elemente implementiert. Jetzt sollten wir alle Elemente in einer Klasse zusammenfassen, die implementiert werden muss 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; }
        }
    }

Jetzt können Sie sehen, dass wir zwei Eigenschaften mit Namen PrimaryAgentund habenSecondaryAgent beide denselben Typ haben. Jetzt können Sie leicht verstehen, warum wir nur eine Implementierungsklasse für diese beiden Elemente hatten.

Bevor Sie diesen neu erfundenen Konfigurationsabschnitt in Ihrer app.config (oder web.config) verwenden können, müssen Sie Ihrer Anwendung nur mitteilen, dass Sie Ihren eigenen Konfigurationsabschnitt erfunden haben, und ihm etwas Respekt geben. Dazu müssen Sie die folgenden Zeilen hinzufügen in app.config (möglicherweise direkt nach dem Start des Root-Tags).

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

HINWEIS : MyAssemblyName sollte ohne DLL sein. Wenn der Name Ihrer Assemblydatei beispielsweise myDll.dll lautet, verwenden Sie myDll anstelle von myDll.dll

Um diese Konfiguration abzurufen, verwenden Sie die folgende Codezeile an einer beliebigen Stelle in Ihrer Anwendung

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

Ich hoffe, der obige Beitrag würde Ihnen helfen, mit etwas komplizierten benutzerdefinierten Konfigurationsabschnitten zu beginnen.

Viel Spaß beim Codieren :)

**** Bearbeiten **** Um LINQ zu aktivieren LaneConfigCollection, müssen Sie implementierenIEnumerable<LaneConfigElement>

Und nach folgender Implementierung von hinzufügen GetEnumerator

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

Für die Leute, die immer noch verwirrt sind, wie Ertrag wirklich funktioniert, lesen Sie diesen schönen Artikel

Zwei wichtige Punkte aus dem obigen Artikel sind

Die Ausführung der Methode wird dadurch nicht wirklich beendet. Yield Return pausiert die Methodenausführung und beim nächsten Aufruf (für den nächsten Aufzählungswert) wird die Methode ab dem letzten Yield Return-Aufruf weiter ausgeführt. Es klingt ein bisschen verwirrend, denke ich ... (Shay Friedman)

Der Ertrag ist kein Merkmal der .Net-Laufzeit. Es ist nur eine C # -Sprachenfunktion, die vom C # -Compiler in einfachen IL-Code kompiliert wird. (Lars Corneliussen)

Mubashar
quelle
3
Vielen Dank für das vollständige Beispiel, das hilft wirklich sehr!
John Leidegren
46

Dies ist generischer Code für die Konfigurationssammlung:

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

Nachdem Sie dies getan haben GenericConfigurationElementCollection, können Sie es einfach im Konfigurationsabschnitt verwenden (dies ist ein Beispiel aus meinem 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"]; }
    }
}

Das Konfigurationselement ist config Here:

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

Die Konfigurationsdatei würde folgendermaßen aussehen:

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

Hoffe es hilft!

Mzf
quelle
Cool! Ich habe darüber nachgedacht und festgestellt, dass ich nicht allein bin. Wünschen Sie, dass MS dies für alle FCL-Konfigurationen implementiert
abatishchev
Irgendwelche Vorschläge, wie man das mit einer BasicMap für die Items macht? Ich möchte Add nicht implementieren, wenn ich es vermeiden kann.
SpaceCowboy74
28

Eine einfachere Alternative für diejenigen, die es vorziehen, das gesamte Konfigurations-Boilerplate nicht manuell zu schreiben ...

1) Installieren Sie Nerdle.AutoConfig von NuGet

2) Definieren Sie Ihren ServiceConfig-Typ (entweder eine konkrete Klasse oder nur eine Schnittstelle).

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

3) Sie benötigen einen Typ, um die Sammlung zu speichern, z

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

4) Fügen Sie den Konfigurationsabschnitt wie folgt hinzu (beachten Sie die Benennung von 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) Karte mit AutoConfig

var services = AutoConfig.Map<IServiceCollectionConfiguration>();
Angst vor einem Hackplaneten
quelle
5
Gott sei Dank für diese Antwort
Svend
Für Leute, die es einfach nur schaffen wollen und nicht unbedingt alles von Grund auf neu erstellen wollen, ist dies die wahre Antwort :)
CodeThief
5

Versuchen Sie, von ConfigurationSection zu erben . Dieser Blog-Beitrag von Phil Haack hat ein Beispiel.

Bestätigt gemäß der Dokumentation für IConfigurationSectionHandler :

In .NET Framework Version 2.0 und höher müssen Sie stattdessen von der ConfigurationSection-Klasse ableiten, um den zugehörigen Handler für Konfigurationsabschnitte zu implementieren.

Jeff Ogata
quelle