Blog - Title

August, 2010

  • Stéphanie Hertrich

    [Sharepoint 2010] API REST: Impossible de charger le type System.Data.Services.Providers.IDataServiceUpdateProvider

    • 0 Comments

    Sharepoint 2010 fournit une API REST (WCF Data Services aka ADO.Net Data Services aka Astoria :) ) permettant d’accéder et de requêter directement les données d’un site Sharepoint.

    Si par-exemple vous tentez d’accéder à une de vos listes: http://<votresite>/_vti_bin/ListData.svc/<votreliste> vous risquez de rencontrer cette erreur :

    Impossible de charger le type System.Data.Services.Providers.IDataServiceUpdateProvider …

    image

    Pour résoudre le problème, installez le patch suivant selon votre OS:

    ADO.NET Data Services Update for .NET Framework 3.5 SP1 for Windows 7 and Windows Server 2008 R2

    ou

    ADO.NET Data Services Update for .NET Framework 3.5 SP1 for Windows 2000, Windows Server 2003, Windows XP, Windows Vista and Windows Server 2008

    N’oubliez pas de rebooter…

  • Stéphanie Hertrich

    Développer un client Silverlight pour Sharepoint 2010 en appliquant le pattern MVVM (2/2)

    • 0 Comments

    Voici la seconde partie de l’article. Vous trouverez la première partie ici : Développer un client Silverlight pour Sharepoint 2010 en appliquant le pattern MVVM (1/2) 

    Ainsi que les sources complètes:

    Nous avons créé une ListBox classique Silverlight, mis en place tous les éléments de l’architecture MVVM ainsi que l’accès aux données dans Sharepoint. Malgré tout, l’application ne fonctionne pas encore tout à fait correctement.

    Dans cette seconde partie, nous verrons:
        les opérations à effectuer sur les classes pour que le binding fonctionne correctement
        l’écueil classique lors d’un développement Silverlight/WPF dans un contexte multithread
        comment passer d’un rendu habituel de liste à quelque chose de plus…original :) grâce à Silverlight et xaml

    Sommaire de cet article

    1. Implémentation de INotifyPropertyChanged par le ViewModel
    2. Qui dit asynchrone dit contexte multi-thread
    3. Modification de l’aspect visuel de la liste
    4. Téléchargez les sources

     

    Implémentation de INotifyPropertyChanged par le ViewModel

    Pour que le binding fonctionne, la classe cible du binding – la couche ViewModel en MVVM - doit implémenter un mécanisme de notification à savoir l’interface INotifyPropertyChanged. Cette interface contient un événement qui doit être levé chaque fois que le binding d’une propriété de la classe doit être réévalué. Notre classe WinesListVM va donc implémenter INotifyPropertyChanged et l’événement sera levé chaque fois que l’on affectera les propriétés SelectedItem et WineItems de cette classe. Voilà comment cela se présente:

    Dans le fichier WinesListVM.cs, ajoutez la référence au namespace suivante :

    using System.ComponentModel;

    Implémentez INotifyPropertyChanged pour la classe WinesListVM:

    public class WinesListVM : INotifyPropertyChanged

    Cliquez droit sur INotifyPropertyChanged et sélectionnez “Implement Interface”. Visual Studio ajoute pour vous les membres de l’interface à implémenter, à savoir ici un événement PropertyChanged.

    public event PropertyChangedEventHandler PropertyChanged;

    Ajoutez la méthode suivante qui permet de déclencher l’événement dont le paramètre est le nom de la propriété modifiée (dans notre cas ce sera “SelectedItem” ou “WineItems”). Le moteur de binding actualisera tous les éléments de l’UI qui sont bindés à cette propriété.

    protected void OnPropertyChanged(string name)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(name));
        }
    }

    Il reste ensuite à lever l’événement, chaque fois que l’on affecte la propriéte “SelectedItem” et “WineItems”:

    IEnumerable<Wine> _wineItems;
    public IEnumerable<Wine> WineItems
    {
        get
        {
            return _wineItems;
        }
        set
        {
            if (_wineItems != value)
            {
                _wineItems = value;
                OnPropertyChanged("WineItems");
            }
        }
    }
     
    Wine _selectedItem;
    public Wine SelectedItem
    {
        get
        {
            return _selectedItem;
        }
        set
        {
            if (_selectedItem != value)
            {
                _selectedItem = value;
                OnPropertyChanged("SelectedItem");
            }
        }
    }

    Voici le code complet de la classe WinesListVM :

    using System;
    using Wines.SL.DAL;
    using System.Collections.Generic;
    using Wines.SL.Model;
    using System.ComponentModel;
    using System.Threading;
     
    namespace Wines.SL.ViewModel
    {
        public class WinesListVM : INotifyPropertyChanged
        {
            
            public WinesListVM(IWinesDAL winesDal)
            {
                winesDal.WineItemsUpdated += ((s, e) => WineItems = e.WineItems);
            }
     
            IEnumerable<Wine> _wineItems;
            public IEnumerable<Wine> WineItems
            {
                get
                {
                    return _wineItems;
                }
                set
                {
                    if (_wineItems != value)
                    {
                        _wineItems = value;
                        OnPropertyChanged("WineItems");
                    }
                }
            }
     
            Wine _selectedItem;
            public Wine SelectedItem
            {
                get
                {
                    return _selectedItem;
                }
                set
                {
                    if (_selectedItem != value)
                    {
                        _selectedItem = value;
                        OnPropertyChanged("SelectedItem");
                    }
                }
            }
     
            public event PropertyChangedEventHandler PropertyChanged;
     
            protected void OnPropertyChanged(string name)
            {
                PropertyChangedEventHandler handler = PropertyChanged;
                if (handler != null)
                {
                    handler(this, new PropertyChangedEventArgs(name));
                }
            }
     
        }
    }

    A présent, chaque fois que l’on modifie la propriété “SelectedItem” du ViewModel, l’élément sélectionné dans la vue sera synchronisé avec cette propriété. De la même manière, chaque fois que l’on rechargera la liste des vins et que l’on réaffectera la propriété WineItems, la contenu de liste xaml sera rafraichie à l’écran avec les nouvelles données.

    Relançons l’application : F5….boum une exception UnauthorizedAccessException : Accès inter-threads non valide : mais courage c’est la dernière étape….

    image

    Qui dit asynchrone dit contexte multi-thread

    Souvenez-vous, dans la DAL, nous accédons aux données de manière asynchrone. Les données provenant de Sharepoint arrivent donc dans une callback par un thread dédié. Que ce soit en Winform, en WPF, en Silverlight…en C# ou même en VB, seul le thread qui a créé le contrôle graphique peut en modifier ses propriétés. Or dans notre cas, si l’on déroule le chemin d’exécution c’est le thread qui exécute le code de la callback dans la DAL qui au final va modifier la propriété ItemsSource du contrôle List de l’UI Silverlight.

    Il faut donc passer la main au thread principal à un moment ou un autre dans la chaine pour que ce soit lui qui exécute le code de réévaluation du binding. Puisque c’est la DAL qui effectue un appel asynchrone, c’est elle qui va prendre la responsabilité de se remettre dans le thread principal. Et voici comment procéder en utilisant le SynchronizationContext (la même opération peut être effectuée avec Dispatcher.BeginInvoke):

    Déclarons un objet de type SynchronizationContext dans la classe WinesFromSP, que l’on initialize de la manière suivante (sans oublier de référencer le namespace System.Threading):

    SynchronizationContext _syncCtxt = SynchronizationContext.Current;

    Puis on encapsule la levée de l’évenement par la méthode Post du SynchronizationContext. Le code situé dans le corps de la clause Post sera exécuté dans le thread principal dès que celui-ci en aura la possibilité, ce qui règle notre problème.

    _syncCtxt.Post(unused => WineItemsUpdated(this, new WineItemsUpdatedEventArgs(WineItems)), null); 

    voici le code complété:

    using Wines.SL.Model;               // Pour utilisation de la couche Model
    using System;
    using Microsoft.SharePoint.Client;  // Référence au ClientObjectModel
    using System.Collections.Generic;   // Pour les Generics
    using System.Linq;
    using System.Threading;                  // Pour Linq
     
    namespace Wines.SL.DAL
    {
        public class WinesFromSP : IWinesDAL
        {
            // Contexte de connexion au serveur
            ClientContext _ctx = new ClientContext(@"http://stephe:2828/MySite");
            // La liste des vins résultante
            ListItemCollection _wines;
            // Pour notifier l'update des éléments
            public event EventHandler<WineItemsUpdatedEventArgs> WineItemsUpdated;
            SynchronizationContext _syncCtxt = SynchronizationContext.Current;
     
     
            public void RequestFromServer()
            {
                // Récupère la liste "Wines"
                var wineList = _ctx.Web.Lists.GetByTitle("Wines");
                // Récupère les éléments de la liste
                _wines = wineList.GetItems(new CamlQuery());
                // Charge les éléments de la liste
                _ctx.Load(_wines);
                // Exécute la requête asynchrone
                _ctx.ExecuteQueryAsync(onWineElemsLoaded, onWineElemsError);
            }
     
            public IEnumerable<Wine> WineItems { get; set; }
     
            private void onWineElemsLoaded(object sender, ClientRequestSucceededEventArgs args)
            {
                // Succes
                // _wines.AsEnumerable() pour repasser en Linq To Object [using System.Linq]
                WineItems = _wines.AsEnumerable().Select(w => new Wine()
                {
                    Name = w["Name"].ToString(),
                    Count = Convert.ToUInt16(w["Count"])
                });
     
                if (WineItemsUpdated != null)
                { 
                    _syncCtxt.Post(unused => WineItemsUpdated(this, new WineItemsUpdatedEventArgs(WineItems)), null); 
                }
                
            }
     
            private void onWineElemsError(object sender, ClientRequestFailedEventArgs args)
            {
                // Traitement sur erreur
            }
     
        }
    }

    On relance l’application et cette fois-ci on peut voir apparaître notre liste de vins.

    image

    Remarquez que grâce au binding du SelectedItem, si vous mettez un point d’arrêt sur le setter de la propriété SelectedItem du ViewModel, on s’y arrête chaque fois que l’on change d’élément sélectionné dans la liste.

    Modification de l’aspect visuel de la liste

    En Silverlight comme en WPF, le choix d’un contrôle s’effectue par-rapport au rôle fonctionnel que l’on souhaite lui attribuer. Le rôle fonctionnel d’une ListBox est de contenir un ensemble d’éléments ainsi qu’un élément courant sélectionné.

    Ainsi nous allons déclarer un contrôle de type “ListBox” dans notre vue, non pas parce qu’elle afficherait une liste au sens où les éléments sont placés les uns en dessous des autres, mais parce que le ViewModel qui lui est associé comprend les 2 fonctionnalités “ensemble d’éléments” (WineItems) et “élément sélectionné” (SelectedItem).

    Pour avoir le rendu ci-dessous, il faudra effectuer les opérations suivantes:
        modifier la position d’affichage des éléments de la liste : plutôt que de les placer les uns au-dessus des autres, on les positionne de manière aléatoire à l’écran
        modifier l’aspect visuel d’un élément de la liste : au lieu du titre de l’élément, on affiche un cercle de diamètre = à la quantité de bouteilles disponibles + son titre

    Le résultat attendu peut être codé de différentes manières en xaml (utilisation de styles, de templates, …). Pour cet exemple j’ai préféré utiliser un code qui fonctionne aussi bien en Silverlight qu’en WPF.

    image

    Modifier la position d’affichage des éléments de la liste

    Par défaut, les éléments d’une ListBox sont affichés dans un conteneur (ListBox.ItemsPanel) de type StackPanel ce qui est parfait si l’on veut disposer les éléments horizontalement ou verticalement les uns par-rapport aux autres. Dans notre cas, on souhaite positionner chaque élément à des coordonnées qui lui sont propres. C’est ce que permet le conteneur de type Canvas.

    Dans le fichier WinesListView.xaml, complétez le code de la ListBox de la manière suivante :

    <Grid x:Name="LayoutRoot">
        <ListBox ItemsSource="{Binding WineItems}" SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
            <ListBox.ItemsPanel>
                <ItemsPanelTemplate>
                    <Canvas Background="DarkSeaGreen" >
                    </Canvas>
                </ItemsPanelTemplate>
            </ListBox.ItemsPanel>
        </ListBox>
    </Grid>

    Relancez l’application (F5) : vous verrez à présent tous les éléments se positionner sur le bord gauche du Canvas. En effet, aucune directive de placement n’a été effectuée pour l’instant:

    image 
    Redéfinissons à présent l’ItemTemplate de la ListBox pour pouvoir modifier les coordonnées et le visuel d’un élément de la liste. Pour l’instant, nous y mettons simplement une zone de texte.

    <ListBox ItemsSource="{Binding WineItems}" SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <TextBlock  Text="{Binding}" 
                                    FontSize="9" FontWeight="Bold" FontStyle="Italic" 
                                    Foreground="Brown" 
                                    HorizontalAlignment="Center" VerticalAlignment="Center" 
                                    Opacity="0.6">
                            <TextBlock.RenderTransform>
                                <TranslateTransform X="{Binding Converter={StaticResource ElemToPositionConverter}}" Y="{Binding Converter={StaticResource ElemToPositionConverter}}" />
                            </TextBlock.RenderTransform>
                        </TextBlock>
                    </DataTemplate>
                </ListBox.ItemTemplate>
                <!-- Modification du type de conteneur de l'ensemble des éléments de la ListBox -->
                <ListBox.ItemsPanel>
                    <ItemsPanelTemplate>
                        <Canvas Background="DarkSeaGreen" >
                        </Canvas>
                    </ItemsPanelTemplate>
                </ListBox.ItemsPanel>
            </ListBox>

    Nous effectuerons une translation du TextBlock dans le Canvas en bindant x (Canvas.Left) et y (Canvas.Top) avec l’élément de la liste en appliquant un converter. Le converter nous permet d’effectuer les ajustements entre ce que nous propose le ViewModel et le rendu que l’on souhaite obtenir dans la View. Le converter sera appelé chaque fois que le binding de l’élément concerné sera réévalué (ici 1 seule fois lors du chargement). Dans notre cas, nous allons récupérer un élément de type Wine en entrée du converter et nous souhaitons renvoyer un nombre aléatoire qui représentera un nombre de pixels. Le paramètre d’entrée du converter ne nous sert à rien. Mais on aurait très bien pu décider de positionner les éléments en fonction de la nom ou de leur quantité.

    Créez un nouveau répertoire “Converters” dans le répertoire “View” du projet. Créez-y une nouvelle classe “ElemToPositionConverter".cs”. Copiez-y le code suivant:

    using System;
    using System.Windows.Data;
     
    namespace Wines.SL.View.Converters
    {
        public class ElemToPositionConverter : IValueConverter
        {
            static Random _rand = new Random();
     
            public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                return _rand.Next(30, 250);
            }
     
            public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                throw new NotImplementedException();
            }
        }
    }

    Un converter doit implémenter l’interface IValueConverter et dans notre cas, seul le sens de conversion Source vers UI sera à définir. Nous renvoyons simplement un nombre aléatoire entre 30 et 250 qui correspondra au positionnement x et y en pixels de chacun de nos éléments sur l’écran. Dans du code xaml, l’utilisation d’un converter s’effectue en l’instanciant dans les resources de la View. On peut ensuite l’utiliser dans les contrôles en le référençant comme une StaticResource.

    Voici le résultat, remarquez qu’à chaque exécution, les éléments se placent différemment.

    image

     

     

    Ajoutons à présent les cercles dont le diamètre représente la quantité de bouteilles. Chaque élément de la liste ne sera plus uniquement représenté par un TextBlock, mais par un TextBlock surmonté d’une Ellipse. On a vu tout à l’heure, que le contrôle qui permet contenir des éléments les uns en dessous des autres est un StackPanel. Nous allons donc placer le TextBlock existant ainsi qu’une Ellipse dans un contrôle StackPanel:

    <navigation:Page x:Class="Wines.SL.View.WinesListView" 
               xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
               xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
               xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
               xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
               mc:Ignorable="d"
               xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
               xmlns:cnv="clr-namespace:Wines.SL.View.Converters"
               d:DesignWidth="640" d:DesignHeight="480"                 
               Title="MainView Page">
        
        <navigation:Page.Resources>
            <cnv:ElemToPositionConverter x:Key="ElemToPositionConverter" />
        </navigation:Page.Resources>
     
        <Grid x:Name="LayoutRoot">
            <ListBox ItemsSource="{Binding WineItems}" SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <StackPanel>
                            <StackPanel.RenderTransform>
                                <TranslateTransform X="{Binding Converter={StaticResource ElemToPositionConverter}}" Y="{Binding Converter={StaticResource ElemToPositionConverter}}" />
                            </StackPanel.RenderTransform>
                            <Ellipse  x:Name="Circle"  
                                        Width="{Binding Count}" 
                                        Height="{Binding Count}">
                                <Ellipse.Fill>
                                    <SolidColorBrush 
                                        Color="Azure" 
                                        Opacity="0.6"/>
                                </Ellipse.Fill>
                            </Ellipse>
                            <TextBlock 
                                    Text="{Binding}" 
                                    FontSize="9" FontWeight="Bold" FontStyle="Italic" 
                                    Foreground="Brown" 
                                    HorizontalAlignment="Center" VerticalAlignment="Center" 
                                    Opacity="0.6"/>
                        </StackPanel>
                    </DataTemplate>
                </ListBox.ItemTemplate>
                <!-- Modification du type de conteneur de l'ensemble des éléments de la ListBox -->
                <ListBox.ItemsPanel>
                    <ItemsPanelTemplate>
                        <Canvas  Background="DarkSeaGreen" >
                        </Canvas>
                    </ItemsPanelTemplate>
                </ListBox.ItemsPanel>
            </ListBox>
        </Grid>   
    </navigation:Page>

    Remarquez que la translation Canvas.Left et Canvas.Top a été déplacé dans le StackPanel pour que l’ellipse ainsi que le texte profitent du placement aléatoire.

     

    Nous avons terminé !! Cet article est assez long car très détaillé, mais si vous téléchargez les sources de la solution, vous constaterez qu’elle contient finalement très peu de lignes de code.

    image

     

     

    Téléchargez les sources :

  • Stéphanie Hertrich

    Développer un client Silverlight pour Sharepoint 2010 en appliquant le pattern MVVM (1/2)

    • 2 Comments

    Cet article décrit comment réaliser une application Silverlight qui présente la liste externe “Wines” (Sharepoint 2010 Dev Partie 1 : Création d’une liste externe basée sur des données stockées dans SqlServer) dans une forme plus ludique, et en appliquant le pattern Model-View-ViewModel. Nous verrons notamment un moyen d’intégrer les requêtes asynchrones du Client Object Model dans un modèle en couche, grâce aux événements C#.
                Vous pouvez suivre cet article en utilisant n’importe quelle liste Sharepoint, du moment qu’elle contient une colonne de type texte et une colonne de type numérique
                Je précise que le pattern MVVM n’a aucun rapport avec le rendu visuel de la liste…vous pouvez donc sauter directement à la deuxième partie de l’article pour la partie xaml

    Téléchargez les sources complètes :

    Sujets abordés dans l’article:

    Ce premier article, abordera les notions suivantes:
        Création d’une application Silverlight
        Mise en place de l’architecture MVVM
        Utilisation du Client Object Model de Sharepoint 2010
        Gestion de notifications asynchrones dans une architecture MVVM

    Dans le deuxième article ici, nous verrons:
        l’implémentation de INotifyPropertyChanged par les classes du ViewModel pour que le binding fonctionne correctement
        l’écueil classique lors d’un développement Silverlight/WPF dans un contexte multithread
        comment passer d’un rendu habituel de liste à quelque chose de plus…original :) grâce à Silverlight et xaml 

    Pour rappel, voici notre liste “Wines” dans Sharepoint:

    image

    Et voici maintenant le rendu de notre liste Silverlight à la fin de l’article. Chaque vin est représenté par un cercle dont le diamètre est fonction de la quantité de bouteilles. Je conviens que ce n’est pas forcément très joli et je suis sûre que vous ferez beaucoup mieux…mais ça a le mérite d’être très différent de la version “Liste” de Sharepoint :)

    image 

    Pourquoi Silverlight, pourquoi MVVM ?

    Grâce à un client Silverlight pour Sharepoint 2010, on pourra facilement :
        obtenir un rendu graphique élaboré
        accéder à des ressources externes pour intégrer d’autres sources d‘information
        accéder aux données de Sharepoint grâce au Client Object Model    
        choisir d’héberger l’application Silverlight comme une WebPart dans Sharepoint, ou l’héberger directement dans IIS pour l’utiliser directement

    Pour plus d’information sur le développement et la technologie Silverlight : Introduction à Silverlight

    Grâce à MVVM on pourra facilement : 
        changer la provenance et les moyens d’accès à la source de données sans remettre en question le code métier ou de présentation
        ajouter de nouvelles fonctionnalités facilement 
        changer la partie visuelle sans toucher au code métier
        faciliter la mise en oeuvre des tests  

    Pour plus d’informations sur MVVM :WPF Apps With The Model-View-ViewModel Design Pattern et aussi http://japf.developpez.com/tutoriels/dotnet/mvvm-pour-des-applications-wpf-bien-architecturees-et-testables/

    Sommaire de l’article 1

    1. Création de la solution dans Visual Studio 2010
    2. Mise en place des éléments pour l’architecture MVVM
    3. Rappels concernant MVVM
    4. Accès à la liste Wines dans Sharepoint
    5. Création du ViewModel
    6. Création de la View

     

    Création de la solution dans Visual Studio 2010

    Lancez Visual Studio 2010 en mode Administrateur. Créer un nouveau projet de type Application Silverlight et appelez-le Wines.SL

    image

    Laissez les valeurs par défaut dans l’écran suivant :

    image

    La solution contient 2 projets :

        Wines.SL : qui contient le code métier et les pages Silverlight 
        Wines.SL.Web : qui contient les éléments nécessaires au chargement et au déploiement de l’application   

     image 

    Déploiement et communication cross-domain de Silverlight:

    Pour pouvoir exécuter les applications Silverlight durant la phase de codage et de débuggage, Visual Studio utilise un hébergement custom. Nous pourrons donc voir directement le résultat dans un navigateur, comme si l’application était hébergée dans IIS. On pourra ensuite uploader le .xap dans Sharepoint, pour pouvoir l’afficher dans une WebPart ou la déployer dans IIS. Ce point fera l’objet d’un article ultérieur.

    Quand l’application Silverlight s’exécute dans une WebPart de Sharepoint, il n’y a pas de problématique Cross-Domain car l’hébergement est identique. Dans notre cas par-contre, il faut autoriser l’application Silverlight à utiliser les services Web du serveur Sharepoint en déployant le fichier “clientaccesspolicy.xml” suivant à la racine du serveur Sharepoint (C:\inetpub\wwwroot\wss\VirtualDirectories\2828) :

    <?xml version="1.0" encoding="utf-8" ?>
    <!-- This file must be put on the Server from where a Silverlight client gets infomration (e.g. SharePoint Site) -->
    <access-policy>
      <cross-domain-access>
        <policy>
          <allow-from http-request-headers="*">
            <domain uri="*"/>
          </allow-from>
          <grant-to>
            <resource include-subpaths="true" path="/"/>
          </grant-to>
        </policy>
      </cross-domain-access>
    </access-policy>

    Plus d’informations : Communications inter-domaines

    Mise en place des éléments pour l’architecture MVVM

    Rappels concernant MVVM

    MVVM (Model-View-ViewModel) est une architecture multi-tiers qui permet de découpler la couche Vue (View) de la couche Métier (Model) à travers la couche Cas d’utilisation (ViewModel).

    Le ViewModel remplit 2 rôles :

        il présente des éléments provenant du model et d’autres ViewModel, pour présenter un ensemble d’informations pertinentes pour un cas d’utilisation fonctionnel de l’application – et donc pour une ou plusieurs Views.
        il implémente les interfaces et propriétés adaptées à l’usage d’une technologie de binding comme Silverlight ou WPF  

    [La dénomination “Cas d’utilisation” pour le View Model est un terme qui m’est propre et n’engage donc que moi, mais je trouve qu’il correspond bien à la fonction de cette couche]. 

    En pratique, la couche View accède à la couche ViewModel par l’intermédiaire du binding.

     

    image

     

     

    Un des intérêts de ce modèle est de localiser tout le code d’accès à la logique métier par la vue dans les classes du ViewModel, et ainsi de ne pas avoir de code behind dans les fichiers .cs des vues. Ainsi, toute la logique d’accès aux métier est située dans le ViewModel et seul le XAML (et les converters) perdurent côté View. Un autre avantage indéniable est de faciliter les tests qui sont alors pratiqués sur la couche ViewModel. Et bien sûr, comme toutes les architectures multi-tiers, il permet de découpler la couche d’accès aux données, le modèle, les cas d’utilisation et l’UI.

    Je ne vais pas m’étendre davantage sur ce pattern dans cet article. Pour plus d’informations sur l’architecture MVVM, je vous recommande l’article du MSDN magazine suivant, avec les sources d’un cas pratique :WPF Apps With The Model-View-ViewModel Design Pattern et aussi http://japf.developpez.com/tutoriels/dotnet/mvvm-pour-des-applications-wpf-bien-architecturees-et-testables/ (en français :) )

    C’est Parti:

    En règle générale, on prépare un répertoire pour chaque couche dans la solution. Créez 3 répertoires View, Model et ViewModel dans le projet Wines.SL (click droit sur le projet –> Ajouter –> Nouveau Répertoire).

    image

     

    Accès à la liste “Wines” dans Sharepoint

    Définition de la couche Model

    Nous allons définir la classe qui représente 1 élément de la liste des vins. Elle proposera autant de propriétés que de colonnes de la liste Sharepoint. Ajoutez une classe “Wine.cs” dans le répertoire “Model”et définissez les attributs suivants: Name, Count et ToString()

    using System;
    using System.Net;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Documents;
    using System.Windows.Ink;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Animation;
    using System.Windows.Shapes;
     
    namespace Wines.SL.Model
    {
        public class Wine
        {
            // = la colonne Name de la liste SP
            public string Name { get; set; }
     
            // = la colonne Count dans la liste SP
            public UInt16 Count { get; set; }
     
            // Affichage par défaut de l'élément
            public override string ToString()
            {
                return Name;
            }
        }
    }

     

    Définition de la couche d’accès aux données

    L’accès aux données dans Sharepoint fait l’objet d’une nouvelle couche “Accès aux données” (Data Access Layer) dans notre solution. En effet, le fait d’accéder aux informations à travers Sharepoint n’est en aucun cas une contrainte du modèle lui-même (qui n’est finalement qu’un IEnumerable<Wine>), mais de la manière dont on a décidé de stocker les données. On trouvera dans la répertoire DAL, les classes qui constituent les différentes manières d’accéder aux informations métier (une seule dans cet article : une liste SharePoint via le Client Object Model).

    Créez un répertoire DAL dans le projet Wines.SL. Ajoutez une classe “WinesFromSP.cs” dans le répertoire “DAL” et ajoutez les références suivantes dans le fichier .cs :

    using Wines.SL.Model;               // Pour utilisation de la couche Model
    using Microsoft.SharePoint.Client;  // Référence au ClientObjectModel
    using System.Collections.Generic;   // Pour les Generics
    using System.Linq;                  // Pour Linq

    Ajoutez une méthode “RequestFromServer” qui se chargera de récupérer les données depuis Sharepoint et les présentera comme un ensemble d’éléments de type Wine dans une propriété WineItems.

    public class WinesFromSP
        {
            public void RequestFromServer()
            {
     
            }
     
            public IEnumerable<Wine> WineItems { get; set; }
        }

    Utilisation du Client Object Model de Sharepoint 2010

    Le Client Object Model (CSOM) est une nouveauté de Sharepoint 2010. C’est une librairie de classes qui permet facilement d’accéder aux objets Sharepoint côté client et évite ainsi d’avoir directement recours aux Web Services de Sharepoint. Le contexte du CSOM va retenir vos opérations de requêtage pour les envoyer en une seule fois au serveur et ainsi optimiser la communication entre le client Silverlight et le serveur Sharepoint. Par défaut, le CSOM fonctionne dans le contexte de l’utilisateur de la session Windows. Assurez-vous donc que les droits sur la liste soient attribués à l’utilisateur avec lequel vous avez ouvert la session Windows pour faire fonctionner l’application.

    Pour pouvoir utiliser le CSOM, il faut référencer les assemblies suivantes qui se trouvent dans  "C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\ClientBin":

    image

    Ce code permet de récupérer la liste des vins. Complétez la classe “WinesFromSP” comme ci-dessous:

    public class WinesFromSP
        {
            // Contexte de connexion au serveur
            ClientContext _ctx = new ClientContext(@"http://stephe:2828/MySite");
            // La liste des vins résultante
            ListItemCollection _wines;
                    
            public void RequestFromServer()
            {
                // Récupère la liste "Wines"
                var wineList = _ctx.Web.Lists.GetByTitle("Wines");
                // Récupère les éléments de la liste
                _wines = wineList.GetItems(new CamlQuery());
                // Charge les éléments de la liste
                _ctx.Load(_wines);
                // Exécute la requête asynchrone
                _ctx.ExecuteQueryAsync(onWineElemsLoaded, onWineElemsError);
            }
     
            private void onWineElemsLoaded(object sender, ClientRequestSucceededEventArgs args)
            {
                // Succes
            }
     
            private void onWineElemsError(object sender, ClientRequestFailedEventArgs args)
            {
                // Traitement sur erreur
            }
        }

    On commence par instancier le contexte client en spécifiant le site concerné. Ce contexte sera enrichi par nos requêtes et demandes de chargement jusqu’à ce que l’on envoie effectivement la demande au serveur.

            ClientContext _ctx = new ClientContext(@http://stephe:2828/MySite);

     

    On spécifie dans le contexte que l’on souhaite utiliser la liste “Wines”.

    var wineList = ctx.Web.Lists.GetByTitle("Wines");

     

    On demande ensuite à récupérer tous les éléments de la liste wines. SP Linq (Linq To Sharepoint) ne fonctionne que côté serveur, il restera donc des instructions CAML dans le code client.

                _wines = wineList.GetItems(new CamlQuery());

    Il faut ensuite charger les éléments que vous souhaitez obtenir en réponse du serveur par Load.

                _ctx.Load(_wines);

    La requête est ensuite envoyée et exécutée côté serveur de manière asynchrone par l'appel de ExecuteQueryAsync. Ceci implique que les éléments récupérés dans _wines seront exploitables dans la callback onWineElemsLoaded. Il n’y a donc pas de communication avec le serveur avant l’appel de ExecuteQueryAsync.

                _ctx.ExecuteQueryAsync(onWineElemsLoaded, onWineElemsError);

     

    Je vous recommande la lecture de l’article Introduction au Client Object Model qui détaille les modalités de requêtage et l’utilisation du Client Object Model ainsi que Sharepoint Guidance 2010 qui contient tout un chapitre très détaillé sur le CSOM.

    Le schéma suivant qui synthétise les étapes du requêtage est extrait de SP2010 Guidance:

    image

    A présent, nous ajoutons un mécanisme de notification accessible facilement de l’extérieur de la classe :
        un événement WineItemsUpdated qui renverra la liste des vins et auquel le ViewModel pourra s’abonner pour être notifié du rafraichissement des données
        une propriété WineItems qui représente la dernière liste des vins chargée

    Nous verrons tout à l’heure comment l’événement sera utilisé par la couche ViewModel pour être notifiée et permettre ainsi à la vue d’être actualisée via le binding.

    Pour rester dans les règles de l’art concernant l’événement WineItemsUpdated :
        la signature doit être de type EventHandler<T>
        le 1er paramètre du delegate est l’émetteur de l’événement
       le second paramètre est l’information <T> à faire passer, qui doit hériter de la classe système EventArgs 

    Ajoutez le fichier “WineItemsUpdatedEventArgs.cs” dans le répertoire DAL:

    using System;
    using System.Collections.Generic;
    using Wines.SL.Model;
     
    namespace Wines.SL.DAL
    {
        public class WineItemsUpdatedEventArgs : EventArgs
        {
            public WineItemsUpdatedEventArgs(IEnumerable<Wine> wineItems)
            {
                WineItems = wineItems;
            }
     
            public IEnumerable<Wine> WineItems 
            { 
                get; 
                private set; 
            }
        }
    }

    Intéressons-nous au code de la callback onWineElemsLoaded :

    public class WinesFromSP
        {
            // Contexte de connexion au serveur
            ClientContext _ctx = new ClientContext(@"http://stephe:2828/MySite");
            // La liste des vins résultante
            ListItemCollection _wines;
            // Pour notifier l'update des éléments
            public event EventHandler<WineItemsUpdatedEventArgs> WineItemsUpdated;
                    
            public void RequestFromServer()
            {
                // Récupère la liste "Wines"
                var wineList = _ctx.Web.Lists.GetByTitle("Wines");
                // Récupère les éléments de la liste
                _wines = wineList.GetItems(new CamlQuery());
                // Charge les éléments de la liste
                _ctx.Load(_wines);
                // Exécute la requête asynchrone
                _ctx.ExecuteQueryAsync(onWineElemsLoaded, onWineElemsError);
            }
     
            public IEnumerable<Wine> WineItems { get; set; }
     
            private void onWineElemsLoaded(object sender, ClientRequestSucceededEventArgs args)
            {
                // Succes
                // wines.AsEnumerable() pour repasser en Linq To Object [using System.Linq]
                WineItems = _wines.AsEnumerable().Select(w => new Wine()
                {
                    Name = w["Name"].ToString(),
                    Count = Convert.ToUInt16(w["Count"])
                });
     
                if(WineItemsUpdated != null)
                {
                    WineItemsUpdated(this, new WineItemsUpdatedEventArgs(WineItems));
                }
            }
     
            private void onWineElemsError(object sender, ClientRequestFailedEventArgs args)
            {
                // Traitement sur erreur
            }
        }

    On récupère la liste d’éléments renvoyés par le serveur. Puis pour chaque élément w de “wines”, on projette une instance de la classe métier “Wine”. La clause Select renvoit un type IEnumerable<Wine> (car dans notre cas, le corps du Select renvoit un type “Wine”) que l’on affecte à la variable WineItems.

    WineItems = _wines.AsEnumerable().Select(w => new Wine()
               {
                   Name = w["Name"].ToString(),
                   Count = Convert.ToUInt16(w["Count"])
               });

    Plutôt que d’expliquer longuement la projection à l’aide de Select, je vais l’écrire sans la syntaxe Linq, et tout sera dit ou presque :

    WineItems = new List<Wine>();
     
    foreach (var w in wines)
    {
        WineItems.Add(new Wine() 
            { Name = w["Name"].ToString(), 
              Count = Convert.ToUInt16(w["Count"]) });
    }
     
    Remarquez que dans la version longue “non Linq”, on instancie directement la liste des vins avant d’y ajouter des éléments :
    WineItems = new List<Wine>();
    Comme on ajoute les éléments les uns après les autres, WineItems doit être une “liste” alors que dans la version Linq, un “enumerable” est suffisant puisqu’il ne sera plus modifié après sa création. De plus, la version Linq permet d’avoir une exécution différée de ce code de projection. Ce dernier sera évalué immédiatement mais ne sera exécuté que lorsque l’on utilisera la variable WineItems. Attention, dans la version avec Linq, on force l’utilisation de Linq To Objects en appelant .AsEnumerable(). En effet, le type ListItemCollection du CSOM ne supporte pas la méthode d’extension .Select.
    Les dernières lignes de la callback déclenchent un événement qui notifiera ceux qui y sont abonnés que la liste des vins a été actualisée.

    Création du ViewModel

    Le ViewModel va s’abonner à l’événement WineItemsUpdated du modèle et va présenter la liste des vins à la couche vue.

    Pour cela, créons une classe WinesListVM.cs dans le répertoire ViewModel du projet. Pour s’abonner à l’évenement du modèle, cette classe doit connaître son type. Or, quand on y pense, tout ce qui intéresse le ViewModel, c’est d’être notifié chaque fois que le liste des vins est réactualisée et de récupérer cette liste à jour comme un IEnumerable<Wine>. Mais le fait que les données proviennent de Sharepoint, cela ne la concerne pas : pour être plus précis, WinesListVM n’a pas besoin de connaître le type concrêt WinesFromSP défini dans notre DAL. Elle pourrait très bien s’abonner à une classe qui récupère la liste des vins en provenance de SqlServer, ou d’un fichier : le code du ViewModel serait le même.

    Pour apporter plus de souplesse, nous allons définir la signature de ce qu’est un modèle, au regard du ViewModel : une interface qui présente un événement dont le delegate a pour argument un WineItemsUpdatedEventArgs.

    Nous retournons donc dans le répertoire DAL et y ajoutons un fichier IWinesDAL.cs contenant une interface qui comporte un événement : le même que celui défini dans WinesFromSP

    using System;
    using System.Collections.Generic;
    using Wines.SL.Model;
     
    namespace Wines.SL.DAL
    {
        public interface IWinesDAL
        {
            event EventHandler<WineItemsUpdatedEventArgs> WineItemsUpdated;
        }
    }

    Notre classe WinesFromSP respecte cette signature et elle implémente donc cette interface comme ceci:

    namespace Wines.SL.DAL
    {
        public class WinesFromSP : IWinesDAL
        { …

    A présent, nous pouvons retourner dans le code du ViewModel, dans la classe WinesListVM. Le constructeur de cette classe prendra en paramètre un type IWinesDAL, c’est à dire n’importe quelle classe qui implémente cette interface, et donc qui respecte la signature de l’événement WinesItemsUpdated. Le viewModel peut ensuite s’abonner à cet événement pour être notifié des changements et récupérer la liste à jour.

    Dans le ViewModel, on ajoute également la propriété WineItems ainsi que SelectedItems pour récupérer l’élément sélectionné dans la vue.

    using System;
    using Wines.SL.DAL;
    using System.Collections.Generic;
    using Wines.SL.Model;
     
    namespace Wines.SL.ViewModel
    {
        public class WinesListVM
        {
            public WinesListVM(IWinesDAL winesDal)
            {
                winesDal.WineItemsUpdated += ((s, e) => WineItems = e.WineItems);
            }
            
            public IEnumerable<Wine> WineItems
            {
                get;
                set;
            }
     
            public Wine SelectedItem
            {
                get;
                set;
            }
        }
    }

    Le code d’abonnement  à l’événement

    public WinesListVM(IWinesDAL winesDal)
    {
        winesDal.WineItemsUpdated += ((s, e) => WineItems = e.WineItems);
    }

    est comparable au code ci-dessous. Il a été simplifié à l’aide d’une expression lambda pour le delegate:

    public WinesListVM(IWinesDAL winesDal)
    {
        winesDal.WineItemsUpdated += new EventHandler<WineItemsUpdatedEventArgs>(winesDal_WineItemsUpdated);
    }
     
    void winesDal_WineItemsUpdated(object sender, WineItemsUpdatedEventArgs e)
    {
        WineItems = e.WineItems;
    }
     

    Création de la View

    Nous allons présenter les vins sous forme de cercles dont la taille dépend du nombre de bouteilles. Les cercles seront placés aléatoirement dans la fenêtre. Dans le répertoire “View”, ajoutez une nouvelle Page Silverlight et appelez-le WinesListView.xaml.  Nous allons commencer par un rendu “normal de liste” et nous changerons l’aspect visuel par la suite. Copiez le code suivant dans votre fichier WinesListView.xaml :

    <navigation:Page x:Class="Wines.SL.View.WinesListView" 
               xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
               xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
               xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
               xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
               mc:Ignorable="d"
               xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
               d:DesignWidth="640" d:DesignHeight="480"
               Title="MainView Page">
        <Grid x:Name="LayoutRoot">
            <ListBox ItemsSource="{Binding WineItems}" SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
            </ListBox>
        </Grid>
    </navigation:Page>

    Remarquez que l’on binde la propriété WineItems du ViewModel à l’ItemsSource de la ListBox et le SelectedItem du ViewModel à la propriété SelectedItem de la ListBox…sauf que pour l’instant, rien ne relie le ViewModel à la vue et c’est ce à quoi nous allons remédier maintenant.

    Ouvrez le fichier App.xaml.cs, et placez-vous sur Application_Startup(). Voici le code qui a été auto-généré pour ce fichier, lors de la création du projet.

    public partial class App : Application
        {
     
            public App()
            {
                this.Startup += this.Application_Startup;
                this.Exit += this.Application_Exit;
                this.UnhandledException += this.Application_UnhandledException;
     
                InitializeComponent();
            }
     
            private void Application_Startup(object sender, StartupEventArgs e)
            {
                this.RootVisual = new MainPage();
            }
     
            private void Application_Exit(object sender, EventArgs e)
            {

    Ajoutez les références aux namespaces :

    using Wines.SL.View;
    using Wines.SL.DAL;
    using Wines.SL.ViewModel;

    Modifiez le code de Application_Startup de la manière suivante, pour instancier notre vue WinesListView plutôt que la page par défaut qui est créée lors de la création d’un projet Silverlight:

    private void Application_Startup(object sender, StartupEventArgs e)
    {
        WinesDALFromSP dal = new WinesFromSP();
        WinesListVM vm = new WinesListVM(dal);
        dal.RequestFromServer();
     
        this.RootVisual = new WinesListView() { DataContext = vm };
    }

    Reprenons le code de Application_Startup ligne par ligne. C’est dans cette méthode que l’on va lier les différentes couches de notre architecture:
        La première ligne permet d’instancier la classe qui communique avec SP côté serveur
        L’instance dal est ensuite passée en paramètre du ViewModel dans la 2ème ligne (relie le ViewModel au Model) 
        La 3ème ligne permet de déclencher le chargement de la liste des vins.
        La 4ème ligne permet d’instancier la View et de binder le DataContext de cette vue au ViewModel. Nous verrons dans l’étape suivante comment le DataContext se comportera dans la vue. (relie la View au ViewModel)

    Il est intéressant de connecter nos couches de manière “externe” (dans du code qui ne fait partie d’aucune des couches, comme App.xaml.cs) c’est à dire qu’aucune couche n’instancie explicitement des classes appartenant à d’autres couches. Ainsi, non seulement nous avons l’avantage de centraliser le code de rattachement entre les couches, mais les couches sont découplées au maximum (on pourrait aller plus loin et faire “sauter” ce code de liaison entre les couches en utilisant un mécanisme d’injection de dépendance comme MEF ou Unity…mais c’est un autre sujet). Il existe également des frameworks MVVM qui effectueront ces opérations pour vous : instanciation des ViewModels et liaison entre les couches.

    Revenons sur le code de la View:

    <navigation:Page x:Class="Wines.SL.View.WinesListView" 
               xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
               xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
               xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
               xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
               mc:Ignorable="d"
               xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
               d:DesignWidth="640" d:DesignHeight="480"
               Title="MainView Page">
        <Grid x:Name="LayoutRoot">
            <ListBox ItemsSource="{Binding WineItems}" SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
            </ListBox>
        </Grid>
    </navigation:Page>

    Plus haut, nous avons initialisé la propriété DataContext de la Page avec notre ViewModel par le code:

        this.RootVisual = new MainView() { DataContext = vm };

    En Silverlight (et en WPF), les DataContext des contrôles héritent de celui de leur parent. Ainsi, notre Grid et notre ListBox utilisent le même DataContext que celui qui a été affecté à MainView, à savoir “vm”. Lorsque l’on binde une propriété, le binding s’effectue dans le contexte du DataContext. En clair : dans notre cas lorsque l’on binde la propriété “ItemsSource” de la ListBox sur “WineItems”, cela revient à binder “ListBox.ItemsSource” sur “vm.WineItems”. Lorsque l’on binde , la propriété “SelectedItem” de la ListBox sur “SelectedItem” cela revient à binder “ListBox.SelectedItem” sur “vm.SelectedItem”.

    Le binding permettra le rafraichissement automatique des propriétés des contrôles, chaque fois que les éléments bindés sont modifiés, et inversement pour les binding 2 ways. Le but de cet article n’étant pas de réexpliquer le fonctionnement du binding et la syntaxe xaml, je vous encourage à suivre les tutoriaux du MSDN ou de voir des Webcasts sur le sujet si cela vous semble flou.

    Pour récapituler, voici un schéma simpliste de la communication entre les différents éléments, pour pouvoir afficher la liste des vins:

    image 

     

    Les différentes couches sont reliées, pourquoi ne pourrait-on faire fonctionner l’application dès maintenant ? Tapez F5 pour lancer l’application et vous obtiendrez…une jolie page blanche. Pas de panique c’est normal…il reste quelques éléments à mettre en place…

    Deuxième partie de l’article ici….

  • Stéphanie Hertrich

    Sharepoint 2010 : Dernière release de Pattern & Practices Sharepoint Guidance

    • 0 Comments

    Une mine d’informations détaillées pour bien comprendre les nouveautés de Sharepoint 2010 côté Développeurs (et une version Pdf disponible).

    http://spg.codeplex.com/

Page 1 of 1 (4 items)