• Facile

Ce cours est visible gratuitement en ligne.

Vous pouvez être accompagné et mentoré par un professeur particulier par visioconférence sur ce cours.

J'ai tout compris !

Mis à jour le 29/04/2014

Le traitement des données

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

Généralement, dans nos applications, nous allons avoir besoin de traiter des données. Des clients, des commandes ou tout autre chose qui a toute sa place dans une ListBox ou dans d’autres contrôles. Nous venons en plus de parler de modèle dans le chapitre sur MVVM en simulant un peu maladroitement un chargement de données, avec des données en dur dans le code.
Dans une application de gestion, les données évoluent au fur et à mesure. La liste de clients s’agrandit, le nombre de produits du catalogue augmente, les prix changent, etc.
Bref, nous allons avoir besoin de gérer des données, aussi nous allons nous attarder dans ce chapitre à considérer différentes solutions pour récupérer des données et les manipuler.

HttpRequest & WebClient

Dans une application pour mobile, il y a souvent beaucoup d’occasions pour récupérer des informations disponibles sur internet, notamment avec la présence de plus en plus importante du cloud.
Récupérer des données sur internet consiste généralement en trois choses :

  • Demander la récupération de données.

  • Attendre la fin du téléchargement.

  • Interpréter ces données.

Dans nos applications Windows Phone, la récupération de données est obligatoirement asynchrone pour éviter de bloquer l’application et garder une interface fonctionnelle. Cela veut dire qu’on va lancer le téléchargement et être notifié de la fin par un événement. C’est à ce moment-là que l’on pourra interpréter les données. Il y a plusieurs façons de faire des appels, la première est d’utiliser les classes WebClient et HttpWebRequest.

Si vous avez simplement besoin de récupérer une chaine (ou du XML brut) le plus simple est d’utiliser la classe WebClient. Par exemple, la page internet http://www.siteduzero.com/uploads/fr/ftp/windows_phone/script_nico.php renvoie la chaine « Bonjour tout le monde ». Pour y accéder, nous pouvons écrire le code suivant :

public partial class MainPage : PhoneApplicationPage
{
    public MainPage()
    {
        InitializeComponent();

        WebClient client = new WebClient();
        client.DownloadStringCompleted += client_DownloadStringCompleted;
        client.DownloadStringAsync(new Uri("http://www.siteduzero.com/uploads/fr/ftp/windows_phone/script_nico.php"));
    }

    private void client_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
    {
        if (e.Error == null)
        {
            string texte = e.Result;
            MessageBox.Show(texte);
        }
        else
        {
            MessageBox.Show("Impossible de récupérer les données sur internet : " + e.Error);
        }
    }
}

Ce qui donne le résultat de la figure suivante.

Affichage d'une donnée récupérée sur internet
Affichage d'une donnée récupérée sur internet

Le principe est de s’abonner à l’événement de fin de téléchargement, de déclencher le téléchargement asynchrone et de récupérer le résultat. Ici, il n’y a pas de traitement à faire pour interpréter les données, vu que nous souhaitons afficher directement la chaine récupérée.

La méthode DownloadStringAsync fonctionne très bien pour tout ce qui est texte brut. Si vous voulez télécharger des données binaires, vous pourrez utiliser la méthode OpenReadAsync.
La classe WebClient est parfaite pour faire des téléchargements simples mais elle devient vite limitée lorsque nous devons faire des opérations plus pointues sur les requêtes, comme modifier les entêtes HTTP ou envoyer des données en POST, etc.
C’est là que nous allons utiliser la classe HttpWebRequest. Elle offre un contrôle plus fin sur la requête web. Nous allons illustrer ceci en faisant une requête sur le formulaire PHP du cours PHP du site OpenClassrooms, disponible à cet emplacement : http://fr.openclassrooms.com/uploads/fr/ftp/mateo21/php/form_text/formulaire.php

Le principe est d’envoyer des données en POST, notamment la donnée : "prenom=Nicolas".
Le code est le suivant :

<TextBlock x:Name="Resultat" TextWrapping="Wrap"/>

Avec le code behind qui suit :

public MainPage()
{
    InitializeComponent();

    HttpWebRequest requete = (HttpWebRequest)HttpWebRequest.Create("http://fr.openclassrooms.com/uploads/fr/ftp/mateo21/php/form_text/formulaire.php");
    requete.Method = "POST";
    requete.ContentType = "application/x-www-form-urlencoded";


    requete.BeginGetRequestStream(DebutReponse, requete);
}

private void DebutReponse(IAsyncResult resultatAsynchrone)
{
    HttpWebRequest requete = (HttpWebRequest)resultatAsynchrone.AsyncState;
    Stream postStream = requete.EndGetRequestStream(resultatAsynchrone);
    string donneesAEnvoyer = "prenom=Nicolas";

    byte[] tableau = Encoding.UTF8.GetBytes(donneesAEnvoyer);
    postStream.Write(tableau, 0, donneesAEnvoyer.Length);
    postStream.Close();
    requete.BeginGetResponse(FinReponse, requete);
}

private void FinReponse(IAsyncResult resultatAsynchrone)
{
    HttpWebRequest requete = (HttpWebRequest)resultatAsynchrone.AsyncState;
    WebResponse webResponse = requete.EndGetResponse(resultatAsynchrone);
    Stream stream = webResponse.GetResponseStream();

    StreamReader streamReader = new StreamReader(stream);
    string reponse = streamReader.ReadToEnd();
    stream.Close();
    streamReader.Close();
    webResponse.Close();

    Dispatcher.BeginInvoke(() => Resultat.Text = reponse);
}

Ce code peut paraître un peu compliqué, mais c’est toujours le même pour faire ce genre de requête. On commence par créer la requête en lui indiquant la méthode POST. Ensuite dans la première méthode on écrit les données à envoyer dans le flux et on invoque la requête asynchrone. Dans la deuxième méthode, on récupère le retour (voir la figure suivante).

Envoi d'un formulaire POST à une page PHP et affichage du retour
Envoi d'un formulaire POST à une page PHP et affichage du retour

Ici, le retour est du HTML, ce qui est normal vu que ce formulaire a été prévu pour une page web. Il aurait pu être judicieux d’interpréter le résultat, en retirant les balises HTML par exemple…

C’est ce que nous avons fait ici avec :

Dispatcher.BeginInvoke(() => resultat.Text = reponse);

Ce qui permet de mettre à jour la propriété Text du TextBlock.

Nous pouvons également consommer facilement des services web avec une application Windows Phone.
Un service web est une espèce d’application web qui répond à des requêtes permettant d’appeler une méthode avec des paramètres et de recevoir en réponse le retour de la méthode.

Mais comment appeler un service web ? Quelle adresse ? Comment connait-on le nom des méthodes à appeler ? Comment indiquer les paramètres et les valeurs que l’on souhaite passer ?

Eh oui, il faut une syntaxe définie, sinon on peut faire ce que l’on veut. C’est là qu’interviennent les organismes de standardisation. Ils ont défini plusieurs normes permettant de décrire le format des échanges. C’est le cas par exemple du protocole SOAP qui est basé sur du XML. Il est associé au WSDL qui permet de décrire le service web. Nous avons à notre disposition également les services web de type REST qui exposent les fonctionnalités comme des URL.

Pour illustrer ce fonctionnement, nous allons utiliser un service web gratuit qui permet de transformer des températures, par exemple de degrés Celsuis en degré Fahrenheit. Ce service web est disponible à l’adresse suivante : http://www.webservicex.net/ConvertTemperature.asmx, et plus précisément, sa description est accessible sur http://www.webservicex.net/ConvertTemperature.asmx?WSDL.

Nous devons ensuite ajouter une référence web, pour cela faites un clic droit sur les références et ajoutez une référence de service, comme indiqué sur la figure suivante.

Ajout d'une référence de service
Ajout d'une référence de service

Ensuite, il faut saisir l’adresse du WSDL http://www.webservicex.net/ConvertTemperature.asmx?WSDL et cliquer sur Aller à (voir la figure suivante).

Fenêtre d'ajout de la référence de service
Fenêtre d'ajout de la référence de service

Remarquez que je laisse l’espace de noms à la valeur ServiceReference1, mais n’hésitez pas à le changer si besoin. Vous aurez alors besoin d’inclure cet espace de nom afin de pouvoir appeler le service web.
Une fois validé, Visual Studio nous génère un proxy en se basant sur le WSDL du service web. Ce proxy va s’occuper d’encapsuler tout la logique d’appel du web service pour nous simplifier la tâche.

Cela veut dire concrètement que Visual Studio travaille pour nous. À partir de l’URL de nos services web, il va analyser le type des données qui doivent être passées en paramètres ainsi que les données que l’on obtient en retour et générer des classes qui leurs correspondent. De même, il génère tout une classe qui encapsule les différents appels aux différentes méthodes du service web. C’est cette classe que l’on appelle un proxy car elle sert de point d’entrée pour tous les appels des méthodes du service web. Toutes ces classes ont été créées dans l’espace de nom que nous avons indiqué.

Nous pourrons alors simplement l’utiliser comme ceci :

public partial class MainPage : PhoneApplicationPage
{
    public MainPage()
    {
        InitializeComponent();

        TemperatureService.ConvertTemperatureSoapClient client = new TemperatureService.ConvertTemperatureSoapClient();
        client.ConvertTempCompleted += client_ConvertTempCompleted;
        client.ConvertTempAsync(25, TemperatureService.TemperatureUnit.degreeCelsius, TemperatureService.TemperatureUnit.degreeFahrenheit);
    }

    private void client_ConvertTempCompleted(object sender, TemperatureService.ConvertTempCompletedEventArgs e)
    {
        if (e.Error == null)
        {
            MessageBox.Show(e.Result.ToString());
        }
    }
}

Ce qui donnera la figure suivante.

Résultat de la conversion degré Celsuis en degré Fahrenheit
Résultat de la conversion degré Celsuis en degré Fahrenheit

Et voilà, 25 degré Celsuis font 77 degré Fahrenheit !

Linq-To-Json

Parlons à présent un peu des services REST. Je ne présenterai pas comment faire un appel REST parce que vous savez déjà le faire, dans la mesure où il s’agit d’une simple requête HTTP. Par contre, ce qu’il est intéressant d’étudier ce sont les solutions pour interpréter le résultat d’un appel REST. De plus en plus, les services REST renvoient du JSON car c’est un format de description de données beaucoup moins verbeux que le XML.
.NET sait interpréter le JSON mais malheureusement l’assembly Silverlight System.Json.dll n’est pas portée pour Windows Phone. Nous pouvons quand même l’utiliser mais c’est à nos risques et périls. Elle a été installée avec le SDK de Silverlight, chez moi à cet emplacement : C:\Program Files\Microsoft SDKs\Silverlight\v4.0\Libraries\Client\System.Json.dll.

Cette assemby nous permet de faire du Linq To Json et d’avoir accès aux objets JsonObject et JsonArray. Lorsque vous allez référencer cette assembly, vous aurez le message d’avertissement suivant :

Fenêtre d'avertissement lors de la référence à System.Json.dll
Fenêtre d'avertissement lors de la référence à System.Json.dll

Vous pouvez cliquer sur oui pour continuer.
Nous allons illustrer le fonctionnement de ces objets à travers un appel REST qui consistera à réaliser une recherche avec le moteur de recherche Google. Par exemple, si je veux chercher la chaine « openclassrooms » sur Google, je pourrai utiliser le service web REST suivant : http://ajax.googleapis.com/ajax/servic [...] penclassrooms
Qui me renverra quelque chose comme :

{
   "responseData":{
      "results":[
         {
            "GsearchResultClass":"GwebSearch",
            "unescapedUrl":"http://fr.openclassrooms.com/",
            "url":"http://fr.openclassrooms.com/",
            "visibleUrl":"fr.openclassrooms.com",
            "cacheUrl":"http://www.google.com/search?q\u003dcache:Wy40FwwsrHMJ:fr.openclassrooms.com",
            "title":"\u003cb\u003eOpenClassrooms\u003c/b\u003e, Le Site du Zéro - Les cours les plus ouverts du Web",
            [...]
         },
         {
            "GsearchResultClass":"GwebSearch",
            "unescapedUrl":"http://fr.openclassrooms.com/informatique/cours",
            "url":"http://fr.openclassrooms.com/informatique/cours",
            [...]
         },
         [...]
      ],
      "cursor":{
         "resultCount":"119 000",
         "pages":[
            {
               "start":"0",
               "label":1
            },
            [...]
         ],
         "estimatedResultCount":"119000",
         "currentPageIndex":0,
         [...]
         "searchResultTime":"0,21"
      }
   },
   "responseDetails":null,
   "responseStatus":200
}

Le format JSON est relativement compréhensible à l’œil nu, mais sa lecture fait un peu mal aux yeux. Nous pouvons quand même décrypter que le résultat contient une série de valeurs correspondant à la recherche.
Construisons une mini application qui effectuera le téléchargement des données, vous savez faire, on utilise la classe WebClient :

public partial class MainPage : PhoneApplicationPage
{
    public MainPage()
    {
        InitializeComponent();
        WebClient client = new WebClient();
        client.DownloadStringCompleted += client_DownloadStringCompleted;
        client.DownloadStringAsync(new Uri("http://ajax.googleapis.com/ajax/services/search/web?v=1.0&q=openclassrooms"));
    }

    private void client_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
    {
        if (e.Error == null)
        {
            JsonObject json = (JsonObject)JsonObject.Parse(e.Result);
        }
    }
}

Nous allons pouvoir obtenir un objet JsonObject grâce à la méthode statique Parse. Le JsonObject est en fait une espèce de dictionnaire où nous pouvons accéder à des valeurs à partir de leur clé. Par exemple, vous pouvez voir que le json récupéré possède la propriété responseData qui contient une sous propriété cursor, contenant elle-même la propriété resultCount fournissant le nombre de résultats de la requête. Nous pouvons y accéder de cette façon :

JsonObject json = (JsonObject)JsonObject.Parse(e.Result);
string nombreResultat = json["responseData"]["cursor"]["resultCount"];

Dans ce cas, chaque JsonObject renvoie un nouvel JsonObject qui est lui-même toujours cette espèce de dictionnaire. En effet, responseData, cursor et resultCount sont des propriétés simples. Ce n’est pas le cas par contre de la propriété results qui est un tableau. On utilisera alors un JsonArray pour pouvoir le parcourir. Et c’est là que Linq To Json rentre en action, nous allons pouvoir requêter sur ce tableau. Par exemple :

List<string> resultats = new List<string>();
JsonObject json = (JsonObject)JsonObject.Parse(e.Result);
string nombreResultat = json["responseData"]["cursor"]["resultCount"];
var listeResultat = 
        from resultat in (JsonArray)(json["responseData"]["results"])
        where ((string)resultat["content"]).IndexOf("cours", StringComparison.CurrentCultureIgnoreCase) >= 0
        select resultat;

foreach (var resultat in listeResultat)
{
    resultats.Add(resultat["titleNoFormatting"]);
}

Ici, je récupère les titres de chaque résultats dont le contenu contient le mot cours, sans faire attention à la casse. Ainsi, avec une ListBox :

<ListBox x:Name="MaListeBox" />

Je pourrais les afficher :

MaListeBox.ItemsSource = resultats;

Et obtenir :

Résultat de la recherche google dans la ListBox
Résultat de la recherche google dans la ListBox

Vous aurez remarqué que traiter du JSON de cette façon n’est pas formidable. C’est plutôt lourd à mettre en place.
Heureusement, il y a une autre solution plus intéressante et qui n’a pas besoin d’aller chercher l’assembly System.Json.dll. Il s’agit de transformer le JSON obtenu en objet. Cela peut se faire avec le DataContractJsonSerializer du framework.NET, qui se trouve dans l’assembly System.Servicemodel.Web. Celui-ci souffre cependant de quelques limitations. On pourra le remplacer avec la bibliothèque open-source JSON.NET que l’on peut télécharger sur codeplex. Téléchargez la dernière version et référencez l’assembly dans votre projet, la version Windows Phone bien sûr ou alors utilisez NuGet :

Installation de Json.NET via NuGet
Installation de Json.NET via NuGet

La première chose à faire est de regarder la réponse renvoyée car nous allons avoir besoin de construire un ou plusieurs objets mappant ce résultat. Nous pouvons les construire à la main ou bien profiter du fait que certaines personnes ont réalisé des outils pour nous simplifier la vie. Allons par exemple sur le site http://json2csharp.com/ où nous pouvons copier le résultat de la requête. Ce site nous génère les classes suivantes :

public class Page
{
    public int label { get; set; }
    public string start { get; set; }
}

public class Cursor
{
    public int currentPageIndex { get; set; }
    public string estimatedResultCount { get; set; }
    public string moreResultsUrl { get; set; }
    public List<Page> pages { get; set; }
    public string resultCount { get; set; }
    public string searchResultTime { get; set; }
}

public class Result
{
    public string GsearchResultClass { get; set; }
    public string cacheUrl { get; set; }
    public string content { get; set; }
    public string title { get; set; }
    public string titleNoFormatting { get; set; }
    public string unescapedUrl { get; set; }
    public string url { get; set; }
    public string visibleUrl { get; set; }
}

public class ResponseData
{
    public Cursor cursor { get; set; }
    public List<Result> results { get; set; }
}

public class RootObject
{
    public ResponseData responseData { get; set; }
    public object responseDetails { get; set; }
    public int responseStatus { get; set; }
}

que nous pouvons inclure dans notre projet.
Ces classes représentent exactement le résultat de la requête sous la forme de plusieurs objets.
Il ne reste plus qu’à faire un appel web comme on l’a vu :

public MainPage()
{
    InitializeComponent();
    WebClient client = new WebClient();
    client.DownloadStringCompleted += client_DownloadStringCompleted;
    client.DownloadStringAsync(new Uri("http://ajax.googleapis.com/ajax/services/search/web?v=1.0&q=openclassrooms"));
}

private void client_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
    if (e.Error == null)
    {
        RootObject resultat = JsonConvert.DeserializeObject<RootObject>(e.Result);
        MaListeBox.ItemsSource = resultat.responseData.results.Select(r => r.titleNoFormatting);
    }
}

et à utiliser la classe JsonConvert pour désérialiser le JSON récupéré et le mettre dans les classes qui ont été générées.

Ensuite, j’extraie uniquement le titre pour l’afficher dans ma ListBox. Et le tour est joué !
Grâce à la bibliothèque JSON.NET, nous pouvons facilement interpréter des données JSON venant d’internet afin d’être efficace dans nos applications.
Remarquez que maintenant que nous avons des objets, nous pouvons utiliser les extensions Linq pour requêter sur les informations issues du JSON.

La bibliothèque de Syndication

Maintenant que nous savons récupérer des données depuis internet, pourquoi ne pas essayer d’en faire quelque chose d’un peu intéressant ? Comme un lecteur de flux RSS par exemple…
Vous connaissez sans doute tous le RSS, c’est ce format qui nous permet de nous abonner à nos blogs favoris afin d’être avertis des nouveaux articles publiés.
Le flux RSS est produit sous forme de XML standardisé, contenant des informations sur le nom du site, les billets qui le composent, le titre du billet, la description, etc.
Le site OpenClassrooms possède bien évidemment des flux RSS, comme le flux d’actualité de son blog disponible à cet emplacement : http://www.simple-it.fr/blog/feed/.
Si vous naviguez sur ce lien, vous pouvez facilement voir le titre du site, la description, ainsi que les différentes actualités ; et tout ça au format XML.

Prenons un autre site pour l’exemple, le blog de l’équipe Windows Phone. Le flux RSS est accessible via cette page : http://blogs.windows.com/windows_phone [...] hone/rss.aspx.

Vous savez déjà récupérer du XML grâce à la classe WebClient. Je vais en profiter pour vous communiquer une petite astuce dont je n’ai pas parlé dans les chapitres précédents. Il est possible de fournir un objet de contexte à la requête de téléchargement et ainsi pouvoir utiliser la même méthode pour l’événement de fin de téléchargement et identifier ainsi différentes requêtes. Il suffit de lui passer en paramètre de l’appel à la méthode DownloadStringAsync. Cet objet de contexte sera récupéré dans l’événement de fin de téléchargement, dans la propriété UserState de l’objet résultat :

public partial class MainPage : PhoneApplicationPage
{
    private WebClient client;

    public MainPage()
    {
        InitializeComponent();

        client = new WebClient();
        client.DownloadStringCompleted += client_DownloadStringCompleted;
        client.DownloadStringAsync(new Uri("http://www.simple-it.fr/blog/feed/"), "OC");
    }

    private void client_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
    {
        if (e.Error == null)
        {
            if ((string)e.UserState == "OC")
            {
                // ce sont les données venant du flux du blog OpenClassrooms

                // lancer le téléchargement suivant
                client.DownloadStringAsync(new Uri("http://windowsteamblog.com/windows_phone/b/windowsphone/rss.aspx"), "WP");
            }
            if ((string)e.UserState == "WP")
            {
                // ce sont les données venant du flux blog de l'équipe windows phone
            }
        }
    }
}

Ceci nous permettra d’utiliser la même méthode pour l’événement de fin de téléchargement.
Nous avons maintenant besoin d’interpréter les données du flux XML retourné. Étant donné que les flux RSS sont standards, il existe une bibliothèque Silverlight qui permet de travailler avec ce genre de flux. C’est la bibliothèque System.ServiceModel.Syndication.dll. Encore une fois, cette assembly n’a pas été écrite pour Windows Phone, mais elle est quand même utilisable avec nos applications Windows Phone.
Pour l’utiliser, nous devons ajouter une référence à celle-ci. Elle se trouve dans le répertoire suivant : C:\Program Files\Microsoft SDKs\Silverlight\v4.0\Libraries\Client.

Comme pour System.Json.dll, vous aurez une boite de dialogue d’avertissement où vous pouvez cliquer sur Oui (voir la figure suivante).

Fenêtre d'avertissement lors de la référence à System.ServiceModel.Syndication.dll
Fenêtre d'avertissement lors de la référence à System.ServiceModel.Syndication.dll

Nous allons donc pouvoir charger l’objet de Syndication à partir du résultat, cet objet sera du type SyndicationFeed. Pour commencer, je me rajoute une variable privée :

private List<SyndicationFeed> listeFlux;

que j’initialise dans le constructeur :

listeFlux = new List<SyndicationFeed>();

N’oubliez pas de rajouter le using qui va bien :

using System.ServiceModel.Syndication;

puis je consolide ma liste à partir du retour de l’appel web :

private void client_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
    if (e.Error == null)
    {
        if ((string)e.UserState == "OC")
        {
            // ce sont les données venant du flux du blog OpenClassrooms
            AjouteFlux(e.Result);

            // lancer le téléchargement suivant
            client.DownloadStringAsync(new Uri("http://windowsteamblog.com/windows_phone/b/windowsphone/rss.aspx"), "WP");
        }
        if ((string)e.UserState == "WP")
        {
            // ce sont les données venant du flux blog de l'équipe windows phone
            AjouteFlux(e.Result);
        }
    }
}

private void AjouteFlux(string flux)
{
    StringReader stringReader = new StringReader(flux);
    XmlReader xmlReader = XmlReader.Create(stringReader);
    SyndicationFeed feed = SyndicationFeed.Load(xmlReader);
    listeFlux.Add(feed);
}

Pour charger un flux de syndication, il suffit d’utiliser la méthode Load de la classe SyndicationFeed en lui passant un XmlReader, présent dans l’espace de nom System.Xml.

Et voilà, nous avons créé une liste d’objet SyndicationFeed qui possède les éléments du flux RSS du blog OpenClassrooms ainsi que ceux du blog de l’équipe Windows Phone. Chaque objet SyndicationFeed contient une liste de billets, sous la forme d’objets SyndicationItem. Par exemple, la date de publication est accessible via la propriété PublishDate d’un SyndicationItem. Le titre du billet est accessible via la propriété Title.Text...

Nous pourrons par exemple afficher le titre de chaque post, ainsi que sa date dans une ListBox :

<ListBox x:Name="LaListeBox">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="{Binding PublishDate}" />
                <TextBlock Text="{Binding Title.Text}" />
            </StackPanel>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

Il nous faudra lier la propriété ItemsSource de la ListBox à, par exemple une ObservableCollection que nous construisons une fois le dernier flux reçu, et qui sera triée par ordre de date de publication de la plus récente à la plus ancienne :

if ((string)e.UserState == "WP")
{
    // ce sont les données venant du flux blog de l'équipe windows phone
    AjouteFlux(e.Result);
    ObservableCollection<SyndicationItem> listeBillets = new ObservableCollection<SyndicationItem>();
    foreach (SyndicationFeed flux in listeFlux)
    {
        foreach (SyndicationItem billet in flux.Items)
        {
            listeBillets.Add(billet);
        }
    }
    LaListeBox.ItemsSource = listeBillets.OrderByDescending(billet => billet.PublishDate);
}

Ce qui donnera le résultat présenté dans la figure suivante.

Affichage du flux RSS dans la ListBox
Affichage du flux RSS dans la ListBox

La présentation laisse à désirer, mais c’est fait exprès. Nous en restons là pour l’instant, mais ne vous inquiétez pas, vous allez y revenir bientôt.

Asynchronisme avancé

Bon, c’est très bien les méthodes asynchrones, mais c’est une gymnastique un peu compliquée. On s’abonne à un événement de fin de téléchargement puis on démarre le téléchargement et quand le téléchargement est terminé, on exploite le résultat dans une autre méthode perdant au passage le contexte de l’appel. Alors oui… il y a des astuces, comme celle que nous venons de voir… mais je vous dis pas les nœuds au cerveau lorsqu’il y a des appels dans tous les sens !

Bonne nouvelle, avec Windows Phone 8 il est possible de se simplifier grandement l’asynchronisme. En fait, c’est surtout le framework 4.5 qu’il faut remercier, mais peu importe, si vous créez une application pour Windows Phone 8, vous pourrez bénéficier de 2 formidables nouveaux petits mot-clés : async et await.

Certaines API de Windows Phone 8 ont été réécrites pour tirer parti de ces nouveaux mots clés, c’est le cas par exemple de certaines opérations sur les fichiers qui peuvent prendre du temps et que nous allons voir juste après. Ça aurait également pu être le cas pour les classes d’accès à Internet comme WebClient et HttpWebRequest, mais malheureusement celles-ci n’ont pas été réécrites.
Heureusement, nous pouvons écrire un wrapper pour bénéficier de ces éléments avec la classe WebClient. Ceci va nous permettre de nous simplifier grandement l’asynchronisme.

Ce qui va nous intéresser c’est la construction suivante, que nous avons vue au tout début de ce chapitre :

public MainPage()
{
    InitializeComponent();

    WebClient client = new WebClient();
    client.DownloadStringCompleted += client_DownloadStringCompleted;
    client.DownloadStringAsync(new Uri("http://www.siteduzero.com/uploads/fr/ftp/windows_phone/script_nico.php"));
}

private void client_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
    if (e.Error == null)
    {
        string texte = e.Result;
        MessageBox.Show(texte);
    }
    else
    {
        MessageBox.Show("Impossible de récupérer les données sur internet : " + e.Error);
    }
}

Commençons par utiliser la classe TaskCompletionSource dans une méthode d’extension :

public static class Extensions
{
    public static Task<string> DownloadStringTaskAsync(this WebClient webClient, Uri uri)
    {
        TaskCompletionSource<string> taskCompletionSource = new TaskCompletionSource<string>();
        DownloadStringCompletedEventHandler downloadCompletedHandler = null;
        downloadCompletedHandler = (s, e) =>
        {
            webClient.DownloadStringCompleted -= downloadCompletedHandler;
            if (e.Error != null)
                taskCompletionSource.TrySetException(e.Error);
            else
                taskCompletionSource.TrySetResult(e.Result);
        };

        webClient.DownloadStringCompleted += downloadCompletedHandler;
        webClient.DownloadStringAsync(uri);

        return taskCompletionSource.Task;
    }
}

Le principe est d’encapsuler l’appel à DownloadStringAsync et de renvoyer un objet de type Task<string>, string étant le type de ce que l’on récupère en résultat de l’appel.
Ainsi, nous allons pouvoir remplacer l’appel du début par :

public MainPage()
{
    InitializeComponent();
    LanceTelechargementAsync();
}

private async void LanceTelechargementAsync()
{
    WebClient client = new WebClient();
    string texte = await client.DownloadStringTaskAsync(new Uri("http://www.siteduzero.com/uploads/fr/ftp/windows_phone/script_nico.php"));
    MessageBox.Show(texte);
}

La méthode LanceTelechargementAsync doit posséder le mot-clé async avant son type de retour pour indiquer qu’elle est asynchrone et doit également par convention avoir son nom qui se termine par Async. Nous appelons la méthode d’extension DownloadStringTaskAsync et nous attendons son résultat de manière asynchrone grâce au mot-clé await. Pas de méthode appelée une fois le téléchargement terminé… Juste un mot-clé qui nous permet d'attendre le résultat de manière asynchrone.

Plutôt simplifié comme construction, non ?

Allez, pour le plaisir, on se simplifie le téléchargement des flux RSS que l’on a fait juste au dessus ?
Gardons toujours la même classe d’extension et écrivons désormais :

public MainPage()
{
    InitializeComponent();

    LanceLeTelechargementAsync();
}

private async void LanceLeTelechargementAsync()
{
    listeFlux = new List<SyndicationFeed>();

    client = new WebClient();
    string rss = await client.DownloadStringTaskAsync(new Uri("http://www.simple-it.fr/blog/feed/"));
    AjouteFlux(rss);
    rss = await client.DownloadStringTaskAsync(new Uri("http://windowsteamblog.com/windows_phone/b/windowsphone/rss.aspx"));
    AjouteFlux(rss);

    ObservableCollection<SyndicationItem> listeBillets = new ObservableCollection<SyndicationItem>();
    foreach (SyndicationFeed flux in listeFlux)
    {
        foreach (SyndicationItem billet in flux.Items)
        {
            listeBillets.Add(billet);
        }
    }
    LaListeBox.ItemsSource = listeBillets.OrderByDescending(billet => billet.PublishDate);
}

C’est quand même bien plus clair !

Le répertoire local

Il n’est pas toujours possible d’accéder à internet pour récupérer des infos ou les mettre à jour. Nous avons encore à notre disposition un emplacement pour faire persister de l’information : à l’intérieur du téléphone.

On appelle cet emplacement le répertoire local (local folder en anglais ou anciennement isolated storage) dans la mesure où nous n’avons pas accès directement au système de fichiers, mais plutôt à un emplacement mémoire isolé dont nous pouvons ignorer le fonctionnement. Tout ce qu’il faut savoir c’est qu’il est possible d’y stocker des informations, comme du texte mais aussi des objets sérialisés.

Il y a deux grandes façons d’utiliser le répertoire local. La plus simple est d’utiliser le dictionnaire ApplicationSettings. On peut y ranger des objets qui seront associés à une chaine de caractères. Par exemple, en imaginant que l’on crée une application où l’on demande le nom de notre utilisateur, il pourra être judicieux d’éviter de le redemander à chaque ouverture de l’application… Pour cela, nous pouvons le faire persister dans le répertoire local. On utilisera alors :

IsolatedStorageSettings.ApplicationSettings["prenom"] = "Nicolas";

Nb : pour utiliser la classe IsolatedStorageSettings, nous devons inclure :

using System.IO.IsolatedStorage;

J’associe ici la chaine de caractères "prenom" à la chaine de caractères "Nicolas".
Au prochain démarrage de l’application, on pourra vérifier si le prénom existe déjà en tentant d’accéder à sa clé "prenom". S’il y a quelque chose d’associé à cette clé, on pourra le récupérer pour éviter de demander à re-saisir le prénom de l’utilisateur :

if (IsolatedStorageSettings.ApplicationSettings.Contains("prenom"))
    prenom = (string)IsolatedStorageSettings.ApplicationSettings["prenom"];

Nous pouvons mettre des objets complexes dans le répertoire local, pas seulement des chaines de caractères :

public class Utilisateur
{
    public int Age { get; set; }
    public string Prenom { get; set; }
}


Utilisateur nicolas = new Utilisateur { Age = 30, Prenom = "Nicolas" };
IsolatedStorageSettings.ApplicationSettings["utilisateur"] = nicolas;

Et de la même façon, on pourra récupérer cette valeur très facilement :

if (IsolatedStorageSettings.ApplicationSettings.Contains("utilisateur"))
    nicolas = (Utilisateur)IsolatedStorageSettings.ApplicationSettings["utilisateur"];
IsolatedStorageSettings.ApplicationSettings.Save();

Le stockage d’objets dans le répertoire local est quand même très pratique. Vous vous servirez très souvent de ce fonctionnement simple et efficace. Parfois vous aurez peut-être besoin d’un peu plus de contrôle sur ce que vous voulez stocker. À ce moment-là, on peut se servir du répertoire local comme d’un flux classique, comme lorsque l’on souhaite enregistrer des données dans un fichier.

Regardons l’enregistrement :

using (IsolatedStorageFileStream stream = new IsolatedStorageFileStream("sauvegarde.txt", FileMode.Create, IsolatedStorageFile.GetUserStoreForApplication()))
{
    using (StreamWriter writer = new StreamWriter(stream))
    {
        writer.WriteLine(30);
        writer.WriteLine("Nicolas");
    }
}

Puis la lecture :

using (IsolatedStorageFileStream stream = new IsolatedStorageFileStream("sauvegarde.txt", FileMode.Open, IsolatedStorageFile.GetUserStoreForApplication()))
{
    using (StreamReader reader = new StreamReader(stream))
    {
        int age = Convert.ToInt32(reader.ReadLine());
        string prenom = reader.ReadLine();
    }
}

Cela ressemble beaucoup à des opérations d’écriture et de lecture dans un fichier classique…
Enfin, si besoin, on pourra supprimer le fichier :

IsolatedStorageFile racine = IsolatedStorageFile.GetUserStoreForApplication();
if (racine.FileExists("sauvegarde.txt"))
    racine.DeleteFile("sauvegarde.txt");

L’objet IsolatedStorageFile que l’on récupère avec la méthode GetUserStoreForApplication permet de créer un fichier, un répertoire, de le supprimer, etc. Bref, quasiment tout ce que permet un système de fichier classique.

À noter que cette technique est tout à fait appropriée pour stocker des images par exemple, pour éviter d’avoir à les re-télécharger à chaque fois.

Enfin, avec Windows Phone 8 nous pouvons tirer parti des nouvelles API de stockages asynchrones. Celles-ci nous permettent de ne pas bloquer le thread courant lorsque nous avons besoin de lire et écrire potentiellement de grosses données dans le répertoire local. Voici par exemple comment écrire des données dans le répertoire local de manière asynchrone :

private async void SauvegardeAsync()
{
    IStorageFolder applicationFolder = ApplicationData.Current.LocalFolder;

    IStorageFile storageFile = await applicationFolder.CreateFileAsync("sauvegarde.txt", CreationCollisionOption.ReplaceExisting);
    using (Stream stream = await storageFile.OpenStreamForWriteAsync())
    {
        byte[] bytes = Encoding.UTF8.GetBytes("30;Nicolas");
        await stream.WriteAsync(bytes, 0, bytes.Length);
    }
}

Et voici comment lire les données précédemment sauvegardées :

private async void LectureAsync()
{
    IStorageFolder applicationFolder = ApplicationData.Current.LocalFolder;

    IStorageFile storageFile = await applicationFolder.GetFileAsync("sauvegarde.txt");
    IRandomAccessStream accessStream = await storageFile.OpenReadAsync();

    using (Stream stream = accessStream.AsStreamForRead((int)accessStream.Size))
    {
        byte[] bytes = new byte[stream.Length];
        await stream.ReadAsync(bytes, 0, bytes.Length);
        string chaine = Encoding.UTF8.GetString(bytes, 0, bytes.Length);
        string[] tableau = chaine.Split(';');
        int age = int.Parse(tableau[0]);
        string prenom = tableau[1];
    }
}

Ici, j’ai choisi de stocker mes données sous la forme de texte séparés par des points virgules, mais libre à vous de spécifier le format de fichier de votre choix. Nous remarquons l’utilisation des mots-clés async et await, dignes témoins de l’asynchronisme de ces méthodes.

  • Le XAML/C# pour Windows Phone dispose de toute une gamme de solutions pour utiliser des données depuis internet ou en local sur le téléphone.

  • Les classes HttpRequest et WebClient permettent de faire des requêtes sur le protocole HTTP.

  • On utilise la bibliothèque open-source JSON.NET pour interpréter les données au format JSON.

  • Il est très facile d’exploiter des flux RSS grâce à la bibliothèque de syndication.

  • L’asynchronisme est grandement facilité dans Windows Phone 8 grâce aux mots-clés async et await.

  • Le répertoire local correspond à un emplacement sur le téléphone, dédié à notre application, où l’on peut faire persister de l’information entre les divers lancements d’une application.

Ça y est, vous savez traiter les données dans vos applications Windows Phone.

Nous avons commencé par étudier le système de navigation ainsi que la ListBox. C’est un contrôle très puissant qui va vous servir énormément dans vos applications, dès que vous aurez besoin d’afficher des listes de quoi que ce soit. Son mécanisme de modèle est particulièrement efficace pour personnaliser l’affichage des éléments.
Nous avons ensuite vu la liaison de données. Il s’agit d’un chapitre très important que vous devez absolument comprendre pour tirer parti du meilleur des applications utilisant le XAML. N’hésitez pas à le relire et à vous entraîner. Il peut paraître obscur et difficile à maîtriser. Je vous rassure, vous ferez plein d’erreur de liaison de données, c’est tout à fait normal. N’oubliez pas de vérifier de temps en temps dans la fenêtre de sortie s’il n’y a pas un message d’erreur, témoin qu’une liaison de données n’a pas fonctionné. Cela vous sera très utile.
MVVM a ensuite pointé le bout de son nez. N’hésitez pas à revenir plus tard sur ce chapitre si vous n’avez pas encore bien compris son intérêt. Ce n’est qu’après un peu de pratique que vous pourrez saisir toute la plus-value d’un tel patron de conception.
Enfin, nous avons vu différentes solutions pour traiter les données, qu’elles viennent d’internet ou bien simplement de l’intérieur du téléphone.
Vous verrez que cette partie est une partie clé dont vous aurez régulièrement besoin. C’est le socle de toute application de gestion réalisée avec XAML/C# pour Windows Phone. N’hésitez pas à vous entraîner et à y revenir si besoin.

Exemple de certificat de réussite
Exemple de certificat de réussite