Let There Be Code RSS 2.0
# Tuesday, February 26, 2013

En 2012 Microsoft avait annoncé l’arrivée à venir de plusieurs titres d’application sur XBox 360. Parmi eux, plusieurs titres de la télévision française. Et pour développer ces applications, Microsoft a notamment fait appel à Ucaya.

En décembre est arrivé MyTF1, puis c’était au tour de Canal Infinity en janvier et aujourd’hui est arrivé Pluzz, les services de replay de France Télévision. Ces applications sont gratuites et accessibles à toute personne possédant une XBox et un abonnement Gold. Pour MyTF1 il vous faudra en plus être abonné Orange.

image

Ces applications vous donnent la possibilité d’accéder aux services de Replay (ou de VOD pour Canal Infinity) avec les fonctionnalités XBox habituelles :

  • contrôles au gamepad ou via la Kinect (gestures ou voix),
  • Recherche InApp ou depuis Bing grâce aux deeplinks,
  • le partage de présence et du contenu vidéo lu pour informer vos amis que vous regardez Dr House, Plus belle la vie, ou un autre de vos programmes préférés
  • le partage Facebook,
  • toutes les fonctionnalités d’un lecteur vidéo (smooth streaming, gestion des sous-titres…)
  • etc…

Pour en savoir plus, il ne vous reste plus qu’à les installer et les tester (et peut être aussi, acheter une télé, une xbox, une connexion internet, et un abonnement Gold…)

Et pour savoir comment se passe le développement sur XBox, c’est ici que ça se passe, dans la rubrique JOB!

Tuesday, February 26, 2013 9:30:00 PM (Romance Standard Time, UTC+01:00)  #    Voir Commentaires
XBox
# Sunday, January 20, 2013

Mon post précédent traitait de l’espace disque utilisé par les applications Windows 8 et montrait que bien souvent elles étaient trop gourmandes dans leur utilisation du stockage local. Parmi ces applications, je parlais notamment des applications Dior Mag et Techdays 2013 qui, sur mon laptop, utilisaient respectivement 1,27Go et 21Mo. Sachez qu’en fin de semaine des mises à jour étaient disponible pour ces 2 applications et que ce bug a été corrigé. Après installation des mises à jour et 2 jours d’utilisation, l’application Techdays 2013 occupe maintenant 5Mo et Dior Mag 28Mo.

Bravo aux développeurs pour leur réactivité. Cela montre qu’ils sont à l’écoute de ce que les utilisateurs peuvent dire de leur travail. Ainsi, n’hésitez pas à noter et commenter, en bien ou en mal, avec toute objectivité bien évidemment, les différentes applications du Store, ça ne fera qu’améliorer la qualité des contenus proposés.

Je continuerai tout de même à penser que ce genre de données à toute sa place dans le répertoire temporaire car :

  • le contenu peut être téléchargé à tout moment,
  • le mode de consommation de ces données par l’utilisateur est purement consultatif,
  • le stockage temporaire n’empêche pas le fonctionnement Offline,
  • le stockage temporaire permet à l’utilisateur de mieux gérer son espace disque, sans avoir à désinstaller des applications

Et vous, où pensez-vous qu’il vaut mieux stocker ces données de type Cache ?

Sunday, January 20, 2013 9:30:10 PM (Romance Standard Time, UTC+01:00)  #    Voir Commentaires
Windows 8
# Wednesday, January 16, 2013

Il y a de plus en plus d’applications disponible sur le Store et si vous êtes comme moi, curieux de voir ce que peuvent développer les autres, vous installez un tas d’applications. Vous les installez afin de les analyser, d’un point de vue design, ergo, etc… Ensuite vous ne les désinstallez pas forcément, pour pouvoir les montrer ou vous inspirer des plus réussies ou encore avoir des exemples de ce qu’il ne faut pas faire.

Dans ce post, je n’attaque personne en particulier, et je ne connais pas forcément le ou les développeurs ayant travaillés sur les applications que je prendrai en exemple. D’ailleurs mon objectif n’est pas de faire la critique d’une ou plusieurs applications, mais de sensibiliser les développeurs d’applications Windows 8 sur un point qui est jusqu’ici très souvent mis de côté, l’espace disque.

Cet espace disque, comme vous le savez, n’est pas infini, et l’est encore moins sur une Surface RT 32Go. Or je constate que beaucoup d’applications stockent des mégaoctets de données voir même pour certaines des gigaoctets dans le répertoire locale de l’application et que ce n’est pas toujours justifié.

Nous avons parlé lors du DevCamp Back to basics de la semaine dernière des différentes API de stockage ou d’accès aux données. Ce n’est que la veille de ce DevCamp que j’ai constaté que les applications que nous installons occupent souvent plusieurs mégaoctets d’espace disque et que l’espace occupé par ces applications grandit au fil des jours et de leur utilisation. Or j’ai du mal à comprendre comment une application pour laquelle je ne produis pas de données, mais que je ne fais qu’en consommer, qui plus est, une infime partie de ce qu’elle me propose, prenne aujourd’hui 10Mo, 100Mo, 500Mo, voir 1Go… Ma pauvre Surface risque de ne pas s’en remettre! Ce constat est intervenu trop tard pour que j’en fasse la démo au DevCamp, mais c’est tout de même pour cette raison que j’ai abordé dans mes slides la notion de TemporaryFolder en réexpliquant à quoi sert ce répertoire…

Mise en Cache et TemporaryFolder

Il existe une API disponible pour les applications Windows 8 qui s’appelle TemporaryFolder (ApplicationData.Current.TemporaryFolder). Cette API est disponible au même endroit que le LocalFolder ou encore que les RoamingSettings. Ce fameux TemporaryFolder sert à stocker des données dans un répertoire temporaire, comme son nom le laisse penser. Cela signifie que l’utilisateur a la possibilité de supprimer le contenu de ces répertoires temporaires via l’outil de nettoyage de disque, ou encore que le système peut lui aussi supprimer ce contenu pour récupérer de la place.

Cela signifie également que si l’on stocke des données dans ce répertoire, on ne peut être sur qu’elles seront encore là au prochain lancement de l’application. C’est en général, le principe de base d’un Cache. Un Cache est souvent utilisé pour stocker des données de manière temporaire. Il a besoin d’être invalidé et rafraichit au bout d’un certain temps, et peut même être supprimé si on n’y a pas accédé depuis un certain laps de temps. Ce qui permet de ne pas occuper d’espace disque inutilement (ou même d’espace mémoire dans le cadre d’un cache mémoire).

Quand doit-on utiliser ce répertoire temporaire ou comment peut-on mieux gérer l’espace occupé par les contenus, souvent éphémères et considéré comme du Cache ?

On peut quasiment toujours utilisé le stockage temporaire. Lorsque j’inspecte les applications installées sur mon poste (je me rends dans le répertoire LocalState des différents packages), je vois très souvent un répertoire nommé “Cache”. Pourquoi dans ce cas ne pas le mettre dans le stockage temporaire ?

Il faut également différencier mise en cache et fonctionnement Offline.

La mise en cache est généralement utilisé pour éviter de devoir télécharger plusieurs fois un même contenu, afin d’améliorer la fluidité de l’application, dans la navigation, les animations, les transitions et éventuellement le chargement.

Prenons l’exemple d’une application de type News/Actus. Il est beaucoup plus agréable en terme d’UX de télécharger les différentes Unes et articles du moment en local, ainsi que les différents médias associés tels que les images. Cependant, a-t-on besoin que ces différents articles soient disponible “à vie” ? Quel serait le soucis si ces données étaient stockées dans le répertoire temporaire ? On aurait les avantages du stockage local, avec en plus l’avantage pour l’utilisateur de gérer son espace disque. Et si le système est en manque de ressources de stockage, il peut lui aussi décider de nettoyer ces données, qui ne sont clairement pas vitales pour l’utilisateur.

Vérifier l’espace occupé par les applications Windows 8

Lorsqu’on est utilisateur de Windows 8, il est possible de vérifier l’espace occupé par les applications depuis les paramètres du PC et ainsi trouvé les applis “Gloutons” :

image

 

Analyse de quelques applications

Ci-dessous quelques exemples d’applications pour lesquelles j’ai analysé le contenu et la taille du répertoire local. Mon choix s’est arrêté sur ces quelques applications pour plusieurs raisons. Premièrement car je les avais installé lorsque j’ai fait ces analyses. Deuxièmement, car elles étaient représentatives du problème soulevé avec des scénarios de stockage différents. Il y a bien sûr d’autres applications sur le Store avec des problèmes de stockage. Tout ça pour dire que le choix des apps n’est pas arbitraire.

L’application Télé7

Prenons tout d’abord l’exemple de l’application Télé7, qui, si vous ne l’avez jamais testé, l’essayer, c’est l’adopter. Elle fonctionne avec une base de données locale de type SQL LITE, qui pèse environ 24Mo. Cette base est téléchargée à l’ouverture de l’application si la base actuelle a plus de 3 jours. Ceci permet à l’application de fonctionner en mode Offline, vous avez donc accès à votre grille des programmes TV sans avoir besoin d’internet. C’est une très bonne idée, et je l’ai d’ailleurs installé sur tous mes devices (laptop, Slate 7 et Surface).

tele7

Par contre en regardant ce qu’il se passe dans le répertoire LocalState je m’aperçois qu’il contient toutes les bases de données téléchargées depuis l’installation de l’application. Une base de données fait environ 24Mo. Pour l’instant j’en suis à 9 base de données locales, donc 216Mo.

Dans le cas de Télé7, on peut effectivement se dire qu’il est préférable de stocker la base de données en local, et non dans le Temp. Mais il faut dans ce cas une stratégie de rétention des données de l’application, afin de supprimer les anciennes bases. Je suis sûr que ce problème sera rapidement corrigé…

L’application Dior Mag

Prenons un autre exemple, cette fois dans une autre dimension… Le luxe a un cout, et pas seulement au niveau du porte monnaie mais également en stockage. La marque Dior a son application Windows 8, Dior Mag, application très jolie soit dit au passage. Attention si vous l’installez sur une Surface RT 32Go, elle prendra bientôt plus de place que l’OS!

diorMag

Actuellement sur mon poste l’application prend pas moins de 1,27Go (11 972 fichiers de type jpeg et text/json), avec en local des fichiers datés d’octobre à janvier. Je vous rassure tout de même, l’utilisateur a la possibilité depuis le Charm des paramètres de l’application de supprimer le Cache. Mais l’utilisateur est-il invité à un moment ou à un autre d’utiliser ce Charm ? D’autre part, l’utilisateur de cette belle application a-t-il besoin d’avoir accès en local à tous les catalogues depuis son installation ?

Si vous voulez faire un petit test, vous pouvez supprimer tout le contenu du répertoire LocalState de ce package, ainsi que le fichier settings.dat. Une fois ces fichiers supprimés, au lancement de l’application, tous ces fichiers seront téléchargés. La logique de présence des fichiers est bien là. Pour les mettre dans le répertoire temporaire, il suffit de changer au niveau du code LocalFolder par TemporaryFolder.

L’application Bing

Un autre exemple, l’application Bing. Cette fois nous revenons dans des tailles de stockage très raisonnable, mais un choix de stockage vraiment incompréhensible.

bing

Si nous inspectons le répertoire de stockage de cette application, nous nous rendons compte qu’elle stocke les images de background utilisée par Bing. Or ces images changent régulièrement, parfois plusieurs fois par jours, et ne sont apparemment jamais supprimées.

Bien sûr ici l’espace de stockage utilisé par ces images n’est que de 4,5Mo (32 images depuis octobre, pour moins d’une dizaine d’ouverture de l’application). Par contre ceci est vraiment incompréhensible, dans le sens où les quelques images du jour ne pèsent que 300Ko et que toutes les autres images ne serviront plus jamais. Et pour cette application, on ne peut pas dire qu’il y a un mode Offline… Ici les images auraient dû être stockées dans le répertoire temporaire.

L’application Techdays 2013

L’application Techdays 2013, que je trouve très réussie, fluide et agréable à utiliser, est idéale pour gérer votre agenda de sessions pour cet évènement.

techdays2013

Cette application est fonctionnelle en mode Offline. Je désactive mon accès internet, et j’ai accès à toutes les sessions, les fiches des speakers, les photos, mon agenda… Nous avons donc en local toutes les informations nécessaires au bon fonctionnement de l’application. De ce côté c’est parfait.

Par contre, si je regarde ce qu’il se passe du côté du stockage local, ça fait peur… Dans un répertoire nommé Cache, j’ai pas moins de 625 fichiers (json), qui pour la plupart ont exactement le même contenu. On s’aperçoit que, quasiment à chaque lancement de l’application, tout le contenu est re-téléchargé en local si connexion internet il y a. Et que l’ancien contenu n’est pas supprimé.

Le fait d’avoir tout le contenu en local est évidemment une bonne idée, et c’est entre autre pour cette raison que l’application se charge rapidement et qu’elle est fluide et agréable à utiliser. Mais d’une part, je ne pense pas qu’il y ait besoin de télécharger et restocker ce contenu à chaque lancement de l’application (un numéro de version dans le json permettrait de palier à ce problème), et d’autre part il faudrait que les contenus précédemment téléchargés soient supprimés. Ce matin j’étais à 15Mo de stockage pour cette application. Cet après midi, après 4 ou 5 ouvertures de l’application, je suis à 21Mo. Et pourtant, le contenu est le même, pas de sessions ou de speakers supplémentaires.

Ici on voit bien que ces données sont utilisées comme un cache local pour fluidifier la navigation de l’application et à la fois pour offrir un mode Offline. Ces données pourraient être placées dans le TemporaryFolder, sauf pour l’agenda, qui doit rester local ou distant. Si l’on persiste à enregistrer ces données dans le LocalFolder, il faut mettre en place une politique de rétention des fichiers.

Conclusion

Je pense que l’API TemporaryFolder est vraiment méconnue des développeurs ou qu’ils n’en connaissent pas l’utilité. Mais ce n’est pas une excuse. Le fait que des anciens fichiers, qui ne serviront plus et qui vont rester dans le stockage local jusqu’à désinstallation de l’application n’est pas normal. L’utilisateur n’a pas d’autre choix que de désinstaller l’application pour nettoyer ce stockage. Vous pouvez vous même faire l’expérience suivante : rendez-vous dans le répertoire Users\YOUR_ACCOUNT\AppData\Local\Packages. Parmi tous ces packages, effectuez une recherche des répertoires TempState, puis LocalState et comparez la taille totale utilisé par chacun de ces répertoires.

Parmi les applications que j’ai installé sur mon laptop, celle qui est la moins jolie, mais qui reste fonctionnelle, c’est l’application Windows Phone, qui permet de transférer/synchroniser des données entre son téléphone et son PC. Et bien parmi toutes les applications que j’ai actuellement (68 applications au total), c’est la seule qui possède du contenu dans le répertoire temporaire… Comme quoi, on peut faire des applications moches, mais fonctionnelles, et techniquement bien pensées.

Si vous pensez que votre application doit finalement utiliser ce stockage temporaire plutôt que le stockage local, il vous suffit simplement de changer dans votre code LocalFolder par TemporaryFolder. Et si ce n’est pas le cas, n’oubliez pas de supprimer les anciens fichiers, sinon ce sont les utilisateurs qui supprimeront votre application.

Wednesday, January 16, 2013 6:51:00 PM (Romance Standard Time, UTC+01:00)  #    Voir Commentaires
Windows 8
# Tuesday, January 15, 2013

image

En attendant que les webcasts soient disponibles, je vous propose en téléchargement les ressources de ma session. Dans cette archive vous trouverez les slides ainsi que les démos.

Cette session portait sur les différentes API de stockage ainsi que les stratégies à adopter sur les plateformes Windows 8 (App Store) et Windows Phone 8.

Après avoir rappelé ce que l’on entend par accès aux données, nous avons abordé les notions suivantes :

  • Accès aux données locales de l’utilisateur (Contacts, Calendrier, Librairies…) avec en démonstration l’accès à la librairie de musique de l’utilisateur,
  • Accès aux données distantes de l’utilisateur (Hotmail, Skydrive, Messenger) avec en démonstration l’utilisation du Live SDK depuis une application Windows Phone 8,
  • Accès aux données locales de l’application, avec pour rappel les API Windows Phone 8 (IsolatedStorage et ApplicationData) et les API Windows 8 (LocalFolder, LocalSettings, TemporaryFolder),
  • Accès aux données itinérantes (Roaming) des applications Windows 8 avec l’API ApplicationData (RoamingFolder et RoamingSettings),
  • Accès aux données distantes de l’application/service. Données pouvant être stockées dans le Cloud ou sur un serveur OnPremise, avec 3 démonstrations :
    • Azure Mobile Services depuis une application Windows 8 et Windows Phone 8
    • Azure Blob Storage depuis une application Windows 8
    • WCF Data Services depuis des applications clientes Windows 8 et Windows Phone 8

Ci-dessous, quelques informations concernant la configuration et l’exécution des différentes démos que vous trouverez dans l’archive.

Démo MusicLibrary

 

Pas de configuration ou d'installation particulière. C'est une application Windows 8 qui explore votre librairie de musique. Voici l’article qui explique ce projet : Windows 8 Développement d'un lecteur audio

Démo Live SDK

Une fois l’application créée, il vous faut récupérer le Client ID, éditer le fichier Main.xaml du projet Windows Phone LiveSDK, et modifier la propriété ClientId du contrôle SignInButton.

 

Pour plus d'exemple de code du Live SDK rendez-vous ici : https://github.com/liveservices/LiveSDK

Et pour la documentation en ligne : http://msdn.microsoft.com/fr-fr/library/live/hh826532.aspx

Démo Azure Mobile Service

 

Pour les projets MobileServiceApp Windows 8 et Windows Phone 8, il vous faut au préalable vous rendre sur le portail Azure et créer un service Mobile. Une fois le service créé, vous pouvez soit télécharger les solutions Windows 8 et Windows Phone 8 proposées par le portail ou éditer les projets de la solution.

Pour configurer les projets MobileServiceApp Windows 8 et Windows Phone 8, éditer le fichier App.xaml.cs et lors de l'instanciation de l'objet MobileServiceClient, renseigner l'URL vers votre Azure Mobile Service ainsi que sa clé.

 

Démo Azure Blob Storage

 

Ce projet nécessite l’accès à un compte de stockage sur votre abonnement Azure. Depuis le portail Azure, vous pouvez créer un compte de stockage, puis récupérer son nom et sa clé.

Dans le projet AzureStorageExplorerApp, ouvrir le fichier App.xaml.cs et renseigner les valeurs des constantes StorageAccountName et StorageKey avec les données récupérées sur le portail Azure.

 

Pour plus de détails sur le code de ce projet, voici l'article à consulter : Windows 8 Azure Storage Explorer

Démo WCF Data Services

  • Télécharger et installer WCF Data Services 5.0 for OData V3
  • Télécharger et installer WCF Data Services Tools for Windows Store Apps
  • Télécharger et installer OData Client Tools for Windows Phone Apps
  • Dans le projet Common/WCFDataServices, créer l'application dans IIS (après l'avoir configurer pour WCF).
  • Dans le projet WCFDataServicesWP8Client, ouvrir le fichier NotesViewModel.cs, puis dans la méthode LoadNotes modifier l'URL avec l’adresse IP de votre machine. Ceci permet de faire fonctionner l'appel au Web Service depuis l'émulateur Windows Phone ou depuis votre device de développement.
  • Il vous faudra éventuellement modifier les règles de trafic de votre pare-feu pour autoriser les requêtes HTTP entrantes
Tuesday, January 15, 2013 7:41:47 AM (Romance Standard Time, UTC+01:00)  #    Voir Commentaires
Windows 8 | WP8
# Monday, January 07, 2013

imageCe jeudi 10 janvier, Microsoft organise le premier après midi du développement autour des données sur les plateformes Windows 8 et Windows Phone 8.

L’évènement aura lieu au Centre de Conférence à Issy Les Moulineaux à partir de 13h30, et pour ma part je vous présenterai les différentes possibilités et API de stockage sur ces plateformes.

Si vous n’êtes pas encore inscrits, dépêchez-vous de le faire… et si vous hésitez encore sachez qu’il y aura une tablette Asus Vivo Tab à gagner…

Pour les inscriptions, suivez ce lien : https://msevents.microsoft.com/CUI/EventDetail.aspx?EventID=1032521905&Culture=fr-FR

Monday, January 07, 2013 8:45:00 AM (Romance Standard Time, UTC+01:00)  #    Voir Commentaires
Windows 8 | WP8
# Monday, December 17, 2012

imageIl s’en est passé des choses depuis mon dernier post… J’ai enfin emménagé en Ile de France et ça fera bientôt 3 mois que j’ai intégré la folle équipe d’Ucaya.

Pour ceux qui ne connaissent pas Ucaya, c’est un “pure player” Microsoft avec une très forte composante Design. Nous réalisons des projets sur les plateformes Windows Phone, Windows 8 et également XBox. La société est basée à Nantes et possède une antenne sur la région parisienne.

Au passage je salue mes collègues de Nantes et en particulier les 2 designers “fous” mais tout aussi excellents, JB et Thib.

Ucaya fait évidemment partie du partenariat Microsoft Apps Circle qui regroupe les sociétés compétentes dans le développement WP, Win8 et le Design. Je ne vais pas citer toutes les applications déjà réalisées par Ucaya et présentes sur les différents stores, mais en voici quelques unes pour la partie Windows 8 : CanalTouch, Première, DailyELLE, Radios Françaises, Vie de Geek, Télé 7 Programme TV, L’Atelier Visual Studio, Fubiz, MonWindowsPhone…

L’intérêt pour moi d’intégrer cette équipe était de me recentrer sur mon cœur de métier : Le développement. Et je peux dire que je ne suis pas déçu! Dès mon arrivée j’ai travaillé sur le développement de l’application CanalTouch, avec notamment mon ancien et nouveau collègue Guillaume Lacasa. Actuellement je travaille depuis plus d’un mois sur des applications XBox… mais je ne peux vous en dire plus.

Ucaya

Une nouvelle composante de mon métier : l’intégration graphique. Je n’avais jamais eu l’occasion de m’initier à cette discipline. Ici chez Ucaya c’est tout simplement un passage obligé pour tous les développeurs. Je ne vous cache pas que j’y passe pas mal de temps, mais ça commence à rentrer…

Nous nous retrouverons rapidement pour différents évènements Microsoft dès la rentrée 2013!

Et si vous êtes bon designer ou bon développeur, n’hésitez pas, rejoignez nous!

imageimage

Monday, December 17, 2012 11:00:15 AM (Romance Standard Time, UTC+01:00)  #    Voir Commentaires

# Wednesday, October 31, 2012

Pour ce 2ème jour, j’ai particulièrement apprécié cette keynote puisqu’elle était consacrée à la présentation de l’offre Azure et de ses nouveautés, qui pour la plupart sont sorties en juin dernier. La keynote était menée par Satya Nadella, President Server and Tools Business.

Voici un petit aperçu du chapiteau accueillant la keynote. Peut être remarquerez vous la différence de qualité des photos en comparaison à mon post d’hier… Merci Nokia!

WP_20121031_005

Durant cette session différents intervenants, avec notamment Scott Guthrie pour nous présenter l’offre Azure.

Voici les sujets abordés pendant cette keynote autour de Windows Azure :

  • Mobile Service
  • Web Site
  • Media Service
  • Azure Store
  • Team Foundation Service

L’offre Mobile Service simplifie le développement d’application Windows 8, Windows Phone 8 ou encore IOS dans le Cloud. Un service de stockage est à disposition, de l’authentification SSO (Live, Twitter, Facebook, Google…) et même la possibilité de créer des triggers en javascript directement depuis la nouvelle interface de gestion Azure. Un exemple de trigger : la possibilité de faire du push notification lors de l’insertion d’un élément.

Pour nous présenter l’offre Web Site, un scénario de démonstration sur la publication et l’hébergement d’un Web Site ASP.NET MVC et la consommation de services REST via les nouvelles Web API. Et un peu de code d’authentification… je dis un peu car les API font à peu près tout, via la classe OAuthWebSecurity (à l’image de la classe WebAuthenticationBrocker de WinRT) qui affiche automatiquement une page d’authentification de compte Facebook, Twitter, Google…

L’offre Media Service nous a été présentée dans un scénarios d’application Multi-tiers par Scott Guthrie :

WP_20121031_013

Dans ce scénario, l’utilisateur a la possibilité de télécharger dans le Cloud des vidéos. Ces vidéos sont envoyées au Media Service. Une fois téléchargées, un Worker Role demande au Media Service de les encoder, puis via un système de Publish/Subscribe appelé SignalR, les clients sont notifiés une fois les vidéos encodées.

L’Azure Store nous a été rapidement présenté par Scott Guthrie. La démonstration nous montrait l’ajout d’un Addon appelé New Relic à un Web Site Azure. Cet addon permet de monitorer les performances de son site via des interfaces et des graphiques extrêmement riches.

Depuis plus d’un an maintenant, Team Foundation Service était disponible gratuitement en preview via le site tfspreview. L’annonce a été faite ce matin pendant la keynote. Team Foundation Service est maintenant disponible en release sur http://tfs.visualstudio.com. L’accès reste gratuit pour l’instant et deviendra payant courant 2013 au delà de 5 utilisateurs. Les personnes possédant un abonnement MSDN (Ultimate, Premium ou Test Professional) ont en leur possession une licence TFService.

Pour terminer, voici une photo du slide résumant les différentes fonctionnalités disponibles sur la plateforme Azure, en release et en preview :

WP_20121031_014

 

Et pour tout vous dire, après le burger de lundi soir, nous n’avons pas résister à la tentation de s’en refaire un hier soir, histoire de comparer les différentes enseignes. Après le Red Robin, cette fois nous nous sommes rendus dans un J Michael’s Pub & Eatery!

WP_20121030_002

Wednesday, October 31, 2012 8:02:49 PM (Romance Standard Time, UTC+01:00)  #    Voir Commentaires
Build 2012

Avant d’attaquer cette première journée et à notre arrivée à Seattle hier soir, avec l’équipe d’Ucaya que j’ai eu le plaisir de rejoindre le 1er octobre, nous nous sommes rassasiés dans un Red Robin Gourmet Burger avec un excellent Royal Red Robin…

WP_000035

Ce matin nous nous sommes rendus au Campus Microsoft pour assister à la 1ère journée de cet évènement tant attendu, la Build Conference 2012.

Il a fallu être patient avant d’arriver dans la salle de la keynote, puisque nous étions loin d’arriver les premiers :

WP_000036

Une fois dans la salle et en attendant le début de la keynote, nous avons eu droit à une magnifique démonstration de synthé de la part de Jordan Rudess, célèbre claviériste de Dream Theater (groupe aux talents exceptionnels!). Une première démonstration sur une application de synthé sous Windows 8 puis sur un clavier physique. Le “metalleux” qui sommeil en moi n’a pu qu’apprécier la démonstration :

WP_000038

Puis Steve Ballmer a démarré la Keynote, avec toujours plus de superlatifs dans son discours : Amazing, fantastic, incredible… Il n’a pas tort lorsque l’on voit le travail réalisé sur Windows 8, Windows Phone 8, XBox Music, Skydrive…

Il nous a fait la démonstration de plusieurs devices tournant sous Windows 8 : HP, Sony, Lenovo, Acer… Surface bien sûr. Mais la démonstration que j’ai préféré, c’était la “Perceptive Pixel”, un écran multitouch de 88 pouces (ou 55 si la 88 est trop grande pour vous…)!!

Lors de cette Keynote, il n’y a pas eu d’annonce particulière puisque Windows 8 a été lancé la semaine dernière et Windows Phone 8 hier.

WP_000043

 

Une fois la keynote terminée, les sessions pouvaient enfin démarrer!! Et pour bien commencer, nous avons eu droit à un talentueux frenchy en la personne de David Rousset qui nous a présenté les nouveautés CSS3 et HTML5 :

WP_000047

Moi qui ne suis pas développeur Web, j’ai appris plein de choses durant cette session. Voici en vrac quelques notions que David nous a présentées : IndexedDB, les transitions et animations, CSS Grid, Cache Manifest, Drag'n Drop, WebWorker…

Pour cette dernière démo, David a même réussi a placer une photo de ces potes, et c’est ainsi que quelques Microsoftees Français se sont retrouvés à la Build :

WP_000049

Nous noterons également que pendant cette session nous avons vu passer sur les projecteurs un tweet de Patrice Lamarche, et nous avons également eu un rappel calendrier de l’anniversaire de l’un des fondateurs d’Ucaya, Anthony.

Pour ma part, j’ai ensuite assisté à une session portant sur Entity Framework 5 présentée par Rowan Miller. L’intitulé de la session : “Building Data Centric Applications for Web, Desktop and Mobile with EF 5”. Finalement la session s’est limitée aux applications Web et Desktop, et la moitié du temps nous avons vu des démonstrations des outils de Code First Migration depuis la Package Manager Console. Parmi les nouveautés d’Entity Framework 5, il y a donc les outils de migrations Code First, les Web API, le support des énumérations, le support des types géographiques, la possibilité de définir plusieurs diagrammes depuis un EDMX (permet de “splitter” son modèle en plusieurs diagrammes pour améliorer la lisibilité)… L’Alpha 1 de EF 6 est déjà disponible et la RTM sera disponible en milieu d’année 2013.

Ma déception aura été du côté du développement Windows 8 et Windows Phone 8, puisque les modèles de développement n’ont pas été unifiés. Il est en effet peut être un peu tôt et il faudra surement attendre les versions 9, mais j’attendais tout de même des avancées de ce côté. Nous devrons pour l’instant nous contenter des simples Portable Library.

Wednesday, October 31, 2012 8:20:51 AM (Romance Standard Time, UTC+01:00)  #    Voir Commentaires
Build 2012
# Thursday, September 13, 2012

Dans les 2 premières parties nous avons expliqué les concepts d’authentification OAuth et la création d’un composant Windows Runtime. Nous terminons cette série avec la mise en place du contrat Contact Picker.

Dans ce scénario notre application devient fournisseur des informations de nos amis Foursquare aux autres applications.

Et voici pour le code complet de cette exemple d’application :

 

Il vous faudra éditer les constantes ClientId et CallbackUri dans la classe App correspondant à votre application Foursquare (voir la première partie pour plus de détails).

Déclaration et activation du contrat

Nous en avons maintenant l’habitude, l’implémentation de contrats se déclare d’abord au niveau du manifeste. Dans l’onglet Declarations, on ajoute le Contact Picker.

Declarations

L’activation du contrat intervient au niveau de la classe App et de la méthode OnActivated :

  1. protected override void OnActivated(IActivatedEventArgs args)
  2. {
  3.     if (args.Kind == ActivationKind.ContactPicker)
  4.     {
  5.         var frame = Window.Current.Content as Frame;
  6.         if (frame == null)
  7.             frame = new Frame();
  8.         Window.Current.Content = frame;
  9.  
  10.         frame.Navigate(typeof(ContactPickerPage), args);
  11.  
  12.         Window.Current.Activate();
  13.     }
  14.     else
  15.     {
  16.         base.OnActivated(args);
  17.     }
  18. }

Affichage des contacts Foursquare

Nous allons afficher la photo et le nom de nos contacts Foursquare. Pour cela nous définissons le DataTemplate suivant :

  1. <Page.Resources>
  2.     <DataTemplate x:Key="ContactTemplate">
  3.         <Grid Width="200" Height="250">
  4.             <Grid.RowDefinitions>
  5.                 <RowDefinition Height="150" />
  6.                 <RowDefinition Height="Auto" />
  7.             </Grid.RowDefinitions>
  8.             <Image Source="{Binding Photo}" Width="150" Height="120" VerticalAlignment="Center" />
  9.             <TextBlock Grid.Row="1" Text="{Binding FullName}" VerticalAlignment="Center" HorizontalAlignment="Center" />
  10.         </Grid>
  11.     </DataTemplate>
  12. </Page.Resources>

 

Et pour terminer une GridView pour afficher et sélectionner des contacts :

  1. <GridView Grid.Row="1"
  2.           ItemsSource="{Binding}"
  3.           SelectionChanged="GridView_SelectionChanged"
  4.           IsItemClickEnabled="True"
  5.           SelectionMode="Multiple"
  6.           ItemTemplate="{StaticResource ContactTemplate}" />

 

Chargement des contacts

Le chargement des contacts Foursquare nécessite un jeton d’authentification OAuth. Il faut ensuite prévoir de les filtrer en fonction de ce qu'e l’application appelante désire comme informations.

Stockage du jeton d’authentification dans le Cloud

Nous allons améliorer notre application de sorte que ce jeton soit stocké dans les paramètres de l’application. Cela évitera de demander une authentification à chaque ouverture de l’application.

Pour stocker des paramètres d’application on peut utiliser la classe ApplicationData. Cette classe possède notamment une propriété RoamingSettings qui va nous permettre de stocker notre jeton. Et grâce aux paramètres d’itinérance l’utilisateur sera authentifier sur toute les machines où il ouvrira une session via son compte Microsoft.

Nous utilisons simplement le dictionnaire Values pour accéder à ses paramètres :

  1. public async static Task<string> GetFrousquareToken()
  2. {
  3.     object oToken = Windows.Storage.ApplicationData.Current.RoamingSettings.Values["AccessToken"];
  4.  
  5.     if (oToken == null)
  6.     {
  7.         string t = await FoursquareService.AuthenticateAsync(App.clientId, App.callbackFoursquareUri);
  8.  
  9.         Windows.Storage.ApplicationData.Current.RoamingSettings.Values.Add("AccessToken", t);
  10.         oToken = t;
  11.     }
  12.  
  13.     return (string)oToken;
  14. }

 

Récupération des contacts

Dans la méthode LoadState de notre Page nous ajoutons le code permettant de récupérer les contacts :

  1. string token = await App.GetFrousquareToken();
  2. var friends = await FoursquareService.GetMyFriendsAsync(token);

 

Filtrage des contacts en fonction des désirs de l’appelant

Lors de l’activation du contrat ContactPicker nous avons reçu un paramètre de type ContactPickerActivatedEventArgs. Cette classe expose un objet de type ContactPickerUI. Cet objet fournit des informations sur ce qui intéresse l’application. Cet objet va également nous permettre d’envoyer nos contacts à l’application appelante.

La classe ContactPickerUI nous permet de connaitre les champs (email, numéro de tel…) dont a besoin l’application. Il nous suffit de parcourir la propriété DesiredFields et de filtrer notre liste de contact :

Toujours dans la méthode LoadState de notre page, voici comment récupérer la classe ContactPickerUI et filtré sur les champs désirés :

  1. var args = navigationParameter as ContactPickerActivatedEventArgs;
  2. if (args == null || args.ContactPickerUI == null)
  3.     return;
  4.  
  5. var contactPickerUI = args.ContactPickerUI;
  6.  
  7. foreach (var field in contactPickerUI.DesiredFields)
  8. {
  9.     switch (Windows.ApplicationModel.Contacts.KnownContactField.ConvertNameToType(field))
  10.     {
  11.         case Windows.ApplicationModel.Contacts.ContactFieldType.Email:
  12.             friends = friends.Where(f => !string.IsNullOrEmpty(f.Email)).ToList();
  13.             break;
  14.         case Windows.ApplicationModel.Contacts.ContactFieldType.InstantMessage:
  15.             // friends =
  16.             break;
  17.         case Windows.ApplicationModel.Contacts.ContactFieldType.Location:
  18.             // friends =
  19.             break;
  20.         case Windows.ApplicationModel.Contacts.ContactFieldType.PhoneNumber:
  21.             // friends =
  22.             break;
  23.         case Windows.ApplicationModel.Contacts.ContactFieldType.Custom:
  24.             // friends =
  25.             break;
  26.         default:
  27.             break;
  28.     }
  29. }

 

Ajouter des contacts au ContactPickerUI

Lorsque l’on sélectionne des contacts via le Contact Picker, ils s’ajoutent dans le bas du contrôle, comme illustré sur l’image suivante :

image

Pour ajouter des contacts, il suffit de créer des objets de type Windows.ApplicationModel.Contacts.Contact et de les ajouter via la méthode Add de l’objet ContactPickerUI. L’ajout de champs à un contact se fait via la liste Fields.

Le code ci-dessous illustre l’ajout des contacts sélectionnés au ContactPickerUI :

  1. private async void GridView_SelectionChanged(object sender, SelectionChangedEventArgs e)
  2. {
  3.     foreach (var item in e.AddedItems.OfType<FoursquareApi.Entities.Contact>())
  4.     {
  5.         var contact = new Windows.ApplicationModel.Contacts.Contact() { Name = item.FullName };
  6.         contact.Fields.Add(new Windows.ApplicationModel.Contacts.ContactField(item.Email, Windows.ApplicationModel.Contacts.ContactFieldType.Email));
  7.  
  8.         contact.Fields.Add(new Windows.ApplicationModel.Contacts.ContactField(item.Email, Windows.ApplicationModel.Contacts.ContactFieldType.Email));
  9.  
  10.         HttpClient client = new HttpClient();
  11.         var stream = await client.GetStreamAsync(item.Photo);
  12.  
  13.         Windows.Storage.Streams.InMemoryRandomAccessStream memory = new Windows.Storage.Streams.InMemoryRandomAccessStream();
  14.         await Windows.Storage.Streams.RandomAccessStream.CopyAsync(stream.AsInputStream(), memory);
  15.  
  16.         var streamReference = Windows.Storage.Streams.RandomAccessStreamReference.CreateFromStream(memory);
  17.         contact.Thumbnail = streamReference;
  18.  
  19.         contactPickerUI.AddContact(item.Id, contact);
  20.     }
  21. }

 

Pour aller plus loin

Dans cet article nous n’avons pas abordé le processus inverse qui consiste à utiliser le picker depuis notre application afin de consommer des contacts provenant d’une autre application. Pour plus de détail sur ce sujet je vous invite à consulter la documentation de la classe ContactPicker qui fournit un exemple d’utilisation.

Et n’oubliez pas les différents évènements Windows 8 organisés par Microsoft et la communauté pour cette rentrée. Voici les liens :

Thursday, September 13, 2012 6:33:07 PM (Romance Daylight Time, UTC+02:00)  #    Voir Commentaires
Windows 8
# Friday, August 31, 2012

HeightSquarePickerCet article fait suite à une 1ère partie dans laquelle nous avons expliqué comment nous authentifier auprès des services Foursquare et plus généralement auprès de services qui implémentent le protocole OAuth.

Cette 2ème partie est un peu plus “Roots”, puisqu’elle est consacrée au développement de composants Windows Runtime dans laquelle nous répondons notamment à la question : comment Visual Studio génère un composant WinRT ? Nous terminons cet article par quelques notions d’asynchronisme avec notamment les types IAsyncInfo et IAsyncOperation<T>.

Comme je l’avais déjà précisé dans le 1er article, le code complet de cette application sera disponible à la fin de cette série, et donc au prochain article ;)

 

 

 

Création et compilation d’un composant Windows Runtime

Lorsque l’on veut développer une API pour WinRT et la rendre accessible quel que soit le langage utilisé (C#, VB.NET, C++, Javascript), cette API doit être compilée en tant que composant Windows Runtime.

Avec Visual Studio rien de plus simple… La première étape consiste à créer un projet de type Windows Runtime Component :

HeightSquare1

Si vous avez travaillé avec les versions Developer ou Consumer Preview de VS2012 , ce type de projet n’existait pas. Il fallait créer un projet de type Class Library, puis dans les propriétés du projet, changer le type de sortie en WinMD.

Compilation d’un projet Windows Runtime

Le fait d’ajouter un projet de type Windows Runtime Component permet à Visual Studio de l’identifier en tant que tel, et de le compiler différemment des autres projets.

Si l’on ouvre le fichier csproj avec l’éditeur XML, on peut voir que le type de sortie est winmdobj :

image

 

Visual Studio va appeler le compilateur C# avec cet argument. On peut retrouver cet argument en ouvrant une invite de commande VS2012 et en consultant l’aide du compilateur via la commande csc.exe /? :

image

Ensuite Visual Studio va exporter le fichier winmdobj résultant de cette compilation en fichier winmd. Cette opération se fait via la tache MSBuild WinMdExp, que l’on retrouve dans la documentation de la nouvelle version de Windows.Build.Tasks.

Cette tache MSBuild génère en sortie un fichier d’extension WINMD. C’est ce type de fichier qui représente un composant Windows Runtime. Un composant avec une extension WINMD est réutilisable dans les applications de type Windows Store quel que soit le langage utilisé.

Un composant WinMD est pour les applications Windows Store ce qu’une assembly .Net est aux applications Desktop.

Un fichier WinMD contient le code compilé et les métadonnées. La MSDN nous précise que ce sont ces métadonnées qui ont un format différent entre .Net et WinRT. C’est donc la raison pour laquelle nous devons exporter la sortie du compilateur csc.exe vers un composant WinMD via WinMDExp.exe.

Prenons par exemple la classe SampleClass définie dans le fichier SampleClass.cs. Ouvrons une invite de commande Visual Studio 2012 et tapons la ligne de commande suivante : csc /target:winmdobj SampleClass.cs

image

Nous obtenons en sortie un fichier d’extension winmdobj :

image

 

Ensuite appelons l’outil winmdexp sur le fichier winmdobj. Il faut ajouter 3 références. 2 références .Net, mscorlib.dll et System.Runtime.dll, et une référence vers le Windows Runtime, Windows.winmd. Voici la ligne de commande à exécuter :

winmdexp.exe /reference:"%ProgramFiles(x86)%\Reference Assemblies\Microsoft\Framework\.NETCore\v4.5\mscorlib.dll" /reference:"%ProgramFiles(x86)%\Reference Assemblies\Microsoft\Framework\.NETCore\v4.5\System.Runtime.dll"  /reference:"%ProgramFiles(x86)%\Windows Kits\8.0\References\CommonConfiguration\Neutral\Windows.winmd" SampleClass.winmdobj

image

Comme nous l’indique l’invite de commande, nous obtenons en sortie un fichier d’extension WINMD :

image

 

L’outil WinMDExp a exporté le code de notre classe dans un fichier de nom SampleWinMD.winmd. Il a simplement utilisé le nom de l’espace de noms racine dans lequel se trouve la classe.

En effet l’une des règles lorsque l’on écrit un composant WIndows Runtime est de mettre toutes les classes que nous exposons dans un même espace de noms racine. On peut bien évidemment les “ranger” dans des sous espaces de noms.

Maintenant que notre composant est compilé et exporté, nous pouvons, depuis un projet WinJS par exemple, l’ajouter en référence :

image

 

Et il ne reste plus qu’à appeler notre classe :

 

image

 

Quelques règles de développement d’un composant Windows Runtime

Nous allons ici détailler quelques règles de base. L’ensemble des règles à respecter lorsque vous développez une composant Windows Runtime est décrit dans cet article de la MSDN.

Exposition de classes “sealed”

Toutes les classes exposées (donc publiques) doivent être scellées (mot clé sealed). Vous pouvez bien évidemment définir ou implémenter des interfaces, mais il n’est pas possible d’utiliser les concepts de l’abstraction ou du polymorphisme par le biais de l’héritage de type pour les classes que vous exposez. Ceci est une restriction qui peut s’avérer assez contraignante, mais nous n’avons pas le choix…

Les types du Windows Runtime

Seuls les types du Windows Runtime, en plus des vôtres (qui respectent les règles) peuvent être exposés, que ce soit pour des valeurs de retour ou des paramètres d’entrée. Vous pouvez exposer certains types primitifs du Framework .Net, comme par exemple les Value types (en fait ils sont également définis dans le WinRT). Par exemple le type DateTime du Framework .Net ne peut pas être exposé depuis un composant WinRT. Il faudra dans ce cas utiliser le type DateTimeOffset. La classe ObservableCollection est également un type .Net et donc non exposable…

Dans cet article de la MSDN vous retrouvez la correspondance des types primitifs du Windows Runtime selon le langage utilisé.

Un autre exemple sur les types .Net que l’on ne peut pas exposer, c’est le type Task, que nous utilisons de plus en plus dans nos développements. Au lieu d’un objet de type Task, il faut exposer un objet de type IAsyncOperation. C’est pour cette raison qu’il existe aujourd’hui une méthode d’extension AsAsyncOperation() qui converti un objet Task en IAsyncOperation. L’inverse existe également, sur un objet IAsyncOperation on peut appeler la méthode AsTask. Thomas nous en parle dans ce post de blog (dans la langue de Shakespeare) et nous y reviendrons également dans la section suivante.

Limitations dans les surcharges des méthodes et des constructeurs

Le concept de surcharge d’une méthode en langage objet est le fait de définir une même méthode plusieurs fois avec des paramètres différents. La surcharge se définit sur le nombre et/ou sur le type des paramètres. Le type de retour reste identique d’une surcharge à l’autre.

Par exemple en C# on peut donc définir les surcharges suivantes :

  1. public void SampleMethod()
  2. {
  3. }
  4.  
  5. public void SampleMethod(int i)
  6. {
  7. }
  8.  
  9. public void SampleMethod(string i)
  10. {
  11. }
  12.  
  13. public void SampleMethod(int i, int j)
  14. {
  15. }

 

Dans un composant Windows Runtime ce code ne compilera pas. En effet en javascript, on fait un peu ce que l’on veut en terme de paramètres… Par exemple, on peut appeler une fonction qui demande un “int” en entrée en lui passant une variable dans laquelle on a mis un “string”.

Si nous voulons persister à écrire ce genre de surcharge, nous devons indiquer quelle est la surcharge par défaut, c’est à dire celle qui sera appelée en cas de doute (en javascript donc…) :

  1. public void SampleMethod()
  2. {
  3. }
  4.  
  5. [Windows.Foundation.Metadata.DefaultOverload]
  6. public void SampleMethod(int i)
  7. {
  8. }
  9.  
  10. public void SampleMethod(string i)
  11. {
  12. }
  13.  
  14. public void SampleMethod(int i, int j)
  15. {
  16. }

 

Par contre cette règle ne s’applique pas aux constructeurs. Dans un composant WinRT il est tout simplement impossible de définir 2 constructeurs avec le même nombre d’arguments.

Pas de paramètre optionnel

Autre restriction, il n’est pas possible de définir de paramètre par défaut comme ci-dessous :

  1. public void SampleMethod(string s = "hello world")
  2. {
  3. }

 

Dans ce cas il faudra utiliser des surcharges. Par exemple on peut utiliser les surcharges en “public” et le paramètre optionnel en “private” :

  1. public void SampleMethod()
  2. {
  3.     PrivateSampleMethod();
  4. }
  5.  
  6. public void SampleMethod(string s)
  7. {
  8.     PrivateSampleMethod(s);
  9. }
  10.  
  11. private void PrivateSampleMethod(string s = "Hello World")
  12. {
  13. }

 

Un peu d’asynchronisme pour terminer

WindowsRuntimeSystemExtensions et les Mondes parallèles…

Nous le disions un peu plus haut, lorsque l’on développe un composant WIndows Runtime et que nous voulons exposer des méthodes asynchrones nous ne pouvons pas retourner un objet de type Task ou Task<TResult>. Nous devons utiliser les types IAsyncAction et IAsyncOperation<TResult> (et IAsyncActionWithProgress<TProgress> et IAsyncOperationWithProgress<TResult, TProgress> pour supporter la mise à jour de la progression).

Pour passer du monde .Net au monde WinRT il faut jeter un œil du côté de la classe WindowsRuntimeSystemExtensions. Cette classe définie des méthodes d’extension aux types Task, Task<T>, IAsyncInfo et IAsyncOperation<T>. On retrouve ces méthodes d’extension dans la classe WindowsRuntimeSystemExtensions :

image

Petite parenthèse au passage... on peut remarquer que des méthodes d’extension GetAwaiter existent pour les types IAsyncAction et IAsyncOperation<T>. C’est grâce à cette méthode qu’il nous est possible d’utiliser le mot clé await sur les composants WinRT. En effet souvenez-vous, les composants WinRT asynchrones ne retournent pas des objets de type Task ou Task<T> mais des objets de type IAsyncAction ou IAsyncOperation<T>. Or contrairement à l’objet Task qui possède bien une méthode GetAwaiter(), les 2 interfaces WinRT n’en définissent pas.

Pour information, l’IntelliSense ne vous montrera pas ces méthodes sur les types IAsyncAction et IAsyncOperation<T> car elles sont marquées par l’attribut EditorBrowsable(EditorBrowsableState.Never) :

  1. [EditorBrowsable(EditorBrowsableState.Never)]
  2. public static TaskAwaiter GetAwaiter(this IAsyncAction source)
  3. {
  4.     return source.AsTask().GetAwaiter();
  5. }

 

Cet attribut a pour effet de cacher le membre aux différents éditeurs de Visual Studio, et notamment à l’éditeur de code. Cela ne nous empêche pas de l’appeler :

image

 

Donc finalement si nous avons une méthode qui nous retourne déjà un objet de type Task, il nous suffit de l’appeler, récupérer la tache résultante, et appeler la méthode AsAsyncOperation() (ou AsAsyncAction()).

L’exemple ci-dessous encapsule dans une méthode privée l’authentification OAuth aux services Foursquare et retourne un objet Task et une autre méthode publique est chargé de retourner l’objet IasyncOperation :

  1. private async static Task<string> AuthenticateTask(string clientId, string callbackUri)
  2. {
  3.     string foursquareUriFormat = "https://foursquare.com/oauth2/authenticate?client_id={0}&response_type=token&redirect_uri={1}";
  4.  
  5.     Uri oauthFoursquareUri = new Uri(string.Format(foursquareUriFormat, clientId, Uri.EscapeDataString(callbackUri)));
  6.     Uri callbackFoursquareUri = new Uri(callbackUri);
  7.  
  8.     var authenticateFoursquareResult = await WebAuthenticationBroker.AuthenticateAsync(WebAuthenticationOptions.None, oauthFoursquareUri, callbackFoursquareUri);
  9.  
  10.     if (authenticateFoursquareResult != null && authenticateFoursquareResult.ResponseStatus == Windows.Security.Authentication.Web.WebAuthenticationStatus.Success)
  11.     {
  12.         Uri tokenFoursquareUri = new Uri(authenticateFoursquareResult.ResponseData.ToString());
  13.  
  14.         WwwFormUrlDecoder decoder = new WwwFormUrlDecoder(tokenFoursquareUri.Fragment);
  15.  
  16.         string accessToken = decoder.GetFirstValueByName("#access_token");
  17.  
  18.         return accessToken;
  19.     }
  20.     else
  21.         throw new Exception("Authenticate fail...");
  22. }
  23.  
  24. public static IAsyncOperation<string> AuthenticateAsync(string clientId, string callbackUri)
  25. {
  26.     return AsyncInfo.Run<string>((token) =>
  27.     {
  28.         return AuthenticateTask(clientId, callbackUri);
  29.     });
  30. }

La classe AsyncInfo

La classe AsyncInfo nous offre une autre possibilité. Elle possède une méthode statique Run qui facilite la création d’objets IAsyncAction et IAsyncOperation. Elle supporte également l’annulation et la progression. Si on reprend l’exemple de l’authentification vu précédemment, on obtient le code suivant :

  1. public static IAsyncOperation<string> AuthenticateAsync(string clientId, string callbackUri)
  2. {
  3.     return AsyncInfo.Run<string>((token) =>
  4.     {
  5.         return AuthenticateInternalAsync(clientId, callbackUri);
  6.     });
  7. }

AsAsyncOperation() et AsyncInfo.Run : Même combat…

Que doit-on utiliser, y-a-t-il un choix plus judicieux que l’autre ?

Et bien pas vraiment, car les 2 font la même chose. Elles utilisent un objet de type TaskToAsyncOperationAdapter (ou TaskToAsyncActionAdapter) pour encapsuler la tâche. Ci-dessous la méthode AsAsyncOperation() :

  1. public static IAsyncOperation<TResult> AsAsyncOperation<TResult>(this Task<TResult> source)
  2. {
  3.     if (source == null)
  4.     {
  5.         throw new ArgumentNullException("source");
  6.     }
  7.     return new TaskToAsyncOperationAdapter<TResult>(source, null);
  8. }

 

Et ici la méthode AsyncInfo.Run :

  1. public static IAsyncOperation<TResult> Run<TResult>(Func<CancellationToken, Task<TResult>> taskProvider)
  2. {
  3.     if (taskProvider == null)
  4.     {
  5.         throw new ArgumentNullException("taskProvider");
  6.     }
  7.     return new TaskToAsyncOperationAdapter<TResult>(taskProvider);
  8. }

 

Finalement notre API Foursquare…

Nous savons maintenant implémenter un composant Windows Runtime asynchrone et réutilisable quel que soit le langage.

Et donc voici le code de notre API Foursquare :

  1. public sealed class FoursquareService
  2. {
  3.     private const string versionDate = "20120828";
  4.  
  5.     public static IAsyncOperation<string> AuthenticateAsync(string clientId, string callbackUri)
  6.     {
  7.         return AsyncInfo.Run<string>((token) =>
  8.         {
  9.             return AuthenticateTask(clientId, callbackUri);
  10.         });
  11.  
  12.     }
  13.  
  14.     public static IAsyncOperation<Contact> GetMyUserDetailsAsync(string token)
  15.     {
  16.         return GetUserDetailsTask(token).AsAsyncOperation();
  17.     }
  18.  
  19.     public static IAsyncOperation<Contact> GetUserDetailsAsync(string token, string userId)
  20.     {
  21.         return GetUserDetailsTask(token, userId).AsAsyncOperation();
  22.     }
  23.  
  24.     /// <summary>
  25.     /// Returns a history of checkins for the authenticated user
  26.     /// </summary>
  27.     /// <param name="token">Access token</param>
  28.     /// <returns></returns>
  29.     public static IAsyncOperation<IEnumerable<Checkin>> GetMyCheckinsAsync(string token)
  30.     {
  31.         return GetCheckinsTask(token).AsAsyncOperation<IEnumerable<Checkin>>();
  32.     }
  33.  
  34.     /// <summary>
  35.     /// Returns a history of checkins for the authenticated user
  36.     /// </summary>
  37.     /// <param name="token">Access token</param>
  38.     /// <param name="limit">Number of results to return, up to 250</param>
  39.     /// <returns></returns>
  40.     public static IAsyncOperation<IEnumerable<Checkin>> GetMyCheckinsAsync(string token, int limit)
  41.     {
  42.         return GetCheckinsTask(token, "self", limit).AsAsyncOperation<IEnumerable<Checkin>>();
  43.     }
  44.  
  45.     /// <summary>
  46.     ///
  47.     /// </summary>
  48.     /// <param name="token">Access token</param>
  49.     /// <param name="limit">Number of results to return, up to 250</param>
  50.     /// <param name="offset">The number of results to skip. Used to page through results.</param>
  51.     /// <returns></returns>
  52.     public static IAsyncOperation<IEnumerable<Checkin>> GetMyCheckinsAsync(string token, int limit, int offset)
  53.     {
  54.         return GetCheckinsTask(token, "self", limit, offset).AsAsyncOperation<IEnumerable<Checkin>>();
  55.     }
  56.  
  57.     /// <summary>
  58.     /// Returns badges for authenticated user
  59.     /// </summary>
  60.     /// <param name="token">Access token</param>
  61.     /// <returns></returns>
  62.     public static IAsyncOperation<IEnumerable<Badge>> GetMyBadgesAsync(string token)
  63.     {
  64.         return GetBadgesTask(token).AsAsyncOperation();
  65.     }
  66.  
  67.     /// <summary>
  68.     /// Returns badges for a given user
  69.     /// </summary>
  70.     /// <param name="token">Access token</param>
  71.     /// <param name="userId">ID for user to view badges for</param>
  72.     /// <returns></returns>
  73.     public static IAsyncOperation<IEnumerable<Badge>> GetBadgesAsync(string token, string userId)
  74.     {
  75.         return GetBadgesTask(token, userId).AsAsyncOperation();
  76.     }
  77.  
  78.     /// <summary>
  79.     /// Returns friends of authenticated user
  80.     /// </summary>
  81.     /// <param name="token">Access token</param>
  82.     /// <returns></returns>
  83.     public static IAsyncOperation<IEnumerable<Contact>> GetMyFriendsAsync(string token)
  84.     {
  85.         return GetFriendsTask(token).AsAsyncOperation();
  86.     }
  87.  
  88.     /// <summary>
  89.     /// Returns friends of authenticated user
  90.     /// </summary>
  91.     /// <param name="token">Access token</param>
  92.     /// <param name="limit">Number of results to return, up to 500.</param>
  93.     /// <returns></returns>
  94.     public static IAsyncOperation<IEnumerable<Contact>> GetMyFriendsAsync(string token, int limit)
  95.     {
  96.         return GetFriendsTask(token, "self", limit).AsAsyncOperation();
  97.     }
  98.  
  99.     /// <summary>
  100.     /// Returns friends of authenticated user
  101.     /// </summary>
  102.     /// <param name="token">Access token</param>
  103.     /// <param name="limit">Number of results to return, up to 500</param>
  104.     /// <param name="offset">Used to page through results</param>
  105.     /// <returns></returns>
  106.     public static IAsyncOperation<IEnumerable<Contact>> GetMyFriendsAsync(string token, int limit, int offset)
  107.     {
  108.         return GetFriendsTask(token, "self", limit, offset).AsAsyncOperation();
  109.     }
  110.  
  111.     /// <summary>
  112.     /// Returns a list of a user's friends
  113.     /// </summary>
  114.     /// <param name="token">Access token</param>
  115.     /// <param name="userId">Identity of the user to get friends of</param>
  116.     /// <returns></returns>
  117.     public static IAsyncOperation<IEnumerable<Contact>> GetFriendsAsync(string token, string userId)
  118.     {
  119.         return GetFriendsTask(token, userId).AsAsyncOperation();
  120.     }
  121.  
  122.     /// <summary>
  123.     /// Returns a list of a user's friends
  124.     /// </summary>
  125.     /// <param name="token">Access token</param>
  126.     /// <param name="userId">Identity of the user to get friends of</param>
  127.     /// <param name="limit">Number of results to return, up to 500</param>
  128.     /// <returns></returns>
  129.     public static IAsyncOperation<IEnumerable<Contact>> GetFriendsAsync(string token, string userId, int limit)
  130.     {
  131.         return GetFriendsTask(token, userId, limit).AsAsyncOperation();
  132.     }
  133.  
  134.     /// <summary>
  135.     /// Returns a list of a user's friends
  136.     /// </summary>
  137.     /// <param name="token">Access token</param>
  138.     /// <param name="userId">Identity of the user to get friends of</param>
  139.     /// <param name="limit">Number of results to return, up to 500</param>
  140.     /// <param name="offset">Used to page through results</param>
  141.     /// <returns></returns>
  142.     public static IAsyncOperation<IEnumerable<Contact>> GetFriendsAsync(string token, string userId, int limit, int offset)
  143.     {
  144.         return GetFriendsTask(token, userId, limit, offset).AsAsyncOperation();
  145.     }
  146.  
  147.     /// <summary>
  148.     /// Returns mayorships of authenticated user
  149.     /// </summary>
  150.     /// <param name="token">Access token</param>
  151.     /// <returns></returns>
  152.     public static IAsyncOperation<IEnumerable<Venue>> GetMyMayorshipsAsync(string token)
  153.     {
  154.         return GetMayorshipsTask(token).AsAsyncOperation();
  155.     }
  156.  
  157.     /// <summary>
  158.     /// Returns a user's mayorships
  159.     /// </summary>
  160.     /// <param name="token">Access token</param>
  161.     /// <param name="userId">Identity of the user to get mayorships for</param>
  162.     /// <returns></returns>
  163.     public static IAsyncOperation<IEnumerable<Venue>> GetMayorshipsAsync(string token, string userId)
  164.     {
  165.         return GetMayorshipsTask(token, userId).AsAsyncOperation();
  166.     }
  167.  
  168.     /// <summary>
  169.     /// Returns the leaderboard of authenticated user
  170.     /// </summary>
  171.     /// <param name="token">Access token</param>
  172.     /// <returns></returns>
  173.     public static IAsyncOperation<IEnumerable<LeaderboardItem>> GetLeaderboardAsync(string token)
  174.     {
  175.         return GetLeaderboardTask(token).AsAsyncOperation();
  176.     }
  177.  
  178.     /// <summary>
  179.     /// Returns the leaderboard of authenticated user
  180.     /// </summary>
  181.     /// <param name="token">Access token</param>
  182.     /// <param name="neighbors">Number of friends' scores to return that are adjacent to your score, in ranked order. The current user's score is returned as well</param>
  183.     /// <returns></returns>
  184.     public static IAsyncOperation<IEnumerable<LeaderboardItem>> GetLeaderboardAsync(string token, int neighbors)
  185.     {
  186.         return GetLeaderboardTask(token, neighbors).AsAsyncOperation();
  187.     }
  188.  
  189.     public static IAsyncOperation<IEnumerable<Checkin>> GetRecentCheckinsByFriends(string token)
  190.     {
  191.         return GetRecentCheckinsByFriendsTask(token).AsAsyncOperation();
  192.     }
  193.  
  194.     public static IAsyncOperation<IEnumerable<Checkin>> GetRecentCheckinsByFriends(string token, int limit)
  195.     {
  196.         return GetRecentCheckinsByFriendsTask(token, limit).AsAsyncOperation();
  197.     }
  198.  
  199.     public static IAsyncOperation<IEnumerable<Checkin>> GetRecentCheckinsByFriends(string token, int limit, double latitude, double longitude)
  200.     {
  201.         return GetRecentCheckinsByFriendsTask(token, limit, latitude, longitude).AsAsyncOperation();
  202.     }
  203.  
  204.     public static IAsyncOperation<IEnumerable<Checkin>> GetRecentCheckinsByFriends(string token, double latitude, double longitude)
  205.     {
  206.         return GetRecentCheckinsByFriendsTask(token, null, latitude, longitude).AsAsyncOperation();
  207.     }
  208.  
  209.     public static IAsyncOperation<IEnumerable<Contact>> FindUserAsync(string token, string name)
  210.     {
  211.         return FindUsersTask(token, name).AsAsyncOperation();
  212.     }
  213.  
  214.     public static IAsyncOperation<IEnumerable<Contact>> FindUsersAsync(string token, string email)
  215.     {
  216.         return FindUsersTask(token, null, email).AsAsyncOperation();
  217.     }
  218.  
  219.     public static IAsyncOperation<IEnumerable<Venue>> FindVenuesAsync(string token, double latitude, double longitude)
  220.     {
  221.         return FindVenuesTask(token, latitude, longitude, null).AsAsyncOperation();
  222.     }
  223.  
  224.     [DefaultOverload]
  225.     public static IAsyncOperation<IEnumerable<Venue>> FindVenuesAsync(string token, double latitude, double longitude, int limit)
  226.     {
  227.         return FindVenuesTask(token, latitude, longitude, null, limit).AsAsyncOperation();
  228.     }
  229.  
  230.     public static IAsyncOperation<IEnumerable<Venue>> FindVenuesAsync(string token, double latitude, double longitude, string queryVenue)
  231.     {
  232.         return FindVenuesTask(token, latitude, longitude, queryVenue).AsAsyncOperation();
  233.     }
  234.  
  235.     public static IAsyncOperation<IEnumerable<Venue>> FindVenuesAsync(string token, double latitude, double longitude, string queryVenue, int limit)
  236.     {
  237.         return FindVenuesTask(token, latitude, longitude, queryVenue, limit).AsAsyncOperation();
  238.     }
  239.  
  240.     public static IAsyncOperation<IEnumerable<Venue>> FindVenuesAsync(string token, double latitude, double longitude, string queryVenue, int limit, int radius)
  241.     {
  242.         return FindVenuesTask(token, latitude, longitude, queryVenue, limit, radius).AsAsyncOperation();
  243.     }
  244.  
  245.     private async static Task<string> AuthenticateTask(string clientId, string callbackUri)
  246.     {
  247.         string foursquareUriFormat = "https://foursquare.com/oauth2/authenticate?client_id={0}&response_type=token&redirect_uri={1}";
  248.  
  249.         Uri oauthFoursquareUri = new Uri(string.Format(foursquareUriFormat, clientId, Uri.EscapeDataString(callbackUri)));
  250.         Uri callbackFoursquareUri = new Uri(callbackUri);
  251.  
  252.         var authenticateFoursquareResult = await WebAuthenticationBroker.AuthenticateAsync(WebAuthenticationOptions.None, oauthFoursquareUri, callbackFoursquareUri);
  253.  
  254.         if (authenticateFoursquareResult != null && authenticateFoursquareResult.ResponseStatus == Windows.Security.Authentication.Web.WebAuthenticationStatus.Success)
  255.         {
  256.             Uri tokenFoursquareUri = new Uri(authenticateFoursquareResult.ResponseData.ToString());
  257.  
  258.             WwwFormUrlDecoder decoder = new WwwFormUrlDecoder(tokenFoursquareUri.Fragment);
  259.  
  260.             string accessToken = decoder.GetFirstValueByName("#access_token");
  261.  
  262.             return accessToken;
  263.         }
  264.         else
  265.             throw new Exception("Authenticate fail...");
  266.     }
  267.  
  268.     private async static Task<Contact> GetUserDetailsTask(string token, string userId = "self")
  269.     {
  270.         string uriFormat = "https://api.foursquare.com/v2/users/{0}?v={1}&oauth_token={2}";
  271.  
  272.         Uri uri = new Uri(string.Format(uriFormat, userId, versionDate, token));
  273.  
  274.         HttpClient client = new HttpClient();
  275.         var response = await client.GetAsync(uri);
  276.  
  277.         if (!response.IsSuccessStatusCode)
  278.             throw new Exception("Error when requested Foursquare : " + response.ReasonPhrase);
  279.  
  280.         string jsonResult = await response.Content.ReadAsStringAsync();
  281.  
  282.         JsonValue jsonValue;
  283.  
  284.         if (!JsonValue.TryParse(jsonResult, out jsonValue))
  285.             throw new Exception("Unable to parse checkins response");
  286.  
  287.         var userJson = jsonValue.GetObject()["response"].GetObject()["user"].GetObject();
  288.  
  289.         Contact contact = DeserializeUser(userJson);
  290.  
  291.         return contact;
  292.  
  293.     }
  294.  
  295.     private async static Task<IEnumerable<Venue>> FindVenuesTask(string token, double latitude, double longitude, string queryVenue, int? limit = null, int? radius = null)
  296.     {
  297.         string queryParam = string.IsNullOrEmpty(queryVenue) ? "" : "&query=" + queryVenue;
  298.         string limitParam = limit.HasValue ? "&limit=" + limit : "";
  299.         string radiusParam = radius.HasValue ? "&radius=" + radius : "";
  300.         string ll = latitude + "," + longitude;
  301.  
  302.         string uriFormat = "https://api.foursquare.com/v2/venues/search?v={0}&ll={1}{2}{3}{4}&oauth_token={5}";
  303.  
  304.         Uri uri = new Uri(string.Format(uriFormat, versionDate, ll, queryParam, limitParam, radiusParam, token));
  305.  
  306.         HttpClient client = new HttpClient();
  307.         var response = await client.GetAsync(uri);
  308.  
  309.         if (!response.IsSuccessStatusCode)
  310.             throw new Exception("Error when requested Foursquare : " + response.ReasonPhrase);
  311.  
  312.         string jsonResult = await response.Content.ReadAsStringAsync();
  313.  
  314.         JsonValue jsonValue;
  315.  
  316.         if (!JsonValue.TryParse(jsonResult, out jsonValue))
  317.             throw new Exception("Unable to parse checkins response");
  318.  
  319.         var venues = new List<Venue>();
  320.  
  321.         foreach (var item in jsonValue.GetObject()["response"].GetObject()["venues"].GetArray().AsParallel())
  322.         {
  323.             var venueItem = item.GetObject();
  324.  
  325.             var venue = DeserializeVenue(venueItem);
  326.  
  327.             venues.Add(venue);
  328.         }
  329.  
  330.         return venues;
  331.     }
  332.  
  333.     private async static Task<IEnumerable<Contact>> FindUsersTask(string token, string name, string email = null)
  334.     {
  335.         string nameParam = string.IsNullOrEmpty(name) ? "" : "&name=" + name;
  336.         string emailParam = string.IsNullOrEmpty(email) ? "" : "&email=" + email;
  337.  
  338.         string uriFormat = "https://api.foursquare.com/v2/users/search?v={0}{1}{2}&oauth_token={3}";
  339.  
  340.         Uri uri = new Uri(string.Format(uriFormat, versionDate, nameParam, emailParam, token));
  341.  
  342.         HttpClient client = new HttpClient();
  343.         var response = await client.GetAsync(uri);
  344.  
  345.         if (!response.IsSuccessStatusCode)
  346.             throw new Exception("Error when requested Foursquare : " + response.ReasonPhrase);
  347.  
  348.         string jsonResult = await response.Content.ReadAsStringAsync();
  349.  
  350.         JsonValue jsonValue;
  351.  
  352.         if (!JsonValue.TryParse(jsonResult, out jsonValue))
  353.             throw new Exception("Unable to parse checkins response");
  354.  
  355.         List<Contact> users = new List<Contact>();
  356.  
  357.         foreach (var item in jsonValue.GetObject()["response"].GetObject()["results"].GetArray().AsParallel())
  358.         {
  359.             var contactItem = item.GetObject();
  360.  
  361.             var contact = new Contact();
  362.  
  363.             contact.Id = contactItem["id"].GetString();
  364.             if (contactItem.ContainsKey("firstName"))
  365.                 contact.FirstName = contactItem["firstName"].GetString();
  366.             if (contactItem.ContainsKey("lastName"))
  367.                 contact.LastName = contactItem["lastName"].GetString();
  368.  
  369.             if (contactItem.ContainsKey("photo"))
  370.             {
  371.                 var photoItem = contactItem["photo"].GetObject();
  372.  
  373.                 var prefix = photoItem["prefix"].GetString();
  374.                 var suffix = photoItem["suffix"].GetString();
  375.  
  376.                 contact.Photo = prefix + suffix;
  377.             }
  378.  
  379.             if (contactItem.ContainsKey("gender"))
  380.                 contact.Gender = contactItem["gender"].GetString();
  381.             if (contactItem.ContainsKey("homeCity"))
  382.                 contact.HomeCity = contactItem["homeCity"].GetString();
  383.             if (contactItem.ContainsKey("bio"))
  384.                 contact.HomeCity = contactItem["bio"].GetString();
  385.  
  386.             var contactInfoItem = contactItem["contact"].GetObject();
  387.  
  388.             if (contactInfoItem.ContainsKey("email"))
  389.                 contact.Email = contactInfoItem["email"].GetString();
  390.             if (contactInfoItem.ContainsKey("facebook"))
  391.                 contact.Facebook = contactInfoItem["facebook"].GetString();
  392.             if (contactInfoItem.ContainsKey("twitter"))
  393.                 contact.Twitter = contactInfoItem["twitter"].GetString();
  394.  
  395.             var tipsItem = contactItem["tips"].GetObject();
  396.             contact.CountTips = Convert.ToInt32(tipsItem["count"].GetNumber());
  397.  
  398.             users.Add(contact);
  399.         }
  400.  
  401.         return users;
  402.     }
  403.  
  404.     private async static Task<IEnumerable<Checkin>> GetRecentCheckinsByFriendsTask(string token, int? limit = null, double? latitude = null, double? longitude = null)
  405.     {
  406.         string limitParam = limit.HasValue ? "&limit=" + limit : "";
  407.         string ll = latitude.HasValue && longitude.HasValue ? "&ll=" + latitude + "," + longitude : "";
  408.  
  409.         string uriFormat = "https://api.foursquare.com/v2/checkins/recent?v={0}{1}{2}&oauth_token={3}";
  410.  
  411.         Uri uri = new Uri(string.Format(uriFormat, versionDate, ll, limitParam, token));
  412.  
  413.         HttpClient client = new HttpClient();
  414.         var response = await client.GetAsync(uri);
  415.  
  416.         if (!response.IsSuccessStatusCode)
  417.             throw new Exception("Error when requested Foursquare : " + response.ReasonPhrase);
  418.  
  419.         string jsonResult = await response.Content.ReadAsStringAsync();
  420.  
  421.         JsonValue jsonValue;
  422.  
  423.         if (!JsonValue.TryParse(jsonResult, out jsonValue))
  424.             throw new Exception("Unable to parse checkins response");
  425.  
  426.         List<Checkin> checkins = new List<Checkin>();
  427.  
  428.         foreach (var item in jsonValue.GetObject()["response"].GetObject()["recent"].GetArray())
  429.         {
  430.             var checkinItem = item.GetObject();
  431.  
  432.             Checkin checkin = new Checkin();
  433.  
  434.             //checkinItem["distance"]
  435.             //    checkinItem["isMayor"]
  436.             //        checkinItem["createdAt"]
  437.             //            checkinItem["shoot"]
  438.  
  439.             checkin.Id = checkinItem["id"].GetString();
  440.  
  441.             if (checkinItem.ContainsKey("distance"))
  442.                 checkin.Distance = Convert.ToInt32(checkinItem["distance"].GetNumber());
  443.             if (checkinItem.ContainsKey("isMayor"))
  444.                 checkin.IsMayor = checkinItem["isMayor"].GetBoolean();
  445.             if (checkinItem.ContainsKey("shoot"))
  446.                 checkin.Shoot = checkinItem["shoot"].GetString();
  447.             if (checkinItem.ContainsKey("createdAt"))
  448.             {
  449.                 var offset = checkinItem["timeZoneOffset"].GetNumber();
  450.                 var da = new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.FromSeconds(offset));
  451.                 da = da.AddSeconds(checkinItem["createdAt"].GetNumber());
  452.                 da = da.ToLocalTime();
  453.                 checkin.CreatedAt = da;
  454.             }
  455.  
  456.             if (checkinItem.ContainsKey("venue"))
  457.             {
  458.                 var venueItem = checkinItem["venue"].GetObject();
  459.  
  460.                 checkin.Venue = DeserializeVenue(venueItem);
  461.             }
  462.  
  463.             if (checkinItem.ContainsKey("user"))
  464.             {
  465.                 var userItem = checkinItem["user"].GetObject();
  466.  
  467.                 checkin.User = DeserializeUser(userItem);
  468.             }
  469.  
  470.             checkins.Add(checkin);
  471.         }
  472.  
  473.         return checkins;
  474.  
  475.     }
  476.  
  477.     private async static Task<IEnumerable<Checkin>> GetCheckinsTask(string token, string userId = "self", int? limit = null, int? offset = null)
  478.     {
  479.         List<Checkin> checkins = new List<Checkin>();
  480.  
  481.         string limitParam = limit.HasValue ? "&limit=" + limit.Value : "";
  482.         string offsetParam = offset.HasValue ? "&offset=" + offset.Value : "";
  483.  
  484.         string uriFormat = "https://api.foursquare.com/v2/users/{0}/checkins?v={1}{2}{3}&oauth_token={4}";
  485.  
  486.         Uri uri = new Uri(string.Format(uriFormat, userId, versionDate, limitParam, offsetParam, token));
  487.  
  488.         HttpClient client = new HttpClient();
  489.         var response = await client.GetAsync(uri);
  490.  
  491.         if (!response.IsSuccessStatusCode)
  492.             throw new Exception("Error when requested Foursquare : " + response.ReasonPhrase);
  493.  
  494.         string jsonResult = await response.Content.ReadAsStringAsync();
  495.  
  496.         JsonValue jsonValue;
  497.  
  498.         if (!JsonValue.TryParse(jsonResult, out jsonValue))
  499.             throw new Exception("Unable to parse checkins response");
  500.  
  501.         foreach (var item in jsonValue.GetObject()["response"].GetObject()["checkins"].GetObject()["items"].GetArray().AsParallel())
  502.         {
  503.             var checkinItem = item.GetObject();
  504.  
  505.             Checkin checkin = new Checkin();
  506.  
  507.             checkin.Id = checkinItem["id"].GetString();
  508.  
  509.             var venueItem = checkinItem["venue"].GetObject();
  510.  
  511.             checkin.Venue = DeserializeVenue(venueItem);
  512.  
  513.             checkins.Add(checkin);
  514.         }
  515.  
  516.         return checkins;
  517.  
  518.     }
  519.  
  520.     private async static Task<IEnumerable<Contact>> GetFriendsTask(string token, string userId = "self", int? limit = null, int? offset = null)
  521.     {
  522.         string limitParam = limit.HasValue ? "&limit=" + limit.Value : "";
  523.         string offsetParam = offset.HasValue ? "&offset=" + offset.Value : "";
  524.  
  525.         string uriFormat = "https://api.foursquare.com/v2/users/{0}/friends?v={1}{2}{3}&oauth_token={4}";
  526.  
  527.         Uri uri = new Uri(string.Format(uriFormat, userId, versionDate, limitParam, offsetParam, token));
  528.  
  529.         HttpClient client = new HttpClient();
  530.         var response = await client.GetAsync(uri);
  531.  
  532.         if (!response.IsSuccessStatusCode)
  533.             throw new Exception("Error when requested Foursquare : " + response.ReasonPhrase);
  534.  
  535.         string jsonResult = await response.Content.ReadAsStringAsync();
  536.  
  537.         JsonValue jsonValue;
  538.  
  539.         if (!JsonValue.TryParse(jsonResult, out jsonValue))
  540.             throw new Exception("Unable to parse checkins response");
  541.  
  542.         List<Contact> contacts = new List<Contact>();
  543.  
  544.         foreach (var item in jsonValue.GetObject()["response"].GetObject()["friends"].GetObject()["items"].GetArray().AsParallel())
  545.         {
  546.             var contactItem = item.GetObject();
  547.  
  548.             var contact = new Contact();
  549.  
  550.             contact.Id = contactItem["id"].GetString();
  551.             if (contactItem.ContainsKey("firstName"))
  552.                 contact.FirstName = contactItem["firstName"].GetString();
  553.             if (contactItem.ContainsKey("lastName"))
  554.                 contact.LastName = contactItem["lastName"].GetString();
  555.  
  556.             if (contactItem.ContainsKey("photo"))
  557.             {
  558.                 var photoItem = contactItem["photo"].GetObject();
  559.  
  560.                 var prefix = photoItem["prefix"].GetString();
  561.                 var suffix = photoItem["suffix"].GetString();
  562.  
  563.                 contact.Photo = prefix + "original" + suffix;
  564.             }
  565.  
  566.             if (contactItem.ContainsKey("gender"))
  567.                 contact.Gender = contactItem["gender"].GetString();
  568.             if (contactItem.ContainsKey("homeCity"))
  569.                 contact.HomeCity = contactItem["homeCity"].GetString();
  570.             if (contactItem.ContainsKey("bio"))
  571.                 contact.HomeCity = contactItem["bio"].GetString();
  572.  
  573.             var contactInfoItem = contactItem["contact"].GetObject();
  574.  
  575.             if (contactInfoItem.ContainsKey("email"))
  576.                 contact.Email = contactInfoItem["email"].GetString();
  577.             if (contactInfoItem.ContainsKey("facebook"))
  578.                 contact.Facebook = contactInfoItem["facebook"].GetString();
  579.             if (contactInfoItem.ContainsKey("twitter"))
  580.                 contact.Twitter = contactInfoItem["twitter"].GetString();
  581.  
  582.             var tipsItem = contactItem["tips"].GetObject();
  583.             contact.CountTips = Convert.ToInt32(tipsItem["count"].GetNumber());
  584.  
  585.             contacts.Add(contact);
  586.         }
  587.  
  588.         return contacts;
  589.  
  590.     }
  591.  
  592.     private async static Task<IEnumerable<Badge>> GetBadgesTask(string token, string userId = "self")
  593.     {
  594.         string uriFormat = "https://api.foursquare.com/v2/users/{0}/badges?v={1}&oauth_token={2}";
  595.  
  596.         Uri uri = new Uri(string.Format(uriFormat, userId, versionDate, token));
  597.  
  598.         HttpClient client = new HttpClient();
  599.         var response = await client.GetAsync(uri);
  600.  
  601.         if (!response.IsSuccessStatusCode)
  602.             throw new Exception("Error when requested Foursquare : " + response.ReasonPhrase);
  603.  
  604.         string jsonResult = await response.Content.ReadAsStringAsync();
  605.  
  606.         JsonValue jsonValue;
  607.  
  608.         if (!JsonValue.TryParse(jsonResult, out jsonValue))
  609.             throw new Exception("Unable to parse checkins response");
  610.  
  611.         List<Badge> badges = new List<Badge>();
  612.  
  613.         var badgeItems = jsonValue.GetObject()["response"].GetObject()["badges"].GetObject();
  614.  
  615.         foreach (var badgeKey in badgeItems.Keys)
  616.         {
  617.             var badgeItem = badgeItems[badgeKey].GetObject();
  618.  
  619.             var badge = new Badge();
  620.             badge.Id = badgeItem["id"].GetString();
  621.             badge.BadgeId = badgeItem["badgeId"].GetString();
  622.             badge.Name = badgeItem["name"].GetString();
  623.             if (badgeItem.ContainsKey("unlockMessage"))
  624.                 badge.UnlockMessage = badgeItem["unlockMessage"].GetString();
  625.             if (badgeItem.ContainsKey("description"))
  626.                 badge.Description = badgeItem["description"].GetString();
  627.             if (badgeItem.ContainsKey("hint"))
  628.                 badge.Description = badgeItem["hint"].GetString();
  629.  
  630.             var imageItem = badgeItem["image"].GetObject();
  631.  
  632.             string prefix = imageItem["prefix"].GetString();
  633.             string name = imageItem["name"].GetString();
  634.  
  635.             badge.ImageUri = prefix + "114" + name;
  636.  
  637.             if (!string.IsNullOrEmpty(badge.Name))
  638.                 badges.Add(badge);
  639.         }
  640.  
  641.         return badges;
  642.     }
  643.  
  644.     private async static Task<IEnumerable<Venue>> GetMayorshipsTask(string token, string userId = null)
  645.     {
  646.         string userParam = userId ?? "self";
  647.  
  648.         string uriFormat = "https://api.foursquare.com/v2/users/{0}/mayorships?v={1}&oauth_token={2}";
  649.  
  650.         Uri uri = new Uri(string.Format(uriFormat, userParam, versionDate, token));
  651.  
  652.         HttpClient client = new HttpClient();
  653.         var response = await client.GetAsync(uri);
  654.  
  655.         if (!response.IsSuccessStatusCode)
  656.             throw new Exception("Error when requested Foursquare : " + response.ReasonPhrase);
  657.  
  658.         string jsonResult = await response.Content.ReadAsStringAsync();
  659.  
  660.         JsonValue jsonValue;
  661.  
  662.         if (!JsonValue.TryParse(jsonResult, out jsonValue))
  663.             throw new Exception("Unable to parse checkins response");
  664.  
  665.         List<Venue> venues = new List<Venue>();
  666.  
  667.         foreach (var item in jsonValue.GetObject()["response"].GetObject()["mayorships"].GetObject()["items"].GetArray().AsParallel())
  668.         {
  669.             var venueItem = item.GetObject()["venue"].GetObject();
  670.  
  671.             var venue = DeserializeVenue(venueItem);
  672.  
  673.             venues.Add(venue);
  674.         }
  675.  
  676.         return venues;
  677.     }
  678.  
  679.     private async static Task<IEnumerable<LeaderboardItem>> GetLeaderboardTask(string token, int? neighbors = null)
  680.     {
  681.         string uriFormat = "https://api.foursquare.com/v2/users/leaderboard?v={0}{1}&oauth_token={2}";
  682.         string neighborsCount = neighbors.HasValue ? "&neighbors=" + neighbors.Value.ToString() : "";
  683.  
  684.         Uri uri = new Uri(string.Format(uriFormat, versionDate, neighborsCount, token));
  685.  
  686.         HttpClient client = new HttpClient();
  687.         var response = await client.GetAsync(uri);
  688.  
  689.         if (!response.IsSuccessStatusCode)
  690.             throw new Exception("Error when requested Foursquare : " + response.ReasonPhrase);
  691.  
  692.         string jsonResult = await response.Content.ReadAsStringAsync();
  693.  
  694.         JsonValue jsonValue;
  695.  
  696.         if (!JsonValue.TryParse(jsonResult, out jsonValue))
  697.             throw new Exception("Unable to parse checkins response");
  698.  
  699.         List<LeaderboardItem> leaderboard = new List<LeaderboardItem>();
  700.  
  701.         foreach (var item in jsonValue.GetObject()["response"].GetObject()["leaderboard"].GetObject()["items"].GetArray())
  702.         {
  703.             var lboardJson = item.GetObject();
  704.  
  705.             var leaderboardItem = new LeaderboardItem();
  706.  
  707.             leaderboardItem.Rank = Convert.ToInt32(lboardJson["rank"].GetNumber());
  708.  
  709.             var userJson = lboardJson["user"].GetObject();
  710.  
  711.             leaderboardItem.User = new Contact();
  712.  
  713.             leaderboardItem.User.Id = userJson["id"].GetString();
  714.             if (userJson.ContainsKey("firstName"))
  715.                 leaderboardItem.User.FirstName = userJson["firstName"].GetString();
  716.             if (userJson.ContainsKey("lastName"))
  717.                 leaderboardItem.User.LastName = userJson["lastName"].GetString();
  718.             leaderboardItem.User.Me = userJson["relationship"].GetString() == "self";
  719.  
  720.             if (userJson.ContainsKey("photo"))
  721.             {
  722.                 var photoItem = userJson["photo"].GetObject();
  723.  
  724.                 var prefix = photoItem["prefix"].GetString();
  725.                 var suffix = photoItem["suffix"].GetString();
  726.  
  727.                 leaderboardItem.User.Photo = prefix + "original" + suffix;
  728.             }
  729.  
  730.             var scoreJson = lboardJson["scores"].GetObject();
  731.  
  732.             leaderboardItem.Score = new Score();
  733.             leaderboardItem.Score.Recent = Convert.ToInt32(scoreJson["recent"].GetNumber());
  734.             leaderboardItem.Score.Max = Convert.ToInt32(scoreJson["max"].GetNumber());
  735.             leaderboardItem.Score.CheckinsCount = Convert.ToInt32(scoreJson["checkinsCount"].GetNumber());
  736.  
  737.             leaderboard.Add(leaderboardItem);
  738.         }
  739.  
  740.         return leaderboard;
  741.     }
  742.  
  743.     private static Contact DeserializeUser(JsonObject userJson)
  744.     {
  745.         Contact contact = new Contact();
  746.  
  747.         contact.Id = userJson["id"].GetString();
  748.         if (userJson.ContainsKey("firstName"))
  749.             contact.FirstName = userJson["firstName"].GetString();
  750.         if (userJson.ContainsKey("lastName"))
  751.             contact.LastName = userJson["lastName"].GetString();
  752.         contact.Me = userJson["relationship"].GetString() == "self";
  753.  
  754.         if (userJson.ContainsKey("photo"))
  755.         {
  756.             var photoItem = userJson["photo"].GetObject();
  757.  
  758.             var prefix = photoItem["prefix"].GetString();
  759.             var suffix = photoItem["suffix"].GetString();
  760.  
  761.             contact.Photo = prefix + "original" + suffix;
  762.         }
  763.  
  764.         if (userJson.ContainsKey("gender"))
  765.             contact.Gender = userJson["gender"].GetString();
  766.         if (userJson.ContainsKey("homeCity"))
  767.             contact.HomeCity = userJson["homeCity"].GetString();
  768.         if (userJson.ContainsKey("bio"))
  769.             contact.Bio = userJson["bio"].GetString();
  770.  
  771.         if (userJson.ContainsKey("contact"))
  772.         {
  773.             var contactInfoItem = userJson["contact"].GetObject();
  774.  
  775.             if (contactInfoItem.ContainsKey("email"))
  776.                 contact.Email = contactInfoItem["email"].GetString();
  777.             if (contactInfoItem.ContainsKey("facebook"))
  778.                 contact.Facebook = contactInfoItem["facebook"].GetString();
  779.             if (contactInfoItem.ContainsKey("twitter"))
  780.                 contact.Twitter = contactInfoItem["twitter"].GetString();
  781.         }
  782.  
  783.         if (userJson.ContainsKey("tips"))
  784.         {
  785.             var tipsItem = userJson["tips"].GetObject();
  786.             contact.CountTips = Convert.ToInt32(tipsItem["count"].GetNumber());
  787.         }
  788.  
  789.         return contact;
  790.     }
  791.  
  792.     private static Venue DeserializeVenue(JsonObject venueItem)
  793.     {
  794.         Venue venue = new Venue();
  795.  
  796.         venue.Id = venueItem["id"].GetString();
  797.         venue.Name = venueItem["name"].GetString();
  798.  
  799.         var locationItem = venueItem["location"].GetObject();
  800.  
  801.         venue.Location = DeserializeLocation(locationItem);
  802.  
  803.         var statsItem = venueItem["stats"].GetObject();
  804.  
  805.         venue.VenueStatistics.CheckinsCount = Convert.ToInt32(statsItem["checkinsCount"].GetNumber());
  806.         venue.VenueStatistics.TipCount = Convert.ToInt32(statsItem["tipCount"].GetNumber());
  807.         venue.VenueStatistics.UsersCount = Convert.ToInt32(statsItem["usersCount"].GetNumber());
  808.  
  809.         var categoryItems = venueItem["categories"].GetArray();
  810.  
  811.         foreach (var catItem in categoryItems)
  812.         {
  813.             var categoryItem = catItem.GetObject();
  814.  
  815.             var category = new VenueCategory();
  816.  
  817.             category.Id = categoryItem["id"].GetString();
  818.  
  819.             if (categoryItem.ContainsKey("icon"))
  820.             {
  821.                 var iconJson = categoryItem["icon"].GetObject();
  822.  
  823.                 var prefix = iconJson["prefix"].GetString();
  824.                 var suffix = iconJson["suffix"].GetString();
  825.                 if (prefix.EndsWith("_"))
  826.                     prefix = prefix.Substring(0, prefix.Length - 1);
  827.  
  828.                 category.Icon = prefix + suffix;
  829.             }
  830.  
  831.             category.Name = categoryItem["name"].GetString();
  832.             category.PluralName = categoryItem["pluralName"].GetString();
  833.             category.ShortName = categoryItem["shortName"].GetString();
  834.  
  835.             venue.VenueCategory.Add(category);
  836.         }
  837.  
  838.         return venue;
  839.     }
  840.  
  841.     private static Location DeserializeLocation(JsonObject locationItem)
  842.     {
  843.         var location = new Location();
  844.  
  845.         if (locationItem.ContainsKey("city"))
  846.             location.City = locationItem["city"].GetString();
  847.         if (locationItem.ContainsKey("address"))
  848.             location.Address = locationItem["address"].GetString();
  849.         if (locationItem.ContainsKey("postalCode"))
  850.             location.PostalCode = locationItem["postalCode"].GetString();
  851.         location.Country = locationItem["country"].GetString();
  852.         location.CountryCode = locationItem["cc"].GetString();
  853.         location.Latitude = locationItem["lat"].GetNumber();
  854.         location.Longitude = locationItem["lng"].GetNumber();
  855.         if (locationItem.ContainsKey("state"))
  856.             location.State = locationItem["state"].GetString();
  857.  
  858.         return location;
  859.     }
  860. }

 

Et ci-dessous un exemple d’utilisation de cette API.

  1. var token = await FoursquareService.AuthenticateAsync(App.clientId, App.callbackFoursquareUri);
  2.  
  3. var friendsTask = FoursquareService.GetMyFriendsAsync(token, 9).AsTask();
  4. var badgesTask = FoursquareService.GetMyBadgesAsync(token).AsTask();
  5. var checkinsTask = FoursquareService.GetMyCheckinsAsync(token, 5).AsTask();
  6. var userDetailsTask = FoursquareService.GetMyUserDetailsAsync(token).AsTask();
  7. var leaderBoardTask = FoursquareService.GetLeaderboardAsync(token, 5).AsTask();
  8. var recentTask = FoursquareService.GetRecentCheckinsByFriends(token, 5).AsTask();
  9. var mayorshipsTask = FoursquareService.GetMyMayorshipsAsync(token).AsTask();
  10.  
  11. await Task.WhenAll(friendsTask, badgesTask, checkinsTask, leaderBoardTask, recentTask, mayorshipsTask);
  12.  
  13. TaskScheduler scheduler = TaskScheduler.FromCurrentSynchronizationContext();
  14.  
  15. lvFriends.ItemsSource = friendsTask.Result;
  16. lvCheckins.ItemsSource = checkinsTask.Result.Select(c => c.Venue).ToList();
  17. lvBadges.ItemsSource = badgesTask.Result.Where(b => !string.IsNullOrEmpty(b.UnlockMessage)).Take(9).ToList();
  18. lvLeaderboard.ItemsSource = leaderBoardTask.Result;
  19. lvFriendsActivity.ItemsSource = recentTask.Result;
  20. lvMayorships.ItemsSource = mayorshipsTask.Result.Take(6).ToList();

 

Dans ce 1er exemple, nous appelons différentes méthodes asynchrones de récupération de nos objets Foursquare et pour chacun on récupère la tâche associée.

Nous appelons ensuite la méthode Task.WhenAll avec le mot clé await qui permet d’attendre que toutes les tâches soient terminées. Puis nous faisons la liaison des résultats aux contrôles de la page.

Ci-dessous un autre exemple qui cette fois n’attend pas la fin de chacun des traitements pour effectuer les liaisons de données. Dès qu’une tache se termine elle continue avec la liaison de son résultat au contrôle de la page :

  1. var token = await FoursquareService.AuthenticateAsync(App.clientId, App.callbackFoursquareUri);
  2.  
  3. var friendsTask = FoursquareService.GetMyFriendsAsync(token, 9).AsTask();
  4. var badgesTask = FoursquareService.GetMyBadgesAsync(token).AsTask();
  5. var checkinsTask = FoursquareService.GetMyCheckinsAsync(token, 5).AsTask();
  6. var userDetailsTask = FoursquareService.GetMyUserDetailsAsync(token).AsTask();
  7. var leaderBoardTask = FoursquareService.GetLeaderboardAsync(token, 5).AsTask();
  8. var recentTask = FoursquareService.GetRecentCheckinsByFriends(token, 5).AsTask();
  9. var mayorshipsTask = FoursquareService.GetMyMayorshipsAsync(token).AsTask();
  10.  
  11. TaskScheduler scheduler = TaskScheduler.FromCurrentSynchronizationContext();
  12.  
  13. friendsTask.ContinueWith(
  14.     (task) =>
  15.     {
  16.         lvFriends.ItemsSource = task.Result;
  17.     }, scheduler);
  18.  
  19. badgesTask.ContinueWith(
  20.     (task) =>
  21.     {
  22.         lvBadges.ItemsSource = task.Result.Where(b => !string.IsNullOrEmpty(b.UnlockMessage)).Take(9).ToList();
  23.     }, scheduler);
  24.  
  25. checkinsTask.ContinueWith(
  26.     (task) =>
  27.     {
  28.         lvCheckins.ItemsSource = task.Result.Select(c => c.Venue).ToList();
  29.     }, scheduler);
  30.  
  31. leaderBoardTask.ContinueWith(
  32.     (task) =>
  33.     {
  34.         lvLeaderboard.ItemsSource = task.Result;
  35.     }, scheduler);
  36.  
  37. recentTask.ContinueWith(
  38.     (task) =>
  39.     {
  40.         lvFriendsActivity.ItemsSource = task.Result;
  41.     }, scheduler);
  42.  
  43. mayorshipsTask.ContinueWith(
  44.     (task) =>
  45.     {
  46.         lvMayorships.ItemsSource = task.Result;
  47.     }, scheduler);

 

Aller plus loin

Du côté de l’asynchronisme je ne me suis pas étendu car le sujet est très vaste et je voulais aller à l’essentiel. Peut être que j’y reviendrai prochainement dans un article consacré à ce sujet. Mais si vous désirez en savoir un peu plus, je vous conseille vivement ces 2 articles, qui ont d’ailleurs été traduits :

Pour le développement de composants Windows Runtime, il faut garder à l’esprit qu’il existe quelques contraintes d’implémentation. Ces contraintes sont essentiellement imposées par le système de type du Windows Runtime et le fait de devoir supporter différents langages (et notamment le javascript…).

Dans le prochain article, nous nous finaliserons cette application et nous verrons comment implémenter le contrat ContactPicker.

Friday, August 31, 2012 12:49:28 PM (Romance Daylight Time, UTC+02:00)  #    Voir Commentaires
C# | Windows 8
Archive
<April 2014>
SunMonTueWedThuFriSat
303112345
6789101112
13141516171819
20212223242526
27282930123
45678910
About the author/Disclaimer

Disclaimer
The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

© Copyright 2014
Benoît Laut
Sign In
All Content © 2014, Benoît Laut
DasBlog theme 'Business' created by Christoph De Baene (delarou)