Silverlight : Interagir avec son serveur, WebRequest ou WebClient ?

Comme toute techno Web un tant soit peu intéressante, Silverlight permet d'effectuer des requêtes vers un serveur et d'en exploiter les résultats.

Mais, entre l'objet WebClient et WebRequest : un choix s'impose !

N'hésitez pas à commenter cet article ! Commentez Donner une note à l'article (5)

Article lu   fois.

L'auteur

Site personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. Introduction

Comme toute techno Web un tant soit peu intéressante, Silverlight permet d'effectuer des requêtes vers un serveur et d'en exploiter les résultats.

Mais, entre l'objet WebClient et WebRequest : un choix s'impose !


Dans cet article, nous allons tenter (humblement) de comparer les différences entre ces deux techniques.
Nous verrons également à travers un exemple concret, le chargement d'un fichier texte et nous tenterons également de charger un assembly dynamiquement.

II. Sources

[FTP]Sources : BDV.Exemple.Silverlight.Requests.zip

[HTTP](lien de secours) Sources : BDV.Exemple.Silverlight.Requests.zip

Les sources sont structurées en 3 assemblies :

     - BDV.Exemple.Silverlight.Requests.Web
       Projet Web dans lequel l'application Silverlight est exécutée.

     - BDV.Exemple.Silverlight.Requests
       Projet qui contient l'application Silverlight (XAP exécuté par l'application Web).
       L'écran principal contient trois onglets,
            le premier pour charger des données via un WebRequest,
            le second pour charger des données via un WebClient
            le troisième, qui à son ouverture, charge dynamiquement un assembly

     - BDV.Exemple.Silverlight.DynamicLoadedAssembly
       Projet de type Bibliothèque de classes Silverlight. Cet assembly est chargé dynamiquement par l'application Silverlight.
       Il contient deux images et une classe.

III. Les requètes : WebClient ou WebRequest ?

WebClient et WebRequest sont deux facons différentes d'aborder les requêtes vers le serveur.

Néanmoins, ils ont en commun deux choses :
  - d'une part, les requêtes effectuées sont asynchrones, (il n'est pas possible de faire autrement en Silverlight),
  - d'autre part, l'objet WebClient et l'objet WebRequest utilisent le même coeur en interne : la classe BrowserHttpWebRequest.

III-A. L'objet WebRequest

Son utilisation est plus "Web philosophy", plus technique.

A la base, WebRequest est une classe abstraite. Les classes qui en héritent sont :

     - HttpWebRequest

     - FtpWebRequest

     - FileWebRequest

L'unique moyen de disposer d'un objet XXXRequest, est d'appeler la méthode statique WebRequest.Create(...).

C'est cette dernière qui, en fonction de l'URI passée en paramètre, retourne un objet de l'un des trois types cité ci-dessus.

Il est important de savoir qu'en Silverlight 2, seul le type HttpWebRequest est disponible. (quid de SL3 !?).

III-A-1. Instanciation

 
Sélectionnez

 
/* 
 * Dans les sources servant pour l'article,
 * méthode initiant la requete 
 */
private void MakeMyWebRequestCall()
{ 
    HttpWebRequest request;
    
    // Récupération de l'URI courante de l'application Silverlight
    String uri = Application.Current.Host.Source.AbsoluteUri;
    
    // Pointe l'uri vers la ressource (ici fichier texte, mais cela aurait pu être un WebService)
    uri = uri.Substring(0, uri.IndexOf("/ClientBin")) + "/ClientBin/TextFile.txt";
    
    // Création de la request...
    request = WebRequest.Create(new Uri(uri,UriKind.Absolute)) as HttpWebRequest;

    // Lance la requete, et pointe la fin de requete vers la méthode WebComplete
    request.BeginGetResponse(new AsyncCallback(WebComplete), request);
}	
 

Dans la syntaxe ci-dessus, voici les points importants à noter :

     - L'objet réellement utilisé pour effectuer la requête est de type HttpWebRequest (La conversion est explicite lors du Create).
Pour rappel, l'objet retourné est un HttpWebRequest, car l'Uri passée à la méthode Create(...) pointe indique le protocole http pour la ressource à traiter !

       L'objet HttpWebRequest assure que la requête utilisera l'ID de session et le cookie courant. 
Ce qui peut être exploitable au niveau du serveur (Exemple : sécurité gérée en ASPX dans l'objet session).

       Ce mécanisme (HttpWebRequest) permet d'annuler la requête.
En effet, l'appel de la méthode request.Abort() permet d'arrêter l'opération, en fonction de critères propre à l'application.

     - Lors de l'exécution de la requête (appel de BeginGetResponse(... , ...)),

       Les paramètres passés sont :
         1) la méthode à appeler lorsque l'upload des données est terminé, et
         2) la requête elle-même, afin de pouvoir récupérer le flux de données,

III-A-2. Exploitation du résultat

Utilisation
Sélectionnez

/// <summary>
/// Methode appelee lorsque l'upload des donnees est termine
/// </summary>
/// <param name="a"></param>
private void WebComplete(IAsyncResult a)
{
    // AsyncState contient le parametre passe lors de l'appel de BeginGetResponse => l'HttpWebRequest
    HttpWebRequest request = (HttpWebRequest)a.AsyncState;

    // Provoque l'arret de la requete, et la recuperation du resultat,
    HttpWebResponse res = (HttpWebResponse)request.EndGetResponse(a);

    string result;
    if (res != null)
    {
        Stream stream = res.GetResponseStream(); // Recup du flux lie au resultat,
        if (stream != null)
        {
            // Traitement du flux...
            using (StreamReader reader = new StreamReader(stream))
            {
                result = reader.ReadToEnd();
            }
            
            Dispatcher.BeginInvoke(delegate() // nécessaire car le thread qui exécute la request est != de celui de l'interface graphique
            {
                this.m_lblResult.Text = System.DateTime.Now.ToString() + " " + result;
            });
        }
    }
}

 

Dans la syntaxe ci-dessus, voici les points importants à noter :

      Tout d'abord, le mécanisme utilisé pour récupérer la réponse :

         - le membre AsyncState du paramètre contient la requête (HttpWebRequest) passée en paramètre lors de l'appel de la méthode BeginGetResponse,
             ce paramètre était (lors de l'appel) l'objet HttpWebRequest lui même.

         - L'appel de la méthode request.EndGetResponse met fin à la requête, et retourne la réponse. C'est cette dernière qui contient le flux uploadé.

      Enfin, la récupération du stream contenu dans la réponse (HttpWebResponse),
      qui se manipule comme tout flux classique.

Etant donné que la requête s'exécute de façon asynchrone, le thread utilisé n'est pas le même que celui qui gère l'interface graphique.
Dès lors, pour permettre la modification éventuelle d'un contrôle graphique lorsque la requête a fini son traitement,
il est nécessaire de passer par un invocation de méthode, d'où le Dispatcher.

III-B. L'objet WebClient

Après avoir vu le plus compliqué (WebRequest), il est intéressant de se pencher sur le cas WebClient.
Ce dernier encapsule un WebRequest, et simplifie grandement l'exécution des requêtes.

III-B-1. Instanciation

 
Sélectionnez

 
/// <summary>
/// Methode effectuant la WebClient call
/// </summary>
private void MakeMyWebClientCall()
{
    WebClient objWebClient;

    // Reuperation de l'URI courante de l'application Silverlight
    String uri = Application.Current.Host.Source.AbsoluteUri;
    
    // Pointe l'uri vers la ressource (ici fichier texte, mais cela aurait pu etre un WebService)
    uri = uri.Substring(0, uri.IndexOf("/ClientBin")) + "/ClientBin/TextFile.txt";
    
    objWebClient = new WebClient();
    
    // Abonnement a&nbsp; l'evenement de fin de chargement...
    objWebClient.DownloadStringCompleted += new DownloadStringCompletedEventHandler(WebClient_DownloadStringCompleted);
    
    // Lance l'execution du traitement asynchrone...
    objWebClient.DownloadStringAsync(new Uri(uri, UriKind.Absolute));
}
 

La syntaxe ci-dessus est "plus simple" dans l'idée...

Il est important de noter les points suivants :

L'instanciation ne nécessite aucune subtilité (simple constructeur)...

Le mécanisme permettant de traiter la fin de la requête se base sur l'abonnement à l'évènement adéquat :

Evènement Description
DownloadStringCompleted Cet évènement est à utiliser avec l'appel de la méthode DownloadStringAsync pour exécuter le traitement d'upload de données String (flux Xml, Json, chaînes de caractères).
OpenReadCompleted Cet évènement est à utiliser avec l'appel de la méthode OpenReadAsync pour exécuter le traitement d'upload de données binaires (fichiers binaires, images, ...).

Etant donné que pour notre exemple nous traitons des chaînes de caractères stoquées dans un fichier, nous utilisons l'évènement DownloadStringCompleted en lien avec l'appel de la méthode DownloadStringAsync pour exécuter le traitement.

III-B-2. Exploitation du résultat

 
Sélectionnez

 
/// <summary>
/// Methode appelee lorsque l'upload est termine (en WebClient)
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void WebClient_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
    if(!e.Cancelled && e.Error == null)
        this.m_lblResultWebClient.Text = System.DateTime.Now.ToString() + " " + e.Result;
}
 

Ici, pour le traitement des données retournées, nous vérifions qu'il n'y a pas eu d'erreur, et que l'opération n'a pas été annulée par l'utilisateur, ensuite, nous exploitons directement la chaine string dans le contrôle graphique (m_lblResultWebClient est un textblock). Ceci est possible sans invocation via le Dispatcher, car WebClient se mappe sur le Contexte utilisateur principal, et donc est exécuté dans le thread courant (ici celui de l'interface graphique).

III-C. Comparaison WebRequest - WebClient

Opération Description WebRequest Description WebClient
Instanciation Ne peut s'instancier que via l'appel d'une méthode statique (Pattern Fabrique), à laquelle il faut passer l'Uri de la ressource.
L'objet retourné est un type dérivé de WebRequest (HttpWebRequest, FtpWebRequest ou FileWebRequest - les deux derniers n'étant pas disponibles en Silverlight2)
Sans complexité, un constructeur classique...
Exécution Nécessite de passer en paramètre à la méthode :
  - Un pointeur sur la méthode responsable de la fin du traitement (pattern IOC)
  - La requête elle même, afin de permettre à la méthode responsable de la fin du traitement de récupérer la ressource
Nécessite de s'abonner à l'évènement lié au type de traitement demandé (cfr plus haut pour le lien méthode - évènement)
Exploitation des résultats Nécessite un traitement plus complexe du flux Juste récupérer la ressource contenue dans le paramètre de type DownloadStringCompletedEventArgs

En conclusion, il est important de bien comprendre les deux mécanismes.
WebClient est plus simple d'utilisation, mais ne permet pas (facilement) de sous traiter le comportement dans un autre thread.
Cette fonctionnalité pouvant s'avérer utile dans les cas d'upload de grosses quantités de données vers l'application Silverlight.

Néanmoins, dans la majorité des cas, WebClient suffira.

Donc, c'est à chacun de savoir quelle technique il préfère.

III-D. Remarque sur les requêtes CrossDomain

Que l'on soit en Silverlight, en Flash, ou en JavaScript, les requêtes effectuées pour récupérer les données doivent être à destination du même serveur que celui qui héberge l'application Web (Same Origin Policy).
En effet, cela est indispensable afin d'éviter qu'une application Web puisse lancer des attaques par phishing.
D'un autre côté, il arrive qu'il soit nécessaire de permettre à une application Silverlight (ou Flash) d'effectuer des requêtes croisées.
Dans ces cas là, il est nécessaire de créer un fichier qui décrit les droits à la racine du domaine d'où les requêtes doivent être effectuées (si la ressource à chercher est à http://www.developpez.com/meteo, le fichier devra se situer à http://www.developpez.com).

Ces fichiers peuvent prendre deux syntaxes différentes :

clientaccesspolicy.xml est le standard utilisé pour les application Flash.
Par compatibilité, les application Silverlight peuvent utiliser ce fichier. Elles s'exécuteront alors avec les mêmes droits que leurs homologues Flash.

crossdomain.xml est le standard créé pour les application Silverlight. Syntaxiquement un peu différent de la version Macromedia, il offre les mêmes fonctionnalités.

Pour la syntaxe contenue dans ces fichiers, je vous redirige vers le lien suivant : MSDN ClientAccessPolicy - CrossDomain

IV. Exemple : Chargement dynamique d'un assembly

IV-A. Introduction

Maintenant que vous possédez tout le savoir nécessaire au chargement de données, je vous propose d'aborder un cas particulier : le chargement dynamique d'un assembly.


Mais avant d'aller plus loin, pourquoi pourrait-on avoir besoin de cela ?

A la base, une application Silverlight est contenue dans un fichier XAP (qui n'est qu'un Zip des ressources nécessaire à son exécution). Le problème, c'est que la taille du fichier XAP est proportionnelle à la taille de l'application à afficher (ce qui est logique).

Imaginons le cas d'une entreprise qui fournirait à ses employés un portail comme outil de travail. Ce portail disposerait d'un nombre important de modules. 
Nous pourrions rencontrer, par exemple, les outils suivants : gestion des clients, gestion des ressources humaines, gestion des stocks, gestion des livraisons,...

Le portail et tous ses modules seraient donc réalisés en Silverlight.

Ajoutons, que notre entreprise dispose de points de vente perdu au fin fond de la campagne européenne (tant qu'à faire un exemple, autant qu'il soit mégalo :) ), et qu'au fond de la campagne européenne, on ne peut assurer l'ADSL... (56ko... si si, cela existe encore, je vous dis)

Si tout les modules étaient nativement dans le même XAP, cela pourrait engendrer des temps de chargement de l'application absolument ...longs... en 56ko.
De plus :

     - pourquoi pénaliser un point de vente avec tout les modules alors qu'il n'utilise que certains modules,

     - pourquoi charger tout les modules alors qu'un utilisateur n'a peut-être pas tout les droits d'accès,

Du coup, il est possible de disposer d'un portail minimaliste (XAP), et de ne charger les assembly satellites (qui contiennent les modules) qu'à la demande (lorsque l'utilisateur clique dans le menu).

Pour l'exemple, ce qui nous intéresse, c'est justement le chargement dynamique. Dès lors, nous allons simplement charger dynamiquement une bibliothèque de classe qui contient une image et une classe. Nous afficherons cette image, et instancierons la classe (avec un appel à la méthode ToString de cette classe).

Cet exemple est contenu dans les sources, déclenche le chargement de l'assembly lorsque l'utilisateur ouvre un onglet.

IV-A-1. Implémentation pour récupérer une image

Dans ce tuto, nous ne mettrons que les étapes essentielles permettant de réaliser le chargement dynamique d'assembly.
En effet, de nombreux tutos existent déjà sur les bases des projets Silverlight dans Visual Studio.

Pour réaliser cet exemple, créez une solution de type Application Silverlight dans une Application Web (ou une Solution Web).

Dans la solution, ajouter une Bibliothèque de classes Silverlight, dans cette biblothèque ajoutez une image JPEG.

Attention, Silverlight supporte entièrement les JPEG et partiellement les PNG.
Pour disposer d'un PNG qui fonctionne dans Silverlight, nous utilisons Paint.NET pour ouvrir l'image (même png) et l'enregistrer/la réenregistrer en png.

Compilez le projet Bibliothèque de classes, et copiez la DLL dans le répertoire ClientBin du projet Web.

Dans l'application silverlight, ajoutez un conteneur, un bouton, et un contrôle Image.

La méthode attachée à l'évènement Click du bouton appelle la méthode suivante :

1) Upload de l'assembly
Sélectionnez

 
/// <summary>
/// Methode qui charge les donnees
/// </summary>
private void LoadDynamicAssembly()
{
    string uri = Application.Current.Host.Source.AbsoluteUri;

    int index = uri.IndexOf("/ClientBin");

	// le nom : votreDll.dll corresond au nom de la dll bibliothèque qui contient l'image
    uri = uri.Substring(0, index) + "/ClientBin/votreDll.dll"; 
    
    WebClient webClient = new WebClient();
    webClient.OpenReadCompleted += assemblyLoaded_OpenReadCompleted; // pour les données binaires... 
    webClient.OpenReadAsync(new Uri(uri)); // Initie le chargement des données binaires
}
 

Les points importants à noter :
Utilisation de l'évènement OpenReadCompleted qui permet de traiter des flux binaires...

Utilisation de la méthode OpenReadAsync pour initier le traitement (car flux binaire)...

Ajoutez la méthode suivante qui sera appelée lors de la fin du traitement de rappatriement de la DLL :
(Méthode attachée à l'évènement OpenReadCompleted)

2) Chargement de l'assembly
Sélectionnez

 
/// <summary>
/// Methode qui est responsable de la fin de traitement
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void assemblyLoaded_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
{
    try
    {
    	// Chargement de l'assembly
        AssemblyPart ass = new AssemblyPart();
        ass.Load(e.Result);

        this.m_imgLoaded.Source = 
		  new System.Windows.Media.Imaging.BitmapImage(
			new Uri("BDV.Exemple.Silverlight.DynamicLoadedAssembly;component/tuxCow.png", UriKind.Relative)
			);

        if (this.m_imgLoaded.Source != null)
            this.m_assemblyLoaded = true;
    }
    catch (Exception)
    { }
}
 

Les points important à noter sont :

Utilisation d'un objet AssemblyPart. Cet objet représente un assembly servant à charger dans le XAP actuellement en mémoire.
Dès lors que nous disposons du flux binaire, rien ne nous empèche d'utiliser un AssemblyPart pour charger cet assembly en mémoire dans l'application Silverlight !

Pour récupérer l'image, la syntaxe de l'Uri relative est la suivante :
[assemblyName];component/[RessourcePath]
BDV.Exemple.Silverlight.DynamicLoadedAssembly;component/tuxCow.png

IV-A-2. Implémentation pour l'instanciation d'une classe

Pour pouvoir instancier une classe, encore faut il qu'elle existe dans l'assembly en question !

Donc, ajoutez dans l'assembly Bibliothèque de classes Silverlight une classe, et ajoutez lui une méthode (ou propriété ou overridez le ToString) qui retourne une chaine de caractères.

Recompilez cet assembly et copiez le dans le répertoire du projet Web "ClientBin"
(C'est le répertoire de base des applications Silverlight dans les projets Web ASPX).

C'est là qu'il faut être attentif : sinon c'est le drame !

Ajoutez une référence dans le projet d'application Silverlight, qui pointe vers le projet Bibliothèque de classes. (Cela sera nécessaire pour pouvoir utiliser la classe dans l'application).

Clickez sur la référence, et affichez les propriétés de cette référence...
Image non disponible
Pour ce faire, clic droit sur la référence et click sur Propriétés...
(Raccourcis Visual Studio.NET : sélectionner la référence + F4)

La propertyBox s'affiche...
Image non disponible

Modifiez le paramètre "Copie locale" à False.
Image non disponible

Et enregistrez le tout (Ctrl+Maj+S).

La copie locale indique juste au compilateur s'il doit inclure, ou non, une copie de l'assembly pointé par la référence dans la sortie par défaut (le fichier XAP, pour une application Silverlight).
En le pointant sur False, nous ne permettons pas à cet assembly de faire partie du package par défaut (XAP).
Si comme moi, vous êtes un "Saint Thomas, je crois ce que je vois", compilez l'application Silverlight, ouvrez le répertoire de génération, utilisez votre outil favoris pour dézipper (7zip par exemple) et ouvrez ce fichier...  Vous verrez que la bibliothèque de classes n'y est pas !

Enfin, dans votre application Silverlight, ajoutez un bouton et dans le corps de cette méthode, ajoutez (par exemple)
une instanciation, et un appel à la méthode ou propriété ajoutée (celle qui retourne une chaine de caractère) (dans un messagebox par exemple).

Exemple
Sélectionnez

	BDV.Exemple.Silverlight.DynamicLoadedAssembly.ClassTest cls = new BDV.Exemple.Silverlight.DynamicLoadedAssembly.ClassTest();
	MessageBox.Show(cls.ToString(),"Instanciation et appel de la methode ToString()",MessageBoxButton.OK);

Lancez votre application en debug... et tadaaaa !

Attention, j'attire votre attention sur un dernier point non négligeable.
Lorsque l'on utilise une classe déclarée, mais dont l'assembly n'a pas été chargé. Et que l'on appelle une méthode qui contient une telle situation.
La CLR va générer une exception qui ne sera pas catchable/visible au sein du code qui entraine l'erreur.
Cette exception sera renvoyée directement au gestionnaire d'exceptions de l'application Silverlight.

Pour pouvoir traiter cette exception, il est nécessaire de s'abonner à l'évènement Application.UnhandledException (évènement claqué pour une telle exception).

L'exception throwée sera de type FileNotFoundException, et le message incluera le nom de l'assembly manquant.

Il est néanmoins possible d'éviter cela, en s'assurant que l'assembly en question a bel et bien été loadé avant l'appel du code concerné.

V. Références

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+