Comment exposer une application WCF RIA Services à d’autres clients : revue de l’application initiale (1/5)

Nous allons voir dans cette série d’articles/tutoriaux comment exposer une application classique basée sur le framework WCF RIA Services et Silverlight 4 vers d’autres clients : Excel, Windows Phone 7 et une application Web standard. Pour cela, nous verrons alors comment exposer vos services RIA Services sous la forme d’un flux OData, sous la forme de services WCF « classiques », et pour finir en utilisant une sérialisation JSON. Voici donc l’agenda des articles que je prévois de publier dans les jours suivants :

1 – Revue de l’application initiale (cet article)
2 – Exposition du service sous la forme d’un flux OData consommé par Excel puis par une application écrite avec WebMatrix
3 – Exposition du service en WCF “classique” pour une consommation depuis WPF puis depuis Windows Phone 7
4 – Exposition du service en JSON pour une consommation par une application HTML5/jQuery
5 – Les étapes à suivre pour porter cette application vers Windows Azure et SQL Azure

Cela permet ainsi de couvrir un scénario qui m’est souvent demandé lors mes rendez-vous clients. Vous partez sur un nouveau projet à courte échéance (quelques mois) sur lequel vous désirez obtenir la productivité la plus importante. Si vous partez alors sur la stack Microsoft complète avec une présentation sous Silverlight, vous pouvez suivre le workflow suivant : votre base de données (SQL Server, Oracle, mySQL, etc.) –> Entity Framework 4.0 –> WCF RIA Services –> Silverlight 4.

Entity Framework 4.0 (notre ORM) s’occupera ainsi de générer la couche d’accès aux données pour vous en effectuant un mapping objet-relationnel de vos tables et en vous fournissant de plus l’accès à un provider LINQ. WCF RIA Services se branchera sur ce provider LINQ pour exposer automatiquement cette couche d’accès aux données à travers le réseau au client Silverlight qui lui proposera un ensemble de contrôles/frameworks de haut niveau vous permettant de gérer rapidement de l’intelligence dans les écrans.

Vous avez donc une architecture n-tiers propre entièrement générée automatiquement pour vous.

Oui mais comment ouvrir ensuite vos données à d’éventuels clients moins riches fonctionnellement ? Justement, nous allons voir cela ensemble ! Et nous verrons ainsi pourquoi je vous avais déjà présenté ce schéma montrant l’ouverture de RIA Services par le passé :

3573504107

Note : j’ai déjà réalisé une série de 3 tutoriaux sur WCF RIA Services très simples à prendre en main. Je vous invite à les consulter si vous n’êtes pas encore familier avec cette technologie permettant de créer rapidement et proprement des applications d’entreprise n-tiers basées sur Silverlight 4/.NET 4.0 : 

- Tutorial Silverlight 4 WCF RIA Services avec 0 ligne de code
- Tutorial Silverlight 4 WCF RIA Services avec 0 ligne de code – partie 2
- Tutorial Silverlight 4 WCF RIA Services avec quelques lignes de code – partie 3

Par ailleurs, je vous conseille également la lecture en complément des articles d’Audrey Petit sur WCF RIA Services : https://blogs.developpeur.org/audrey/archive/tags/WCF+RIA+Services/default.aspx

Le but de ces nouveaux tutoriaux n’étant pas de vous faire découvrir RIA Services (pour cela, rendez-vous à mes 3 tutoriaux précédents), nous allons partir sur une application déjà conçue dont nous allons rapidement analyser l’anatomie dans ce premier article.

Vous pouvez télécharger le code source de la solution de base ici :

Et vous pouvez tester l’application « Live » hébergée dans Windows Azure ici :

Ou ici dans une autre fenêtre : https://bookclub.cloudapp.net et découvrir les fonctionnalités décrites ci-dessous. Vous pouvez même installer cette application sur votre machine en exploitant les capacités natives “Out of browser” de Silverlight en faisant un simple clic-droit sur l’application :

BookClubScreen007

L’une des étapes se proposera d’ailleurs de vous expliquer le travail à faire pour héberger une solution WCF RIA Services dans Windows Azure/SQL Azure.

Note : je suis parti de l’application proposée par Deepesh Mohnani sur son blog : https://blogs.msdn.com/b/deepm . Je l’ai légèrement modifiée pour les besoins de cet article.

Avant de démarrer, vous remarquerez si vous avez déjà joué avec WCF RIA Services que les copies écrans de l’application qui va nous servir de base présentent un design d’une application Silverlight Business Application potentiellement différente de celle livrée par défaut lorsque vous installez les outils Silverlight 4 pour Visual Studio 2010. En effet, entre temps, plusieurs nouveaux templates ont été livrés et proposent des designs différents (qui a dit plus joli ?). Vous pouvez les télécharger ici : nouveaux thèmes pour projets Silverlight Business Application et j’en compte aujourd’hui 4 de plus que celui livré par défaut.

Voici à quoi ils ressemblent après un « New Project » et un coup de F5 :

WCFRIASvcThemes 
Dans notre cas, nous allons partir dans la suite sur une base du « Cosmopolitan Theme » rappelant un peu les concepts du design Metro de Windows Phone 7. Vous remarquerez alors que de nombreux éléments sont restylés avec ces thèmes : les child window, les contrôles type progress bar, datagrid, etc. Bref, avec ça, vous devriez être armés pour faire des applications pas trop moches même si vous êtes un développeur peu doué en design comme moi ! 

Pour vous rendre compte du résultat, vous pouvez les tester Live ici :

- Accent : https://www.silverlight.net/content/samples/sl4/themes/accent.html
- Cosmopolitan : https://www.silverlight.net/content/samples/sl4/themes/cosmopolitan.html
- Jet Pack : https://www.silverlight.net/content/samples/sl4/themes/jetpack.html
- Windows 7 : https://www.silverlight.net/content/samples/sl4/themes/windows7.html

Le Jet Pack reste l’un de mes préférés :

WCFRIASvcThemesJetPack 

Allez, c’est parti, regardons un peu comment cette application qui nous permet de gérer une bibliothèque est constituée :

BookClubScreen002 
Voici rapidement la liste des contrôles présents sur ce diagramme (modélisant à la louche le formulaire Home.xaml) à connaitre pour la suite :

1 – categoryListBox de type ListBox
2 – bookListBox de type ListBox
3 – sans nom de type DataPager
4 – ctrlBook de type BookControl (contrôlé personnalisé présent dans le répertoire \Controls

A – txtFilter de type TextBox
B – btnEdit de type Button contenu dans le contrôle BookControl
C – btnSubmit de type Button
D – cboSort de type ComboBox

Par ailleurs, vous noterez la présence de 2 « DomainDataSource » qui alimentent le client Silverlight. Le 1er nommé categoryDomainDataSource est le plus simple :

 <riaControls:DomainDataSource AutoLoad="True" d:DesignData="{d:DesignInstance my1:Category, CreateList=true}" 
  Height="0" LoadedData="categoryDomainDataSource_LoadedData_1" 
  Name="categoryDomainDataSource" QueryName="GetCategoriesQuery" Width="0">
    <riaControls:DomainDataSource.DomainContext>
        <my:BookClubContext />
    </riaControls:DomainDataSource.DomainContext>
</riaControls:DomainDataSource>

Il se contente en effet d’appeler la méthode GetCategoriesQuery() qui pilote donc notre couche d’accès aux données serveur à travers la méthode distante suivante : 

 public IQueryable<Category> GetCategories()
{
    return this.ObjectContext.Categories.OrderBy(c => c.CategoryName);
}

présente dans notre Domain Service RIA nommé BookClubService

Ce 1er Domain Service sert de source pour le binding de notre contrôle 1 affiché à gauche s’occupant de présenter la liste des catégories de nos différents bouquins :

 <ListBox ItemsSource="{Binding ElementName=categoryDomainDataSource, Path=Data}" 
Name="categoryListBox" ItemTemplate="{StaticResource CategoryItemTemplate}" />

Le contrôle 2 au centre de l’écran est responsable de l’affichage des bouquins en fonction de plusieurs critères. Sa source de données est bien évidemment le DomainDataSource nommé bookDomainDataSource. Cependant, cette source de données est pilotée de plusieurs manières. Le plus simple est de jeter un coup d’œil au XAML décrivant ce DomainDataSource et de le commenter :

 <riaControls:DomainDataSource AutoLoad="True" d:DesignData="{d:DesignInstance my1:Book, CreateList=true}" 
  Height="0" LoadedData="bookDomainDataSource_LoadedData_1" Name="bookDomainDataSource" 
  QueryName="GetBooksQuery" Width="0">
    <riaControls:DomainDataSource.SortDescriptors>
        <riaControls:SortDescriptor PropertyPath="{Binding Path=SelectedItem.Content, ElementName=cboSort}" />
    </riaControls:DomainDataSource.SortDescriptors>
    <riaControls:DomainDataSource.FilterDescriptors>
        <riaControls:FilterDescriptor Operator="IsEqualTo" 
                PropertyPath="CategoryID" Value="{Binding ElementName=categoryListBox, Path=SelectedItem.CategoryID, FallbackValue=1}" />
        <riaControls:FilterDescriptor Operator="Contains" 
                PropertyPath="{Binding Path=SelectedItem.Content, ElementName=cboSort}" Value="{Binding ElementName=txtFilter, Path=Text}" />
    </riaControls:DomainDataSource.FilterDescriptors>
    <riaControls:DomainDataSource.DomainContext>
        <my:BookClubContext />
    </riaControls:DomainDataSource.DomainContext>
</riaControls:DomainDataSource>

Tout d’abord, la source principale des données va être la méthode GetBooksQuery() qui par défaut nous retourne logiquement l’ensemble des livres stockés dans notre base de données. Cependant, comme cette méthode expose des objets sous la forme d’un IQueryable, nous allons pouvoir piloter cette source de données distantes avec des critères de tris et de filtres.

1er critère appliqué est un critère de tri que l’on voit avec ce morceau particulier :

 <riaControls:DomainDataSource.SortDescriptors>
    <riaControls:SortDescriptor PropertyPath="{Binding Path=SelectedItem.Content, ElementName=cboSort}" />
</riaControls:DomainDataSource.SortDescriptors>

On indique ici que l’on souhaite trier la liste des livres actuellement affichés dans le contrôle 2 au centre trié par auteur ou par titre. Nous sommes donc « bindé » à la valeur actuellement sélectionné dans le contrôle D de type ComboBox. Dès que l’utilisateur va modifier cette valeur, grâce à la magie du binding, la source de données va être pilotée en conséquence avec le DomainDataSource et les données vont remonter les différents tiers.

Ensuite, 2 critères de filtres sont appliqués dynamiquement à notre DomainDataSource. Le 1er est le suivant :

 <riaControls:FilterDescriptor Operator="IsEqualTo" PropertyPath="CategoryID" 
Value="{Binding ElementName=categoryListBox, Path=SelectedItem.CategoryID, FallbackValue=1}" />

Il s’occupe de demander à n’afficher uniquement les livres correspondant à la catégorie actuellement retenue dans le contrôle 1 à gauche affichant justement la liste des catégories. En conséquence, si je passe de la catégorie « Business » à « Technology », à nouveau grâce à la magie du Binding, l’objet DomainDataSource va être notifié de ce changement et va paramétrer la méthode distante GetBooksQuery() pour lui demander de rapatrier les bouquins de type « Technology ». Comme notre contrôle au centre est bindé à cet objet de haut niveau qu’est le DomainDataSource, il sera lui aussi notifié lorsque les données seront revenues jusqu’au dernier tiers (le client Silverlight) et se rafraichira automatiquement.

Le 2ème filtre suivant :

 <riaControls:FilterDescriptor Operator="Contains" 
PropertyPath="{Binding Path=SelectedItem.Content, ElementName=cboSort}" 
Value="{Binding ElementName=txtFilter, Path=Text}" />

s’occupe quant à lui de filtrer sur la propriété « Author » ou « Title » en fonction de ce que vous tapez dans le contrôle TextBox A et de ce qui est choisi dans la ComboBox. Donc si vous tapez par exemple le mot clé « Papa » dans cette zone de texte, que vous filtrer sur « Author » et que vous avez choisi la catégorie « Technology », vous aurez le résultat suivant :

BookClubScreen003

Pour la pagination, c’est encore le même DomainDataSource qui est sollicité. C’est décidément la partie centrale de notre application !

Ce dernier est tout simplement piloté par le contrôle de haut niveau 3 (DataPager) à travers le XAML suivant :

 <sdk:DataPager PageSize="10" Source="{Binding ElementName=bookDomainDataSource, Path=Data}" />

On indique que l’on souhaite afficher les livres par lot de 10. Le client Silverlight ne chargera donc les données que 10 par 10 et cela évitera donc de renvoyer systématiquement l’ensemble des livres à chaque fois côté client.

Pour finir sur les contrôles « bindés », il y a bien sûr le livre actuellement sélectionné et affiché en détail dans la vue tout à droite dans le contrôle 4 personnalisé. Dans son cas, c’est extrêmement simple :

 <appControls:BookControl DataContext="{Binding ElementName=bookListBox, Path=SelectedItem}" x:Name="ctrlBook" />

Le contrôle BookControl est tout simplement alimenté par l’élément actuellement sélectionné dans le contrôle 2 nommé bookListBox. A nouveau, du coup, le simple fait de changer le livre sélectionné dans la liste va automatiquement faire rafraichir notre contrôle de détails BookControl.

Tout cela s’est donc opéré grâce aux contrôles de haut niveau fournis par WCF RIA Services pour Silverlight. Vous noterez ainsi que vous n’avez aucun code spécifique behind surveillant les changements d’état des contrôles de saisies ou des méthodes de callback gérées pour ensuite notifier l’interface graphique du changement de contexte des données. Pourtant, on a en place des notions relativement avancées comme la pagination, des notions de synchronisation entre les contrôles, etc. On profite ici pleinement de la magie de WCF RIA Services et de la puissance du moteur de binding de Silverlight 4 !

Si vous souhaitez voir un peu ce qu’il se passe sur le réseau en fonction des manipulations que vous faites dans l’interface Silverlight, vous pouvez utiliser la barre de développement d’IE9 en appuyant sur la touche F12 et en lançant une trace réseau. C’est en quelque sorte notre Firebug à nous. Voici alors la requête qui part lorsque je charge pour la première fois l’application et que je navigue vers la 2ème page avec le contrôle DataPager :

BookClubScreen004

Ce qui donne l’URL suivante en détail :

https://localhost:62206/ClientBin/BookShelf-Web-Services-BookClubService.svc/binary/GetBooks$where=((it.CategoryID.CompareTo(2)%253d%253d0)%2526%2526(it.Author.ToLower().Contains(%2522%2522)%253d%253dTrue))&$orderby=it.Author&$skip=10&$take=10&$includeTotalCount=

Le fait de prendre par exemple la 2ème page se matérialise par cette partie de notre requête HTTP : $skip=10&$take=10. En gros, ne prends pas les 10 premiers, je les ai déjà mais prends les 10 suivants (donc bien la 2ème page avec des pages de 10 éléments).

Finissons l’anatomie de notre application en regardant le comportement des 2 contrôles de type Button B & C. Vous noterez que par défaut ces boutons sont grisés et semblent inaccessibles. Pour pouvoir avoir accès au bouton « Edit » permettant d’éditer un livre, il faut que vous soyez authentifié. Cela se fait simplement en cliquant sur le bouton « Login » et en vous créant un nouveau compte si ce n’est déjà fait. Je vous rappelle que cela est basé sur l’authentification d’ASP.NET avec ses Membership Provider comme déjà vu dans la partie 3 de mes premiers tutoriaux.

J’ai d’ailleurs légèrement modifié le comportement des écrans de login comme vous pouvez le voir ci-dessous en incluant les étapes proposées dans mon tutorial suivant : Tutorial Silverlight 4: ajout d’une image dans le profile d’authentification utilisé par WCF RIA Services . Cela nous permet ainsi d’ajouter une image à notre profile utilisateur depuis la WebCam, une image sur le disque ou par drag’n’drop.

BookClubScreen005

Une fois logué, on peut alors bien éditer un livre en pressant sur le bouton qui n’est désormais plus grisé :

BookClubScreen006 

Cet activation ou non du bouton se fait grâce à cette ligne de code behind dans le contrôle BookControl :

 this.btnEdit.SetBinding(Button.IsEnabledProperty, 
   new Binding("User.IsAuthenticated") { Source = App.Current.Resources["WebContext"] });

On vérifie simplement si l’utilisateur est actuellement authentifié et si c’est le cas on change la propriété IsEnabled de false à true.

Il faut également coupler cela avec des attributs sur les méthodes côté serveur pour n’autoriser la mise à jour (à travers la méthode UpdateBook) que pour une population donnée. Par exemple, si en ajoutant cet attribut côté serveur sur le DomainService :

 [RequiresRole("Admin")] 
public void UpdateBook(Book currentBook)

Cela permet de n’autoriser que les utilisateurs membres du groupe “Admin” à faire des mises à jour. Ainsi, même si vous vous authentifiez en vous créant un compte sur mon application hébergée dans Azure, vous ne pourrez pas mettre à jour les données de ma base SQL Azure et vous aurez une exception de levée lorsque vous presserez le bouton « Submit » .

Pour finir, vous noterez que le bouton « Submit » (contrôle C) n’est actif que lorsqu’il y a eu des données marquées comme ayant changées (« dirty »). Cela se passe grâce au XAML suivant dans Home.xaml :

 <Button Command="{Binding ElementName=bookDomainDataSource, Path=SubmitChangesCommand}" 
  Content="Submit" Height="26" Name="btnSubmit" Width="69" 
  HorizontalAlignment="Right" Grid.Column="3" Margin="0,3,6,8" />

On utilise ici les capacités de « Commanding » arrivées avec Silverlight 4 en branchant le bouton sur la commande SubmitChangesCommand.

Voilà, nous avons fini de faire le tour du propriétaire. Il me paraissait intéressant de jeter un coup d’œil rapide à la manière dont cette application était conçue car elle utilise pas mal de concepts intéressants. Certains diront qu’elle aurait pu être plus propre en implémentant des patterns de type MVVM pour éviter d’utiliser certains converters et rendre nos vues plus simples mais c’est un autre débat. ;-)

Allez, rendez-vous à la 2ème étape pour savoir comment commencer à ouvrir nos données à d’autres clients avec une exposition de type OData.

David