Accéder à Sharepoint dans une application Silverlight pour Windows Phone 7 (3/3) : Côté client Silverlight pour WP7

Cet article est le dernier d’une série de 3 (je ne compte pas le bonus qui est pour les curieux !):

  1. Accéder à Sharepoint dans une application Silverlight pour Windows Phone 7 (1/3) : Introduction
  2. Accéder à Sharepoint dans une application Silverlight pour Windows Phone 7 (2/3) : Côté Sharepoint 
  3. Accéder à Sharepoint dans une application Silverlight pour Windows Phone 7 (3/3) : Côté client Silverlight pour WP7
  4. Bonus : Les requêtes web asynchrones avec Rx

Je vous recommande vivement de lire le premier article qui définit le besoin et introduit les concepts qui seront mis en place pour arriver à nos fins.

A ce stade, et grâce au deuxième article, nous avons configuré une WebApp Sharepoint avec les Claims en FBA et NTLM pour que les sites soient accessibles aussi bien à partir d’une application Silverlight pour Windows Phone 7 que directement à partir d’un navigateur qui utilise les credentials de la session.

Il nous reste à coder l’application Silverlight qui dans notre exemple nous permettra d’afficher une liste.
Dans cet article, vous trouverez une assemblie à télécharger que vous pourrez réutiliser dans vos projets, qui vous aidera à mettre en place la phase d’authentification en un minimum de temps et de code.

 

Notre Cas Pratique

Je souhaite afficher une simple liste qui se trouve sur mon site Sharepoint. Comme on ne change pas une équipe qui gagne, je reste sur l’exemple de ma liste des vins.

Un petit avant goût avant/après:

Ma liste dans Sharepoint :

image

Ma liste sur Windows Phone 7 dans un contrôle Panorama

image

Les étapes

  1. Création d’une application Silverlight pour Windows Phone 7
  2. L’authentification de notre application auprès de Sharepoint
  3. Récupérer la liste des vins en utilisant le CookieContainer
  4. Créer une ergonomie sympa en xaml pour notre liste des vins

La dernière partie qui affiche la liste sous la forme d’un contrôle Panorama peut tout à fait être suivie indépendamment que les données proviennent de Sharepoint.

 

1. Création de l’application Silverlight pour WP7

Pour développer une application pour Windows Phone 7, il faut installer le kit de développement pour Windows Phone 7.
Ce kit est gratuit et contient entre autres une version Express de Visual Studio, Blend 4 pour WP7 ainsi qu’un émulateur qui vous permet de tester vos applications.

Démarrez Visual Studio en mode administrateur.

Créez un nouveau projet Silverlight pour Windows Phone 7 et choisissez-lui un petit nom (ici SPListOnWP7).

image

Par défaut, Visual Studio crée une page xaml appelée MainPage. Dans notre cas, nous allons utiliser un contrôle Panorama pour page principale. Nous allons donc nous baser sur une page Panorama par défaut, plutôt que la MainPage déjà créée.

Ampoule Vous aurez peut-être remarqué qu’il existe un type de projet Panorama : je ne l’ai pas choisi car il est + loin de l’ergonomie finale que nous souhaitons obtenir. C’est malgré tout un choix tout à fait pertinent pour créer une application basée sur une page de type Panorama.

Supprimez le fichier MainPage.xaml de votre projet. Puis ajoutez un nouvel élément “Panorama Page” à votre projet que vous appellerez MainPage, pour remplacer l’ancien:

image

C’est dans le code behind de la page principale MainPage.xaml.cs que nous accèderons à nos listes Sharepoint.

AmpouleDans les exemples et démos de ce blog, vous trouverez souvent du code métier ou d’accès aux données dans le code-behind d’une page xaml. Ce genre d’écriture n’est pas recommandé car il mélange les couches Vue/Métier/Accès aux données. Malgré tout, il est pratiqué pour la démo, pour montrer le minimum de code qui ne cible pas directement le sujet de l’article. En mode projet, je vous recommande d’utiliser le pattern MVVM dont vous trouverez un exemple ici, dans un article dédié à ce sujet. Spécifiquement pour Windows Phone, vous pouvez utiliser le toolkit MVVM Light qui prend en charge la problématique de navigation. Je ne m’étends pas plus sur le sujet dans ce post...mais j’y reviendrai bientôt dans un prochain article.

 

2. L’authentification de notre application auprès de Sharepoint

Ce que l’on voudrait faire

Avec Sharepoint 2010, s’offrent 2 nouveaux moyens d’accéder à Sharepoint à partir d’un client Silverlight. L’une ou l’autre des possibilités serait un choix pertinent:

Pourquoi ça coince

Il n’y a pas une mais plusieurs raisons au fait que cela coince.

    • L’API du Client Object Model n’est pas supportée sur la version de Silverlight pour Windows Phone 7
    • A la date où j’écris ce billet, l’implémentation du Client OData pour Windows Phone 7 ne supporte pas Linq, et les requêtes sont donc à construire à la main sous la forme d’une URI http. Il n’y a donc plus vraiment d’intérêt à utiliser OData pour attaquer notre Sharepoint.
  • Dans le sdk client OData pour Windows Phone 7 on trouve bien une propriété Credentials sur la classe du contexte client, mais elle ne fonctionne pas sur la version 3 de Silverlight pour Windows Phone 7.

Comment je fais alors ?

La solution la plus simple est d’utiliser les Web Services (.asmx) qui existaient déjà dans Sharepoint 2007. Il faudra commencer par utiliser le service Web d’authentification de Sharepoint pour récupérer un jeton (CookieContainer) qui sera ensuite passé en paramètre dans dans les requêtes du modèle objet de Sharepoint.

n0etihda

  1. L’application Silverlight pour WP7 s’authentifie (FBA) auprès du service Authentication.asmx
  2. Elle récupère le CookieContainer provenant de la trame en réponse à l’authentification
  3. Elle passe ce CookieContainer dans les requêtes du modèle objet de Sharepoint. Dans cet exemple, nous utiliserons les listes et donc le service Web Lists.asmx.

Pour simplifier l’appel au service Web d’authentification de Sharepoint, je vous propose d’utiliser une assemblie (que je mets en pièce jointe) qui va se charger de nous authentifier auprès du service Authentication.asmx de notre site et qui nous renvoie le CookieContainer que l’on réinjectera dans les requêtes sur le modèle objet de Sharepoint.

Pour ne pas alourdir ce post (et ne pas embêter les lecteurs qui s’en fichent Clignement d'œil), j’ai déporté l’explication de ce projet dans la 4ème partie de cet article. J’y explique le fonctionnement de ce projet ainsi que l’utilisation de Rx framework qui nous permet ici de simplifier l’appel asynchrone à des services web.

Téléchargez cette assemblie :

et référencez-là dans le projet SPListOnSP7 :

image

Ouvrez le fichier MainPage.xaml.cs et ajoutez la référence au namespace de SPProxyForWP7. Puis instanciez la classe Context qui va nous permettre de nous authentifier en passant en paramètres:

  • l’URL du site Sharepoint
  • l’id de l’utilisateur pour l’authentification Forms
  • le password de l’utilisateur pour l’authentification Forms

Vous obtenez ce code:

 using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Net;
 using System.Windows;
 using System.Windows.Controls;
 using System.Windows.Documents;
 using System.Windows.Input;
 using System.Windows.Media;
 using System.Windows.Media.Animation;
 using System.Windows.Shapes;
 using Microsoft.Phone.Controls;
 using SPProxyLibForWP7;
  
 namespace SPListOnWP7
 {
     public partial class MainPage : PhoneApplicationPage
     {
         Context ctx = new Context(
             @"https://stephe-msft:16483",    // URL du site
             "stephe",                       // Id User FBA
             "xxxxxx");                      // Password User FBA
  
         // Constructor
         public MainPage()
         {
             InitializeComponent();
         }
     }
 }

A présent, nous allons déclencher l’authentification en appelant Authenticate dans le constructeur de la page et en passant 2 delegates qui correspondent respectivement au code à exécuter sur une authentification réussie, et sur erreur.

Remarquez que sur une authentification réussie, on récupère un CookieContainer en paramètre du delegate.

 // Constructor
 public MainPage()
 {
     InitializeComponent();
  
     _ctx.Authenticate(OnAuthenticated, OnError);
 }
  
 void OnAuthenticated(CookieContainer cc)
 {
 }
  
 void OnError(Exception e)
 {
 }

A ce stade, l’authentification est déjà opérationnelle ! Pour le vérifier, placez un point d’arrêt dans votre méthode OnAuthenticated. Démarrez votre application (F5), vous devriez tomber sur votre point d’arrêt.

3. Récupérer la liste des vins en utilisant le CookieContainer

Le CookieContainer sera vu comme un jeton d’authentification et sera réinjecté dans mes requêtes aux listes à travers les Web Services de Sharepoint (Lists.asmx). Nous allons à présent utiliser le service web Lists.asmx pour récupérer les items de notre liste des vins.

Cliquez sur “Add Service Reference” pour votre projet SPListOnWP7:

image

Ajoutez une référence au service lists.asmx en suffixant l’adresse de votre site par /_vti_bin/lists.asmx et cliquez Go.

Cette opération va nous permettre d’avoir accès un ensemble de classes proxy qui nous aideront à appeler facilement les services web pour accéder aux listes de notre site.

Modifiez le nom du namespace pour qu’il soit plus explicite, ici : SPWebServices.

Cliquez Ok.

image

Votre service a été ajouté au projet, ainsi qu’un fichier ServiceReferences.ClientConfig.

image

Le fichier ServiceReferences.ClientConfig contient la configuration des endpoints et bindings utilisés pour appeler les services web. Pour utiliser le CookieContainer dans les requêtes, il faut ajouter l’attribut suivant sur la configuration du binding:

 enableHttpCookieContainer="true"

Voici votre fichier modifié :

 <configuration>
     <system.serviceModel>
         <bindings>
             <basicHttpBinding>
                 <binding name="ListsSoap" enableHttpCookieContainer="true" maxBufferSize="2147483647" maxReceivedMessageSize="2147483647">
                     <security mode="None" />
                 </binding>
             </basicHttpBinding>
         </bindings>
         <client>
             <endpoint address="https://stephe-msft:16483/_vti_bin/lists.asmx"
                 binding="basicHttpBinding" bindingConfiguration="ListsSoap"
                 contract="SPWebServices.ListsSoap" name="ListsSoap" />
         </client>
     </system.serviceModel>
 </configuration>

Repassons à présent côté code dans le fichier MainPage.xaml.cs et ajoutez la référence au namespace du service :

 using SPListOnWP7.SPWebServices;

Dans la callback OnAuthenticated, écrivons le code nous permettant de récupérer les items de la liste des vins.

En plus détaillé:

On instancie la classe proxy cliente du service lists.asmx.

On injecte le CookieContainer dans la classe cliente.

En Silverlight, toutes les opérations d’appel à des services web se font en asynchrone, c’est pourquoi on associe une callback à l’événement de fin récupération des items de la liste.

L’appel à GetListsItemsAsync déclenche la récupération des éléments de la liste.

 void OnAuthenticated(CookieContainer cc)
 {
     ListsSoapClient lists = new ListsSoapClient();
  
     lists.CookieContainer = cc;
  
     lists.GetListItemsCompleted += new EventHandler<GetListItemsCompletedEventArgs>(lists_GetListItemsCompleted);
  
     lists.GetListItemsAsync(
         "Wines",                //List Name
         String.Empty,           //View Name
         null,                   //query
         null,                   //view fields
         null,                   //row limit
         null,                   //query options
         null);                  //webID
 }
  
 void lists_GetListItemsCompleted(object sender, GetListItemsCompletedEventArgs e)
 {
 }

A ce stade, vous pouvez positionner un point d’arrêt dans votre méthode

 void lists_GetListItemsCompleted(object sender, GetListItemsCompletedEventArgs e)

Vous devriez y tomber si vous démarrez l’application en mode debug (F5).

A présent, nous allons parser le flux en retour pour en extraire les items et les projeter dans un ensemble d’éléments fortement typés.

Pour cela, nous commençons par créer une classe qui représente un élément de la liste des vins, à savoir son nom et le nombre de bouteilles qui restent dans ma cave.

Cette classe nous permettra d’accéder aux différentes informations (titre, nb bouteilles) en relatif dans le binding dans la page xaml.

         public class WineElem
        {
            public string Title { get; set; }
            public int BottlesCount { get; set; }
            public override string ToString()
            {
                return Title;
            }
        }

Parsons à présent le flux et effectuons la projection:

 void lists_GetListItemsCompleted(object sender, GetListItemsCompletedEventArgs e)
 {
     IEnumerable<XElement> rows = e.Result.Descendants(XName.Get("row", "#RowsetSchema"));
  
     IEnumerable<WineElem> wines = from element in rows
        select new WineElem
        {
            Title = (string)element.Attribute("ows_LinkTitle"),
            BottlesCount = (int)double.Parse(element.Attribute("ows_Count").Value)
        };
 }

Tout est prêt à présent pour raccorder notre collection wines à la source de données d’un contrôle Silverlight WP7.


4. Créer une ergonomie sympa pour notre liste des vins


Notre liste sera représentée par un contrôle typique de l’ergonomie Windows Phone 7: le contrôle Panorama.

Chaque élément de la liste sera représenté par une bouteille (une image wine.png):

wine

Ajoutez ce fichier à votre projet, ainsi que l’image suivante qui servira de fond d’écran pour l’application (fondVins.png):

fondVins

Modifiez les propriétés de ce fichier dans Visuel Studio pour qu’ils soient déployés correctement:

image

Vous pouvez télécharger ces images directement ici :

Ouvrez votre fichier MainPage.xaml. Vous y trouverez le code type pour l’utilisation d’un contrôle Panorama, à savoir:

 <!--LayoutRoot contains the root grid where all other page content is placed-->
 <Grid x:Name="LayoutRoot">
     <controls:Panorama Title="my application">
  
         <!--Panorama item one-->
         <controls:PanoramaItem Header="item1">
             <Grid/>
         </controls:PanoramaItem>
  
         <!--Panorama item two-->
         <controls:PanoramaItem Header="item2">
             <Grid/>
         </controls:PanoramaItem>
     </controls:Panorama>
 </Grid>

Modifiez le titre en remplaçant

     <controls:Panorama Title="my application">

par

 <controls:Panorama Title="Ma Cave A Vins">

Nommez le contrôle PanoWinesList, puis supprimez les 2 items déclarés “en dur” : nous allons utiliser la propriété ItemsSource pour renseigner dynamiquement la source de données du contrôle Panorama.

 <controls:Panorama x:Name="PanoWinesList" Title="Ma Cave A Vins" ItemsSource="{Binding}">
 </controls:Panorama>

En affectant ItemsSource à {Binding}, on définit que la source de données est relative à la propriété DataContext que l’on va initaliser dans le code-behind. Rouvrez le fichier MainPage.xaml.cs et ajoutez la dernière ligne à la méthode :

 void lists_GetListItemsCompleted(object sender, GetListItemsCompletedEventArgs e)
 {
     IEnumerable<XElement> rows = e.Result.Descendants(XName.Get("row", "#RowsetSchema"));
  
     IEnumerable<WineElem> wines = from element in rows
                               select new WineElem
                               {
                                   Title = (string)element.Attribute("ows_LinkTitle"),
                                   BottlesCount = (int)double.Parse(element.Attribute("ows_Count").Value)
                               };
  
     PanoWinesList.DataContext = wines; 
 }

Ainsi, dans le contrôle Panorama, chaque élément sera rattaché à une instance de WineElem.

A présent, nous allons définir la représentation d’un item de la liste des vins. Nous utiliserons pour cela 3 éléments graphiques différents:

  • L’en-tête qui affichera le nom du vin
  • Une image représentant une bouteille
  • Une zone de texte représentant le nombre de bouteilles

Pour cela, nous allons modifier la propriété ItemTemplate du contrôle Panorama, qui définit le représentation d’un élément :

 <controls:Panorama x:Name="PanoWinesList" Title="Ma Cave A Vins" ItemsSource="{Binding}">
     <controls:Panorama.ItemTemplate>
         <DataTemplate>
             <Grid>
                 <Image Source="wine.png" Canvas.Top="100"  Width="300" Height="400" HorizontalAlignment="Center" VerticalAlignment="Center" />
                 <TextBlock Text="{Binding BottlesCount}" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="48"></TextBlock>
             </Grid>
         </DataTemplate>
     </controls:Panorama.ItemTemplate>
 </controls:Panorama>

Nous avons choisi une grille comme conteneur de nos éléments. Par défaut les éléments contenus dans la grille s’affichent en haut à gauche, donc nous centrerons tous les contrôles.

Reprenons l’explication élément par élément :

  • L’en-tête qui affichera la liste des vins:

    par défaut, il affiche le texte de l’élement bindé, dans notre cas, nous avons surchargé la méthode ToString() de la classe WineElem en renvoyant le nom du vin. Donc il n’y a rien à faire côté xaml, l’en-tête de l’élément affichera le nom du vin.

  • Une image représentant une bouteille :

    nous utilisons une contrôle de type Image qui pointe sur le fichier wine.png.

  • Le nombre de bouteilles sera représenté par une zone de text : TextBlock

    Remarquez que le DataContext est relatif à 1 instance de WineElem et que l’on peut donc se baser sur la propriété BottlesCount de WineElem en la bindant sur la propriété Text du TextBlock.

On ajoute également une image de fond sur la grille principale, ce qui nous amène au code complet suivant:

 <phone:PhoneApplicationPage 
     x:Class="SPListOnWP7.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"
     FontFamily="{StaticResource PhoneFontFamilyNormal}"
     FontSize="{StaticResource PhoneFontSizeNormal}"
     Foreground="{StaticResource PhoneForegroundBrush}"
     SupportedOrientations="Portrait"  Orientation="Portrait"
     shell:SystemTray.IsVisible="False">
  
     <!--LayoutRoot contains the root grid where all other page content is placed-->
     <Grid x:Name="LayoutRoot">
         <Image Source="fondVins.png" HorizontalAlignment="Stretch" VerticalAlignment="Top"></Image>
         <controls:Panorama x:Name="PanoWinesList" Title="Ma Cave A Vins" ItemsSource="{Binding}">
             <controls:Panorama.ItemTemplate>
                 <DataTemplate>
                     <Grid>
                         <Image Source="wine.png" Canvas.Top="100"  Width="300" Height="400" HorizontalAlignment="Center" VerticalAlignment="Center" />
                         <TextBlock Text="{Binding BottlesCount}" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="48"></TextBlock>
                     </Grid>
                 </DataTemplate>
             </controls:Panorama.ItemTemplate>
         </controls:Panorama>
     </Grid>
  
     <!--Panorama-based applications should not show an ApplicationBar-->
  
 </phone:PhoneApplicationPage>

Ca y est, votre application fonctionne !

Vous pouvez la démarrer en obtiendrez le résultat suivant, en vous déplaçant de page en page :

image

 


Conclusion


Dans cet article, nous avons vu comment créer une nouvelle application Silverlight pour Windows Phone 7.

Nous avons accédé aux listes d’un site Sharepoint en nous authentifiant en mode forms grâce aux Web Services (.asmx) de Sharepoint, en utilisant l’astuce du CookieContainer.

Nous avons également affiché une liste dans un contrôle typique WP7 : le Panorama.

Pour les plus curieux, rendez-vous avec le Bonus : l’utilisation de Reactive Framework pour simplifier l’appel asynchrone à des services web. Nous nous baserons sur le code de l’assemblie fournie en téléchargement un peu plus haut dans cet article.