DataSets typés et multi-bases de données

ADO.NET supporte plusieurs bases de données, et pas seulement SQLServer. Vous pouvez faire de l’Oracle, principalement, et vous disposez aussi d’un driver OLEDB. Mais ne comptez que sur vous-mêmes si vous souhaitez développer une application supportant plusieurs bases de données. Vous pouvez utiliser les interfaces de programmation pour la connexion, quelques classes de résultats, mais dès que vous faites une commande paramétrée, vous devrez prendre en compte les différents cas possibles.

Le problème devient plus complexe lorsque vous désirez utiliser des DataSets typés. Si vous créez les mêmes structures dans deux bases de données différentes, les types C# correspondants ne sont pas les mêmes dans tous les cas, et surtout, le code généré contient le pointeur vers la base ainsi qu’une grosse partie de code spécifique à la base de données pointée.

Bref, si vous souhaitez écrire du code générique utilisant un DataSet typé dans deux bases de données, vous êtes bons pour écrire une couche intermédiaire. Et c’est là que ça devient drôle : comme les fonctions du DataSet typé renvoyent elles-mêmes des objets typés selon la base, vous êtes bons pour réécrire une redirection pour toutes les fonctionnalités que vous utilisez. En tout cas, je n’ai personnellement pas trouvé d’autre solution.

Le mieux que j’ai trouvé pour l’instant et de créer une classe chapeautant les deux DataSet typés et contenant des instances pour chacune des bases, sachant qu’une seule sera effectivement instanciée à la fois. On fait la même chose en interne, à savoir que les objets dont on a besoin sont eux-mêmes rendus génériques en créant une classe contenant une instance de chaque sous-objet typé. Ensuite, l’idée est de mettre en place des constructeurs différenciés pour chaque instance typée, et dans chaque classe correspondant à une table, de pointer sur le DataSet parent, ce qui nous permettra de retrouver le DataSet typé à utiliser à chaque moment.

Enfin, si vous avez du code qui bouclait sur la collection Rows, vous implémentez l’interface IEnumerable sur ces tables, l’implémentation de la fonction GetEnumerator contenant le code ci-dessous :

foreach (DataSet.MaTableRow Ligne in InstanceDataSetParent.InstanceDataSetTypee.MaTable.Rows)
    yield return new MaTableRow(Ligne);

Personnellement, c’est la première fois que je trouve une utilité hors des exemples standards pour le mot clé yield. Dans ce cas précis, je ne sais pas comment j’aurais fait pour transformer la collection sans trop perdre en performance.

Si vous êtes intéressé par le code un peu plus complet que cette rapide description, n’hésitez pas à me contacter.

Posted in .NET | Leave a comment

Microsoft UI Automation : déçu, encore une fois…

Que je vous expose mon problème, avant tout : ça fait presque dix ans que je cherche une méthode efficace pour tester des interfaces rapides. J’ai essayé des outils commerciaux (Mercury et consorts), des outils gratuits, des méthodes (instrumentation, scripting VSA, invocation sur hooks, etc.), des bidouilles ignobles (surcharger la librairie ManagedSpy de Ben Wulfe de Microsoft R&D, entre autres). J’ai posé le problème aux DevDays suite à la présentation de Rosario, harcelé des gens de Microsoft pour leur demander quand on aurait enfin un outil pour faire du test d’interface.

Et il y a deux jours, j’ai cru avoir enfin trouvé la voie. Un article de Programmez parlait de UI Automation, qui est un framework d’accessibilité, permettant de parcourir et de lire les contrôles de manière externe au programme cible. Ce framework est intégré à .NET 3.5. Je me renseigne un peu, trouve quelques liens intéressants, et me demande pourquoi mes recherches ne m’avaient pas fait trouvé cette technologie. J’ai pourtant passé un bon bout de temps à chercher ce qui se faisait en .NET, mais visiblement pas avec les bons mots clés.

Bref, je me mets à l’oeuvre en ouvrant Visual Studio 2008, et en rajoutant les références sur UIAutomationClient et UIAutomationTypes, et je reprends l’exemple trouvé sur http://blogs.developpeur.org/tom/archive/2007/07/25/wpf-wpf-ui-automation-rendez-vos-interfaces-graphiques-accessibles.aspx en me disant qu’il ne devrait pas être trop dur de l’étendre pour insérer du texte dans un notepad depuis une application console extérieure.

C’est là que les choses se corsent. J’ai d’abord la surprise de découvrir que UISpy ne fait pas partie du SDK 6.0. Voir à ce sujet http://blogs.msdn.com/windowssdk/archive/2008/02/18/where-is-uispy-exe.aspx. Ce n’est pas grave, je télécharge, j’installe le tout comme un 6.1, et je découvre la structure du notepad grâce à l’outil UISpy. Il y a un élément de type document, qui semble être le contrôle par défaut. Je lui associe un TextPattern, sur lequel un DocumentRange me donne accès à GetText(). Bizarre, il n’y a pas de SetText()…

Mon premier réflexe est de me dire que l’injection doit se faire avec un autre pattern que la lecture de texte. Je trouve un bon candidat, à savoir le InvokePattern, mais ça ne sert apparemment pas à ça, ou bien je n’ai pas compris comment ça fonctionne exactement. Avant de poursuivre plus loin, je me dis que je ferais mieux de me renseigner un peu plus sur le pourquoi de l’absence d’un SetText() dans le TextPattern. Et ce que je trouve sur http://msdn.microsoft.com/en-us/library/ms745158.aspx à la rubrique Security me confirme mes doutes : dixit l’article, « Microsoft UI Automation text providers supply read-only interfaces and do not provide the ability to change the existing text in a control ». Bref, c’est plié encore une fois pour l’automatisation des tests d’interface.

Au passage, la sécurité a bon dos : c’est tout à fait possible d’injecter des valeurs dans un contrôle en passant par les API Win32, ou même en utilisant ManagedSpy.dll (je tiens le code à disposition si quelqu’un est intéressé). Si on voulait simplement empêcher la nouvelle technologie de prendre ce risque, il était également possible de mettre en place un système autorisant la modification distante à condition qu’une autorisation ait été donnée par avance. Ca peut être fait avec un certificat, un token, n’importe quoi. Même si ça ne fonctionne pas avec des applications existantes et que ça force les applications cibles à rajouter une propriété quelconque dans leur code. Ou bien un setting quelque part dans la base de registre, n’importe.

Ma conclusion est la suivante. De deux choses l’une :

  1. Microsoft ne veut pas qu’on fasse de l’automatisation de tests automatisés d’interface avec ses outils. Il est également possible que les problèmes qu’on rencontre avec une approche ManagedSpy (impossibilité de sauter d’un handle de fenêtre principale à un handle de dialogue modal, etc.) existent toujours avec l’approche UI Automation (je ne serais d’ailleurs pas étonné que les deux approches utilisent le même principe, vu la proximité étonnante de UISpy.exe avec ManagedSpy.exe). Peut-être que c’est tout simplement impossible sous Windows…
  2. Je suis passé à côté d’un truc, et il est possible d’envoyer du texte dans un contrôle depuis UI Automation en utilisant un autre pattern. Je dois dire que ce cas-là me ravirait… Oui, je serais content de me faire traiter d’andouille par n’importe qui qui pourra m’apporter un code réalisant enfin ce que je veux faire depuis 10 ans que je bosse dans l’informatique.
Posted in .NET, Tests | Tagged | Leave a comment

Je suis (enfin) MCSD.NET !

Ca y est : quatre ans après avoir passé la première certification .NET de Microsoft, j’ai enfin réussi à finir le premier cycle de certifications pour être Microsoft Certified Solution Developer. A cette époque là, c’était le top du top… Malheureusement (enfin, pour être honnête, c’est normal), Microsoft a depuis rajouté des niveaux à ses certifications. Il va falloir passer encore deux (ou trois ?) examens passerelles pour me mettre à jour sur le nouveau programme et devenir MCPD.

J’espère vraiment que ça ne changera pas trop vite après, histoire de ne pas passer ma vie à courir après des examens. Encore, tant que c’est des examens comme WinForms ou ASP.NET ou la sécurité, ça va : le sujet est intéressant, et surtout, si on connaît bien le code, on n’est pas pris au piège par les questions. Mais le 70-300, qui est celui sur l’architecture, est vraiment très différent : ce sont des exercices de mise en situation réelle, avec des questions sur les choix d’architecture conceptuelle ou technique. Les réponses sont parfois un peu bizarres, et il y a quelques cas où, vraiment, tout ce qui et proposé paraît ne pas coller à la question. Ca ne vient pas de l’anglais (oui, c’est un des rares examens à ne pas être disponible en français), je suis à peu près bilingue. Les questions sont parfois très vagues, et en architecture informatique, chacun fait son choix. Ce n’est pas comme du code où c’est bon ou mauvais.

La prochaine certification sera le MCTS 70-341 sur SQLServer 2005. Ensuite, les examens passerelles, et peut-être que je pousserai le vice jusqu’à passer les examens WPF et WCF, qui ont été rajoutés récemment. Personnellement, je ne peux que recommander ce cycle de certification. Je ne sais pas ce que valent celles sur les systèmes, SQLServer ou même les certifications non-Microsoft, mais je peux dire que celles que j’ai passées m’ont fait apprendre pas mal de choses en lisant les bouquins de préparation et en faisant les exercices proposés. Il y a bien sûr beaucoup de choses à apprendre, et on se retrouve à bachoter pour certaines classes qu’on n’utilise presque jamais, mais qu’on est obligés d’apprendre pour parer à toute éventualité, mais dans l’ensemble, ce sont des techniques qu’on peut vraiment appliquer au travail.

Posted in Uncategorized | Leave a comment

XSL/FO et .NET

Vous êtes-vous déjà demandé comment faire pour générer un PDF en XSL-FO depuis ASP.NET, ou pour le coup n’importe quelle application .NET ? J’ai eu beau chercher, je n’ai pas trouvé de moteur XSL-FO écrit en code managé et open-source.

La solution glanée sur différents sites web est de faire de l’interop avec ApacheFOP, qui est écrit en Java. Mais là, deux voies sont possibles :

– Ou bien on recompile le code Java en J#, et on obtient des assemblages .NET nécessitant les redistribuables J# pour fonctionner. Avantage : le tout ne pèse que quelques Mo. Inconvénient : ça n’a aucune chance de fonctionner sous Mono.

– Ou bien on exécute en mémoire les JAR en passant par IKVM, qui est une machine virtuelle embarquée dans un processus .NET. Avantage : c’est propre et tout tourne en mémoire, de plus pas de problème théoriquement avec Mono, vu qu’IKVM est carrément livré avec. Inconvénient : ça pèse une trentaine de Mo, car il faut livrer avec la totalité des classes Java. Il doit y avoir moyen de faire autrement en générant une dll IKVM.ClassPath.dll avec seulement ce qui est nécessaire, mais je n’ai pas essayé.

On va expliquer sur ce post la deuxième méthode, qui de mon point de vue est la meilleure. Oui, je sais, ça a déjà été fait sur d’autres pages web, mais je n’ai rien trouvé qui était à jour avec la dernière version de FOP et avec la liste des références nécessaires.

Récupération des librairies

On commence par récupérer FOP sur le site Apache, http://xmlgraphics.apache.org/fop/download.html (la toute dernière version stable est la 0.94). On va également télécharger la dernière version en date de IKVM, à savoir la 0.36.0.11 sur http://sourceforge.net/project/showfiles.php?group_id=69637.

Petite remarque au passage : je suis toujours autant émerveillé de la modestie des équipes capables de réaliser un développement aussi remarquable, et de laisser un numéro de version inférieur à 1. Quand on voit des logiciels soi-disant professionnels toujours aussi buggés en version 10, on se dit que beaucoup d’entre eux devraient prendre exemple sur les développements OpenSource.

On décompresse ensuite tout ça dans deux répertoires /FOP et /IKVM.

Recompilation des librairies FOP par IKVM

IKVM fournit un environnement d’exécution .NET d’une VM Java sous forme d’un exécutable, mais également un compilateur permettant de générer des assemblages .NET à partir des JAR, et qui se nomme IKVMC.

Dans notre cas, on se place dans /IKVM/bin, et on lance successivement les commandes suivantes :

ikvmc -target:library
    -reference:IKVM.OpenJDK.ClassLibrary.dll
    /fop-0.94/lib/xml-apis-1.3.02.jar

ikvmc -target:library
    -reference:IKVM.OpenJDK.ClassLibrary.dll
    -reference:xml-apis-1.3.02.dll
    /fop-0.94/lib/xercesImpl-2.7.1.jar

ikvmc -target:library
    -reference:IKVM.OpenJDK.ClassLibrary.dll
    -reference:xml-apis-1.3.02.dll
    /fop-0.94/lib/avalon-framework-4.2.0.jar

ikvmc -target:library
    -reference:IKVM.OpenJDK.ClassLibrary.dll
    -reference:xml-apis-1.3.02.dll
    /fop-0.94/lib/batik-all-1.6.jar

ikvmc -target:library
    -reference:IKVM.OpenJDK.ClassLibrary.dll
    /fop-0.94/lib/commons-logging-1.0.4.jar

ikvmc -target:library
    -reference:IKVM.OpenJDK.ClassLibrary.dll
    /fop-0.94/lib/commons-io-1.3.1.jar

ikvmc -target:library
    -reference:IKVM.OpenJDK.ClassLibrary.dll
    /fop-0.94/lib/xmlgraphics-commons-1.2.jar

ikvmc -target:library
    -reference:IKVM.OpenJDK.ClassLibrary.dll
    -reference:xml-apis-1.3.02.dll
    -reference:batik-all-1.6.dll
    -reference:commons-logging-1.0.4.dll
    -reference:xmlgraphics-commons-1.2.dll
    -reference:commons-io-1.3.1.dll
    -reference:avalon-framework-4.2.0.dll
    /fop-0.94/build/fop.jar

Si vous avez besoin d’assemblages signés, il suffit de spécifier la directive -keyfile:[votre fichier .snk] ou bien -key:[le nom de votre container de clé]. IKVMC contient de nombreuses autres options qui peuvent se réveler très utiles. La prise en compte de la signature par nom fort, dans mon cas, était très importante, et ce fut un vrai soulagement de voir que le paramètre était présent. Je me voyais déjà désassembler l’IL depuis la DLL puis recompiler en ajoutant la signature, et je ne suis même pas sûr que ça soit possible…

Utilisation des assemblages créés

Une fois ceci fait, vous pouvez créer un projet .NET dans lequel vous allez simplement mettre en référence les librairies fop.dll et IKVM.ClassPath.dll générées précédemment. Placer au même endroit toutes les autres références, qui ne seront pas nécessaires pour la compilation, mais indispensables lors de l’exécution. Vous pouvez ensuite taper le genre de code suivant pour vérifier que tout fonctionne. J’ai enlevé tout ce qui est gestion des exceptions et génération des noms de fichiers pour simplifier le code et ne faire voir que ce qui a vraiment du sens pour un tutoriel sur XSL-FO.

Note : je préfère écrire systématiquement les noms de classes complets dans ce genre d’exemple. Je trouve qu’on a toujours du mal à savoir d’où viennent les classes sinon, surtout qu’il est très facile d’oublier de donner les using lorsqu’on copie une simple portion de classe. Dans la pratique, il est bien sûr plus clair d’utiliser des noms de classe simples avec des using.

public void GenererPDF(System.Xml.XmlDocument Source, string FichierXSLFO, string FichierResultatPDF)
{
    // Récupération du fichier XSL
    System.Xml.Xsl.XslCompiledTransform Moteur = new System.Xml.Xslt.XslCompiledTransform();
    Moteur.Load(FichierXSLFO);

    // Application de la transformation
    string FichierResultatFO = System.IO.Path.GetTempFileName();
    System.Xml.XmlTextWriter Scribe = new System.Xml.XmlTextWriter(FichierResultatFO, System.Text.Encoding.Default);

    Moteur.Transform(Source.CreateNavigator(), Scribe);
    Scribe.Close();

    // Création du PDF par interop IKVM avec ApacheFOP
    java.io.File fo = new java.io.File(FichierResultatFO);
    java.io.File pdf = new java.io.File(FichierPDFGenere);
    org.apache.fop.apps.FopFactory fopFactory = org.apache.fop.apps.FopFactory.newInstance();
    org.apache.fop.apps.FOUserAgent foUserAgent = fopFactory.newFOUserAgent();
    java.io.OutputStream sortie = new java.io.BufferedOutputStream(new java.io.FileOutputStream(pdf));
    org.apache.fop.apps.Fop fop = fopFactory.newFop("application/pdf", foUserAgent, sortie);
    javax.xml.transform.TransformerFactory factory = javax.xml.transform.TransformerFactory.newInstance();
    javax.xml.transform.Transformer transformer = factory.newTransformer();
    javax.xml.transform.Source src = new javax.xml.transform.stream.StreamSource(fo);
    javax.xml.transform.Result res = new javax.xml.transform.sax.SAXResult(fop.getDefaultHandler());
    transformer.transform(src, res);
    sortie.close();
}

 

Résultat

Une fois que les premiers résultats fonctionnels ont été atteint, la curiosité pousse à aller voir combien tout ça prend en mémoire. Il y a en effet finalement 13 librairies à mettre en place pour faire fonctionner le tout, pour un total de 36 Mo. Vu que toutes les classes de Java ont été reproduites en IKVM, c’est un peu logique, mais ce qui est intéressant, c’est que la CLR, comme pour des librairies .NET standards, ne charge que ce dont elle a besoin, à savoir en gros 5 Mo, et prend quelques dizaines de Mo pour un gros PDF, mais cette partie de mémoire est considérée comme ponctuelle, et sera libérée lors du prochain passage du garbage collector.

Bref, ça marche ! Honnêtement, je ne me suis pas penché sur IKVM pour vous indiquer clairement comment, mais je préfère avoir un produit qui fonctionne sans savoir pourquoi que l’inverse.

[Edit]

Je reprends les commentaires manuellement depuis mon ancienne plateforme de blog, depuis laquelle je migre les articles pour celle-ci.

mrique :

Merci de bloguer ça, c’est juste un retour pour te dire qu’on va s’en servir et que ton investissement de bloggueur n’est pas une perdu au fond de l’océan du web : y’a des vrais gens qui sont contents à l’autre bout ;-)
by

mrique :

PS : le luxe ce serait qu’on puisse télécharger les assemblages !!!!! (je sais j’abuse)

Alexandre :

Effectivement ça aurait été parfait avec les assemblages ^^

Sinon comment faire avec les nouvelles versions de FOP et d’IKVM ??

JP Gouigoux :

En fait, il y a eu tellement de versions mineures que c’est difficile de refaire les lignes de commandes et les références pour tout. Et pour ce qui est du téléchargement, c’est rendu encore plus complexe par le fait que le tout pèse quelques dizaines de Mo… Si vous me remettez un commentaire avec les versions exactes, je peux vous reposter les lignes de commandes, et les libs.

Alexandre :

Les dernières versions sont FOP 1.0 et IKVM 0.46.0.1.
Mon principal problème est qu’il n’y a plus IKVM.OpenJDK.ClassLibrary.dll , sinon changer les versions des librairies je peux faire ^^

JP Gouigoux :

Je viens de télécharger FOP 1.0 et IKVM 0.46.0.1 pour voir, et effectivement ClassLibrary.dll n’existe plus. Par contre, en enlevant purement et simplement la référence sur la première ligne de commande (celle pour xml-api), ça passe très bien. J’imagine que ikvmc doit prendre des références par défaut sur tout ce qui est classes standards de Java, et j’ai l’impression que celles-ci ont été découpées en plusieurs librairies. Ce qui est une bonne idée car ClassLibrary était énorme, si je me rappelle bien.

Je te laisse faire le test sur les autres, n’hésite pas à demander si tu tombes sur des cas où ça ne passe pas. J’essaierai d’aider.

Alexandre :

OK j’ai réussi à générer les nouveaux assemblages, mais pas mal de Warning…
Par contre aucun fichier IKVM.ClassPath.dll
J’ai alors importer toutes les dll de IKVM/bin et celles générées.
J’ai repris le code d’exemple, et la j’ai une exception levée à cette ligne :
Dim fopFactory As org.apache.fop.apps.FopFactory = org.apache.fop.apps.FopFactory.newInstance()
qui est : L’exception System.TypeInitializationException n’a pas été gérée.Une exception a été levée par l’initialiseur de type pour ‘org.apache.xmlgraphics.image.loader.ImageManager’.

Une idée ?
Merci bien !

JP Gouigoux :

Est-ce que la librairie xmlgraphics recompilée par IKVM est bien présente au même endroit que la lib FOP ?

Alexandre :

Oui tout est dans le même dossier

JP Gouigoux :

Vous pouvez recopier le code que vous utilisez ?

Alexandre :

Dim Source As New System.Xml.XmlDocument
Source.Load(« D:\Documents\test.xml »)
Dim FichierXSLFO As String = « D:\Documents\test_xsl.xsl »
Dim FichierResultatPDF As String = « D:\Desktop » & « /test_IKVM.pdf »

‘ Récupération du fichier XSL
Dim Moteur As System.Xml.Xsl.XslCompiledTransform = New System.Xml.Xsl.XslCompiledTransform()
Moteur.Load(FichierXSLFO)

‘ Application de la transformation
Dim FichierResultatFO As String = System.IO.Path.GetTempFileName()
Dim Scribe As New System.Xml.XmlTextWriter(FichierResultatFO, System.Text.Encoding.UTF8)

‘Moteur.Transform(Source.CreateNavigator(), Scribe)
Scribe.Close()

‘ Création du PDF par interop IKVM avec ApacheFOP
Dim fo As New java.io.File(FichierResultatFO)
Dim pdf As New java.io.File(FichierResultatPDF)
Dim fopFactory As org.apache.fop.apps.FopFactory = org.apache.fop.apps.FopFactory.newInstance()
Dim foUserAgent As org.apache.fop.apps.FOUserAgent = fopFactory.newFOUserAgent()
Dim sortie As java.io.OutputStream = New java.io.BufferedOutputStream(New java.io.FileOutputStream(pdf))
Dim fop As org.apache.fop.apps.Fop = fopFactory.newFop(« application/pdf », foUserAgent, sortie)
Dim factory As javax.xml.transform.TransformerFactory = javax.xml.transform.TransformerFactory.newInstance()
Dim transformer As javax.xml.transform.Transformer = factory.newTransformer()
Dim src As javax.xml.transform.Source = New javax.xml.transform.stream.StreamSource(fo)
Dim res As javax.xml.transform.Result = New javax.xml.transform.sax.SAXResult(fop.getDefaultHandler())
transformer.transform(src, res)
sortie.close()

JP Gouigoux :

Oups, j’ai répondu à mon message précédent au lieu de celui-ci.

Je viens de me rendre compte qu’il y avait aussi des fichiers supplémentaires dans \lib qu’il n’y avait pas avant: xalan, xml-apis-ext. Je pense qu’il va falloir compiler tout ça aussi pour ne plus avoir de classe manquante. Je te laisse ajouter les références une par une. Je pense que c’est la bonne voie de reprendre toutes les erreurs IKVMC0100 depuis les premières références, et de rajouter tout ce qu’il faut pour qu’il n’y ait plus de warning. Malheureusement, ça prend pas mal de temps, et je dois arrêter là.

Préviens moi de ton avancement. J’essaierai de jeter un oeil à nouveau à un autre moment de libre…

JP Gouigoux :

Je vais essayer de faire le test. Je ne vous promets pas de date, parce que j’ai deux projets en cours en ce moment qui me prennent pas mal de temps.

JP Gouigoux :

Désolé pour le temps de réaction…

Je viens de faire le test ce soir, et j’ai aussi ce genre d’exception, mais sur des autres classes. Du coup, j’ai regardé plus précisément les logs, et certains des warnings sont en fait des erreurs qui arriveront à la runtime. D’après ce que je comprends, si IKVM ne trouve pas les références qui lui sont nécessaires pour transformer le bytecode en IL, il émet du code IL qui va envoyer une ClassNotFoundException.

Du coup, je pense que l’approche doit consister à ajouter explicitement toutes les dépendances nécessaires, et en reprenant tout depuis la première librairie, sinon on reproduira le problème plus loin.

J’ai réussi à améliorer les résultats sur fop.jar : en stockant les logs de ikvmc, je suis passé d’un demi-mega à seulement 16 Ko d’avertissements. Mais je suis maintenant coincé sur les dépendances que j’ai créées avant et qui doivent elles aussi avoir des classes qui étaient absentes.

Je ne vais pas avoir le temps ce soir de continuer, mais je pense que le plus simple est peut être de passer ikvmc en warnaserror quand c’est possible. Quand ce n’est pas le cas, il faut éplucher les logs pour être sûr qu’on n’a pas oublié de références. Et du coup, tout reprendre depuis le début en vérifiant à chaque fois qu’on a tout…

Posted in .NET | Tagged , | Leave a comment