Erstellen von benutzerdefinierten Konfigurationsabschnitten mit ConfigurationSection

Im folgenden Beispiel wird gezeigt, wie eine Auflistung in einem benutzerdefinierten Konfigurationsabschnitt mit Hilfe der Klasse ConfigurationSection ausgelesen werden kann.

In einem vorherigen Blog habe ich schon gezeigt, wie mit Hilfe des Interfaces IConfigurationSectionHandler auf eigene Bereiche der Konfiguratuionsdatei zugegriffen werden kann. Auch habe ich darauf hingewiesen, dass es seit .NET 2.0 eine weitere Möglichkeit gibt, nämlich mit Hilfe der Klasse System.Configuration.ConfigurationSection. Es finden sich reichlich Beispiele im Netz, in denen gezeigt wird, wie einfach es ist, ein Wert mit diversen Attributen auszulesen. Hierzu brauchen nur die Eigenschaften der Klasse, die von ConfigurationSection abgeleitet ist, mit bestimmten Attributen dekoriert werden. Schwieriger wird die Sache, sobald eine Auflistung von Werten aus der Konfigurationsdatei gelesen werden soll. Genau dieser Fall soll näher an einem Beispiel erklärt werden. Hier erstmal die Konfigurationsdatei:

<?xml version="1.0"?>
<configuration>
  <configSections>
    <section name="mySection"
             type="MySection.MyConfigurationSection, MyConfigurationSection"/>
  </configSections>
  <mySection>
    <values>
      <value id="1" valueAttribute="valAtt01" newValue="11.1" oldValue="1.1"/>
      <value id="2" valueAttribute="valAtt02" newValue="22.2" oldValue="2.2"/>
      <value id="3" valueAttribute="valAtt03" newValue="33.3" oldValue="3.3"/>
    </values>
  </mySection>
</configuration>

Ein Element wird mit Hilfe der Klasse System.Configuration.ConfigurationElement abgebildet. Da diese Klasse abstrakt ist, muss eine eigene Klasse von dieser abgeleitet werden. Durch die Vererbung erhält die Klasse eine Auflistung vom Typ System.Configuration.ConfigurationPropertyCollection. Diese Auflistung enthält Elemente vom Typ System.Configuration.ConfigurationProperty. Diese Auflistung enthält entweder die Elementattribute (wie in diesem Beispiel) oder untergeordnete Elemente. Dadurch könnte die XML-Struktur weiter verschachtelt werden.
Wie erfolgt jetzt die Zuordnung der einzelnen Attribute des XML-Elements zu der Klasse, die von ConfigurationElement
abgeleitet wurde? Ganz einfach: durch Eigenschaften. Jedes XML-Attribut wird einer Eigenschaft zugeordnet. Diese Zuordnung kann auf zwei verschiedene Weisen erfolgen:

  • programmgesteuertes Modell
  • deklaratives Modell

Bei dem programmgesteuerten Modell ist jedes Property ein Objekt der Klasse ConfigurationProperty. Über die Parameter im Konstruktor werden die einzelnen Eigenschaften (z.B. Datentyp) festgelegt. Anschliessend muss über die Add-Methode einer Klasse vom Typ ConfigurationPropertyCollection diese Eigenschaft bekannt gemacht werden.
Das deklarative Modell ist deutlich handlicher, da es sich die Möglichkeiten von Attributen und Reflektion zu Nutze macht. Auch hier muss für jedes Attribut des XML-Elements eine Eigenschaft angelegt werden. Die Eigenschaften werden dann mit dem .NET Attribut ConfigurationPropertyAttribute versehen. Optional kann noch ein Validator-Attribut (StringValidator, IntegerValidator, …) hinzugefügt werden. Entspricht ein eingelesener Wert nicht den Vorgaben, so wird automatisch eine Ausnahme ausgelöst. Das .NET Konfigurationssystem erhält auf diese Weise die notwendigen Informationen und kann die erforderlichen Initialisierungen durchführen.
Im folgenden Listing ist die Klasse, die jedes Element der Auflistung representiert. In der Konfigurationsdatei ist es das XML-Element mit den entsprechenden XML-Attributen. Die Klasse muss von ConfigurationElement abgeleitet werden.

using System;
using System.Configuration;

namespace MySection
{
  class SectionValue : ConfigurationElement
  {
    [ConfigurationProperty("id", DefaultValue = "1", IsRequired = true, IsKey = true)]
    [IntegerValidator(MinValue = 1, MaxValue = 1000, ExcludeRange = false)]
    public int Id
    {
      get { return (base["id"] == null) ? (int)0 : (int)base["id"]; }
      set { base["id"] = value; }
    }

    [ConfigurationProperty("valueAttribute", IsRequired = true, IsKey = false)]
    [StringValidator(InvalidCharacters = "~!@#$%^&*", MinLength = 0, MaxLength = 60)]
    public string Attribute
    {
      get { return base["valueAttribute"] as string; }
      set { base["valueAttribute"] = value; }
    }

    [ConfigurationProperty("newValue", IsRequired = true, IsKey = false)]
    public double NewValue
    {
      get { return (base["newValue"] == null) ? (double)0 : (double)base["newValue"]; }
      set { base["newValue"] = value; }
    }

    [ConfigurationProperty("oldValue", IsRequired = true, IsKey = false)]
    public double OldValue
    {
      get { return (base["oldValue"] == null) ? (double)0 : (double)base["oldValue"]; }
      set { base["oldValue"] = value; }
    }
  }
}

Eine Auflistung von ConfigurationElement-Objekten (bzw. dessen Ableitung) wird durch eine Klasse realisiert, die von System.Configuration.ConfigurationElementCollection abgeleitet sein muss. Die Klasse stellt einige Methoden und Eigenschaften zur Verfügung, die überschrieben werden müssen:

  • void CreateNewElement()
  • object GetElementKey(ConfigurationElement element)
  • string ElementName
  • ConfigurationElementColletionType CollectionType

Zusätzlich sollte noch ein Indexer implementiert werden. Dieser ist notwendig, um gezielt per Index auf einzelne Elemente der Auflistung zugreifen zu können.

using System;
using System.Configuration;

namespace MySection
{
  class SectionValueCollection : ConfigurationElementCollection
  {
    protected override ConfigurationElement CreateNewElement()
    {
      return new SectionValue();
    }

    protected override object GetElementKey(ConfigurationElement element)
    {
      return ((SectionValue)element).Id;
    }

    protected override string ElementName
    {
      get { return "value"; }
    }

    public override ConfigurationElementCollectionType CollectionType
    {
      get { return ConfigurationElementCollectionType.BasicMap; }
    }

    public SectionValue this[int index]
    {
      get { return base.BaseGet(index) as SectionValue; }
    }
  }
}

Jetzt noch die Klasse, die den benutzerdefinierten Abschnitt in der Konfigurationsdatei darstellt. Diese Klasse muss von System.Configuration.ConfigurationSection abgeleitet sein. Die Klasse enthält nur eine Eigenschaft;  SectionValue vom Typ SectionValueCollection. In der Konfigurationsdatei entspricht dieses dem XML-Element . Da ConfigurationSection von ConfigurationElement abgeleitet ist, erbt diese eine Auflistung vom Typ System.Configuration.ConfigurationPropertyCollection (siehe oben). In dieser Auflistung wird ein Element abgespeichert und zwar die Auflistung vom Typ SectionValueCollection.

using System;
using System.Configuration;

namespace MySection
{
  class MyConfigurationSection : ConfigurationSection
  {
    [ConfigurationProperty("values", IsDefaultCollection = true, IsRequired = true)]
    public SectionValueCollection SectionValues
    {
      get { return (SectionValueCollection)this["values"] ?? new SectionValueCollection(); }
      set { base[""] = value; }
    }
  }
}

Fertig! Zum Schluss noch ein Beispiel in der die Anwendung gezeigt wird. Die Klasse ConfigurationManager stellt die statische Methode GetSection(string sectionName) zur Verfügung. Der Bereich mySection wird somit typsicher aus der Konfigurationsdatei ausgelesen.

MyConfigurationSection myConfigurationSection = (MyConfigurationSection)ConfigurationManager.GetSection("mySection");
listViewSection.Items.Clear();
foreach (SectionValue sectionValue in myConfigurationSection.SectionValues)
{
  ListViewItem listItem = new ListViewItem(sectionValue.Id.ToString());
  listItem.SubItems.Add(sectionValue.Attribute);
  listItem.SubItems.Add(sectionValue.OldValue.ToString());
  listItem.SubItems.Add(sectionValue.NewValue.ToString());
  listViewSection.Items.Add(listItem);
}

Statt einer Iteration kann auch gezielt auf einzelne Elemente zugegriffen werden. Dazu ist in der Klasse SectionValueCollection der (nullbasierte) Index zu implemetieren (siehe oben Zeile 24 … 27).

SectionValue sectionValue = myConfigurationSection.SectionValues[2];

Mit einigen kleineren Änderungen kann auch mit einem Schüssel gearbeitet werden. Dazu muss in der Klasse SectionValue das Property Id von einem Referenztyp sein, z.B. string.

[ConfigurationProperty("id", DefaultValue = "1", IsRequired = true, IsKey = true)]
[StringValidator(InvalidCharacters = "~!@#$%^&*", MinLength = q, MaxLength = 60)]
public string Id
{
  get { return base["id"] as string; }
  set { base["id"] = value; }
}

Der zusätzliche Indexer muss in SectionValueCollection implementiert werden:

public SectionValue this[string key]
{
  get { return base.BaseGet(key) as SectionValue; }
}

Der Zugriff per Schlüssel sieht dann wie folgt aus:

SectionValue sectionValue = myConfigurationSection.SectionValues["2"];

Author: Stefan Henneken

I’m Stefan Henneken, a software developer based in Germany. This blog is just a collection of various articles I want to share, mostly related to Software Development.

One thought on “Erstellen von benutzerdefinierten Konfigurationsabschnitten mit ConfigurationSection”

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: