• Synchroniser des données avec un PocketPC

    Si vous avez déjà développé des applications pour PocketPC, il vous ai déjà – certainement – arrivé de vous demander comment synchroniser les données du device avec celles d’un serveur. Trois solutions s’offrent à vous (si l’utilisation de SQLCE n’est pas prévue) :

    1. déclencher la synchronisation à partir du Pocket PC
    2. ajouter un module COM à ActiveSync
    3. Se servir de RAPI pour transferer les données

    La solution 1 fera – probablement – l’objet d’un second billet, la 2 est pratiquement irréalisable – l’API de ActiveSync est l’une des plus obscures que j’ai eu l’occasion de voir dans le monde MS –; reste donc le point N°3 qui sera le sujet de ce jour.

    Cet exemple se base sur l’utilisation de fichiers XML comme source de données sur le Pocket PC

    Etape 1 : lancer le logiciel lors de la connexion.

    Pour cela, rien de plus facile : ActiveSync le fera pour vous pour peu que vous preniez la peine de lui demander. Il vous suffit juste d’ajouter une valeur dans la base de registre :

    Chemin de la clef :
    HKEY_LOCAL_MACHINE\
       SOFTWARE\
          Microsoft\
             Windows CE Services\
                AutoStartOnConnect

    Il vous suffit d’ajouter une valeur de type REG_SZ ayant un nom unique (un Guid serait parfait) et qui contient le path vers votre application.

    Etape 2 : Se connecter au périphérique

    Maintenant, voyons un peu le code. Pour simplifier l’accès à RAPI (l’API permettant d’exécuter à distance des commandes sur un périphérique Windows CE), j’ai décidé d’utiliser une assembly de l’OpenNetCF, qui a le double avantage d’être simple à utiliser et maintenue par des gens qui savent de quoi ils parlent. Si vous preferez gérer tout cela «à la dure», un petit tour par PInvoke.net, vous permettra de récuperer les définitions nécesaires.

    Le code est ensuite assez simple :

    RAPI rapi = new RAPI();
    if (rapi.DevicePresent)
    {
       rapi.Connect();
    // effectuer la synchronisation
    DoSync(rapi);
    }

    Etape 3 : Envoyer/Recevoir des fichiers XML

    Je ne parlerai pas des moyens permettant d’obtenir les données à mettre sur le Pocket PC ou ceux permettant de mettre à jour un serveur : cela ne concerne que vous – bien que l’utilisation de web-services ai personnellement ma préférence - mais uniquement sur les moyens de transférer un fichier depuis (ou vers) le périphérique.

    // tester la présence d'un fichier est
    // d'un simplicité enfantine avec
    // la classe openNetCF
    if (rapi.DeviceFileExists(
    @"\program files\mcarbenay\demo.xml"))
    {
       // de même que le copier vers le PC
    rapi.CopyFileFromDevice(destination, 
    @"\program files\mcarbenay\demo.xml");

       // faire la synchro
       . . .
       // puis retransférer le fichier dans l'autre sens
    rapi.CopyFileToDevice(nouveauFichier,
    @"\program files\mcarbenay\demo.xml",true);
    }

  • BuildProviders

    Cela faisait un petit moment déjà que j'avais envie de parler de toutes ces fonctionnalités qui font de asp.net une vraie merveille pour le developpeur. Si la version 1.0 (et 1.1) présentait déjà de nombreux interêts, la v2.0 apporte son nombre de nouveautés et d'améliorations, et c'est celles-ci que je vais essayer de vous présenter.

    Premier post, premier outil incroyable : les BuildProviders. "Kezako ?", en voila une bonne question ! Eh bien les buildProviders c'est la possibilité de compiler certains fichiers d'une application web en classes. Pourquoi, par exemple, ne pas compiler ce fameux fichier de workflow que vous vous embetez à parser, ou encore ce template de document ? La classe qui sera construite dans ce post fournira une fonctionnalité très banale: un gestionnaire de connexion à une base de données, c'est la façon de le réaliser qui sera un peu différente.

    Le but du jeu est donc de transformer en classe le fichier xml suivant:

    <Connections>
      <Connection
         id="MaConnection"
         dataSource="(local)"
         initialCatalog="MaBase"
         useSSPI="yes" />
    </Connections>

    Pour cela, nous allons réaliser une classe qui dérive de System.Web.Compilation.BuildProvider :

    public class CnxCompiler : BuildProvider
    {
    }

    Il n'y a pas des beaucoup de méthodes/propriétés à implémenter pour obtenir une classe qui fonctionne. La première chose à faire est de surcharger la propriété CodeCompilerType qui permet au module de préciser quel language et quelles options de compilation (entendez principalement : quelles assemblies doivent être référencées) sont utilisées. Pour notre exemple, il s'agit simplement d'utiliser le compilateur C# par défaut :

        private CompilerType _compilerType = null;
        public CnxCompiler()
        {
            _compilerType = GetDefaultCompilerTypeForLanguage("C#");
        }
    
        public override CompilerType CodeCompilerType
        {
            get
            {
                return _compilerType;
            }
        }
    

    Viens ensuite le gros du travail : réaliser l'ecriture de la classe. Ce n'est pas très compliqué, mais c'est évidemment un peu verbeux... Pour réaliser cette classe, il existe deux possibilités :

    • soit fournir du code C# "en brut" qui sera compilé par la plateforme (c'est ce que nous allons faire : c'est de TRES loin le plus simple)
    • soit utiliser le CodeDOM
        public override void GenerateCode(AssemblyBuilder assemblyBuilder)
        {
            XmlDocument doc = new XmlDocument();
            using (Stream st = this.OpenStream())
                doc.Load(st);
    
            // CreateCodeFile permet de générer le code
            // sous la forme d'un pseudo fichier de sources
            TextWriter tw = assemblyBuilder.CreateCodeFile(this);
    
            // il ne reste plus qu'a générer le code !
            tw.WriteLine("using System;");
            tw.WriteLine("using System.Data;");
            tw.WriteLine("using System.Data.SqlClient;");
    
            tw.WriteLine("public static partial class ConnectionManager {");
            foreach (XmlElement elm in doc.SelectNodes("/Connections/Connection"))
            {
                // ComputeSafeName ne fait que transformer un nom
                // quelconque en identifiant C# valide (retrait des 
                // caracteres spéciaux, changement de casse, etc.)
                string id = ComputeSafeName(elm.GetAttribute("id"));
                tw.Write(" public static SqlConnection ");
                tw.Write(id);
                tw.WriteLine(" {");
                tw.WriteLine("  get {");
                try
                {
                    // BoolConvert converti "true", "yes", "1" en true
                    bool useSSPI = BoolConvert(elm.GetAttribute("useSSPI"));
                    string username = elm.GetAttribute("username");
                    string password = elm.GetAttribute("password");
                    string catalog = elm.GetAttribute("initialCatalog");
                    string datasource = elm.GetAttribute("dataSource");
                    if (string.IsNullOrEmpty(datasource)
                        || string.IsNullOrEmpty(catalog))
                    {
                        tw.Write("throw new ApplicationException(");
                        tw.WriteLine("\"connexion invalide\");");
                    }
                    if (!useSSPI && string.IsNullOrEmpty(username))
                    {
                        tw.Write("throw new ApplicationException(");
                        tw.WriteLine("\"pas d'authentification\");");
                    }
    
                    // la chaine de connexion
                    StringBuilder blrCnString = new StringBuilder();
                    blrCnString.Append("Data Source=");
                    blrCnString.Append(datasource);
                    blrCnString.Append(";Initial Catalog=");
                    blrCnString.Append(catalog);
                    if (useSSPI)
                    {
                        blrCnString.Append(";Integrated Security=SSPI");
                    }
                    else
                    {
                        blrCnString.Append(";User Id=");
                        blrCnString.Append(username);
                        blrCnString.Append(";password=");
                        blrCnString.Append(password);
                    }
    
                    tw.WriteLine("SqlConnection cn;");
                    tw.Write("cn = new SqlConnection(\"");
                    tw.Write(blrCnString);
                    tw.WriteLine("\");");
                    tw.WriteLine("cn.Open();");
                    tw.WriteLine("return cn;");
                }
                catch
                {
                }
                finally
                {
                    tw.WriteLine("  }");
                    tw.WriteLine(" }");
                }
            }
            tw.WriteLine("}");
            tw.Close();
        }
    

    Bon, le code n'est pas exceptionnel, mais il fait son boulot : pour chaque paramètre de connexion dans le fichier, on génère une nouvelle propriété qui se connecte à la base. C'est un peu bourrin et demande à ce que l'appelant ferme les connexions lui-même, mais c'est pour la beauté de l'exemple, pas pour avoir un framework bullet-proof. A noter l'utilisation du mot clef partial dans la définition de la classe qui permettra d'avoir plusieurs fichiers de paramètres et de tout compiler en une seule classe.

    Il faut ensuite surcharger GetGeneratedType pour spécifier quelle est la classe que nous venons de réaliser :

        public override Type GetGeneratedType(CompilerResults results)
        {
            return results.CompiledAssembly.GetType("ConnectionManager");
        }
    

    Voila, le build provider est réalisé, reste à dire à asp.net que nous avons une nouvelle classe de compilation, pour cela nous allons éditer le fichier web.config. Il suffit d'ajouter un noeud buildProviders dans la section system.web/compilation :

    <system.web>
        <compilation debug="true">
          <buildProviders>
            <add extension=".cntx" 
                 type="BuildProviders.CnxCompiler, BuildProviders" />
          </buildProviders>
          </compilation>
    </system.web>
    

    A noter que les build-providers travaillent sur une extension de fichier, pas sur un nom de fichier. Ici nous avons donc défini un nouveau "compilateur" pour les fichiers .cntx. Pour que tout fonctionne bien, vous devrez placer ces fichiers dans le dossier App_Code.

    Le plus beau dans l'affaire ? c'est qu'après redémarrage de VS (ou en vidant le cache de Intellisense), la classe ConnectionManager est dispo pour la "complétition de code" (c'est comme ça que l'on dit ?) :

    C'est quand même la classe ce truc non ?

    Technorati: ,