Ce sujet est assez bien couvert dans les différents billets sur Silverlight, mais je l’aborde de nouveau avec une vue orientée aux contraintes de RIA d’entreprise.

En effet, Silverlight couvre un large périmètre allant de la bannière publicitaire en passant par du contenu multi-média piloté par des interfaces plus ou moins sophistiquées. Mais de part sa conception, c’est tout naturellement que cette technologie est de plus en plus adoptée dans un contexte d’entreprise que ce soit pour une utilisation interne ou grand public. Dans les deux cas, il s’agit d’applications de gestion disposant d’un certain volume de données, d’écrans et de règles métier.

Et c’est bien sur ce point que le bat blesse… Contrairement à une application navigationnelle Web où les pages (en fait les données+logique applicative + IHM) sont téléchargées au fur à mesure de la navigation, une application RIA précharge tous les écrans pour éviter les aller/retour liés à la navigation. Bien sur, il n’est pas interdit d’effectuer des aller/retour avec le serveur, mais ces opérations ne doivent pas détériorer l’expérience utilisateur. Mais disposer de tous les écrans et les données a un cout : l’augmentation du temps de chargement.

Une parade est de découper physiquement en plusieurs assembly son application et de mettre en place un mécanisme de téléchargement dynamique du code. Selon les besoins ou les contraintes, une assembly pourra contenir un composant, un écran, une succession d’écran, etc. Dans certain cas, elles peuvent même ne contenir que des données. Bien pensé, ce découpage rendra l’accès et l’utilisation de l’application plus fluide quelque soit sa complexité et sa taille en nombre d’écran.

La mise à disposition de l’assembly

Pour illustrer ce concept de téléchargement de code en fonction des besoins notre assembly contiendra un formulaire contenu dans un conteneur grid. Ce formulaire se trouve dans l’assembly nommé CustomerFormContainer.dll et la classe représentant le formulaire se nomme CustomerFormContainer.CustomerForm.

Physiquement l’assembly doit être visible à l’application Silverlight de base. Il convient donc de créer un projet Web pour vos tests ou bien de disposer d’un serveur Web. Dans le cadre d’un projet créer par Visual Studio, choisissez d’ajouter une application ASP.NET à votre projet et copier les assembly à télécharger dans le sous répertoire nommé Assemblies de Clientbin.

Le téléchargement de l’assembly

Dans un premier temps, il convient de récupérer en local l’assembly que l’on veut utiliser. Cette opération s’effectue par une requête http à l’aide de la classe WebClient. L’appel se fait en mode asynchrone afin de ne pas bloquer l’IHM.

WebClient downloader = new WebClient();

downloader.OpenReadCompleted +=
   
new OpenReadCompletedEventHandler(downloader_OpenReadCompleted);

downloader.OpenReadAsync(new Uri(assemblyName, UriKind.Relative));

L’instanciation de classe ne se fera qu’une fois le download fini. Son code doit être défini dans le gestionnaire d’événement OpenReadCompleted. Le soucis de ce template de code est que si le même code doit instancier différentes classe, on ne peut pas facilement lui transférer un contexte applicatif. Une solution élégante consiste à utiliser un delegate au lieu d’un gestionnaire d’évènement comme ceci :

WebClient downloader = new WebClient();

downloader.OpenReadCompleted +=
   
delegate(object s, OpenReadCompletedEventArgs args)
    {
        // code excuter une fois le tlchargement fini
    };

downloader.OpenReadAsync(new Uri(assemblyName, UriKind.Relative));

Le code de fin de téléchargement défini dans le delegate étant dans le même contexte que le téléchargement lui même, celui-ci dispose du contexte. Il suffit alors d’encapsuler ces lignes dans une méthode afin de passer en paramètre le nom de l’assembly et la classe à instancier.

private void DownloadAssemblyAndExecuteClass(string assemblyName, string controlClassName)
{
   
WebClient downloader = new WebClient();

   
downloader.OpenReadCompleted +=
       
delegate(object s, OpenReadCompletedEventArgs args)
        {
            
// assemblyName et controlClassName sont accessibles ici !!!
        
};

    downloader.OpenReadAsync(new Uri(assemblyName, UriKind.Relative));
}

L’instanciation de la classe

La dernière étape consiste à instancier la classe en chargeant dans un premier temps l’assembly. Cette opération est rendu possible grâce à la classe AssemblyPart de l’espace de nom System.Reflection. Sa méthode Load permet de charger une assembly à partir d’un Stream. Cela nous convient parfaitement puisqu’on reçoit un stream la fin du téléchargement de l’assembly par la classe WebClient. Une fois l’assembly en mémoire, l’instanciation d’une de ses classes s’effectue simplement par la méthode CreateInstance. Par conséquent, voici le code source complet qui insère l’instance du formulaire dans un conteneur de l’application nommé HostContainer :

private System.Reflection.Assembly loadedAssembly;

const string Directory = "Assemblies";
const string assemblyName = "CustomerFormContainer.dll";
const string controlClass = "CustomerFormContainer.CustomerForm";

private void Button_Click_Remote(object sender, RoutedEventArgs e)
{
   
// On passe le contexte applicatif

    DownloadAssemblyAndExecuteClass(Directory + "/" + assemblyName, controlClass);
}

private void DownloadAssemblyAndExeClass(string assemblyName, string controlClassName)
{
   
WebClient downloader = new WebClient();

    downloader.OpenReadCompleted +=
       
delegate(object s, OpenReadCompletedEventArgs args)
        {
           
AssemblyPart ap = new AssemblyPart();
            loadedAssembly = ap.Load(args.Result);
           
UserControl usercontrol =
                (
UserControl)loadedAssembly.CreateInstance(controlClassName);

            HostContainer.Children.Clear();
            HostContainer.Children.Add(usercontrol);
};

downloader.OpenReadAsync(new Uri(assemblyName, UriKind.Relative));
}

Et après ?

Que ce soit le chargement de l’assembly ou l’instanciation d’une classe, les opérations s’effectue uniquement à partir d’une chaine de caractère. On peut alors très facilement construire un framework consistant de chargement d’assembly à la volée. Assembly qui d’ailleurs peut être contenu dans un fichier xap comme le montre l’exemple de Pierre Lagarde ici : http://blogs.msdn.com/pierlag/archive/2008/10/27/chargement-d-assembly-externe-en-silverlight-2.aspx

Quant à moi, je montrerai dans prochain billet comment cacher de manière intelligente ces assembly en local pour encore plus minimiser les chargements au lancement d’une application Silverlight.