Tutoriel : Travailler avec les fichiers de configuration en C#

Nico-pyright(c)

Cet article a pour but de présenter diverses utilisations des fichiers de configuration dans une application Winforms en C#.

N'hésitez pas à commenter cet article ! Commentez ?

- Titre : Tutoriel : Travailler avec les fichiers de configuration en C#

- Auteur : Nico-pyright(c)

- Parution : 5 décembre 2007

- Mise à jour : 5 décembre 2007

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2007 Nico-pyright(c). Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts. Droits de diffusion permanents accordés à Developpez LLC.

Sommaire

- 1.Introduction

- 2.Avant-propos

- 3.Les fichiers de configuration

- 4.Démarrer avec Visual C#

- 4.1.Ajouter le fichier app.config

- 4.2.Référence

- 4.3.Exemple de lecture simple pour la section prédéfinie AppSettings

- 5.Autre exemple simple avec un type prédéfini : ConnectionStrings

- 5.1.App.config

- 5.2.Code

- 6.Créer sa propre section à partir d'un type prédéfini

- 6.1.Dictionarysectionhandler

- 6.2.NameValueSectionHandler

- 6.3.SingleTagSectionHandler

- 7.Modification

- 8.Section personnalisée avec types

- 8.1.Section simple héritant de ConfigurationSection

- 8.2.Section simple avec nœuds XML, implémentant IConfigurationSectionHandler

- 8.3.Section avec des collections

- 9.Conclusion

- Remerciements

- Contact

1.Introduction

Vous êtes un développeur .Net utilisant le C# et vous souhaitez utiliser les fichiers de configuration dans votre application Winforms ?
Alors ce tutoriel est pour vous.

Vous apprendrez dans ce cours comment paramétrer et utiliser le fichier app.config.

2.Avant-propos

Ce tutoriel est dédié aux applications Windows Forms et utilisera le fichier de configuration app.config. Beaucoup de ces concepts sont également transposables pour une programmation en ASP.NET, utilisant le fichier web.config. Notez seulement qu'il y a beaucoup plus de possibilités pour ASP.NET et qu'ici, nous ne verrons qu'une partie de l'utilisation des fichiers de configuration.

Dans cet article, nous allons utiliser des propriétés C# et des opérateurs d'indexations. Nous allons surcharger des méthodes et des propriétés aussi.

3.Les fichiers de configuration

Les fichiers de configuration sont des fichiers XML qui contiennent la configuration de notre exécutable. Cela peut être des chaines de connexions, des valeurs paramétrables, une url de web services, des préférences utilisateurs, des contrôles personnalisés, etc.
Ces fichiers doivent se situer dans le même répertoire que l'exécutable.

Pourquoi utiliser un fichier de configuration ?
- pour éviter de mettre des valeurs en dur dans le code. Imaginons que nous utilisions une url de web service dans notre application, si l'url change, on aimerait ne pas avoir à recompiler le code ;
- pour éviter d'utiliser la base de registre et de donner les droits de modification de base de registre.

Ces fichiers permettent de typer les données à sauvegarder et le framework .Net dispose de méthodes pour y accéder facilement.

L'intérêt d'utiliser un fichier XML plutôt qu'un fichier binaire est que ce fichier est lisible et compréhensible facilement. On peut également le modifier à la main sans un système évolué permettant de faire des modifications.

4.Démarrer avec Visual C#

4.1.Ajouter le fichier app.config

Pour ajouter un fichier de configuration : bouton droit sur le projet -> add -> new item -> et choisir Application Configuration File

4.2.Référence

N'oubliez pas d'ajouter la référence à System.Configuration.
bouton droit sur projet -> références -> add new references

4.3.Exemple de lecture simple pour la section prédéfinie AppSettings

Le plus simple pour commencer est de travailler avec la section appSettings qui est gérée par le ConfigurationManager. C'est une section où on peut mettre, comme son nom le suggère, les propriétés de l'application.
On utilise une paire clé/valeur pour stocker des informations.

Dans le fichier, ajouter des clés et des valeurs comme ceci :

<configuration>
<appSettings>
<add key="nom" value="pyright"/>
<add key="prenom" value="nico"/>
</appSettings>
</configuration>

Pour y accéder, on utilise le code suivant :

string nom = ConfigurationManager.AppSettings["nom"];
string prenom = ConfigurationManager.AppSettings["prenom"];
Console.WriteLine("Je m'appelle {0} {1}", prenom, nom);

On utilise l'objet ConfigurationManager.AppSettings qui mappe cette section particulière.
On peut également utiliser des index numériques pour y accéder, mais je trouve que cela manque vraiment de clarté :

string nom = ConfigurationManager.AppSettings[0];

Il est possible aussi de boucler sur toutes les valeurs contenues dans la section :

foreach (string aValue in ConfigurationManager.AppSettings)
{
Console.WriteLine("Utilisateur : {0} - {1}", aValue, ConfigurationManager.AppSettings[aValue]);
}

5.Autre exemple simple avec un type prédéfini : ConnectionStrings

ConnectionString est un type prédéfini pour les chaines de connexion. On peut y stocker entre autres le nom du provider, la chaine de connexion, le nom de la base, etc. …

5.1.App.config

<configuration>
<connectionStrings>
<add name="MaConnection" providerName="System.Data.SqlClient"
connectionString="Data Source=localhost; Initial Catalog=MonCatalog; Integrated Security=true"/>
<add name="MaConnection2" providerName="System.Data.SqlClient"
connectionString="Data Source=localhost; Initial Catalog=MonCatalog; Integrated Security=true"/>
</connectionStrings>
</configuration>

5.2.Code

De la même façon, on utilise un objet spécialisé ConfigurationManager.ConnectionStrings pour accéder à cette section.

ConnectionStringSettings connex = ConfigurationManager.ConnectionStrings["MaConnection"];
Console.WriteLine("{0} ; {1}", connex.Name, connex.ConnectionString);

On peut également boucler sur toutes les chaines de connexion :

foreach(ConnectionStringSettings aValue in ConfigurationManager.ConnectionStrings)
{
Console.WriteLine("{0} ; {1}", aValue.Name, aValue.ConnectionString);
}

6.Créer sa propre section à partir d'un type prédéfini

Il est possible de créer sa propre section à partir d'un type prédéfini, par exemple pour créer une section du même genre que la section appSettings, qui utilise une paire clé/valeur, on peut utiliser un handler DictionarySectionHandler. Il existe plusieurs types prédéfinis, que nous allons étudier ci-dessous.

6.1.Dictionarysectionhandler

Dictionarysectionhandler est une classe qui fournit les informations de configuration des paires clé/valeur d'une section de configuration. Elle implémente l'interface IConfigSectionHandler.

Pourquoi utiliser une section particulière Dictionarysectionhandler plutôt que d'utiliser appsettings qui utilise le même système de clé/valeur ?
Le but de pouvoir faire des sections particulières est d'organiser sémantiquement son fichier app.config, pour découper logiquement son fichier au lieu d'avoir tout dans la même section.

Pour illustrer le fait qu'on peut avoir plusieurs sections personnalisées dans le même fichier de configuration, ce fichier app.config exemple va contenir l'illustration du handler Dictionarysectionhandler ainsi que le suivant NameValueSectionHandler, qui utilise aussi une paire clé/valeur. Pourquoi deux handler différents me diriez vous si chacun fournit des informations de configuration clé/valeur ? Allez… réponse dans le prochain chapitre…

<configuration>
<configSections>
<sectionGroup name="MonGroupe">
<section name="MaSectionUne" type="System.Configuration.NameValueSectionHandler" />
<section name="MaSectionDeux" type="System.Configuration.DictionarySectionHandler" />
</sectionGroup>
</configSections>
<MonGroupe>
<MaSectionUne>
<add key="cle1" value="valeur1" />
<add key="cle2" value="valeur2" />
<add key="cle3" value="valeur3" />
</MaSectionUne>
<MaSectionDeux>
<add key="id1" value="valeur4" />
<add key="id2" value="valeur5" />
<add key="id3" value="valeur6" />
</MaSectionDeux>
</MonGroupe>
</configuration>

Pour récupérer notre section personnalisée, on va utiliser la méthode GetSection en lui passant le nom de la section en paramètre.
On va dans le cas du Dictionarysectionhandler récupérer un objet Hashtable, et on pourra accéder à la valeur correspondant à une clé comme suit :

Hashtable section = (Hashtable)ConfigurationManager.GetSection("MonGroupe/MaSectionDeux");
Console.WriteLine(section["id2"]);

On pourra toujours bien sûr boucler sur tous les éléments :

foreach(DictionaryEntry d in section)
{
Console.WriteLine("{0} ; {1}", d.Key, d.Value);
}

6.2.NameValueSectionHandler

Alors, voici la différence entre le Dictionarysectionhandler et le NameValueSectionHandler. Chacun fournit les informations de configuration des paires clé/valeur d'une section de configuration. La différence consiste en l'objet construit et retourné qui contient les informations de configuration.
Ici en l'occurrence, nous allons récupérer une collection spécialisée NameValueCollection.

On pourra accéder à un élément ainsi :

NameValueCollection section = (NameValueCollection)ConfigurationManager.GetSection("MonGroupe/MaSectionUne");
Console.WriteLine(section["cle1"]);

et boucler sur nos valeurs ainsi :

foreach(string aKey in section)
{
Console.WriteLine("{0} ; {1}", aKey, section[aKey]);
}

Après, à chacun de décider avec quel objet il préfère travailler ensuite.

NB : notez que lorsqu'il y a plusieurs sections, on peut les regrouper dans une section group, on y accèdera par l'url « MonGroupe/MaSection ».

6.3.SingleTagSectionHandler

Ce troisième handler permet de gérer des sections de configuration qui sont représentées par une balise XML unique dans le fichier .config. L'avantage ici est qu'on n'est pas limité dans le nombre d'attributs de la section et par leurs noms. Le framework saura retrouver les noms que l'on aura renseignés dans le fichier de configuration. L'inconvénient est que la section ne pourra apparaitre qu'une seule fois dans le fichier de configuration.

<configuration>
<configSections>
<section name="sampleSection" type="System.Configuration.SingleTagSectionHandler" />
</configSections>
<sampleSection monAttribut="Value1" unAutreAttribut="deuxieme valeur" cequejeveux="avec mes configs"/>
</configuration>

Le code pour récupérer les valeurs :

Hashtable section = (Hashtable)ConfigurationManager.GetSection("sampleSection");
Console.WriteLine(section["monAttribut"]);

Ainsi que notre boucle habituelle :

foreach(DictionaryEntry d in section)
{
Console.WriteLine("{0} ; {1}", d.Key, d.Value);
}

NB : ici il n'y a pas de groupe de section, on accède à la section directement grâce au nom de la section, en utilisant GetSection.

7.Modification

Il peut être utile de pouvoir modifier le fichier de configuration depuis son application.
Reprenons notre premier exemple :

<configuration>
<appSettings>
<add key="nom" value="pyright"/>
<add key="prenom" value="nico"/>
</appSettings>
</configuration>

Voici comment faire :

string nom = ConfigurationManager.AppSettings["nom"];
string prenom = ConfigurationManager.AppSettings["prenom"];
Console.WriteLine("{0} - {1}", prenom, nom);

Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
config.AppSettings.Settings.Remove("prenom");
config.AppSettings.Settings.Add("prenom", "Nouveau prenom");
config.Save(ConfigurationSaveMode.Modified);
ConfigurationManager.RefreshSection("appSettings");

prenom = ConfigurationManager.AppSettings["prenom"];
Console.WriteLine("{0} - {1}", prenom, nom);

Vous pouvez aller vérifier votre fichier de configuration app.config (attention, celui qui est dans le même répertoire que l'exécutable (debug par défaut)), la nouvelle valeur aura été modifiée. Mais pas celui qui est dans la solution, puisque c'est l'IDE qui va recopier le fichier app.config dans le répertoire de l'exécutable.

8.Section personnalisée avec types

8.1.Section simple héritant de ConfigurationSection

Une des façons de créer des sections personnalisées est d'hériter de la classe ConfigurationSection. On pourra dès lors ajouter des attributs en créant des propriétés et en utilisant l'attribut ConfigurationProperty pour associer notre propriété à un attribut XML.
Ainsi, pour lire un fichier de configuration de ce genre :

<configuration>
<configSections>
<section name="MaSection" type="ConsoleApplication1.MaSection, ConsoleApplication1" />
</configSections>
<MaSection nom="pyright" prenom="nico"/>
</configuration>

on utilisera :

using System;
using System.Configuration;

namespace ConsoleApplication1
{
class MaSection : ConfigurationSection
{
[ConfigurationProperty("nom", IsRequired = true)]
public string nom
{
get { return (string)this["nom"]; }
set { this["nom"] = value; }
}

[ConfigurationProperty("prenom", IsRequired = true)]
public string prenom
{
get { return (string)this["prenom"]; }
set { this["prenom"] = value; }
}
}

class Program
{
static void Main()
{
MaSection section = (MaSection)ConfigurationManager.GetSection("MaSection");
Console.WriteLine("{0} ; {1}", section.nom, section.prenom);

}
}
}

Dans le fichier app.config, on aura remarqué le type de la section type=« ConsoleApplication1.MaSection, ConsoleApplication1 ».
Il s'agit du nom de la classe qui aura pour charge de gérer la section. On la précède de son namespace.

8.2.Section simple avec nœuds XML, implémentant IConfigurationSectionHandler

Une autre façon de créer une section personnalisée est d'implémenter l'interface IConfigurationSectionHandler. Ceci implique de surcharger la méthode Create qui va s'occuper de désérialiser la section.
On aura donc un fichier de configuration de cette forme :

<configuration>
<configSections>
<section name="MaSection" type="ConsoleApplication1.MaSectionHandler, ConsoleApplication1" />
</configSections>
<MaSection type="ConsoleApplication1.MaSection, ConsoleApplication1">
<Nom>Nico</Nom>
<Prenom>pyright</Prenom>
</MaSection>
</configuration>

On constate encore le type de la section qui nous renvoie à notre classe.
Il ne reste plus qu'à définir la classe de section avec ses propriétés qui feront office de champs XML.

using System;
using System.Configuration;
using System.Xml;
using System.Xml.Serialization;

namespace ConsoleApplication1
{
public class MaSection
{
private string _nom;

public string Nom
{
get { return _nom; }
set { _nom = value; }
}

private string _prenom;

public string Prenom
{
get { return _prenom; }
set { _prenom = value; }
}

}

class MaSectionHandler : IConfigurationSectionHandler
{
public object Create(object parent, object configContext, XmlNode section)
{
XmlSerializer xs = new XmlSerializer(typeof(MaSection));
XmlNodeReader xnr = new XmlNodeReader(section);
return xs.Deserialize(xnr);

}
}

class Program
{
static void Main()
{
MaSection maSection = (MaSection)ConfigurationManager.GetSection("MaSection");
Console.WriteLine("{0} ; {1}", maSection.Nom, maSection.Prenom);
}
}
}

8.3.Section avec des collections

Dans ces deux exemples de sections personnalisées, on constate à chaque fois qu'on peut seulement définir un élément. Il pourrait être intéressant dans certains cas d'avoir une section personnalisée qui puisse contenir plusieurs éléments.
Comme ci-dessous :

<configuration>
<configSections>
<section name="MaSection" type="ConsoleApplication1.MaSection, ConsoleApplication1" />
</configSections>
<MaSection>
<masection nom="nico" prenom="pyright"/>
<masection nom="CLI" prenom="C++"/>
</MaSection>
</configuration>

Alors, j'arrête tout de suite ceux qui pensent qu'ici on pourrait utiliser une section NameValueSectionHandler. Ils ont raison effectivement, mais là le but est de pouvoir faire une section qui possède plus qu'une paire de clé/valeur, c'est un hasard si notre section personnalisée en contient deux (pas tant un hasard que ça, c'est plutôt pour la lisibilité du code). Rien n'empêche de rajouter des propriétés.
C'est la qu'interviennent les ConfigurationPropertyCollection.
Le principe est de créer un élément (la classe qui s'appelle ici ListeElement qui surcharge ConfigurationElement) qui sera contenu dans une collection d'éléments (ici la classe ListesElementCollection, qui hérite de ConfigurationElementCollection) et notre section qui va utiliser ces éléments (ici MaSection qui hérite de ConfigurationSection).
Voilà pour le code :

using System;
using System.Configuration;

namespace ConsoleApplication1
{
class ListeElement : ConfigurationElement
{
private static readonly ConfigurationPropertyCollection _proprietes;
private static readonly ConfigurationProperty _nom;
private static readonly ConfigurationProperty _prenom;

static ListeElement()
{
_nom = new ConfigurationProperty("nom", typeof(string), null, ConfigurationPropertyOptions.IsKey);
_prenom = new ConfigurationProperty("prenom", typeof(string), null, ConfigurationPropertyOptions.IsRequired);
_proprietes = new ConfigurationPropertyCollection();
_proprietes.Add(_nom);
_proprietes.Add(_prenom);
}

public string Nom
{
get { return (string)this["nom"]; }
set { this["nom"] = value; }
}

public string Prenom
{
get { return (string)this["prenom"]; }
set { this["prenom"] = value; }
}

protected override ConfigurationPropertyCollection Properties
{
get { return _proprietes; }
}
}

class ListesElementCollection : ConfigurationElementCollection
{

public override ConfigurationElementCollectionType CollectionType
{
get { return ConfigurationElementCollectionType.BasicMap; }
}
protected override string ElementName
{
get { return "masection"; }
}

protected override ConfigurationPropertyCollection Properties
{
get { return new ConfigurationPropertyCollection(); }
}

public ListeElement this[int index]
{
get { return (ListeElement)BaseGet(index); }
set
{
if (BaseGet(index) != null)
{
BaseRemoveAt(index);
}
base.BaseAdd(index, value);
}
}

public new ListeElement this[string nom]
{
get { return (ListeElement)BaseGet(nom); }
}

public void Add(ListeElement item)
{
base.BaseAdd(item);
}

public void Remove(ListeElement item)
{
BaseRemove(item);
}

public void RemoveAt(int index)
{
BaseRemoveAt(index);
}

public void Clear()
{
BaseClear();
}

protected override ConfigurationElement CreateNewElement()
{
return new ListeElement();
}

protected override object GetElementKey(ConfigurationElement element)
{
if (element != null)
return ((ListeElement)element).Nom;
else
return null;
}
}

class MaSection : ConfigurationSection
{
private static readonly ConfigurationPropertyCollection _proprietes;
private static readonly ConfigurationProperty _listes;

static MaSection()
{
_listes = new ConfigurationProperty("", typeof(ListesElementCollection),
null, ConfigurationPropertyOptions.IsRequired | ConfigurationPropertyOptions.IsDefaultCollection);
_proprietes = new ConfigurationPropertyCollection();
_proprietes.Add(_listes);
}

public ListesElementCollection Listes
{
get { return (ListesElementCollection)base[_listes]; }
}

public new ListeElement this[string nom]
{
get { return Listes[nom]; }
}

protected override ConfigurationPropertyCollection Properties
{
get { return _proprietes; }
}
}

class Program
{
static void Main()
{
MaSection section = (MaSection)ConfigurationManager.GetSection("MaSection");
ListeElement element1 = section["nico"];
Console.WriteLine("{0} ; {1}", element1.Nom, element1.Prenom);

ListeElement element2 = section["CLI"];
Console.WriteLine("{0} ; {1}", element2.Nom, element2.Prenom);
}
}
}

On récupère la section de manière classique avec GetSection et ensuite on manipule les objets ListeElement de notre section.

Télécharger le source exemple (7 ko).

9.Conclusion

Voilà pour ce tutoriel sur les fichiers de configuration app.config. J'espère qu'il vous aura aidé à maîtriser un peu plus cette technique de configuration d'application.

NB : j'ai utilisé beaucoup de string, car j'ai utilisé les exemples simples de nom et prénom, mais sachez qu'on peut très bien typer les valeurs autrement et utiliser un int par exemple pour stocker l'âge. C'est un des intérêts des sections par rapport aux vieux fichiers ini.

Remerciements

Je remercie toute l'équipe Dotnet pour ses relectures attentives du document.

Contact

Si vous constatez une erreur dans le tutoriel, dans le source, dans la programmation ou pour toutes informations, n'hésitez pas à me contacter par mail, ou par le forum.