Tutoriel : Ma première application Silverlight WP7 connectée en OData en 20 minutes chrono

Ce tutoriel découverte vous permettra de développer une application Silverlight basique pour Windows Phone 7.
L’application utilise le service OData de Netflix pour afficher une liste de films par genres, dans un contrôle Panorama.

En fait, j’ai préparé cette petite démo pour un évènement pendant lequel ma durée d’intervention était très courte. Bien que l'on démarre from scratch, le résultat est obtenu très rapidement grâce au template de type Panorama qui prépare les principales classes métier et le binding.

Voici le résultat dans une vingtaine de minutes:

image

Bon d’accord, elle ne sera pas internationalisée et ne gère pas le tombstoning…mais le but ici est de vous montrer qu’il est très facile (et rapide) d’exploiter des données distantes dans une application pour Windows Phone.

Pré-requis:


  1. Installez le kit de développement WP7 gratuit
  2. Téléchargez les librairies WCF Data Services pour WP7 et copiez les dans le répertoire de votre choix

Et aussi des liens utiles:

image

 

L’architecture:


Avant de commencer, voyons un peu l’architecture de notre application:

image

Notre application Silverlight va utiliser les librairies clientes WCF Data Services pour faciliter l'exploitation des données publiées en OData par NetFlix.

Si vous ne savez pas ce qu’est OData et que vous êtes curieux, vous pouvez jeter un oeil ici : Mais c'est quoi à la fin OData ?.
Mais pour notre article, il suffit de savoir que c’est un protocole qui nous permet de manipuler des collections de données mises en ligne par de plus en plus d’applications/producteurs (ex : ebay, netflix, SQL Azure, Sharepoint, …).
Pour une plateforme “nomade” et connectée comme un téléphone, c’est parfait.

WCF Data Services va s’occuper de

  1. générer les classes proxy correspondant aux modèles de classes publiées par netflix pour ses films
  2. envoyer nos requêtes http REST au service netFlix
  3. parser le flux Atom reçu en retour pour qu’on puisse le traiter facilement à travers ces fameuses classes proxy

 

Les étapes:


  1. Création de l’application Silverlight pour Windows Phone 7 avec le template Panorama
  2. Génération des classes proxy du service OData Netflix, en ligne de commande
  3. Création de l’arborescence des contrôles dans la page xaml et définition des templates pour le header et l’item du contrôle Panorama
  4. Création du contexte métier pour la page (1 page = 1 item Panorama = 1 genre de films)
  5. Chargement asynchrone des films provenant de Netflix 

 

1. Création de l’application Silverlight pour Windows Phone 7 avec le template Panorama


Créez une nouveau projet Visual Studio en utilisant le template Panorama Silverlight pour WP7

image

Ce template prépare une page principale MainView contenant un contrôle Panorama. MainView est bindée à une classe MainViewModel qui servira d’intermédiaire avec le contexte métier (les classes proxy du service NetFlix).

 

2. Génération des classes proxy du service OData Netflix, en ligne de commande 


N’oubliez pas de télécharger les librairies WCF Data Services pour WP7

WCF Data Services pour WP7 apporte (à ce jour) un peu moins de fonctionnalités que son homologue classique pour Silverlight :

  • Il faut générer les classes proxy avec une ligne de commande au lieu de faire un simple “add service reference” dans Visual Studio
  • Il ne sait pas générer les requêtes REST OData à partir de Linq

Peu importe, ça reste très facile à utiliser.

Commençons par référencer les 2 librairies clientes dans notre projet:

image

Puis générons les classes proxy à l’aide de la commande suivante:

DataSvcUtil.exe /uri:https://odata.netflix.com/Catalog/ /out:.\NetflixModel.cs /Version:2.0 /DataServiceCollection

Le plus simple pour cela est de télécharger le .bat suivant et de le copier dans le répertoire dans lequel vous avez téléchargé le client WCF Data Services pour WP7 et de l’exécuter:

Cette commande va générer un fichier NetFlixModel.cs contenant les classes proxy permettant de manipuler des données provenant du service OData Netflix.

Ajouter ce fichier dans notre projet Silverlight.

3. Création de l’arborescence des contrôles dans la page xaml et définition des templates pour le header et l’item du contrôle Panorama


Passons à présent dans la partie “View” et le code xaml de la page MainPage

Le contrôle Panorama affiche une collection de pages représentant la liste des films par genre : 1 page = 1 item Panorama = 1 genre de films.

Voici l’organisation du binding entre les contrôles et les données, que l’on va retrouver des les sources.
Les classes MainViewModel et ItemViewModel existent déjà et sont déjà bindées correctement par l’intermédiaire du DataContext de la MainPage. Il suffit d’y ajouter/supprimer les éléments qui nous intéressent dans notre application, à savoir le genre de films (propriété Genre) et la collection qui lui correspond (propriété Films).

Un film est représenté par la classe proxy NetFlixModel.Title du fichier NetflixModel.cs généré dans l’étape précédente.

image

Pour cela, nous allons modifier le code de MainPage.xaml:

Commençons par modifier la source du contrôle panorama et bindons là à la propriété Items du MainViewModel. Ainsi, nous aurons autant de pages dans notre contrôle Panorama, que l’éléments dans la collection MainViewModel.Items.

         <!--Panorama control-->
        <controls:Panorama Title="NetFlix Browser" ItemsSource="{Binding Items}">
            <controls:Panorama.Background>
                <ImageBrush ImageSource="PanoramaBackground.png"/>
            </controls:Panorama.Background>
 
Ensuite, définissons le template du header du Panorama : il représente le genre de films affiché par la page, sous la forme d’un TextBlock bindé à la propriété Genre de la classe ItemViewModel :
             <controls:Panorama.HeaderTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Genre}" FontSize="40"></TextBlock>
                </DataTemplate>
            </controls:Panorama.HeaderTemplate>
  
 Définissons le template de l’item du Panorama, qui sera une liste de films
             <controls:Panorama.ItemTemplate>
                <DataTemplate>
                    <ListBox ItemsSource="{Binding Films}"/>
                </DataTemplate>
            </controls:Panorama.ItemTemplate>

Chaque élément de cette liste sera représenté par:

  • une Image représentant l’affiche du film (propriété BoxArt.SmallUrl de la classe proxy Title)
  • un TextBlock représentant le titre du film (propriété Name de la classe proxy Title)
  • Une TextBlock représentant le synopsis du film (propriété ShortSynopsis de la classe proxy Title)
     <ListBox.ItemTemplate>
         <DataTemplate>
              <StackPanel Orientation="Horizontal">
                  <Image Source="{Binding BoxArt.SmallUrl}"></Image>
                  <StackPanel Margin="0,0,0,17" Width="432">
                      <TextBlock Text="{Binding Name}" TextWrapping="Wrap" Style="{StaticResource PhoneTextExtraLargeStyle}"/>
                      <TextBlock Text="{Binding ShortSynopsis}" TextWrapping="Wrap" Margin="12,-6,12,0" Style="{StaticResource PhoneTextSubtleStyle}"/>
                  </StackPanel>
              </StackPanel>
          </DataTemplate>
     </ListBox.ItemTemplate>

Ce qui donne comme code complet:

 <phone:PhoneApplicationPage 
    x:Class="WindowsPhonePanoramaApplication8.MainPage"
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
    xmlns:controls="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls"
    xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006" 
    mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="800" 
    d:DataContext="{d:DesignData SampleData/MainViewModelSampleData.xaml}"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="Portrait"  Orientation="Portrait"
    shell:SystemTray.IsVisible="False">

    <!--LayoutRoot is the root grid where all page content is placed-->
    <Grid x:Name="LayoutRoot" Background="Transparent">
 
        <!--Panorama control-->
        <controls:Panorama Title="NetFlix Browser" ItemsSource="{Binding Items}">
            <controls:Panorama.Background>
                <ImageBrush ImageSource="PanoramaBackground.png"/>
            </controls:Panorama.Background>
 
            <controls:Panorama.HeaderTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Genre}" FontSize="40"></TextBlock>
                </DataTemplate>
            </controls:Panorama.HeaderTemplate>
            
            <controls:Panorama.ItemTemplate>
                <DataTemplate>
                    <ListBox ItemsSource="{Binding Films}">
                        <ListBox.ItemTemplate>
                        <DataTemplate>
                            <StackPanel Orientation="Horizontal">
                                <Image Source="{Binding BoxArt.SmallUrl}"></Image>
                            <StackPanel Margin="0,0,0,17" Width="432">
                                <TextBlock Text="{Binding Name}" TextWrapping="Wrap" Style="{StaticResource PhoneTextExtraLargeStyle}"/>
                                <TextBlock Text="{Binding ShortSynopsis}" TextWrapping="Wrap" Margin="12,-6,12,0" Style="{StaticResource PhoneTextSubtleStyle}"/>
                            </StackPanel>
                            </StackPanel>
                        </DataTemplate>
                        </ListBox.ItemTemplate>
                    </ListBox>
                </DataTemplate>
            </controls:Panorama.ItemTemplate>
            
        </controls:Panorama>
    </Grid>
 
    <!--Panorama-based applications should not show an ApplicationBar-->

</phone:PhoneApplicationPage>

4. Création du Contexte métier pour la page:


Ajoutons une propriété Genre dans la classe ItemViewModel. Elle servira à filtrer les films par genre pour chaque page et s’affichera dans le header du contrôle panorama.

 private string _genre;

public string Genre
{
    get
    {
        return _genre;
    }
    set
    {
        if (value != _genre)
        {
            _genre = value;
            NotifyPropertyChanged("Genre");
        }
    }
}

Instancions 2 ItemsViewModel qui représenteront chacun une page de notre contrôle Panorama et un genre de film. Nous choisissons 2 genres de films : Zombies et Blockbusters.

Pour cela, nous modifions la méthode LoadData de la classe MainViewModel de la manière suivante:

         /// <summary>
        /// Creates and adds a few ItemViewModel objects into the Items collection.
        /// </summary>
        public void LoadData()
        {
            // Sample data; replace with real data
            this.Items.Add(new ItemViewModel() { Genre = "Zombies"});
            this.Items.Add(new ItemViewModel() { Genre = "Blockbusters" });
            this.IsDataLoaded = true;
        }

5. Chargement asynchrone des films en utilisant le service OData Netflix


Ajoutons une propriété Films dans la classe ItemViewModel, qui représente la liste des films associés à la propriété Genre.

NetFlixCatalog.Model.Title est une des classes proxy générée dans NetFlixModel.cs et représente 1 film. Pour bénéficier de WCF Data Services afin d’appeler le service OData NetFlix et récupérer une collection de films, le type de la propriété Films doit être DataServiceCollection<NetFlixCatalog.Model.Title>.

 DataServiceCollection<Title> _Films;
public DataServiceCollection<Title> Films 
{
    get
    {
        return _Films;
    }
    set
    {
        if (_Films != value)
        {
            _Films = value;
            NotifyPropertyChanged("Films");
        }
    }
}

Chaque page chargera ses films en fonction du genre. Nous allons donc préparer la requête utilisée dans l’appel au service netflix, au format OData. Les librairies WCF Data Services vont nous aider à utiliser le service OData en soumettant nos requêtes (sous la forme d’Uri Http REST) et vont parser le flux Atom en retour en ventilant les éléments dans les classes proxy appropriées.

La version classique de WCF Data Services pour Silverlight nous aurait permis de soumettre une requête Linq plutôt qu’une Uri pour bénéficier du filtrage à la source, mais la version pour WP7 ne bénéficie pas (encore) de cette fonctionnalité bien pratique.

Nous allons donc construire nos requêtes “à la main”. Vous pouvez tester très facilement votre requête dans le navigateur et consulter le site www.odata.org qui en explique la syntaxe.

Voici la requête que nous allons utiliser, où $GENRE = le genre à filtrer :

 https://odata.netflix.com/v1/Catalog/Genres(' $GENRE')/Titles?$select=Name,BoxArt,ShortSynopsis
 Ajoutons une méthode LoadFilms dans la classe ItemViewModel:
 NetflixCatalog.Model.NetflixCatalog _netflix;
Uri _netFlixUri = new Uri("https://odata.netflix.com/Catalog");

private void LoadFilms()
{
    var genreUri = new Uri(string.Format("/Genres('{0}')/Titles?$select=Name,BoxArt,ShortSynopsis", Genre), UriKind.Relative);

    _netflix = new NetflixCatalog.Model.NetflixCatalog(_netFlixUri);
    Films = new DataServiceCollection<Title>(_netflix);
    Films.LoadAsync(genreUri);
}
 Il faut à présent instancier un contexte de type NetflixCatalog en lui passant l’adresse du service Netflix, ce qui nous permettra d’adresser le service et de travailler sur la collection Films. 
La requête est passée en paramètre à LoadAsync qui se chargera de l’exécuter de manière asynchrone, et de peupler la collection Films. 
Comme la classe DataServiceCollection<T> hérite de ObservableCollection<T> et grâce au binding, notre vue sera notifiée une fois les films récupérés et notre liste rafraichie automatiquement. 
 Il reste à appeler la méthode LoadFilms, ce que nous pouvons faire dans le setter de Genre:
 public string Genre
{
    get
    {
        return _genre;
    }
    set
    {
        if (value != _genre)
        {
            _genre = value;
            LoadFilms();
            NotifyPropertyChanged("Genre");
        }
    }
}
 Notre modèle métier est maintenant prêt. Remarquez que nous avons bénéficié des classes proxy pour représenter un film (classe NetflixModel.Title), ce qui représente un réel gain de temps.
 Vous pouvez à présent démarrer votre application, qui se déploiera dans l’émulateur. 
 Le but de ce tutoriel était de montrer qu’en très peu de temps (et de lignes de codes), il est possible de réaliser une application Silverlight pour WP7 qui manipule des données d’un service distant OData comme Netflix. 
Votre application bénéficie même d’un comportement dynamique : si vous ajoutez un élément supplémentaire dans la propriété Items du MainViewModel, vous obtiendrez automatiquement une page supplémentaire pour un type de films différent. 
Par contre, c’est une application très basique, qui n’aborde pas les aspects localisation et tombstoning qui n’étaient pas le sujet de cet article.
 Vous pouvez télécharger les sources ici: