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