07.06.09
Sortir du moule avec le pattern provider
Actuellement je travaille sur un site Web capable de s’adapter à plusieurs marques et pour différents pays.
L’idée générale est de construire un moule auquel tout le monde devra s’adapter.
Ce n’est pas toujours possible, en particulier la difficile question de certaines règles métiers qui peuvent différer fortement.
Par exemple les prix, promotion et autres cadeaux clients répondent à des logiques différentes selon la marque.
On a donc besoin par moment de sortir localement du moule et exécuter un code ou un autre selon le contexte. Comment s’y prendre concrètement ?
Si on ne parlait que de quelques lignes on pourrait envisager de placer une ou deux clauses if et tester par exemple le nom de la marque. Evidemment si une nouvelle marque vient s’ajouter, on devra prendre le risque de modifier un code qui marche.
Ce blog est publié sur un site qui fait depuis toujours la promotion de l’architecture de code alors on va essayer de faire mieux avec un patron de conception que j’aime bien : le provider.
L’idée générale n’est pas très révolutionnaire. On a (au moins) trois étapes à implémenter :
1. On écrit une interface qui définit la liste des méthodes et propriétés communes. Lorsque je parle d’interface, il s’agit aussi de classe abstraite. A vous de voir ce qui est utile dans votre cas
2. On implémente cette interface autant de fois que nécessaire (une implémentation par marque a priori)
3. On isole la logique d’instanciation de la classe réelle selon le contexte dans une classe factory qui retourne une instance de notre interface.
4. Puisque l’on est ambitieux, on voudrait que la factory soit paramétrable de façon à ne pas avoir besoin de la modifier si une nouvelle marque vient s’ajouter
Le provider est omniprésent dans .Net, il n’est donc pas étonnant que Microsoft nous fournissent une plomberie pour l’implémenter facilement. Découvrons la ensemble.
On commence par l’interface qui doit avoir une dépendance technique avec la classe ProviderBase [1].
public abstract class PricesProvider : ProviderBase
La suite du code définit les méthodes, propriétés… dont nous aurons besoin. Par exemple :
/// <summary> /// Obtient le meilleur prix d'un article donné compte tenu des réductions auxquelles le client est eligible /// </summary> /// <param name="userContext">Instance de <see cref="IPrincipal"/> détenant le profil du client</param> /// <param name="idProduit">Id du produit</param> /// <returns>Meilleurs prix proposé</returns> public abstract double GetBestPrice(IPrincipal userContext, string idProduit);
Je vous conseille vivement d’adopter une cohérence dans la signature des méthodes. Par exemple dans notre cas elles dépendent toutes du profil client que nous chargeons dans l’IPrincipal en cours (on a écrit un principal personnalisé). Celui-ci est par convention le premier paramètre de la ligne et porte systématiquement le même nom et les autres paramètres sont toujours positionnés dans le même ordre.
Croyez moi, c’est très pratique à l’usage et peut à l’occasion éviter quelques bogues de copier/coller.
Il ne vous reste plus qu’à implémenter votre logique métier pour chaque marque :
public sealed class ContosoPricesProvider : PricesProvider { Random rnd = new Random(DateTime.Now.Millisecond);public double GetBestPrice(IPrincipal userContext, string idProduit) { return 500 * rnd.NextDouble(); } }
Note : ce code n’est pas contractuel !
Implémentons la factory. Le corps de la classe est le suivant :
public static class PricesFactory { // le code ici }
Avant de procéder à son implémentation des étapes intermédiaires nous attendent.
Tout d’abord une collection de fournisseurs nous rendra quelques services :
public sealed class PricesProviderCollection : ProviderCollection { new public PricesProvider this[string name] { [DebuggerStepThrough] get { return (PricesProvider)base[name]; } }public override void Add(ProviderBase provider) { if (provider == null) { throw new ArgumentNullException(Ressources.PricesProviderCollection_ProviderNull); }
if (!(provider is PricesProvider)) { throw new ArgumentException("Le type du provider doit être PricesProvider"); }
base.Add(provider); } }
Il s’agit simplement de typer fortement ProviderCollection qui ne semble pas exister en version générique. Rien d’obligatoire, mais conseillé tout de même.
Nous souhaiterions déclarer dans le fichier web.config le provider qui sera utilisé. Pour cela on doit écrire une classe de configuration. Rien de plus simple :
public sealed class PricesProviderConfiguration : ConfigurationSection { [ConfigurationProperty("providers")] public ProviderSettingsCollection Providers { get { return (ProviderSettingsCollection)base["providers"]; } }[ConfigurationProperty("default")] public string DefaultProviderName { [DebuggerStepThrough] get { return base["default"] as string; }
[DebuggerStepThrough] set { base["default"] = value; } } }
On peut ajouter diverses fioritures selon ce que vous souhaitez faire, mais vous voyez l’idée générale qui est de pouvoir ajouter dans le fichier de configuration quelque chose comme ceci :
<PricesConfigSection default="ContosoPricesProvider"> <providers> <add name="ContosoPricesProvider" type=" PriceModule.ContosoPricesProvider, Business" /> </providers> </PricesConfigSection>
Dans la section <providers> vous ajoutez autant de déclaration de classe réelle que nécessaire, et l’attribut default permet de sélectionner celle que le provider devra charger.
Une petite formalité toutefois, il faut aussi ajouter une déclaration similaire à celle-ci :
<section name="PricesConfigSection" type=" PriceModule.PricesProviderConfiguration, Business" />
Pour que l’analyseur du fichier web.config sache comment traiter la section ajoutée.
Revenons donc à notre factory.
Le constructeur (forcément static) est très simple :
static PricesFactory()
{
Initialize();
}
La méthode Initialize va simplement charger la collection de fournisseur trouvée dans le fichier de configuration. On implémente un patron singleton.
private static void Initialize() { if (IsInitialized) { return; }// début de l'initialisation lock (InitializationLock) { // pattern du double-checkin if (IsInitialized) { return; }
PricesProviderConfiguration configSection = (PricesProviderConfiguration)ConfigurationManager.GetSection("PricesConfigSection");
if (configSection == null) { throw new … }
_Providers = new PricesProviderCollection(); ProvidersHelper.InstantiateProviders(configSection.Providers, _Providers, typeof(PricesProvider)); _ProviderSettings = configSection.Providers;
if (_Providers[configSection.DefaultProviderName] == null) { throw new … } _Default = _Providers[configSection.DefaultProviderName];
IsInitialized = true; } }
/// <summary> /// <b>false</b> indique que la classe n'a pas été initialisée /// </summary> private static bool IsInitialized; /// <summary> /// Variable utilisée pour poser un verrou /// </summary> private static object InitializationLock = new object();
Notez l’utilisation des classes ConfigurationManager et ProvidersHelper qui font l’essentiel du travail. Donc au total un code relativement simple.
Ce n’est pas tout, on doit encore déclarer la propriété suivante qui sera alimentée par la méthode que nous venons de voir :
[DebuggerBrowsable(DebuggerBrowsableState.Never)] private static PricesProvider _Default;public static PricesProvider Default { [DebuggerStepThrough] get { return _Default; } }
Elle remonte le provider par défaut déclaré dans le fichier de configuration. Notez bien que la propriété est statique.
On peut agrémenter la classe en rendant public les collections ProvidersCollection ou ProviderSettingsCollection.
Bibliographie
[1] Aide en ligne sur ProviderBase
http://msdn.microsoft.com/fr-fr/library/system.configuration.provider.providerbase.aspx
22.02.09
Des sons mystérieux
Pour mon anniversaire je ne vais pas parler informatique, encore qu’aucunes de ces informations ne seraient connues sans, mais de sujets plus légers : les mystères de la nature !!!!
En permanence des organismes de recherche ou militaires écoutent les sons entendus au fond des océans. La plupart d’entre eux sont catalogué et identifiés. La plupart parce que certains sont classés secret défense.
Par exemple tant qu’un sous marin de notre flotte nucléaire est encore en activité, le détail de ses pérégrinations sous les mers est tenu secret. C’est au cas où une puissance étrangère aurait enregistré son passage, avec des recoupements elle pourrait parvenir à l’identifier et donc le repérer.
Mais durant l’été 1997 le réseau hydrophone de l’US Navy a entendu ceci autour de 50° S 100° W (côte sud-ouest de l’Amérique du Sud) :
Ce son est appelé le bloop dans la littérature. Il ne s’est jamais reproduit depuis cet été là.
Alors c’est quoi ?
• Il ressemble aux bruits émis par une baleine. Mais si c’est le cas elle serait encore plus gigantesque que la baleine bleue et il y a fort à parier qu’elle aurait été aperçue depuis longtemps.
• On ne pense pas que ce soit un calmar géant car aucune des espèces connue n’a les dispositions anatomiques pour produire un son de cette nature.
• Un sous marins ? On ne voit pas très bien comment un son aussi intense pourrait être produit.
Bref on n’en sait rien.
Le plus étonnant est ailleurs. Souvenez-vous des coordonnées. Elles sont étonnamment proches de celle-ci : 48°50S 123°20W.
Ce point est appelé par les géographes le point Nemo. Il s’agit du point le plus éloignés de toutes les terres.
![]()
Par une étrange coïncidence, ce point est également proche de la ville fictive de R’lyeh imaginée par H. P. Lovecraft. Dans sa nouvelle L’Appel de Cthulhu, Lovecraft avait situé R’lyeh à 47°9′S 126°43′W dans l’Océan Pacifique sud. Dans la mythologie lovecraftienne, le grand ancien Cthulhu était enfermé dans cette cité mythique.
Des sons mystérieux il y en a bien d’autres.
Par exemple le slow-down[5] enregistré la même année (19 mai 1997) dans l’océan pacifique et jamais réentendu.

Certains signaux sont extraterrestres. Ainsi le signal Wow [4] capté le 15 août 1977 par un radio télescope pendant 72 secondes dans le cadre du projet SETI. On ne sait pas du tout ce que c’est, inutile de dire que son côté extraterrestre a échauffé bien des esprits fertiles…
Bibliographie
[1] Le bloob sur l’irremplaçable Wikipédia:
http://fr.wikipedia.org/wiki/Bloop
[2] Mais il a aussi sa home page :
http://www.bloopwatch.org/thebloop.html
[3] Le point Nemo
http://fr.wikipedia.org/wiki/Point_N%C3%A9mo
[4] Le Wow
http://fr.wikipedia.org/wiki/Signal_Wow!
[5] Le slow down
http://fr.wikipedia.org/wiki/Slow_Down
15.02.09
Optimiser l'indexation de votre site avec un nouveau tag
Les sites marchands ont tous besoin d'une stratégie d'indexation. En clair être reconnus par Google et ses concurrents principaux: Yahoo et Live.
Un problème récurrent est qu'il arrive que deux url différentes aboutissent à la même page (présence ou non d'ordre de tri par exemple) sans que cela soit significatif du point de vue de l'indexation.
Saluons donc l'initiative coinjointe de Google, Yahoo et Live de prendre en charge un nouveau tag: canonical pour justement nous permettre de déclarer la page étant la plus significative à indexer et d'y consolider tous les liens différents qui pointent vers elle.
Les explication complètes (en français!) sont ici sur le site Moteurs News [1].
Et si vous souhaitez connaître les détails d'implémentation liés à chaque société: [2], [3] et [4]
Le hasard fait bien les choses, c'est justement au moment où le client nous demandait s'il était possible de résoudre ce problème que l'annonce a été faite...
Bibliographie
[1] URL canonique, nouveau tag de Google Yahoo! et Microsoft
[2] Annonce Google:
http://googlewebmastercentral.blogspot.com/2009/02/specify-your-canonical.html
[3] Annonce Yahoo:
http://ysearchblog.com/2009/02/12/fighting-duplication-adding-more-arrows-to-your-quiver/
[4] Annonce Microsoft:
Lab sur le bloc d'application Validation
Le groupe Patterns & Practices a pour objectif de proposer des règles de conception et de développement pour tirer le meilleur profit de la plateforme Microsoft. Fréquenter régulièrement leur site est donc obligatoire pour tout architecte ou développeur sérieux [1].
Vous y trouvez beaucoup de matériaux : des livres en ligne, des framework prêt à l’emploi (appelés Application Block), des vidéos, des tutoriels, des labs…
Mais aussi des gourous qui ne s’appellent pas MVP, mais Champions dans la terminologie du groupe !
L’accent est placé très fort sur la documentation et l’aide à la formation. Si je n’avais qu’un regret à formuler et que tout est en anglais, mais cela ne doit absolument pas vous décourager.
Si vous souhaitez en savoir plus sur ce groupe et leurs méthodes de travail, rendez vous ici [2].
La dernière livraison des Entreprises Library (Entlib) porte le numéro de version 4.1. Ceux qui sont allés au TechEd on pu suivre une conférence de Grigori Melnik sur les nouveautés qu’elle apporte avec une amusante application de régulation des feux rouges à Seattle.
En janvier Microsoft a publié un très intéressant lab sur l’application block Validation [3].
Comme toujours, la finition des AB est très soignée et le lab couvre pratiquement toutes ses possibilités:
• Un jeu d’attributs à poser sur les membres d’une classe pour définir les contraintes auxquelles ils doivent répondre:
être un entier, non null, une chaîne d’au moins 4 caractères, une valeur parmi une liste donnée…
• Le framework offre un support de validation des éléments d’une collection
• Plutôt que de jouer avec des attributs, on peut paramétrer la stratégie de validation depuis le fichier de configuration soit à la main, soit à l’aide d’un assistant
• Support de plusieurs stratégies de validation à travers l’application de règles:
Certains membres peuvent être obligatoires dans un contexte et pas un autre par exemple
• Déclaration de méthodes d’auto-validation lancées automatiquement au moment de la validation
• Support de WCF
• Des composants pour les Winforms et ASP.NET
• Modèle de données extensible, on n’est pas limité aux stratégies de validation fournies par le framework
Bref du très beau travail.
Bibliographie
[1] Page d’accueil du groupe Patterns & Practices
http://msdn.microsoft.com/fr-fr/practices/default(en-us).aspx
[2] Qui sont t’ils ?
http://msdn.microsoft.com/fr-fr/practices/bb190357(en-us).aspx
[3] Lab du bloc d’application Validation :
http://www.microsoft.com/downloads/details.aspx?FamilyId=C8CA14D0-05EA-4A44-AE78-F5E4DF6208AF&displaylang=en
[4] Télécharger Entlib 4.1 :
http://www.microsoft.com/downloads/details.aspx?FamilyId=1643758B-2986-47F7-B529-3E41584B6CE5&displaylang=en
09.02.09
Encodage des urls
Un récent article de Rick Strahl me rappelle un problème rencontré sur un projet.
J'avais la charge de l'écriture d'un frontal qui consolidait des résultats reçus de divers services Web. Nous avons passé pas mal de temps à nous bagarrer contre des problèmes de codage/décodage incompatibles entre eux et qui apparaissaient de façon en apparence aléatoire…
Que se passait-il?
Pour diverses raisons ces services pouvaient (ou non) encoder certaines données de différentes façons. De mon côté je devais donc faire l'opération inverse pour afficher correctement le résultat et permettre aux utilisateurs d'éventuellement les modifier. Oui, mais laquelle, selon quels critères? Bref on s'est pas mal amusé…
L'espace de noms System.Web contient les méthodes d'encodages suivantes:
1. UrlEncoding
2. UrlDecoding
3. HtmlEncoding
4. HtmlDecoding
Decoding défaisant ce qu'Encoding fait.
On retrouve également UrlEncoding/Decoding dans System.
La question que l'on peut se poser est a quoi servent autant de méthodes. Voici un exemple adapté du papier de Rick Strahl qui devrait nous servir de base de discussion:
string test = "This is a value & I don't care for it.t\"quoted\" 'single quoted',<% alligator %>#";string UrlHttpUtility_Encoded = System.Web.HttpUtility.UrlEncode(test); string UriDataEscaped_Encoded = System.Uri.EscapeDataString(test); string UriEscaped_Encoded = System.Uri.EscapeUriString(test);
Le résultat est résumé ici ans l'ordre où ils sont générés:
This+is+a+value+%26+I+don't+care+for+it.%09%22quoted%22+'single+quoted'%2c%3c%25+alligator+%25%3e%23 This%20is%20a%20value%20%26%20I%20don't%20care%20for%20it.%09%22quoted%22%20'single%20quoted'%2C%3C%25%20alligator%20%25%3E%23 This%20is%20a%20value%20&%20I%20don't%20care%20for%20it.%09%22quoted%22%20'single%20quoted',%3C%25%20alligator%20%25%3E#
Ces méthodes disposent d'une méthode de décodage. Euuh, en fait pas toutes. Par exemple je n'ai rien trouvé pour EscapeUriString.
Il se pose donc la question de la compatibilité de ces méthodes entre elles, ce que nous pouvons tester derechef! (derechef: on m'aurait dit que je caserais un jour ce mot…)
string s1 = System.Uri.UnescapeDataString(UrlHttpUtility_Encoded); string s2 = System.Web.HttpUtility.UrlDecode(UrlHttpUtility_Encoded);string s3 = System.Web.HttpUtility.UrlDecode(UriDataEscaped_Encoded); string s4 = System.Uri.UnescapeDataString(UriDataEscaped_Encoded);
string s5 = System.Web.HttpUtility.UrlDecode(UriEscaped_Encoded); string s6 = System.Uri.UnescapeDataString(UriEscaped_Encoded);
On obtient alors:

Soit 5 résultats corrects sur 6, ce n'est pas si mal! Et surtout, remarquez que la variante Decode sait traiter correctement sa sœur en Encode.
La conclusion est qu'il est peut être préférable de ne pas encoder ses url avec System.Web.HttpUtility.UrlEncode, si on peut l'éviter, à moins d'être certains d'employer la bonne méthode de décodage. Ce qui suppose de connaître à l'avance la méthode d'encodage et c'était justement un de nos problèmes…
Et sur le fond, c'est une assez bonne nouvelle comme le souligne Rick Strahl. En effet, on n'a alors plus besoin de référencer System.Web qui pèse tout de même 2,5 Mo dans une application non Web pour faire une malheureuse opération d'encodage/décodage d'url.
Pour encoder on utilise EscapeDataString(), mais pour décoder:
public static string UrlDecode(string text) { // au cas où l'encodage a été fait avec // System.Web.HttpUtility.UrlEncode() text = text.Replace("+", " "); return System.Uri.UnescapeDataString(text); }
Oui, il faut tout de même prévoir le cas où l'url a été encodée avec System.Web. On a donc une méthode universelle de décodage qui ne passe pas par System.Web. Rick Strahl montre aussi une fonction qui sait récupérer une donnée dans une chaîne de requête encodée.
Pour éliminer la référence à System.Web il reste encore à traiter le cas de l'encodage HTML. La solution proposée est la suivante:
public static string HtmlEncode(string text) { if (text == null) { return null; }StringBuilder sb = new StringBuilder(text.Length);
int len = text.Length; for (int i = 0; i < len; i++) { switch (text[i]) { case '<': sb.Append("<"); break; case '>': sb.Append(">"); break; case '"': sb.Append("""); break; case '&': sb.Append("&"); break; default: if (text[i] > 159) { // decimal numeric entity sb.Append("&#"); sb.Append(((int)text[i]).ToString(CultureInfo.InvariantCulture)); sb.Append(";"); } else { sb.Append(text[i]); } break; } } return sb.ToString(); }
La principale différence entre cette solution et l'implémentation proposée par Microsoft est qu'à Redmond ils encodent avec des entités uniquement sur la plage [160-255] et non pas tout ce qui est au dessus de 159. Si quelqu'un a une explication…
Bibliographie
Article de Rick Strahl:
http://www.west-wind.com/weblog/posts/617930.aspx
Message subliminal à Sami:
C'est quand que tu nous installe la version suivante de B2Evolution que tu nous a fais miroiter il y a un an?
02.01.09
Trucs en vrac pour démarrage en douceur
Outre mes meilleurs vœux à vous tous, je voudrai signaler un extraordinaire tutoriel sur Entity Framework:
500 pages tout de même!!
Ce n'est pas tout, Zeeshan Hirani a gentiment accepté que je traduise en français son travail. Ce ne sera pas prêt pour la semaine prochaine, mais c'est en cours!
Sur un autre sujet Imar Spaanjaars a publié sa version pour .NET 3.5 de son tutoriel sur le modèle NTiers:
http://imar.spaanjaars.com/QuickDocId.aspx?quickdoc=476
Bon pour l'instant il n'y a encore que les deux premières parties d'une série de 6. Mais en attendant vous pouvez lire la version pour .NET 2.0 (même lien).
Encore un truc que j'aimerai bien avoir le temps de traduire…
Autre annonce, Microsoft vient de publier une galerie de modèles de site adaptés pour son framework MVC. Vous pouvez les réutiliser ou ajouter vos œuvres, c'est ici:
http://www.asp.net/mvc/gallery/default.aspx?supportsjs=true
Et je termine en signalant un petit blog amusant de François Remy sur tous ces fichiers système qui nous pourrissent les répertoires:
27.12.08
Tester l'existence d'une ressource avec une requête http
Dernier blog de l'année.
Lorsque le navigateur rencontre une ressource qui n'existe pas dans son script HTML il lève selon le cas une exception ou affiche un petit carré rouge bien connu (image par exemple). Au niveau des logs on voit apparaître une erreur http 404.
Ce comportement est parfois gênant en particulier pour la mise en page. On peut donc souhaiter faire un test côté serveur avant de réaliser le rendu de la page.
Si la ressource fait partie du site ou de la base de données sous jacente c'est assez simple, mais que se passe t'il s'il s'agit d'une image installée sur un autre serveur?
On peut alors être tenté d'écrire un code de ce genre:
public static bool CheckImageExists(string url) { WebResponse response = null; try { WebRequest request = HttpWebRequest.Create(url); response = request.GetResponse(); // la ressource existe return true; } catch (WebException) { return false; } finally { if (response != null) { response.Close(); } } }
On passe à la méthode l'url de la ressource à tester et on fait une requête http. Une exception Web avec le statut 404 est levée si les choses se passent mal (ou tout autre exception si les choses se passent très mal!). Il ne reste plus qu'à implémenter la logique de comportement selon le statut du retour de la fonction.
Cela fonctionne très bien si la ressource n'existe pas. Dans la négative cela fonctionne TROP bien...
On a certes notre résultat, mais on a remonté la ressource complète juste pour tester son existence et ce travail devra être fait une deuxième fois par le navigateur client.
Même si on ne parle pas d'une vidéo il n'est pas certain que l'on optimise l'utilisation de la bande passante!
Voici une version légèrement modifiée qui résout ce problème:
public static bool CheckImageExists(string url) { WebResponse response = null; try { WebRequest request = HttpWebRequest.Create(url); // pour ne lire que le header, pas les données request.Method = "HEAD"; response = request.GetResponse(); // la ressource existe return true; } catch (WebException) { return false; } finally { if (response != null) { response.Close(); } } }
La différence est que la méthode de la requête est maintenant HEAD ce qui demande au serveur Web de ne transférer que la partie header de la requête. Le flux est considérablement allégé.
Bibliographie
Ben oui, j'ai pas la science infuse. C'est même le client qui a trouvé ceci:
20.12.08
Histoire de chemin virtuel
Le rythme de mes blogs s'en ressent, mais depuis bientôt un an je suis sur un projet sans doute le plus ambitieux de ma carrière.
Nous sommes arrivés à la dernière itération et je suis donc en train de rédiger la 3ème version de sa documentation technique. Ce qui me donne l'occasion de faire le point sur les dizaines de détails plus ou moins triviaux qu'il nous a fallu affronter. Je pense que certains prendront la forme d'un blog ou d'un article au cours des mois qui suivent. A commencer par cet article.
Le contexte technique
Le site est structuré autour d'un backoffice, de services Windows et de diverses versions du site Web (production, recette, prévisualisation) qui proposent ou non certaines fonctionnalités.
Tout cela est parfois complexe à tester depuis l'environnement Visual Studio. Nous avons donc écrit un script MSBUILD qui se charge de redéployer les 6 modules constituants le projet.
Le problème
Le site web est un site commercial multilingue.
On a décidé de détecter la langue en analysant l'url. Par exemple une url sera typiquement:
http://www. marque.be/nl/listeproduits.aspx
Cette url me renseigne sur le fait qu'il s'agit de la filiale belge et la langue demandée est le flamand. Ainsi il existe un code qui analyse à chaque requête l'url et en déduit que l'on doit afficher les informations en flamand.
Nous avons du résoudre deux problèmes:
1. Comment faire depuis Visual Studio (VS)
2. Comment faire depuis IIS
Par défaut, VS et IIS se contentent de reproduire le chemin physique dans les url. Ainsi une page située dans le répertoire /Pages apparaîtra comme:
http://localhost:2453/Pages/ListeProduits.aspx
Une première idée est donc de créer des répertoires physiques pour chaque langue concernée et faire autant de déploiement du site que de cas de figure.
Cela fonctionne, mais cela complique le déploiement inutilement et n'est pas simple à mettre en place si on lance le site depuis VS. Les chemins virtuels offrent une alternative plus intéressante.
Visual studio
VS propose un paramétrage pour contrôler le chemin virtuel utilisé. Il se trouve dans les paramètres du projet, onglet Web.

Si par exemple j'écris /fr, la page précédente apparaît dans le navigateur sous la forme:
http://localhost:2453/fr/Pages/ListeProduits.aspx
Ce qui suffit à notre bonheur.
IIS
Rappelons peut être ce qu'est au juste un répertoire virtuel par rapport au répertoire physique.
Le répertoire physique est un répertoire qui existe (au sens d'accessible depuis Windows Explorer) sous le répertoire de base d'un site.
Le répertoire virtuel est simplement un alias vers un autre répertoire qui existe ailleurs, éventuellement sur un autre serveur. Simplement du point de vue de IIS il est vu comme un sous répertoire du répertoire de base.
On peut donc créer un site comme l'agrégation de plusieurs répertoires virtuels correspondants à des répertoires distribués un peu partout sur votre serveur. C'est donc très pratique pour basculer rapidement un site d'une version à l'autre.
Première étape, on créée un répertoire local pour le site Web, par exemple:
C:\acme
Le répertoire est vide et va d'ailleurs le rester.
Le site proprement dit est présent dans un autre répertoire. Par exemple celui de la marque Marque1:
C:\SitesWeb\ marque1
Deuxième étape on créé un site Web dont le répertoire local est c:\acme.
Troisième étape, on crée des sous répertoires virtuels pour chaque langue. Eux par contre, pointent sur c:\SitesWeb\marque1. Pour cela:
On sélectionne le site web dans le gestionnaire IIS, clic droit et New/Virtual Directory.
On sélectionne fr comme alias pour le site français, nl pour le site flamand… Le chemin est bien sûr celui du site. On recommence pour chaque langue. Mais évidemment on pointe toujours au même endroit.

Après c'est très simple, pour accéder au site on entre l'url qui va bien:
http://localhost/fr/default.aspx
http://localhost/nl/default.aspx
On a donc bien construit un site multilingue. Bien sûr chaque langue utilise le même port puisque le site de base est le même.
La beauté des répertoires virtuels est que l'on a un seul répertoire à mettre à jour quelle que soit la langue.
En fait l'application existe avec plusieurs modes de fonctionnement et fonctionne pour plusieurs marques dans plusieurs pays. Le script est donc un peu plus compliqué que cela…
Conclusion
Bonne années à tout le monde!
Plus particulièrement à Guillaume, Grégory, Stéphane, Aymeric, François, Pascal, Damien, les Alex, Laure et les Christophe qui ont apportés leur pierre et leur enthousiasme à l'édifice. Des gens de qualité, merci beaucoup.
PS: ceci étant, s'il est arrivé quoi que ce soit à mon hippopotame en peluche, attention à la rentrée (si je suis en forme!!!)
19.12.08
O Reilly, c'est fini
C'est une triste nouvelle que je viens d'apprendre par hasard.
Depuis mai dernier la maison O'Reilly France a fermée ses portes.
C'est l'éditeur qui publiait par exemple "Pratique de .Net" de Patrick Smacchia et les excellentissimes bouquins de la collection Tête La Première...
Les éditions Ellipse ont rachetées le fond, mais ne feront pas de réédition, donc si certains ouvrages vous intéressent dépêchez vous! Après ils ne seront plus qu'en anglais. J'ai lu dans certains blogs que quelques auteurs proposent leur bouquin en PDF...
En ce moment je lis justement un bouquin de chez eux... mais en anglais: "Head first Physics"
Rien à voir avec l'informatique, mais un bouquin de physique niveau seconde avec lequelle je m'amuse bien et c'est dommage qu'il ne sera peut être jamais traduit en français.
http://blog.immateriel.fr/2008/05/09/oreilly_france_c_est_deja_du_passe
Alors, y aura t'il un "Pratique .NET 4 et C# 4"? Il y aurai largement de la matière je dois dire!
14.11.08
Ma semaine au TechEd
J'ai donc passé la semaine au TechEd de Barcelone.
Même si j'ai réservé mes comptes rendus au blog interne de ma boite, je vais en parler un petit peu ici.
Le message du jour
L'événement marquant fut clairement la plateforme Azure et le cloud computing.
Si vous êtes architecte allez voir dès maintenant. Il est possible que l'informatique de la prochaine décennie se joue là, en tout cas une chose est certaine Microsoft y croit très fort. La preuve ils sont en train de construire un datacenter géant qui va consommer à peu près le quart d'une tranche de centrale nucléaire à lui seul!
Des tonnes de ressources ici en attendant les vidéos du TechEd:
http://msdn.microsoft.com/fr-fr/msdn.pdc2008.azure.aspx
Et bien sûr:
http://www.microsoft.com/azure/default.mspx
Pas besoin d'Azure pour commencer à jouer avec le SDK.
Reste la question qui fâche: est-ce que les utilisateurs auront suffisamment confiance en Microsoft pour… Non, non, j'ai rien dit!
Moi aussi je fais le kéké
Apprendre des trucs qui n'intéressent personne ce n'est pas réservé à Sébastien Ros! Moi-même par exemple j'ai appris ceci au détour d'un lab:
Normalement on ne peut pas ajouter des attributs à des membres existants d'une classe partielle… Sauf avec le contournement suivant:
Prenons le cas d'une classe Product générée par le designer dbml par exemple.
On commence par créer une classe public dont le seul rôle sera de porter les métadonnées.
Appelons la MetadataProduct (mais AuegfOaiuzbcpiuYfpiaius fait aussi l'affaire, à vous de voir).
On décore Product de l'attribut:
[MetaDataType(typeof(MetadataProduct))]
Et c'est presque terminé. Si par exemple vous souhaitez ajouter des métas à la propriété Product.Nom, vous ajoutez dans MetadataProduct ceci:
public String Produit {get;set}
Et enfin vous ajoutez vos attributs et ça marche!
Oui je sais, tout le monde s'en fiche...
L'annonce du jour
Par contre, plus intéressant, depuis le 1er octobre dernier, DBPro (en fait il a encore changé de nom, cela doit faire bien 4 ou 5 en deux versions!) fait maintenant partie de Visual Studio Pro!!! Les abonnés MSDN peuvent même charger le patch pour VS 2008 dès maintenant. Je pense que je vais en profiter pour remettre à jour le tutoriel que j'avais fais lors de la sortie de la première version il y a deux ans déjà!
Un autre truc que je ne connaissais pas
Depuis le SP1 de VS 2008 on dispose d'un nouveau type de projet: Dynamic Data Web Site (version Linq for Sql et version Entities).
L'idée est de créer sans ligne de code un site complet qui fournit un accès CRUD vers toutes les tables déclarées dans un DBML. C'est quasiment magique.
Bon on peut aussi paramétrer et personnaliser un peu, mais c'est pour les sites très orientés données.
L'utilisation immédiate que je vois, c'est pour fournir une interface plus conviviale que Management Studio pour tester une appli et voir ce qui se passe dans les données. En tout cas je sais quel sera ma première tâche lundi!
A quoi ça sert?
Alors peut être vous demandez vous si c'est utile d'aller à une manifestation comme le TechEd.
A chacun de juger, mais personnellement j'ai passé une semaine à n'avoir rien d'autre à faire qu'assister à des conférences sur des tas de trucs que je connaissais, mais il y a toujours des choses à découvrir. Des tas de trucs que je ne connaissais pas aussi. Des tas de trucs mis de côté faute de temps d'aller y voir.
Une semaine!! Rendez vous compte!
Chose quasiment impossible à faire en temps ordinaire. En une semaine j'ai donc fait pratiquement 6 mois de veille technologique. C'est rentable à mon avis.
Et puis je suis revenu avec des tonnes d'idées intéressantes à mettre en œuvre ou à tester. J'ai remplis deux blocs notes complets d'informations diverses qui vont pas mal me servir tout au long de l'année.
Bref allez au TechEd si vous le pouvez.
Une fois la bas, inutile d'aller voir 50 conf sur le même sujet. Jouez plutôt la diversité. Et soyez en forme!
Evidemment c'est en anglais et certains conférenciers ne font pas beaucoup d'effort pour les auditeurs non anglophones qui essayent de prendre des notes en même temps…
Je dirai qu'il vous reste les blogs. Mais en informatique l'anglais, vous en aurez besoin à mon avis.
06.11.08
Les chaînes et le contexte linguistique
Les méthodes de manipulation de chaînes (StartWith, Equals, Compare…) ont évoluées avec le framework 2.0 afin de clarifier la prise en charge du contexte linguistique de l'application.
En parallèle, Microsoft a mis à jour ses recommandations [1] d'usage. C'est ce que nous allons examiner dans cet article.
Pour vous convaincre de ne pas abandonner tout de suite une lecture peut être austère, voici le genre de bug subtil qui pourrait vous arriver si vous ne prenez pas les bonnes précautions:
Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US"); Console.WriteLine("Culture = {0}", Thread.CurrentThread.CurrentCulture.DisplayName); Console.WriteLine("(file == FILE) = {0}", (String.Compare("file", "FILE", true) == 0));Thread.CurrentThread.CurrentCulture = new CultureInfo("tr-TR"); Console.WriteLine("Culture = {0}", Thread.CurrentThread.CurrentCulture.DisplayName); Console.WriteLine("(file == FILE) = {0}", (String.Compare("file", "FILE", true) == 0));
On compare les mots file et FILE dans un contexte culturel US puis turc. On obtient:
Culture = English (United State)
(file == FILE) = True
Culture = Turkish (Turkey)
(file == FILE) = False
Imaginez maintenant que l'on compare des mots de passe par exemple…
Les bonnes précautions, quelles sont t'elles au juste?
D'une façon générale il est recommandé d'utiliser systématiquement les surcharges des méthodes avec le paramètre du type StringComparison.
Il s'agit d'une énumération prenant ses valeurs dans la liste suivante:
1. Ordinal
2. OrdinalIgnoreCase
3. CurrentCulture
4. CurrentCultureIgnoreCase
5. InvariantCulture
6. InvariantCultureIgnoreCase
Microsoft recommande d'utiliser a priori l'une des deux premières valeurs, ce qui convient dans la plupart des cas. La comparaison est effectuée au niveau des octets, il s'agit donc d'une comparaison très rapide.
De fait le bug se résout effectivement avec OrdinalIgnoreCase.
L'implémentation actuelle des méthodes sans surcharge utilise CurrentCulture. Mais la version 4.0 du framework utilisera Ordinal[2] à l'exception de CompareTo et Compare puisque ces méthodes sont en général utilisées pour des opérations de tri.
Il est donc important de prendre l'habitude de se servir des variantes avec surcharge ne serai-ce que pour faciliter la maintenance, c'est toujours sain d'être clair sur ses intentions, ou bien de développer des classes utilitaires.
Par ailleurs Equals utilise non pas CurrentCulture, mais Ordinal. Ce qui fait une complication supplémentaire lorsqu'on utilise les méthodes sans surcharge.
Initialement Microsoft recommandait d'utiliser l'option InvariantCulture. Le problème est que cette option amène .NET à effectuer certains choix conduisant de ce fait à des résultats parfois imprévisibles lorsque les caractères manipulés n'appartiennent pas au jeu de caractères ASCII.
Vous trouverez en [1] un tableau récapitulatif des cas d'utilisation des valeurs de StringComparison.
Bibliographie
[1] http://msdn.microsoft.com/fr-fr/library/ms973919(en-us).aspx
[2] http://blogs.msdn.com/bclteam/default.aspx
18.10.08
Retour sur le ViewState
Cette semaine j'ai reçu un email d'une personne qui venait de lire mon article sur le viewstate[1] émaillé de plusieurs questions.
Comme il y a longtemps que je n'ai pas fais de blog je vais y répondre ici. D'autant plus que la bonne maîtrise du fonctionnement du ViewState me semble indispensable en ASP.NET.
Quand tu écris: "...Seules les données dont la propriété IsDirty est à false seront recopiées..." (partie "Créer correctement un composant dynamique"), je ne comprend pas pourquoi.
Pourquoi aller sauvegarder des propriétés qui n'ont pas été modifiées dans le code (code behind après la phase d'init et donc le marked à true) au niveau du ViewState ??? Ne souhaitons-nous pas plutôt sauvegarder ce qui a été changé dans le code afin de faire persister ces données ???
IsDirty sert à différencier deux situations.
Une propriété peut être présente dans le code d'initialisation du composant, c'est-à-dire soit alimentée au cours de son événement Init, soit déclaré dans la page ASPX comme ceci:
<asp:Button ID="Button1" runat="server" Text="Valider" />
Dans les deux cas on va trouver quelque chose de ce genre dans le code d'initialisation:
this.Button1.Text = "Valider"; (1)
Dans ce cas il est inutile de sauvegarder la valeur de la propriété Text dans le ViewState car elle est déjà dans le code généré par ASP pour initialiser la page.
Mais ce n'est pas toujours le cas. Notre code peut contenir ceci dans l'événement Page.Load:
this.Button1.Text = MaBll.MaFonction(); (2)
Si MaFonction() retourne une information rapide à obtenir et/ou non pérenne (un cours boursier!) on doit effectivement réexécuter ce code à chaque renvoi. Mais le plus souvent c'est inutile et on préfère l'enregistrer dans le ViewState pour le recharger automatiquement (on n'oublie pas d'implémenter la règle d'alimentation dans ce cas!).
Si vous comparez (1) et (2), vous constatez qu'il s'agit du même code, mais dans deux situations distinctes. IsDirty permet de les distinguer et dire à ASP.NET si on doit ou non sérialiser l'information dans le ViewState.
Voici donc ma compréhension du mécanisme.
Quand tu écris "...Simplement le fait que les composants qui implémentent IPostBackEventHandler, comme les TextBox, gèrent eux même leur état..." (partie "Persistance d'un Label"), je suis personnellement et actuellement (car j'ai peut-être pas bien compris) à moitié d'accord. En effet, le TextBox n'est persistant qu'au niveau de la propriété Text uniquement.
En effet, si je prend le TextBox suivant:
<asp:TextBox ID="txtBox1" EnableViewState="false" runat="server" />Et que dans le page_load, je fais:
if (!this.IsPostBack)
{
txtBox1.ToolTip = "Def";
txtBox1.Text = "test";
}
La propriété Text sera bien persistante mais pas la propriété ToolTip. Ceci doit être du au fait que le POST ne "poste" que la valeur du champ input>.
Le code standard pour créer une propriété personnalisée qui persiste sa valeur dans le viewstate est le suivant:
#region MyProperty /// <summary> /// (Description) /// </summary> [Category("Data"), Description("(Description)")] public String MyProperty { [DebuggerStepThrough] get { if (this.ViewState["MyProperty"] == null) { // valeur par défaut return default(String); } return (String)this.ViewState["MyProperty"]; }[DebuggerStepThrough] set { this.ViewState["MyProperty"] = value; } } #endregion
(Notez la présence des commentaires, je suis un intégriste religieux la dessus!).
Je suis certain que vous comprendrez sans difficultés comment ça marche.
Si vous décompilez la classe TextBox vous verrez un code similaire pour sa propriété Text. Si vous faites de même pour ToolTip (hérité de WebControl), vous trouvez aussi la même chose!
De fait la persistance de ToolTip est active, comme celle de Text. Chez moi le code fonctionne comme prévu. Mais avec VS2008. Peut être que cela à changé par rapport à des versions plus anciennes de ASP.NET.
Depuis la rédaction de cet article, j'ai appris des choses que j'ignorai au sujet des coulisses du mécanisme de persistance qui ne doit rien à ASP.NET, mais est natif à HTML. J'ai expliqué cela dans un autre blog que je vous encourage à lire[2] également.
Je vous y encourage même vivement, car par expérience il m'aurait évité de m'arracher les cheveux sur des composants dynamiques qui ne se réinitialisent pas bien sur pas mal de projets alors que la solution tenait en une seule ligne!
Bibliographie
[1] http://www.dotnetguru.org/articles/dossiers/viewstate/viewstate.htm
[2] http://www.dotnetguru2.org/amethyste/index.php?p=785&more=1&c=1&tb=1&pb=1
18.09.08
SAGA 5: sérialisation XML
De retour de vacances pour rédiger ce qui sera sans doute le dernier de la série.
Au programme d'aujourd'hui:
• Utilisation de certains attributs mals connus
• Comment personnaliser la sérialisation
Dans le deuxième opus de cette saga on a vu comment on pouvait gérer la présence d'éléments inattendus à l'aide de nouveaux événements de XmlSerializer.
Une méthode alternative plus ancienne est basée sur les deux attributs XmlAnyAttributeAttribute et XmlAnyElementAttribute que l'on applique à un membre retournant un tableau de XmlNode ou de XmlElement voire XmlAttribute.
La différence essentielle entre les deux attributs est que le premier ne concerne que les attributs (au sens XML) inattendus, tandis que le deuxième concerne les éléments. A ceci près, le fonctionnement est identique.
Sachez aussi que l'utilisation de ces attributs déconnecte l'appel aux événements UnknownNode et UnknownAttribute.
Un détail que j'ai mis un moment à comprendre avec XmlAnyAttribute est que les attributs dont on parle sont ceux de la classe mère à laquelle appartient le membre déclaré avec cet attribut. Par exemple si je désérialise une classe Pays avec le fichier:
<Pays feteNationale="14 juillet" > <Nom langueOfficielle="français">France</Nom> <Capitale>Paris</Capitale> </Pays>
Je vais lire feteNationale, mais pas langueOfficielle.
Pour des raisons que j'ignore, les dictionnaires génériques ne sont pas nativement sérialisables en .NET. C'est ennuyeux car on les rencontre fréquemment. C'est ici qu'intervient l'interface IXmlSerializable qui permet de personnaliser la sérialisation.
Cette interface expose deux méthodes: ReadXml() et WriteXml(). Elles fournissent toutes deux un reader et un writer. Ces méthodes ne sont pas spécialement difficiles à utiliser c'est pourquoi je vais aller assez vite dessus.
Vous trouverez un exemple de dictionnaire générique sérialisable sur le blog de Paul Welter [1]. J'ai déjà testé ce code dans des applications et il fonctionne correctement.
La semaine prochaine on va parler de localisation. J'ai un peu souffert la dessus cet été!
Bibliographie
[1] Ecriture d'un dictionnaire générique sérialisable:
http://weblogs.asp.net/pwelter34/archive/2006/05/03/444961.aspx
Notes d'ailleurs
Les contraintes sont parfois aussi un puissant moyen de stimuler l'imagination et le talent.
Certaines contraintes sont naturelles.
Il existe par exemple un phénomène très rare (peut être unique) en Croatie où l'île de Mljet est la seule île connue à abriter elle-même un îlot, une île dans une île!
C'est sur cette île que la légende voudrait qu'Ulysse fût retenu par la nymphe Calypso.
D'autres sont humaines et forcent d'autant plus l'admiration.
On ne saura jamais quelle était exactement la voix de la belle-sœur de Mozart pour laquelle il écrivit le fameux air de la Reine de la Nuit. Mais on connait celle de Lucia Popp qui a chanté la version de référence (selon moi en tout cas) de cet air plein de rage et de vengeance:
http://fr.youtube.com/watch?v=_ufeyarJxNQ
Morceau qui nécessite agilité vocale et puissance données à peu de sopranos.
En littérature lorsque l'on parle de contraintes, c'est Perec qui vient tout de suite à l'esprit.
Le très délicat "La Vie Mode d'Emploi" à la précision mathématique certes, mais que dire de "La Disparition" qui n'utilise jamais la lettre E, lettre qui revient dans "Les revenentes", mais cette fois ce sont les autres voyelles qui disparaissent!
Pierre Benoît, auteur ambigu, avait pour habitude de donner à ses héroïnes un prénom commençant par la lettre A. Le même Perec le cite dans son "Je me souviens".
Pour information, P. Benoit situe l'Atlantide dans le Sahara (aah, Antinea!).
Et puis si vous êtes joueur, cette rentrée littéraire nous propose un roman de Mathias Enard (Zone) de 517 pages. La première phrase fait exactement ... 517 pages! La phrase la plus longue de Proust ne fait que 243 mots par comparaisons.
Je dirai qu'il est prudent de ne pas tenter ça au BAC.
24.08.08
SAGA 4: sérialisation XML
Cette semaine nous allons rester un peu avec les collections avant d'aborder la sérialisation des énumérations et examiner un dernier cas de figure:
List<Pays> liste = new List<Pays>(); liste.Add(new Pays { Capitale = "Paris", Nom = "France" }); liste.Add(new Pays { Capitale = "Bruxelle", Nom = "Belgique" });
Un premier essai donne ceci après le nettoyage d'usage:
<?xml version="1.0" encoding="utf-8"?> <ArrayOfPays> <Pays capitale="Paris"> <nom>France</nom> </Pays> <Pays capitale="Bruxelles"> <nom>Belgique</nom> </Pays> </ArrayOfPays>
Peut-on remplacer le ArrayOfPays?
Nous l'avions fait précédemment à l'aide des attributs XmlArrayAttribute et XmlEllementAttibute qui ne s'appliquent pas à des déclarations de classe. C'est le rôle de XmlRootAttribute.
Dans notre cas il est nécessaire de définir une nouvelle classe et d'y appliquer l'attribut XmlRoot.
[DebuggerDisplay("{Count}")] [XmlRoot("pays")] public sealed class PaysCollection : List<Pays> { }
Cette fois on obtient:
<pays> <Pays capitale="Paris"> <nom>France</nom> </Pays> <Pays capitale="Bruxelles"> <nom>Belgique</nom> </Pays> </pays>
Notons que les collections, du point de vue de la sérialisation, ne sont pas très différentes des Array.
Terminons sur un dernier cas de figure: la sérialisation des énumérations.
On pourrait modéliser l'appartenance à un continent via une énumération:
public enum NomContinent { Europe, Asie, AmeriqueNord, AmeriqueSud, Oceanie, Afrique, Antartique }
Et un nouveau membre de la classe Pays:
public NomContinent Continent;
Telle quel une sérialisation donne dans le cas de la précédente collection:
<pays> <Pays capitale="Paris"> <Continent>Europe</Continent> <nom>France</nom> </Pays> <Pays capitale="Bruxelle"> <Continent>Europe</Continent> <nom>Belgique</nom> </Pays> </pays>
Tout comme pour un autre membre on peut renommer la sortie standard à l'aide de l'attribut XmlEnumAttribute:
public enum NomContinent { [XmlEnum("europe")] Europe, [XmlEnum("asie")] Asie, [XmlEnum("amérique du nord")] AmeriqueNord, [XmlEnum("amérique du sud")] AmeriqueSud, [XmlEnum("océanie")] Oceanie, [XmlEnum("afrique")] Afrique, [XmlEnum("antartique")] Antartique }
Notes d'ailleurs
Il y a quelques temps Michel Fugain s'est laissé aller envers les chanteurs modernes qui ne connaissent pas leur métier, n'ont aucune culture et s'imaginent tout connaître et terminât par cette diatribe: "de notre temps nous écoutions des merveilles".
Je laisse de côté ce débat qui avait déjà cours du temps de ma jeunesse pour vous proposer d'écouter une chanson de mon adolescence, chantée par un chanteur de celle de mes parents avec une chanteuse des années de l'adolescence des miens.
Je trouve tout de même que le talent ça pourrait ressembler à ça:
17.08.08
SAGA 3: Sérialisation XML
Cette semaine nous allons sérialiser de différentes manières des collections.
On commence par créer la classe Continent suivante:
public sealed class Continent { #region Constructeurs /// <summary> /// Constructeur par défaut /// </summary> public Continent() { Pays = new List<Pays>(); } #endregionpublic List<Pays> Pays; }
La sérialisation faite nous donne quelquechose qui ressemble à:
<?xml version="1.0" encoding="utf-8"?> <Continent> <Pays> <Pays capitale="Paris"> <nom>France</nom> </Pays> <Pays capitale="Berlin"> <nom>Allemagne</nom> </Pays> </Pays> </Continent>
Transformons la balise <Continent> avec des minuscules grâce à l'attribut XmlRoot.
[XmlRoot("continent")] public sealed class Continent { }
Je vous laisse découvrit le (très) prévisible résultat.
Profitons en pour découvrir un très méconnu comportement de XmlElement. Effectuons le remplacement suivant:
[XmlElement("pays")] public List<Pays> Pays;
On obtient alors:
<continent> <pays capitale="Paris"> <nom>France</nom> </pays> <pays capitale="Berlin"> <nom>Allemagne</nom> </pays> </continent>;
Un niveau disparaît. Pour le conserver on doit utiliser d'autres attributs:
[XmlArray("nation")] [XmlArrayItem("pays")] public List<Pays> Pays;
On obtient:
<continent> <nation> <pays capitale="Paris"> <nom>France</nom> </pays> <pays capitale="Berlin"> <nom>Allemagne</nom> </pays> </nation> </continent>;
Pour l'instant nous nous sommes contenté de situations pour laquelle le type des membres de la classe sérialisée est connu. Mais dans la réalité ce n'est pas toujours possible.
Complétons par exemple la classe Pays avec la propriété suivante:
public ResourceEnEau Eau;
ResourceEnEau est la classe abstraite suivante:
public abstract class ResourceEnEau { public string Nom; }
elle admet deux dérivés:
public class Lac : ResourceEnEau { public int Surface; } public class Riviere : ResourceEnEau { public int Longueur; }
Pas les même classes, pas les mêmes membres. On vérifie facilement qu'en l'état la sérialisation ne fonctionne pas. La raison est assez facile à comprendre. Lors de la déclaration et l'instanciation de XmlSerializer, on fournit le type de données qui sera sérialisé:
XmlSerializer serialiseur = new XmlSerializer(typeof(Pays));
En interne le compilateur va créer une classe spécialisée dans la sérialisation de Pays. La classe n'est pas créée à la volée, ce qui assure des performances meilleures.
Seulement dans notre nouvelle situation on rencontre une classe abstraite, on aurait même pu avoir une interface. Le compilateur ignore alors tout de la classe réelle et ne pourra pas terminer son travail... à moins d'être aidé!
Il existe deux approches possibles.
Si la liste des types possible est connue et ne varie pas, les attributs peuvent venir à notre secours:
[XmlElement(Type=typeof(Lac))] [XmlElement(Type=typeof(Riviere))] public ResourceEnEau Eau;
Si on ne peut pas accéder au source des classes ou si la liste des types varie et doit donc être obtenue par exemple par réflexion:
Type[] Types = new Type[] {typeof(Lac),typeof(Riviere) }; XmlSerializer serialiseur = new XmlSerializer(typeof(Continent),Types);
Dans les deux cas on obtient quelque chose de similaire (il y a une légère différence dans le résultat):
<continent> <nation> <pays capitale="Paris"> <Riviere> <Nom>Seine</Nom> <Longueur>776</Longueur> </Riviere> <nom>France</nom> </pays> <pays capitale="Berlin"> <Lac> <Nom>Constance</Nom> <Surface>538</Surface> </Lac> <nom>Allemagne</nom> </pays> </nation> </continent>
Suite la semaine prochaine pour voir d'autres cas de figure.
Notes d'ailleurs
Une des premières photographie prise par Neil Armstrong lors de son arrivée sur la Lune fut le pied du LEM. Ce cliché est devenu une tradition à la NASA et se trouve être une des premières images prises par une sonde qui se pose sur un autre sol. On l'appelle depuis le "cliché Armstrong".
Ce cliché a une utilité pratique bien sur, il permet de vérifier que l'appareil s'est posé sur un terrain stable.
