Dot.Blog

C#, XAML, WinUI, WPF, Android, MAUI, IoT, IA, ChatGPT, Prompt Engineering

Silverlight : Enum.GetValues qui n'existe pas, binding et autres considérations

Silverlight, comme vous le savez, propose dans quelques méga octets un mini framework .NET qui est tellement bien "découpé" que la plupart du temps on ne se rend même pas compte qu'il manque des dizaines de méga de code binaire... Pourtant entre une installation complète du Framework 3.5 ou 4.0 à venir et l'installation du plugin Silverlight il y a comme une énorme différence ! De deux choses l'une, soit Silverlight ne sait rien faire tellement il est diminué par ce découpage, ce qui n'est pas le cas, soit le Framework complet est juste gonflé avec des fichiers inutiles pour "faire sérieux", ce qui n'est pas le cas non plus :-) Un découpage savant  Donc ce n'est ni l'un ni l'autre. La troisième possibilité, qui est la bonne réponse, est que le Framework complet est d'une richesse infinie dans les moindres détails, et que le Framework Silverlight "ruse" en zappant beaucoup de détails mais sans perdre l'essentiel. Cela donne l'impression qu'on peut tout faire en Silverlight comme en WPF. C'est "presque" vrai. Assez rapidement on tombe sur les fameux petits détails qui manquent. Cela implique de les compenser par du code. Cela dit loin d'être une critique négative de Silverlight s'en est au contraire une apologie ! Je trouve en effet le découpage savant qui a été effectué dans Silverlight particulièrement bien fait. L'approche est très sensée : il est rarissime (même impossible) qu'une application utilise et nécessite d'accéder à toutes les méthodes, toutes les propriétés de toutes les classes du Framework tellement celui-ci est riche. Du coup, en faisant des coupes bien choisies, on peut laisser les squelettes de presque tout le Framework ainsi que les principales méthodes et propriétés utilisées le plus souvent. On obtient le Framework Silverlight dans lequel un code standard trouvera 95% de tout ce qu'il lui faut pour tourner. Beaucoup d'applications simples ne verront même pas qu'il manque quelque chose. En revanche pour les autres cas, le développeur ajoutera les contournements nécessaires ce qui grossira un peu son code binaire Silverlight, mais d'une petite fraction très supportable. Rien à voir avec le coût d'une installation du Framework complet. Il n'en reste pas moins vrai que parfois pour des choses très simples on se retrouve un peu le bec dans l'eau. "Tiens, c'est bizarre, j'aurais juré que la méthode xxx existait !" Et non, on n'a pas rêvé, elle existe bien, mais dans le Framework complet, pas dans celui de Silverlight. Un exemple tout simple : la méthode GetValues() de la classe Enum. Enum.GetValues où es tu ? Un cas d'utilisation très basique qui fait voir immédiatement qu'il manque quelque chose : essayez de faire un binding entre une combobox et une énumération. Truc classique par excellence. Que le binding soit fait par Xaml ou bien par code, à un moment où un autre il faut que l'énumération sache retourner la liste de ses valeurs. C'est justement la fonction de la méthode Enum.GetValues(). Mais dans le Framework Silverlight cette méthode n'existe tout simplement pas. Victime de la cure d'amaigrissement évoquée plus haut. Il ne s'agit donc ni d'un oubli ni d'un dommage collatéral, c'est un parti pris, assumé. Et alors on fait comment ? Assumé, assumé... comme il y va ! Non, je vous l'assure, c'est assumé. Par l'équipe qui développe le Framework Silverlight en tout cas. Mais pas forcément par les développeurs qui l'utilisent ! Car pour eux c'est une autre histoire puisque, en effet, il va falloir réinventer cette méthode. A l'aide d'un peu de Linq to Object et de Reflexion, on peut s'en sortir. Linq et Reflexion Il existe en effet un moyen d'obtenir les valeurs d'une Enum par la réflexion, en utilisant la méthode GetFields() sur le type de l'Enum dont on souhaite obtenir les valeurs.  GetFields() retourne un tableau de FieldInfo. Une Enum présente alors ses différentes valeurs comme un tableau de champs. En plus de ces champs, GetFields() retournera aussi des éléments qui ne sont pas des valeurs de l'énumération mais d'autres champs de la classe. Au sein de FieldInfo vous trouverez un ensemble de méthodes nommées Isxxx(), l'une d'entre elles nous intéresse plus particulièrement ici; c'est IsLiteral. Toutes les valeurs de l'énumération retournent True. La solution est alors simple en ajoutant à la Réflexion un peu de Linq to Object : 1: var enumType = typeof(monEnumeration); 2: var fields = from field in enumType.GetFields() 3: where field.IsLiteral 4: select field.GetValue(null).ToString(); 5: LaCombobox.ItemsSource = fields; A partir du type de l'énumération (ligne 1) on construit une requête Linq to Object qui filtre tous les champs ayant IsLiteral à vrai et qui sélectionne la valeur de ce champ sous la forme d'une string. Ne reste plus qu'à faire le binding entre cette requête Linq et ItemsSource de la combo box. Il faudra ajouter un peu de code pour transformer la chaîne sélectionnée dans la combo en une valeur de l'énumération grâce à un appel à Enum.Parse(). C'est la version simple et courte. Bien entendu dans le cas où on souhaite faire du binding plus automatisé, notamment directement en Xaml, la solution donnée ici est un peu trop simple. L'esprit est le bon mais il manque des petites choses comme un convertisseur de valeurs. D'autres versions plus sophistiquées Il est bien sûr possible d'aller plus loin et de formuler une solution plus sophistiquée qui permettent de faire du binding en Xaml notamment. Je vous laisse y réfléchir, ça fait un bon excercice C# et ce n'est pas un MVP C# qui vous dira que s'entraîner mentalement de la sorte sur le langage est inutile ! :-) Mais sachez que d'autres y ont déjà pensé et ont proposé des solutions souvent assez proches (ce problème ne peut pas être résolu de dix milles façons). En voici quelques unes dans lesquelles vous pourrez piocher matière à aller plus loin : Blog'A'Little (missing GetValues) Forum Silverlight (binding button group) Forum Silverlight (databinding to an enumeration) Brant's Web blog (binding combobox) Telerik forums (enumeration binding) et d'autres que vous trouverez certainement vous-mêmes ! Silverlight c'est sympa et en plus ça fait travailler les méninges, que du bon !  Stay Tuned !      

Créer un arbre des répertoires avec XML (et l'interroger avec LINQ to XML)

Pour les besoins d'un prochain billet je voulais obtenir une structure arborescente de test sous la forme d'un fichier XML. Une telle chose peut se faire à la main mais cela est fastidieux et à force de copier/coller les données seront trop semblables et donc peu utilisables pour des tests et du debug, ce qui est dommage puisque tel était le but recherché... Pour générer des données aléatoires mais réalistes je possède déjà un outil fantastique (puisque c'est moi qui l'est fait :-) ), DataGen, un logiciel puissant mais qui n'est pas étudié pour générer des données dont la strucuture est récursive. Restait donc à concevoir un utilitaire pour satisfaire mon besoin de l'instant. Je n'aime pas développer "pour rien" donc il fallait que cet utilitaire en soit bien un. Tout disque dur de développeur contient une telle quantité de données que l'ensemble des répertoires forment une superbe structure arborescente ni trop petite ni trop gigantesque. Exactement ce qu'il faut pour des tests ! Ainsi, l'outil devra fabriquer un fichier XML à partir de n'importe quel niveau de répertoire, la structure étant hiérarchique. Je m'empresse donc de vous faire partager ce petit bout de code qui vous sera utile un jour ou l'autre je pense. using System; using System.IO; using System.Linq; using System.Xml.Linq;   namespace Enaxos.Tools.Xml { /// <summary> /// This class can generate an XML file from a folder hierarchy. /// </summary> public static class DirToXml { /// <summary> /// Builds the tree document. /// </summary> /// <param name="dirname">The name of the starting folder.</param> /// <returns>a LINQ to XML <c>XDocument</c></returns> public static XDocument BuildTreeDocument(string dirname) { return new XDocument(new XDeclaration("1.0", "utf-8", "yes"), new XComment("Structure au "+DateTime.Now),new XElement("directories", BuildTree(dirname))); }   /// <summary> /// Builds the tree (recursive method). /// </summary> /// <param name="dirName">Name of the starting folder.</param> /// <returns>a LINQ to XML <c>XElement</c> being the root (or 1st item) of the tree.</returns> public static XElement BuildTree(string dirName) { var di = new DirectoryInfo(dirName); var files = di.GetFiles(); var dirsize = 0l; foreach (var file in files) { dirsize += file.Length; } var subdirs = di.GetDirectories(); // each item is a "directory" having 5 attributes // name is the name of the folder // fullpath is the full path including the name of the folder // size is the size of all files in the folder (in bytes) // files is the number of files in the folder // subdirs is the count of possible sub directories in the folder var elem = new XElement("directory", new XAttribute("name", di.Name), new XAttribute("fullpath",dirName), new XAttribute("size", dirsize), new XAttribute("files",files.Count()), new XAttribute("subdirs",subdirs.Count())); foreach (var dinf in subdirs) { var elemDir = BuildTree(dirName + "\\" + dinf.Name); elem.Add(elemDir); }   return elem; } } } Ce code peut être utilisé sans passer par un fichier disque puisqu'on récupère le document en mémoire. L'exemple ci-dessous montre comment appeler le code et comment interroger la structure via Linq To Xml pour connaître les répertoires vides (n'ayant ni fichier ni sous répertoire) : 1: var d = DirToXml.BuildTreeDocument(@"D:\WpfToolkit"); 2: d.Save(@"d:\test.xml"); 3: Console.WriteLine(d.ToString()); 4: Console.WriteLine(new string('-',60)); 5:   6: var q = from e in d.Descendants("directory") 7: where (int) e.Attribute("files") == 0 8: && (int)e.Attribute("subdirs") == 0 9: orderby (string) e.Attribute("fullpath") 10: select e.Attribute("fullpath"); 11:   12: Console.WriteLine("Répertoires vides"); 13: foreach (var element in q) 14: { Console.WriteLine(element); } 15: Console.WriteLine(string.Format("{0} répertoires vides",q.Count()));   A bientôt pour un prochain billet, so.. Stay Tuned !

Class Helper, LINQ et fichiers CSV

C# 3.0 a apporté des nouveautés syntaxiques qui ne se bornent pas à être seulement des petits suppléments qu'on peut ignorer, au contraire il s'agit de modifications de premier plan qui impactent en profondeur la façon d'écrire du code si on sait tirer partie de ces nouveautés. J'en ai souvent parlé, au travers de billets de ce blog ou d'articles comme celui décrivant justement les nouveautés syntaxiques de C#3.0. Je ne vais donc pas faire de redites, je suppose que vous connaissez maintenant tous ces ajouts au langage. Ce que je veux vous montrer c'est qu'en utilisant correctement C# 3.0 on peut écrire un code incroyable concis et clair pour résoudre les problèmes qui se posent au quotidien au développeur. Le problème à résoudre Vous recevez un fichier CSV, disons le résultat d'une exportation depuis un soft XY des ventes à l'export de votre société. Vous devez rapidement ajouter la possibilité d'importer ces données dans l'une de vos applications mais après les avoir interprétées, triées, voire filtrées. La méthode la plus simple Rappel : un fichier CSV est formé de lignes comportant plusieurs champs séparés par des virgules. Les champs peuvent être délimités par des double quotes. A partir de cette description voyons comment avec une requête LINQ nous pouvons lire les données, les filtrer, les trier et les mettre en forme. Le but ici sera de générer en sortie une chaîne par enregistrement, chaîne contenant des champs "paddés" par des espaces pour créer un colonnage fixe. On tiendra compte des lignes débutant par le symbole dièse qui sont considérées comme des commentaires. 1: string[] lines = File.ReadAllLines("Export Sales Info-demo.csv"); 2: var t1 = lines 3: .Where(l => !l.StartsWith("#")) 4: .Select(l => l.Split(',')) 5: .OrderBy(items=>items[0]) 6: .Select(items => String.Format("{0}{1}{2}", 7: items[1].PadRight(15), 8: items[2].PadRight(30), 9: items[3].PadRight(10))); 10: var t2 = t1 11: .Select(l => l.ToUpper()); 12: foreach (var t in t2.Take(5)) 13: Console.WriteLine(t); La sortie (avec le fichier exemple fourni) sera : SAN JOSE       CITY CENTER LODGE             CA SAN FRANCISCO  KWIK-E-MART                   CA SEATTLE        LITTLE CORNER SWEETS          WA SEATTLE        LITTLE CORNER SWEETS          WA IRVINE         PENNY TREE FOODS CORPORATION  CA  Cette méthode, très simple, ne réclame rien d'autre que le code ci-dessus. La solution est applicable à tout moment et s'adapte facilement à toute sorte de fichiers sources. Elle possède malgré tout quelques faiblesses. Par exemple les champs contenant des doubles quotes ou les champs mal formés risquent de faire échouer la séquence. Dans certains cas cela sera inacceptable, dans d'autres on pourra parfaitement protéger la séquence dans un bloc try/catch et rejeter les fichiers mal formés. Une fois encore il ne s'agit pas ici de montrer un code parfaitement adapté à un problème précis, mais bien de montrer l'esprit, la façon d'utiliser C# 3.0 pour résoudre des problèmes courants.  Expliquons un peu ce code : la ligne 1 charge la totalité du fichier CSV dans un tableau de strings. La méthode peut sembler "brutale" mais en réalité elle est souvent très acceptable car de tels fichiers dépassent rarement les quelques dizaines ou centaines de Ko et la RAM de nos machines modernes n'impose en rien une lecture bufferisée, tout peut tenir d'un bloc en mémoire sans le moindre souci. Cela nous arrange ici il faut l'avouer mais l'utilisation d'un flux bufferisé resterait parfaitement possible. Nous disposons maintenant d'un tableau de chaînes, chacune étant formatée en CSV. La première requête LINQ (variable "t1" en ligne 2) fait l'essentiel du travail : gestion des commentaires (ligne 3) décomposition des champs (ligne 4) tri sur l'un des champs (ligne 5) génération d'une sortie formatée (lignes 6 à 9) Ce qui est merveilleux ici c'est que nous avons en quelques lignes un concentré de ce qu'est une application informatique : acquisition de données, traitement de ces données, production de sorties exploitables par un être humain ou une autre application. En si peu de lignes nous avons réalisé ce qui aurait nécessité une application entière avec les langages de la génération précédente comme C++ ou Delphi. C'est bien ce bond en avant que représente C# 3.0 qui est ici le vrai sujet du billet. Une méthode plus complète La séquence que nous avons vu plus haut répond au problème posé. Elle possède quelques lacunes. Celles liées à sa trop grande simplicité (certains cas du parsing CSV ne sont pas correctement pris en compte) et celles liées à sa forme : c'est un bout de code qu'il faudra copier/coller pour le réutiliser et qui viendra "polluer" nos requêtes LINQ les rendant plus difficiles à lire. Si ces lacunes sont acceptables dans certains cas (règlement ponctuel d'un problème ponctuel) dans d'autres cas on préfèrera une approche plus facilement réutilisable. Notamment si nous sommes amenés à traiter plus ou moins souvent des fichiers CSV nous avons intérêt à encapsuler le plus possible le parsing et à le rendre plus facilement reexploitable. L'une des voies serait de créer une classe totalement dédiée à cette tâche. C'est une solution envisageable mais elle est assez lourde et amène son lots de difficultés. Nous allons choisir ici une autre approche, celle de la création d'un class helper. C'est à dire que nous souhaitons non pas créer une classe qui traite un fichier CSV comme un tout, mais nous voulons pouvoir parser n'importe quelle chaîne de caractères formatée en CSV. L'approche est très différente. Dans le premier cas il nous faudra complexifier de plus en plus notre classe, voire créer une hiérarchie de classes pour traiter les fichiers CSV mais aussi les flux mémoire CSV, et puis encore les services Web retournant du CSV, etc, etc. Dans le second cas nous allons plutôt ajouter la capacité à la classe string de parser une chaîne donnée. La source de la chaîne ne nous importe pas. Il pourra s'agir d'un élément d'un tableau de chaîne comme dans le premier exemple autant que d'une chaîne lue depuis un flux mémoire, une section data d'un fichier XML, etc. D'un certain sens nous acceptons d'être moins "complet" que l'option "classe dédiée CSV", mais nous gagnons en agilité et en "réutilisabilité" en faisant abstraction de la provenance de la chaîne à parser. Bien entendu nous pouvons nous offrir le luxe d'une telle approche car nous savons que nous pouvons nous reposer sur C# 3.0, ses class helpers et LINQ... Le projet qui accompagne ce billet contient tout le code nécessaire et même un fichier CSV d'exemple, nous n'entrerons pas dans les détails de l'implémentation du class helper lui-même qui étend la classe string, parser du CSV n'est qu'un prétexte sans plus d'intérêt dans ce billet. Le code utilisé pour l'exemple provient d'ailleurs d'un billet de  Eric White dont vous pouvez visiter le blog si vous lisez l'anglais. la façon d'écrire un class helper est décrite dans mon article sur C# 3.0, regardons juste sa déclaration : public static string[] CsvSplit(this String source) Cette méthode est déclarée au sein d'une classe statique de type "boîte à outils" pouvant centraliser d'autres méthodes utilitaires. La déclaration nous montre que le class helper s'applique à la classe String uniquement (this String source) et qu'elle retourne un tableau de chaîne (string[]). Une fois le class helper défini (code complet dans le projet accompagnant l'article) il nous est possible d'écrire des requêtes LINQ du même type que celle du premier exemple. Mais cette fois-ci le parsing CSV est réalisé par le class helper ce qui permet à la fois de le rendre plus sophistiqué et surtout de ne plus avoir à le copier/coller dans les requêtes LINQ... Voici un exemple d'utilisation du class helper sur le même fichier CSV. Ici nous parsons la source, nous la trions, nous éliminons les lignes de commentaire mais aussi nous créons en réponse une classe anonyme contenant le nom du contact, sa ville et le nom de sa société. Il est dès lors possible de traiter la liste d'objets résultat comme n'importe quelle liste : affichage, traitement, génération d'état, etc. 1: var data = File.ReadAllLines("Export Sales Info-demo.csv").Where(s=>!s.StartsWith("#")) 2: .Select(l => 3: { 4: var split = l.CsvSplit(); 5: return new 6: { 7: Contact = split[0], 8: City = split[1], 9: Company = split[2] 10: }; 11: } ).OrderBy(x=>x.Contact); 12:   13: foreach (var d in data.Take(5)) 14: Console.WriteLine(d); Ce qui, avec le même fichier source, affichera à la console : { Contact = Allen James, City = San Jose, Company = City Center Lodge } { Contact = Bart H. Perryman, City = San Francisco, Company = Kwik-e-mart } { Contact = Beth Munin, City = Seattle, Company = Little Corner Sweets } { Contact = Beth Munin, City = Seattle, Company = Little Corner Sweets } { Contact = Bruce Calaway, City = Irvine, Company = Penny Tree Foods Corporation }  Conclusion La bonne utilisation de C# 3.0 permet de réduire significativement la taille du code donc de réduire dans les mêmes proportions les bugs et d'augmenter la productivité. Apprendre à utiliser cette nouvelle approche c'est gagner sur tous les plans...   Le projet exemple : LinqToCsv.zip (9,46 kb)

Un éclairage sur les techniques d'accès aux données sous .NET

Depuis la sortie de .NET Microsoft n'arrête plus sa course folle ! La plupart des technologies publiées faisaient partie d'un plan presque totalement connu à l'avance, comme WPF, WCF etc. Il a fallu du temps pour qu'émerge ses "modules" de .NET car même le plus puissant des éditeurs de logiciels du monde ne peut pas releaser la totalité d'une montagne comme .NET en un seul morceau. S'il était clair que WPF serait la nouvelle gestion des interfaces utilisateurs et que Windows Forms n'était qu'un "os à ronger" en attendant, le foisonnement des technologies tournant autour des données n'était pas forcément visible ni prévisible il y a quelques années. Et même aujourd'hui savoir ce qui existe et comment s'en servir avec l'ensemble des autres technologies n'est pas forcément une mince affaire ! Un petit dessin valant tous les discours, voici un diagramme qui tourne sur les blogs américains de Microsoft et que j'ai en partie traduit pour vous, en espérant que cela vous aidera à y voir plus clair ! Quelques précisions pour certains acronymes ou noms de technologies : EDM : Entity Data Model - Modèles de données de l'Entity Framework (EF) RDBMS : SGBD, une base de données ASP.NET Dynamic Data : nouveau système de .NET 3.5 SP1 simplifiant et améliorant la prise en charge des données dynamiques dans ASP.NET ASP.NET MVC : Nouvelle couche permettant d'appliquer le paradigme Modèle-Vue-Controleur aux développement ASP.NET ADO.NET Data Services : couche de communication transformant un modèle EDM en un service Web [Faite un clic-droit sur l'image et copiez-la pour l'afficher en 100% dans Word ou autre ]

LINQ to {tapez ce que vous voulez ici}. Des providers à foison !

Vous connaissez LINQ to Object, LINQ to Dataset, LINQ to XML, etc... Microsoft livre déjà une large palette de fournisseurs de données (providers) pour LINQ. Dans sa grande sagesse Microsoft a aussi publié les spécifications (et des exemples) permettant à tous de développer des fournisseurs de données pouvant fonctionner avec LINQ. Cette ouverture extraordinaire permet d'interroger avec LINQ des données de toute sorte sans avoir besoin de les transformer au préalable. Mais savez-vous qu'à ce jour il existe déjà plus d'une trentaine de providers LINQ ? Et que forcément certains vous rendrons des services inestimables, à condition d'en connaître l'existence... Vous pouvez même écrire vos propres providers LINQ ! Je vous conseille d'ailleurs cette page du Wayward blog qui propose une série de billets expliquant comment créer un tel provider. Un exemple parmi tant d'autres pour illustrer le propos : LINQ To Amazon. Il permet tout simplement d'interroger directement Amazon pour sélectionner des livres selon autant de critères que nécessaire, et sans autre programmation qu'une requête LINQ. Fantastique non ? Un petit exemple pour concrétiser la chose : var query =   from book in new Amazon.BookSearch()   where     book.Title.Contains("ajax") &&     (book.Publisher == "Manning") &&     (book.Price <= 25) &&     (book.Condition == BookCondition.New)   select book;   Cool non ? LINQ to Amazon est présenté dans le livre LINQ in Action (que je vous conseille au passage) et vous pouvez accéder au blog de Fabrice qui le présente. Mais cela n'est qu'un exemple, et il existe bien d'autres fournisseurs de données LINQ, en voici une liste (avec liens - shift clic pour les ouvrir dans une nouvelle fenêtre) : LINQ to Amazon LINQ to Active Directory LINQ to Bindable Sources (SyncLINQ) LINQ to C# project LINQ to Continuous Data (CLinq) LINQ to CRM LINQ To Geo - Language Integrated Query for Geospatial Data LINQ to Excel LINQ to Expressions (MetaLinq) LINQ Extender (Toolkit for building LINQ Providers) LINQ to Flickr LINQ to Google LINQ to Indexes (LINQ and i40) LINQ to IQueryable (Matt Warren on Providers) LINQ to JSON LINQ to LDAP LINQ to NHibernate LINQ to JavaScript LINQ to LLBLGen Pro LINQ to Lucene LINQ to Metaweb(freebase) LINQ to MySQL, Oracle and PostgreSql (DbLinq) LINQ to NCover LINQ to Opf3 LINQ to Parallel (PLINQ) LINQ to RDF Files LINQ to Sharepoint LINQ to SimpleDB LINQ to Streams LINQ to WebQueries LINQ to WMI http://tomasp.net/blog/linq-expand.aspx http://tomasp.net/blog/linq-expand-update.aspx Linq To WIQL LINQ to XtraGrid Charlie Calvert, que les delphistes connaissent bien, et qui comme bon nombre des meilleurs a depuis longtemps choisi la voie .NET avec Microsoft, met à jour régulièrement cette liste qui est issue de son blog. Si vous lisez l'anglais, bookmarquez la page Links to LINQ de son blog qui, en plus de la liste des providers, fournit de nombreux liens en rapport avec cette technologie. Conclusion LINQ est une technologie fantastique dont je ne cesse de dire du bien (et d'utiliser avec bonheur dans la totalité des applications que j'écris), je ne peux imaginer utiliser demain ou dans 10 ans un langage qui n'implémentera pas une feature de même type. LINQ to "n'importe quoi" est la preuve que cette technologie est en plus ouverte et que seule notre imagination est la limite. Borland utilisait le slogan "the sky is the limit" durant ses grandes années de gloire. Microsoft ne le dit pas, mais la firme de Redmond rend ce rêve possible aujourd'hui...