Au cours de ce tutoriel, vous allez développer une application Open Data pour Windows 8, ou plus exactement Windows 8 Consumer Preview telle que mise à disposition lors du Mobile World Congress à Barcelone en février dernier. Pour les besoins du tutoriel, vous pouvez télécharger une image ISO de cette version avec l’ensemble des outils de développement nécessaire ici.

Comme vous l’avez peut-être vu en regardant quelques vidéos des sessions de la conférence BUILD ou en participant à l’évènement Windows 8 Megacamp dans les locaux de Microsoft France le 29 mars 2012 pour prendre en main Windows 8 et Visual Studio 11, le nouveau modèle d’applications de Windows 8 intitulé « Metro » permet notamment de réaliser des applications riches mettant en scène des collections de données ainsi que des contenus média. (Si ce n’est pas d’ores et déjà le cas, vous pouvez notamment consulter les vidéos Build polished collection and list apps using XAML et Stand out with styling in your XAML app pour avoir un premier perçu en la matière.)

A l’instar d’un précédent billet sur Silverlight 5, nous allons donc créer une application mettant en scène des photos sous forme d’un album que l’on pourra parcourir, organiser, et enfin disposer de la possibilité de classer les photos préférées dans une catégorie �� Favoris ».

image

Pour illustrer ce scénario, nous allons, une fois de plus, nous appuyer sur les données du département de Saône-et-Loire via son portail Open Data 71 qui tire bénéfice du kit de démarrage OGDI (Open Government Data Initiative) pour mettre à disposition un service exposant les données.

Parmi les données exposées, se trouve notamment un jeu de données contenant des cartes postales de collection avec les liens pointant vers les images stockées dans des blobs du stockage Azure.

image

Sans plus attendre, voici le résultat que vous obtiendrai à la fin de ce billet. Si la capture d’écran le retraduit mal, les images sont dans la pratique agrémentées d’une tuile dynamique permettant d’afficher des informations contextuelles sur la photo associée, un peu à l’image du nouveau menu Démarrer proposé par Windows 8 Consumer Preview :

image

image

Nous décrivons dans les sections suivantes les différentes étapes permettant de conduire à ce résultat, à commencer par la conception à proprement parler.

Conception

Pour concevoir cette application, vous l’aurez compris, vous aurez besoin, d’une part, de consommer le flux de données des cartes postales et, d’autre part, d’afficher les données ainsi consommées.

Nous pouvons donc, par exemple, imaginer un composant réutilisable qui servirait de proxy vers le service de données OGDI et qui nous permettrait de consommer les flux de données JSON sous forme d’objets directement utilisables par l’application Métro qui n’aurait plus, dès lors, qu’à afficher ces objets dans l’interface utilisateur.

Pour cela, nous vous proposons le diagramme suivant pour illustrer les composants que nous allons développer. (Attention, dans un but de simplification du billet, cette conception ne suit pas le design pattern MVVM normalement recommandé pour les applications Silverlight et WPF) :

image

Dans le diagramme précédent, le composant OgdiClient est le composant servant de proxy vers une instance OGDI. Il contient la classe OgdiConsumer destinée à faire la translation entre les flux en JSON et des objets en C# directement consommables par les couches supérieures (via la méthode LoadNextDataChunk).

Côté application Metro, se trouve notamment la classe CartePostaleModel qui représente les objets que l’on consomme depuis les flux JSON (JavaScript Object Notation) au travers la classe OgdiConsumer, et, bien sûr, les classes HomePage et SelectedPhotoPage qui représentent les écrans de l’application.

La classe HomePage correspond à la page principale permettant de naviguer dans le catalogue de cartes postales via un système de boutons « Précédent » et « Suivant », les cartes postales ainsi téléchargées étant stockées dans un dictionnaire (_catalog) faisant correspondre un numéro de page à la collection des cartes appartenant à cette page. On trouve également des méthodes permettant de trier les données et de naviguer vers les pages de sélection de photo et la page des favoris.

Compte tenu de ces éléments, nous allons maintenant passer à l’écriture du code en tant que tel.

Ecriture du code du proxy client OGDI

Tout d’abord créez une nouvelle solution vide dans Visual Studio 11 depuis Windows 8 Consumer Preview. Ensuite, ajoutez à la solution un projet de type Class Library dans la catégorie Windows Metro style (nommez ce projet OgdiClient pour les besoins du billet) :

image

Vous avez également besoin du composant Newtonsoft.Json disponible sous licence libre sur la forge Codeplex. Téléchargez-le et ajoutez une référence vers la bibliothèque Newtonsoft.Json.dll située dans le dossier /Bin/Net de l’archive téléchargée. Une autre solution consiste à installer le composant Json.NET par le gestionnaire de paquets NuGet.

image

Supprimez le fichier appelé Class1.cs de la solution et ajoutez ensuite, comme suit, les classes OgdiCollection et OgdiConsumer au projet.

Classe OgdiCollection :

using System.Collections.Generic;

 

namespace OgdiClient

{

   /// <summary>

   /// Collection interne permettant de désérialiser les données JSON depuis le résultat

   /// de la requête.

   /// < /summary>

   /// <typeparamname="T"></typeparam>

   internal class OgdiCollection<T>

   {

      public ICollection<T> d { get; set; }

   }

}

 

Classe OgdiConsumer :

using System;

using System.Collections.Generic;

using System.Linq;

using System.Net.Http;

using System.Text;

using System.Threading.Tasks;

 

namespace OgdiClient

{

   /// <summary>

   /// Classe représentant un proxy vers une instance OGDI et permettant de consommer

   /// les jeux de données au format JSON et de les retourner dans des objets C#.

   ///< /summary>

   public class OgdiConsumer

   {

      private string _nextPartitionKey = null;

      private string _nextRowKey = null;

 

      /// <summary>

      /// Url vers le jeu de données que l'on veut consommer

      ///< /summary>

      public string DatasetUrl { get; set; }

 

      /// <summary>

      /// Filtre OData que l'on veut appliquer sur le jeu de données

      ///< /summary>

      public string QueryFilter { get; set; }

 

      /// <summary>

      /// Méthode pour consomer un jeu de données OGDI de façon paginée

      /// </summary>

      /// <typeparam name="T">Type d'objets "proxy" à retourner</typeparam>

      /// <param name="count">Nombre d'objets à retourner</param>

      /// <param name="collection">Collection d'objets à populer</param>

      /// <returns>Une opération asynchrone retournant true  si le jeu de données

      /// contient encore des entités, false sinon.</returns>

      public async Task<bool> LoadNextDataChunk<T>(int count, ICollection<T> collection)

      {

         if (collection == null)

         {

            throw new ArgumentNullException("collection");

         }

 

         // Construction de la requête

         StringBuilder oDataRequestBuilder = new StringBuilder(DatasetUrl);

         oDataRequestBuilder.Append("/?");

 

         if (!string.IsNullOrEmpty(QueryFilter))

         {

            oDataRequestBuilder.Append("$filter=");

            oDataRequestBuilder.Append(QueryFilter);

            oDataRequestBuilder.Append("&");

         }

 

         oDataRequestBuilder.Append("$top=");

         oDataRequestBuilder.Append(count);

 

         if (_nextPartitionKey != null&& _nextRowKey != null)

         {

            oDataRequestBuilder.Append("&NextPartitionKey=");

            oDataRequestBuilder.Append(_nextPartitionKey);

            oDataRequestBuilder.Append("&NextRowKey=");

            oDataRequestBuilder.Append(_nextRowKey);

         }

         oDataRequestBuilder.Append("&format=json");

 

         // Exécution de la requête

         HttpClient client = new HttpClient();

         HttpResponseMessage message = await client.GetAsync(oDataRequestBuilder.ToString());

 

         if (!message.IsSuccessStatusCode)

         {

            throw new InvalidOperationException("Response code from " +

                      oDataRequestBuilder.ToString() +

                      " : " + message.StatusCode);

            }

 

            // Analyse de la réponse (recherche des jetons de continuation de la table Azure)

            IEnumerable<string> continuationNextPartitionKeyHeader = null;

            IEnumerable<string> continuationNextRowKeyHeader = null;

            bool result = message.Headers.TryGetValues("x-ms-continuation-NextPartitionKey",

                  out continuationNextPartitionKeyHeader);

            result = message.Headers.TryGetValues("x-ms-continuation-NextRowKey",

                  out continuationNextRowKeyHeader);

 

            if (result == true)

            {

                _nextPartitionKey = continuationNextPartitionKeyHeader.First();

                _nextRowKey = continuationNextRowKeyHeader.First();

            }

            else

            {

                _nextPartitionKey = null;

                _nextRowKey = null;

            }

 

            // Désérialisation des objets en JSON

            string jsonContent = await message.Content.ReadAsStringAsync();

            OgdiCollection<T> tmpCollection = Newtonsoft.Json.JsonConvert.DeserializeObject<OgdiCollection<T>>(jsonContent);

 

            // Ajout des objets désérialisés à la collection passée en paramètre

            // collection = tmpCollection.d;

            foreach (var item in tmpCollection.d)

            {

                collection.Add(item);

            }

 

            if (_nextPartitionKey == null&& _nextRowKey == null)

            {

                return false;

            }

            else

            {

                return true;

         }

     }

 

      ///< summary>

      /// Méthode pour remettre à zéro la pagination

      /// </summary>

      public void ResetPagination()

      {

         _nextPartitionKey = null;

         _nextRowKey = null;

      }

   }

}

La connectivité aux données étant assurée, nous pouvons à présent nous concentrer sur les éléments de code importants de l’application Metro en tant que tel. C’est l’objet de la seconde partie de ce tutoriel.