Le Post Infeeny

Les articles des consultants et experts Infeeny

Archives Mensuelles: février 2012

Bonnes pratiques pour stocker des informations dans les fichiers de configuration

Lors du développement d’une application, il est courant de stocker des informations dans les fichiers de configuration, que ce soit « app.config » pour les clients lourds, ou « web.config » pour les applications web.

La méthode la plus simple pour stocker ces informations est d’utiliser la section « AppSettings ».

<AppSettings>
  <add key="ContactMail" value="contact@mcnext.com" />
</AppSettings>

 

string mail = ConfigurationManager.AppSettings["ContactMail"];

C’est une manière pratique de conserver des données qui pourront être facilement modifiées en production, car ces fichiers ne sont pas compilés.

Malheureusement, on constate souvent que ces données ont tendance à s’empiler au fur et à mesure que le développement avance, au point de se retrouver parfois avec d’interminables sections « AppSettings ».

Première alternative : créer ses propres SectionHandler

Une alternative à ce problème est de créer ses propres sections de configuration. Il faut pour cela créer une classe qui hérite de ConfigurationSection.

namespace ApplicationNamespace
{
    public class ContactSectionHandler : ConfigurationSection
    {
        [ConfigurationProperty("Mail", IsRequired = true)]
        public string Mail
        {
            get
            {
                return (string)this["Mail"];
            }
        }
    }
}

 

<configuration>
  <configSections>
    <section name="contact" type="ApplicationNamespace.ContactSectionHandler, NomDeLAssembly" />
  </configSections>
  <contact Mail="contact@mcnext.com" />
</configuration>

L’accès aux données se faisant ensuite tout aussi simplement qu’avec « AppSettings » :

string mail = ((ContactSectionHandler)ConfigurationManager.GetSection("contact")).Mail;

Ces sections de configuration personnalisées peuvent aussi contenir des collections de pairs clef/valeur. Cela se fait en créant une classe qui hérite de ConfigurationElementCollection pour représenter la collection, et une autre classe qui hérite de ConfigurationElement pour représenter un élément de cette collection. On peut alors obtenir ce type de représentations :

<contacts>
  <contact nom="Hémery" prenom="Pierre-Yves" fonction="Directeur du Pôle .NET" mail="pole-dotnet@mcnext.com" />
  <contact nom="Baduel" prenom="Roch" fonction="Directeur du Pôle Biztalk" mail="pole-biztalk@mcnext.com" />
</contacts>

Expliquer dans le détail comment créer ces sections de configuration personnalisées serait un peu long. Je vous invite à vous reporter à la documentation sur MSDN pour obtenir des informations supplémentaires. Car ce qui nous intéresse ici, c’est la seconde alternative : celle qui permet de ne pas avoir à créer de section personnalisées, tout en évitant de se retrouver avec des fichiers de configuration où tout est mélangé.

Seconde alternative : utiliser les SectionHandler « clefs en main »

Une alternative peu connue et beaucoup plus simple que la création de sections personnalisées est l’utilisation des SectionHandler « clefs en main » fournis en standard dans le Framework .NET. Ceux-ci sont au nombre de trois :

  • SingleTagSectionHandler, qui permet de créer une section simple contenant des attributs.
  • NameValueSectionHandler, qui permet de créer une collection de paires clef/valeur, où chaque clef peut être répétée plusieurs fois.
  • DictionarySectionhandler, qui permet de créer une collection de paires clef/valeur, où chaque clef doit être unique.

Il faut tout d’abord déclarer ces sections dans le nœud « configuration/configSections » :

<configuration>
  <configSections>
    <!-- Les noms donnés à chaque section sont purement arbitraires -->
    <section name="singleSection" type="System.Configuration.SingleTagSectionHandler" />
    <section name="nameValueSection" type="System.Configuration.NameValueSectionHandler" />
    <section name="dictionarySection" type="System.Configuration.DictionarySectionHandler" />
  </configSections>
  <!-- ... -->
</configuration>

Il ne reste ensuite plus qu’à utiliser ces sections :

<singleSection nom="Hémery" prenom"Pierre-Yves" fonction="Directeur du Pôle .NET" mail="pole-dotnet@mcnext.com" />
<nameValueSection>
  <add key="ValeurEnDouble" value="toto" />
  <add key="ValeurEnDouble" value="titi" />
  <add key="AutreValeur" value="foobar" />
</nameValueSection>
<dictionarySection>
  <add key="DRH" value="drh@mcnext.com" />
  <add key="CONTACT" value="contact@mcnext.com" />
</dictionarySection>

La récupération des données dans le code C# se fait là encore très facilement. SingleTagSectionHandler et DictionarySectionHandler vous renverront une Hashtable, tandis que NameValueSectionHandler vous renverra une NameValueCollection.

string mail = ((Hashtable)ConfigurationManager.GetSection("singleSection"))["mail"];
// valeurEnDouble aura la valeur "toto,titi" (nos deux valeurs séparées par une virgule)
string valeurEnDouble = ((NameValueCollection)ConfigurationManager.GetSection("nameValueSection"))["ValeurEnDouble"];
string drh = ((Hashtable)ConfigurationManager.GetSection("dictionarySection"))["DRH"];

Comme on le voit, ces SectionHandler « clefs en main » permettent d’organiser très simplement les données stockées dans les fichiers de configuration, sans avoir à en passer par l’exercice souvent fastidieux de la création de sections personnalisées.

Une autre bonne pratique qu’il est bon de noter est d’encapsuler l’accès à ces données dans des classes statiques, et d’y gérer l’éventuelle levée d’exceptions. Par exemple en contrôlant qu’une certaine clef existe bel et bien.

public static class ConfigurationData
{
    private static readonly Hashtable _sectionDatabaseAccess = (Hashtable)ConfigurationManager.GetSection("databaseAccess");

    public static int Timeout
    {
        get
        {
            if (_sectionDatabaseAccess.ContainsKey("timeout"))
            {
                int val;
                if (!int.TryParse(_sectionDatabaseAccess["timeout"].ToString(), out val))
                    throw new Exception(@"La clef ""timeout"" de la section ""databaseAccess"" doit stocker un nombre entier.");

                return val;
            }

            throw new Exception(@"La section de configuration ""databaseAccess"" devrait contenir une valeur pour la clef ""timeout"".");
        }
    }
}

L’accès aux données s’en trouve nettement facilité :

int timeout = ConfigurationData.Timeout;

Localiser la saisie utilisateur côté client dans ASP.NET MVC 3

Depuis la première version d’ASP.NET MVC, un des composants qui a subi le plus de modifications est sans doute la validation côté client. Au départ Microsoft avait développé sa propre librairie Javascript, avant de se tourner peu à peu vers jQuery, une librairie très populaire qu’ils ont choisi d’intégrer directement à leur solution.

Le contrecoup de ce revirement est que selon qu’une application est basée sur ASP.NET MVC 1, 2 ou 3, la validation côté client peut fonctionner selon des mécanismes assez différents. La localisation de cette validation en particulier peut s’avérer assez délicate à mettre en place.

La localisation de la validation dans ASP.NET MVC 3

Quand on parle de localisation de la validation, on ne fait pas seulement allusion au fait que les messages qui s’affichent soient dans la langue attendue, il s’agit aussi que les informations saisies par l’utilisateur soient validées selon les règles adéquates. Par exemple, que penser d’une application entièrement en français qui refuserait le nombre décimal « 5,5 » parce que celui-ci contient une virgule comme séparateur décimal? Ce n’est probablement pas ce à quoi s’attendraient la plupart des utilisateurs.

Avec ASP.NET MVC 3, parvenir à mettre en place cette localisation est un vrai parcours du combatant. En effet, tout repose maintenant sur le plugin jQuery Validation, qui utilise un système de « règles » pour valider la saisie utilisateur. Les règles fournies par défaut sont adaptées à la culture anglaise, et il faut les surcharger, c’est à dire les remplacer et les adapter, si l’on souhaite que notre application accepte de valider une culture différente.

Ce que fournit jQuery Validation

En standard, jQuery Validation fourni trois autres groupes de règles, pour les cultures allemande, néerlandaise et brésilienne. Mais non seulement celles-ci sont très incomplètes (la néerlandaise et la brésilienne ne surchargent que la règle de validation des dates) mais aucune ne colle à la culture française. Et voici ce que propose le fichier de règle pour la culture allemande :

/*
 * Localized default methods for the jQuery validation plugin.
 * Locale: DE
 */
jQuery.extend(jQuery.validator.methods, {
	date: function(value, element) {
		return this.optional(element) || /^\d\d?\.\d\d?\.\d\d\d?\d?$/.test(value);
	},
	number: function(value, element) {
		return this.optional(element) || /^-?(?:\d+|\d{1,3}(?:\.\d{3})+)(?:,\d+)?$/.test(value);
	}
});

Les allemands utilisent bien la virgule comme séparateur décimal; en revanche ils utilisent le point comme séparateur de milliers (alors que nous utilisons un espace blanc). Bien sûr il serait facile de recopier le contenu de ce fichier avant de l’adapter, mais ce scénario n’est réaliste que si notre application n’est conçue que pour la langue française. Que faire si cette dernière est multilingue?

La librairie jQuery Globalization

L’intérêt récent de Microsoft pour jQuery les a amené à proposer leurs propres plugins : jQuery DataLink pour la liaison de données, jQuery Templates pour le templating et jQuery Globalization pour la localisation. Jusqu’à il y a peu, ces trois plugins, encore à l’état de béta version, étaient considérés par l’équipe jQuery comme des plugins « officiels ». Ce n’est plus le cas, toutefois les deux premiers plugins ont vu leur existence prolongée par leur créateur, sous la forme de JsRender et JsViews. Ils devraient être à terme inclus dans la librairie jQuery UI. Quand à jQuery Globalization, il était déjà très complet et parfaitement utilisable en production, et c’est heureux puisque nous allons nous en servir pour rendre notre application multilingue.

jQuery Globalization est composé de très nombreux fichiers correspondant à autant de cultures. Ces fichiers exposent les caractéristiques de chacune de ces cultures, ainsi que des fonctions permettant de traduire aussi bien les dates que les nombres décimaux, et bien plus encore.

Pour implémenter ce plugin dans une application web ASP.NET MVC 3, rien de plus simple. Après l’avoir téléchargé et placé dans notre solution, nous devons tout d’abord le référencer :

<script type="text/javascript" src="@Url.Content("~/Scripts/jquery/plugins/glob/jquery.global.js")"></script>

Ensuite nous devons charger dynamiquement le fichier de localisation correspondant à la culture de l’utilisateur, et configurer le plugin pour utiliser celui-ci :

<script type="text/javascript" src="@Url.Content("~/Scripts/jquery/plugins/glob/globinfo/jquery.glob." + Request.UserLanguages[0] + ".js")"></script>
<script type="text/javascript">
    jQuery.global.preferCulture("@Request.UserLanguages.ToCorrectCase()");
</script>

Request.UserLanguages est un tableau permettant d’obtenir côté serveur la liste des cultures configurées (par ordre de préférence) dans le navigateur de l’utilisateur. Vous vous demandez sans doute à quoi sert la fonction ToCorrectCase(). Malheureusement certain navigateurs comme Firefox renvoient « fr-fr » pour la langue française quand ils devraient renvoyer « fr-FR » (notez la différence de casse), et jQuery Globalization est sensible à ce détail; il faut donc s’y adapter et c’est à ça que sert cette fonction.

N’oubliez pas que les fichiers source de jQuery sont disponibles sur le CDN de Microsoft, tout comme de nombreux plugins.

Tout le nécessaire est maintenant en place pour permettre la localisation multilingue de notre application.

Comment relier jQuery Validation et jQuery Globalization

La dernière étape va consister à relier jQueryValidation, qui s’occupe de la mécanique de validation côté client de l’application, à jQuery Globalization, qui s’occupe exclusivement de la localisation.

Pour cela, nous devons faire référencer un nouveau fichier Javascript par notre application, et y ajouter le code suivant :

jQuery.extend(jQuery.validator.methods, {
    date: function (value, element) {
        return this.optional(element) || jQuery.global.parseDate(value) != null;
    },
    number: function (value, element) {
        return this.optional(element) || !isNaN(jQuery.global.parseFloat(value));
    },
    range: function (value, element, param) {
        value = jQuery.global.parseFloat(value);
        return this.optional(element) || (value &gt;= param[0] &amp;&amp; value &lt;= param[1]);
    }
});

Par le biais de ce code nous surchargeons trois règles de validation de jQuery Validation : la validation de la date, des nombres décimaux, et des intervalles. D’autres règles pourraient potentiellement être également surchargées.

Grâce à ce « pont » établi entre les deux plugins, jQuery Validation va maintenant se servir des fonctions de jQuery Globalization pour valider la saisie de l’utilisateur. Supposons que celui-ci doive indiquer quel sera le pourcentage de TVA sur un produit quelconque. La propriété correspondante dans le modèle de la vue ressemblerait à ceci :

/// <summary>
/// TVA applicable sur le produit
/// </summary>
[Range(0.0, 100.0)]
public double TVA { get; set; }

Notez que la règle de validation de nombre décimal est implicite dès lors qu’il s’agit d’un type « double » (nombre décimal à double précision).

Maintenant si par exemple l’utilisateur a son navigateur configuré dans la culture française, et qu’il saisit « 5,5 », nous serons en mesure de valider que ce nombre est dans un format décimal correct, et qu’il est compris dans une intervalle allant de 0 à 100. Notre application web est donc bien multilingue.