[Tutorial] Konfigurationsmodell im .NET Framework

Noodles
Das neue Konfigurationsmodell im .NET Framework 2.0

Im .NET Framwork 1.1 können Konfigurationsdaten in der app.config gespeichert, aber nicht zur Laufzet geschrieben werden. Die Daten können über die Klasse ConfigurationSettings und deren Eigenschaft AppSettings abgerufen werden. Dies geschah über einen Indexer, was unter anderem den Nachteil hat, dass Laufzeitfehler durch Rechtschreibfehler entstehen konnten.
Weiterhin konnte man Daten nur lesen und nicht speichern.
Damit ist jetzt Schluß!
Im .NET Framework 2.0 können nun Anwendungsdaten und Benutzerdaten in einer Konfigurationsdatei gespeichert und Benutzerdaten auch geschrieben werden. Man sieht dies in dem folgenden Beispiel, einer Windows Forms Anwendung ( der Code befindet sich am Ende des Artikels ). Als erstes werden in den Projekteigenschaften, auf der Seite Settings, die Konfigurationsdaten angelegt.


 Volle Bildgröße

In diesem Beispiel ist es ein Connectionstring mit dem Scope Application und eine Color Eigenschaft mit dem Scope User. Die Scopes kennzeichnen Anwendungs- und Benutzerdaten.
In der app.config sieht man nun unter applicationSettings bzw. userSettings die Daten.

XML-Code:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <configSections>
        <sectionGroup name="userSettings" type="System.Configuration.UserSettingsGroup, System, Version=2.0.0.0, [...]" >
            <section name="MyCsharpConfigurationSample.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=2.0.0.0, [...]" />
        </sectionGroup>
        <sectionGroup name="applicationSettings" type="System.Configuration.ApplicationSettingsGroup, System, Version=2.0.0.0, [...]" >
            <section name="MyCsharpConfigurationSample.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=2.0.0.0, [...]" />
        </sectionGroup>
    </configSections>
    <userSettings>
        <MyCsharpConfigurationSample.Properties.Settings>
            <setting name="MyBackColor" serializeAs="String">
                <value>255; 255; 128</value>
            </setting>
        </MyCsharpConfigurationSample.Properties.Settings>
    </userSettings>
    <applicationSettings>
        <MyCsharpConfigurationSample.Properties.Settings>
            <setting name="NothwindConnectionString" serializeAs="String">
                <value>Data Source=(local);Initial Catalog=Northwind;Integrated Security=SSPI</value>
            </setting>
        </MyCsharpConfigurationSample.Properties.Settings>
    </applicationSettings>
</configuration>

Da in die app.config nicht geschrieben werden kann gibt es eine weitere Konfigurationsdatei, die user.config. Diese Datei findet man unter ( der Pfad ist für dieses Beispiel, daher kann es ab Anwendungsdaten abweichen )
C:\Dokumente und Einstellungen\UserName\Lokale Einstellungen\Anwendungsdaten\MyCsharpConfigurationSam\MyCsharpConfiguratio
nSamp_Url_heky24hmkwdp50ge24kv42daxdajmqcc\1.0.0.0
In dieser Datei werden nun die Daten gespeichert, die veränderbar sind, sich also im Scope User befinden.
Die Datei wird erst geschrieben, sobald die erste Eigenschaft gespeichert wird, werden die Daten nur gelesen, wird auch keine user.config geschrieben.
Das Visual Studio generiert nun die Klasse Settings ( ProjectNameSpace.Properties ), in dieser Klasse findet man die typisierten Eigenschaften.
Jetzt erstellen wir das Beispiel und sehen die, in den Settings angegeben Farbe als Hintergrundfarbe der Form und den Connectionstring als Titel ( ist etwas realitiätsfremd, aber es soll ja auch nur als Beispiel dienen. ).
In dem Click Ereignis des Buttons öffnet sich ein Colordialog.



Wird dieser mit Ok bestätigt, wird die ausgewählte Farbe in der user.config. gespeichtert.

XML-Code:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <userSettings>
        <MyCsharpConfigurationSample.Properties.Settings>
            <setting name="MyBackColor" serializeAs="String">
                <value>255; 128; 255</value>
            </setting>
        </MyCsharpConfigurationSample.Properties.Settings>
    </userSettings>
</configuration>

Anschließend laden wir Daten erneut ( Funktion: LoadConfigSettings() ), damit wir die Änderung sofort sehen.



Wenn man jetzt in der AssemblyInfo.cs die Versionsnummer der Assemblies verändert und das Beispiel erneut ausführt, sieht man, dass für die neue Version eine neue user.config Datei angelegt wird.

Der Code:

C#-Code:
namespace MyCsharpConfigurationSample
{
    public partial class Form1 : Form
    {
        MyCSharpConfigurationSample.Properties.Settings mySettings = null;

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            this.LoadConfigSettings();
        }

        private void LoadConfigSettings()
        {
            mySettings = new MyCSharpConfigurationSample.Properties.Settings();
            this.BackColor = mySettings.MyBackColor;
            this.Text = mySettings.NothwindConnectionString;
        }

        private void save_Click(object sender, EventArgs e)
        {
            if( colorDialog.ShowDialog() == DialogResult.OK )
            {
                mySettings.MyBackColor = colorDialog.Color;
                mySettings.Save();
                this.LoadConfigSettings();
            }
        }
    }
}
pegasus2
Vielen Dank für Deinen Artikel, hat mir die Zeit verkürzt Zugang zu dieser Thematik zu finden.

Bei mir funktioniert das Beispiel auch, kann aber leider keine Konfigurationsdatei finden (user.config), in der die geänderten Daten geschrieben werden. Habe bei mir nur eine Datei TEST_SETTINGS.EXE-17368469.pf im Verzeichnis Windows\Prefetch gefunden, die aber den XML-Vorgaben nicht entspricht.

Mir ist aufgefallen, das beim Start des Test_Settings\Test_Settings\obj\Debug\Test_Settings.exe Files andere Einstellungen gespeichert werden als wenn ich das Programm aus der Entwicklungsumgebung laufen lasse.

Gruß pegasus2
pegasus2
Zitat:
Original von pegasus2
Vielen Dank für Deinen Artikel, hat mir die Zeit verkürzt Zugang zu dieser Thematik zu finden.

Bei mir funktioniert das Beispiel auch, kann aber leider keine Konfigurationsdatei finden (user.config), in der die geänderten Daten geschrieben werden. Habe bei mir nur eine Datei TEST_SETTINGS.EXE-17368469.pf im Verzeichnis Windows\Prefetch gefunden, die aber den XML-Vorgaben nicht entspricht. Habe dazu die gesamte Festplatte nach Datum durchsucht. Seltsamerweise werden die Daten aber nach Neu-Start korrekt angezeigt.

Mir ist aufgefallen, das beim Start des Test_Settings\Test_Settings\obj\Debug\Test_Settings.exe Files andere Einstellungen gespeichert werden als wenn ich das Programm aus der Entwicklungsumgebung laufen lasse.

Gruß pegasus2

Ups! Da ist wohl was daneben gegangen.
KRambo
Hmm mir is das ganze noch ein wenig suspekt und einfach zuwenig flexibel. Warum sieht MS nicht mal ein, dass man (ich) den Pfad und Namen der Config selber festlegen können sollte?

Was nützt mir ne App.Config in nem Pfad, der wie der oben?
Da installiert man 20x .NET 2.0 Programme und hat dann nach der Deinstallation nur noch Mull rumliegen. Warum nich die Config in denselben Ordner wie das Assembly?

Ne also da benutz ich doch lieber meine eigene Klasse, die die Einstellungen handelt. Is zwar auch nicht wirklich flexibel, aber da kann ich obige Punkte selbst bestimmen...genau dasselbe is nämlich mit dem AutoUpdate Teil. Viel zu unflexibel, da hab ich als Programmierer halt gerne die Kontrolle was passiert. Die Teile sind total undurchsichtig unglücklich
Noodles
Zitat:
Original von pegasus2
Bei mir funktioniert das Beispiel auch, kann aber leider keine Konfigurationsdatei finden (user.config), in der die geänderten Daten geschrieben werden. Habe bei mir nur eine Datei TEST_SETTINGS.EXE-17368469.pf im Verzeichnis Windows\Prefetch gefunden, die aber den XML-Vorgaben nicht entspricht.

Was findest Du denn im Ordner:
C:\Dokumente und Einstellungen\<username>\Lokale Einstellungen\Anwendungsdaten

Zitat:
Original von pegasus2
Mir ist aufgefallen, das beim Start des Test_Settings\Test_Settings\obj\Debug\Test_Settings.exe Files andere Einstellungen gespeichert werden als wenn ich das Programm aus der Entwicklungsumgebung laufen lasse.

Erkläre das mal bitte genauer?
Es wird immer die zuletzt gespeicherte Farbe angezeigt.
Hast Du vielleicht die Configuration auf Release und nicht auf Debug stehen?
maxE
Zitat:
Was nützt mir ne App.Config in nem Pfad, der wie der oben?
Da installiert man 20x .NET 2.0 Programme und hat dann nach der Deinstallation nur noch Mull rumliegen. Warum nich die Config in denselben Ordner wie das Assembly?

Ganz einfach, weil das Verzeichnis, in dem eine Assembly installiert ist im Normalfall immer read only sein sollte. Schreibst du deine Configs ins Programmverzeichnis, dann läuft dein Prog nicht mehr mit eingeschränken Benutzerkonten. Das AppData Verzeichnis zu nutzen ist also durchaus sinnvoll.

Du glaubst nicht wie oft ich mich schon über schlecht programmierte Software aufgeregt habe, die ganau das macht und einfach nicht mit mehreren Benutzerkonten zurecht kommt. Es gibt leider immer noch Programmierer die meinen das alle Konfigurationen ins Programmverzeichnis gehören. Frei nach dem Motto: "It's not a bug it's a feature"

Und ja, du musst es nicht benutzen. Ich verwende auch mein eigenes Konfigurationssystem.
pegasus2
Erst mal vielen Dank für die Resonanz smile , habe wahrscheinlich bei der Suche das falsche Datum eingegeben, das hat man nun davon wenn man länger als 12 h am PC sitzt. Besitze insgesamt aber auch noch 8 andere Laufwerke, da kann man schon mal durcheinander kommen. Die Datei findet sich bei mir im Verzeichnis:
H:\Dokumente und Einstellungen\Peter\Lokale Einstellungen\Anwendungsdaten\org\Test_Settings.exe_Url_vjqladyrjy2zvqbvv4vupcc5evo5fue3\1.0.0.0

Na ja, vielleicht doch etwas zu gut versteckt. Jedenfalls hat das ganze dazu geführt, eine kleine Exkursion in XML zu starten. Da Registry-Einträge unter NET jetzt wieder im lokalen Verzeichnissen stehen sollten, keine schlechte Übung.

Hier noch ein bischen Code mit dem ich eine XML-Datei sowohl schreiben als auch lesen kann:

C#-Code:
    private void Lese_XML_Konfiguration()
    {
      // Lese XML-Datei
      XmlDocument xmlDoc = new XmlDocument();
      XmlElement root;
      XmlNode node1,node2,node3;
      try
      {
        //Also hier bitte Vorsicht!! Probleme bei eingeschränkten Benutzerrechten!!
        xmlDoc.Load(Application.StartupPath + "\\Konfiguration.Xml");
        root = xmlDoc.DocumentElement;
        node1 = root;
      }
      catch
      {
        MessageBox.Show("Datei Konfiguration.xml nicht gefunden!");
        return;
      }
      string stnodeName = node1.Name;//Konfiguration
      if (node1.ChildNodes.Count > 0)
      {
        for (int K = 0; K < node1.ChildNodes.Count;K++)
        {
          node2 = node1.ChildNodes.Item(K);//Datenbank,Ansicht  

          if (node2.Name == "Datenbank")
          {
            if (node2.ChildNodes.Count > 0)
            {
              for (int I = 0; I < node2.ChildNodes.Count; I++)
              {
                node3 = node2.ChildNodes.Item(I);//Alias,User,Password
                if (node3.Name == "Alias")
                {
                  tB_Alias.Text = node3.InnerText;
                }
                if (node3.Name == "User")
                {
                  tB_User.Text = node3.InnerText;
                }
                if (node3.Name == "Password")
                {
                  tB_Password.Text = node3.InnerText;
                }
              }
            }
          }//Ende Datenbank

          if (node2.Name == "Ansicht")
          {
            if (node2.ChildNodes.Count > 0)
            {
              for (int I = 0; I < node2.ChildNodes.Count; I++)
              {
                node3 = node2.ChildNodes.Item(I);//Alias,User,Password
                if (node3.Name == "Color_Ruestzeit")
                {
                  tB_Ruestzeit.Text = node3.InnerText;
                }
                ...
                ...
            }
          }//Ende Ansicht
        }
      }
    }

    //Einträge in XML-Dokument übernehmen
    private void btn_Uebernehmen_Click(object sender, EventArgs e)
    {
      //Also hier bitte Vorsicht!! Probleme bei eingeschränkten Benutzerrechten!!
      XmlTextWriter w = new XmlTextWriter(Application.StartupPath + "\\Konfiguration.Xml",Encoding.GetEncoding("ISO-8859-1"));
      try
      {
        w.WriteStartDocument();
        w.WriteComment("Konfigurationseinstellungen fuer Programm");
        w.WriteStartElement("Konfiguration");

        w.WriteStartElement("Datenbank");
        w.WriteStartElement("Alias");
        w.WriteString(tB_Alias.Text);
        w.WriteEndElement();
        w.WriteStartElement("User");
        w.WriteString(tB_User.Text);
        w.WriteEndElement();
        w.WriteStartElement("Password");
        w.WriteString(tB_Password.Text);
        w.WriteEndElement();
        w.WriteEndElement();//Datenbank

        w.WriteStartElement("Ansicht");
        w.WriteStartElement("Color_Ruestzeit");
        w.WriteString(tB_Ruestzeit.Text);
        w.WriteEndElement();
        w.WriteStartElement("Color_Abruestzeit");
        w.WriteString(tB_Abruestzeit.Text);
        w.WriteEndElement();
        ...
        ...
        w.WriteEndElement();//Ansicht

        //w.WriteStartElement("Letzter_Zugriff");
        //w.WriteString(tB_Letzter_Zugriff.Text);
        //w.WriteEndElement();
        w.WriteEndElement();//Konfiguration
        w.WriteEndDocument();
        w.Flush();
      }
      finally
      {
        //MessageBox.Show(ex.Message);
        w.Close();
      }//Erzeuge XML mit Writer
    }

Zur Vollständigkeit die XML-Datei:

C#-Code:
<?xml version="1.0" encoding="iso-8859-1" ?>
- <!-- Konfigurationseinstellungen fuer Programm
  -->
- <Konfiguration>
- <Datenbank>
  <Alias>Oracle10g</Alias>
  <User>peter</User>
  <Password>test</Password>
  </Datenbank>
- <Ansicht>
  <Color_Ruestzeit>Color [A=255, R=128, G=255, B=128]</Color_Ruestzeit>
  <Color_Auftrag_Produktion>Color [Lime]</Color_Auftrag_Produktion>
  </Ansicht>
  </Konfiguration>

Grüße von pegasus2
maxE
C#-Code:
xmlDoc.Load(Application.StartupPath + "\Konfiguration.Xml");

Es ist doch immer wieder das Selbe großes Grinsen
Siehe Posting von mir um 19:54. Ich resigniere ...
herbivore
Hallo maxE,

jemanden, der die Bedeutung von PC = Personal Computer = persönlicher Computer ernst nimmt, kannst du nicht wirklich vorwerfen, wenn er nur eine Konfigurationsdatei und nicht eine pro Nutzer ablegt. Auf einem PC in obigem Sinne ist die Nutzerzahl immer gleich eins.

Daten (und seien es Konfigurationsdaten) in Programmverzeichnissen und nicht in Datenverzeichnissen abzulegen, kann man dagegen immer kritisieren - aber es ist nunmal ungemein praktisch (für den Entwickler).

herbivore
maxE
Zitat:
Original von herbivore
Hallo maxE,

jemanden, der die Bedeutung von PC = Personal Computer = persönlicher Computer ernst nimmt, kannst du nicht wirklich vorwerfen, wenn er nur eine Konfigurationsdatei und nicht eine pro Nutzer ablegt. Auf einem PC in obigem Sinne ist die Nutzerzahl immer gleich eins.

Daten (und seien es Konfigurationsdaten) in Programmverzeichnissen und nicht in Datenverzeichnissen abzulegen, kann man dagegen immer kritisieren - aber es ist nunmal ungemein praktisch (für den Entwickler).

herbivore

Eine große Windowsschwäche ist, dass der Nutzer dazu verleitet wird, immer als Admin angemeldet zu sein. Viele Sicherheitsprobleme könnten mit einer konsequenten Rechtevergabe gelöst werden.
Man muss daran denken, dass man ein Programm auch mal weitergeben möchte und dieser Nutzer richtet deswegen vielleicht auch mehre Konten ein.
Schreibt man solche Programme wie oben, unterstützt man diese Unart nur noch weiter (Was ja leider auch schon seit Jahren geschieht). Auch dem Kunden/SysAdmin, dem du solche Anwendung übergibst, werden solche Amateurfehler einfach nur verärgern.

Nicht nur ich denke so, sondern auch Microsoft kapiert es so langsam und die Linux/Unix/MacOS Community hat es schon längst begriffen Augenzwinkern

Übrigens: Longhorn wird nun endlich standardmäßig eingeschränkte Benutzerkonten für User einrichten. Man kann MS eigentlich nur gratulieren, da endlich den Riegel vorzuschieben und mit dem Kompatibilitätsdogma zu brechen. Programme wie von Pegasus2 werden dann - es lebe die Vernunft - nicht mehr funktionieren.

Nochmal kurz: Soll eure Software auch noch für zukünftige Betriebssystemversionen fit sein, dann seht zu, dass sie auch mit eingeschränkten Benutzerkonten zurecht kommt.
herbivore
Hallo maxE,

was man z.B. dadurch erreichen kann, dass man im Installationsprogramm dafür sorgt, dass der Benutzer (= Jeder) Schreibrechte für das Anwendungsverzeichnis erhält. :-)

Versteh mich nicht falsch! Ich will deinen Kampf für gute Software nicht sabotieren. Und ich stimme deiner Ansicht, wie gute Software aussieht, zu. Ich wollte bloß motivieren, wie es dazu kommt, dass Software eben gegen diese Regeln verstößt. Deinem Beitrag entnehme ich, dass dir das durchaus bewußt ist.

Ich finde es einfach verfehlt, den Entwicklern vorzuwerfen einem Dogma zu folgen, dass Microsoft jahrelang verbreitet und vorgelegt hat und erst in letzter Zeit davon abgeht. Wenn ein Konzern wie Microsoft jahrelang selbst Anfängerfehler macht, kann man das einem tatsächlichen Anfänger erst recht nicht ankreiden. An die neue Situation wird sich die Entwicklergemeinde erst langsam gewöhnen.

herbivore
maxE
Hi herbivore!

Zitat:
was man z.B. dadurch erreichen kann, dass man im Installationsprogramm dafür sorgt, dass der Benutzer (= Jeder) Schreibrechte für das Anwendungsverzeichnis erhält. :-)

Na ja, ich glaube das wäre so mit das schlimmste was in einer Multiuserumgebung passieren könnteAugenzwinkern

Natürlich hat hier Microsoft gepennt.
Und die Hauptprobleme stammen noch aus der Windows 95 - Me Ära.
Wegen Kompatibilitätsgründen hat man das komplette Rechtemanagment untergraben und muss heute mit allen Problemen leben.
Glücklicherweise wird Microsoft da in Zukunft etwa rigider.
Aber ich bitte einfach alle Programmierer nicht mehr in Windows 95 Zeiten stehen zu bleiben und ihre Anwendungen jetzt anzupassen. Der Mehraufwand ist minimal. Seit Windows 2000/XP da ist und die alten Windowsversionen abgelöst hat, sollte man sich einfach ein paar grundlegende Dinge angewöhnen.

Bitte denkt also daran: Eure Anwendungsdaten gehören nicht irgendwo hin, sondern es gibt spezielle Verzeichnisse die dafür vorgesehen sind.
pegasus2
Habe ja richtig für Aufregung gesorgt. smile Natürlich hat maxE Recht, Konfigurationsdateien sollten niemals in das gleiche Verzeichnis gelegt werden. War für mich einfach nur bequemer das Resultat sofort überprüfen zu können, als jedesmal in den Verästelungen des Explorers zu suchen.

Habe mich selbst auch schon oft darüber aufgeregt, das manche Applikationen sich gar nicht mit eingeschränkten Benutzerrechten starten lassen. Es gibt allerdings viele Einstellungen, die für jederman gültig sein sollen und nicht benutzerabhängig gespeichert werden sollen (siehe u.a. HKEY_LOCAL_MACHINE + HKEY_CURRENT_USER der Registry) und bei eingeschränkten Rechten dennoch gelesen werden können.
maxE
Jup, auch für Benutzereinstellungen, die für alle User gültig sein sollen, gibt es ein Verzeichnis: "C:\Dokumente und Einstellungen\All Users\Anwendungsdaten" (Je nach Sprachversion). Die Verzeichnisse können alle problemlos mit Environment.GetFolderPath() ausgelesen werden.
Wie gesagt, ich wollte nur noch mal darauf hinweisen. Auch in Hinblick auf Longhorn erspart ihr euch ne Menge Probleme (wenn es denn irgendwann mal draußen sein sollte Augenzwinkern )
Timur Zanagar
Gibt es eine Möglichkeit auf die Settings von anderen Projekten draufzuzugreifen über die Settings Klasse?

Hintergrund ist folgender:

Meine Anwendung hat einen Dienst, FrontEnd (Windows.Forms) und zur Vereinfachung eine Lib.

Dienst und Anwendung greifen auf die Lib zu. In der Lib ist eine Settings Datei / Klasse. Nun möchte ich aber vom Dienst oder FrontEnd aus auch auf diese Settings Datei / Klasse mind. lesend zugreifen.

Wie kann ich dies ohne großen Aufwand bewerkstelligen? Ohne das ich selber die Konfigurationsdatei auslese und auswerte.
svenson
Baue dir doch einfach das .NET 2.0-Konfig-Modell nach. Kostet 5 Min Zeit und geht auch auf 1.1! Da kannst du dann jedes beliebige File (egal wo) laden, also auch fremde. Speichern geht dann auch.

Man nehme:

1. XSD-Schema mit den Settings
2. XSD.EXE welches eine Klasse für die Setting generiert
3. Den XmlSerializer, welches die Setting lädt und speichert.

Fertig ist die Laube. Mache ich nur so. Die App.Config kann nix!
Timur Zanagar
Ich weiß nicht was du damit meinst. Warum soll ich nochmal das Rad neu erfinden?
Michael Schuler
Zitat:
Warum soll ich nochmal das Rad neu erfinden?

Manchmal ist das neue Rad das Bessere Augenzwinkern
svenson
Weil es - wie bereits angedeutet - keine ähnlich flexible Lösung in .NET existiert.

Auch die 2.0-Variante krankt nach wie vor an der Tatsache, dass die Config-Struktur flach ist. Komplexe Strukturen sind nicht abzubilden. Unverständlich, wo nun offenbar xsd.exe bereits hinter den Kulissen werkelt. Warum aber ein festes Schema vorgeben?

Zudem werden Configurations immer auf Anwendungs-Ebene (geht jetzt wenigstens Assembly?) definiert. Was ist wenn ich eine Config pro Modul/Komponente/Klasse brauche? Was ist mit PlugIn-Konfigurationen?

Kurz gesagt: M$ hat in 2.0 eine wirklich mächtige Lösung vermissen lassen und wieder nur eine Easy-To-Use-Lösung für Trivial-Szenarien mit ein paar kleinen Verbesserungen gefrickelt. Wenn ich gemein sein wollte, könnte ich behaupten, die Verbesserungen adressieren vor allen die Probleme des gemeinen Amateur-Programmierers... smile
harrylask
diverse fragen werden hier beantwortet
 http://blogs.msdn.com/rprabhu/articles/433979.aspx