Comment exposer une application WCF RIA Services à d’autres clients : OData endpoint (2/5)

Nous allons ici voir comment ouvrir l’application vue dans l’article précédent avec un point d’accès de type OData. Nous découvrirons les limitations du support d'OData par WCF RIA Services et ses différences avec WCF Data Services. Pour finir, nous consommerons ce point d’accès depuis Excel et WebMatrix.

Cet article est le deuxième d’une série de 5 :

1 – Revue de l’application initiale 
2 – Exposition du service sous la forme d’un flux OData consommé par Excel puis par une application écrite avec WebMatrix (cet article)
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

Introduction à OData

Avant toute chose, il faut commencer par savoir ce qu’est OData. Ainsi, si vous ne connaissez pas du tout OData, je vous conseille de lire les articles français suivants qui en font une très bonne introduction :

1 - Introduction à Open Data Protocol et WCF Data Services de Florian Casabianca (le plus détaillé et complet des 3 articles)
2 – Un article de mon collègue Thomas Conté : Interopérabilité des données avec OData 
3 – Un article de ma collègue Stéphanie Hertrich : Mais c’est quoi à la fin OData ?

D’une manière générale, vous pouvez aussi bien sûr de vous rendre sur notre site officiel en anglais : https://www.odata.org/

Aujourd’hui, on retrouve OData un peu partout chez Microsoft. Pour les développeurs, vous pouvez très facilement exposer vos données en utilisant le framework WCF Data Services de .NET 4.0. Ce framework s’occupera alors de se brancher sur une source LINQ et de l’exposer sous la forme HTTP REST avec support du CRUD (Create Read Update Delete) de vos entités. Vous aurez également un provider LINQ côté client (Silverlight, WPF, Windows Phone 7, etc.) pour requêter votre source de données distante.

Côté producteur, SharePoint 2010 expose ses listes sous la forme d’un flux OData comme vous pouvez le découvrir dans un autre tutorial de Stéphanie. SQL Azure peut exposer ses données directement sous un flux OData en cochant une simple case (actuellement disponible uniquement via SQL Labs). Windows Azure expose ses tables de données en OData, etc.

Pour la consommation, tout le monde est capable de manger du OData quelque soit le langage qu’il pratique : .NET (Silverlight, Windows Phone 7 ou client lourd), PHP, Java, JavaScript, Objective C : https://www.odata.org/developers/odata-sdk . Bref, exposer ses données via OData, c’est la garantie d’une excellente interopérabilité !

SchemaOData
Pour une présentation plus complète, rendez vous sur MSDN : https://msdn.microsoft.com/en-us/data/aa937697.aspx

Activation d’un endpoint OData sur un DomainService WCF RIA Services

Il y a 2 manières d’ajouter un endpoint OData à vos services RIA Services. Pendant la phase de génération automatique via l’assistant, il vous suffit de cocher une case:

BookClubScreen008

Et le tour est joué. Si vous avez une solution existante, vous pouvez bien sûr l’ajouter à posteriori. Pour cela, la 1ère chose à faire est de déclarer le endpoint dans votre fichier web.config à l’aide du XML suivant:

 <domainServices>
  <endpoints>
    <add name="OData" type="System.ServiceModel.DomainServices.Hosting.ODataEndpointFactory, 
               System.ServiceModel.DomainServices.Hosting.OData, Version=4.0.0.0, 
               Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
  </endpoints>
</domainServices>

Juste en dessous de la balise:

 <system.serviceModel>

Ensuite, il faut indiquer dans le code de votre DomainService quelle sera la méthode appelée par défaut sur le /NomDeVotreEntité à l’aide de l’attribut suivant :

 [Query(IsDefault = true)]

Ainsi si l’on reprend notre application Book Club, cela correspond à reprendre les 2 méthodes suivantes:

 [Query(IsDefault = true)]
public IQueryable<Book> GetBooks()
{
    return this.ObjectContext.Books.OrderBy(b => b.Title);
}
 [Query(IsDefault = true)]
public IQueryable<Category> GetCategories()
{
    return this.ObjectContext.Categories.OrderBy(c => c.CategoryName);
}

Pour terminer, l’URL sur laquelle se connecter est formatée de la manière suivante :

https://nomdevotreserveur:port/ClientBin/NomDeLaSolution-Web-NomDuDomainService.svc/OData

Si vous avez généré votre solution depuis le template “Silverlight Business Application”. Si vous avez vous-même ajouté un DomainService dans votre projet ASP.NET, cela ressemblera surement plus à cela :

https://nomdevotreserveur:port/NomDeLaSolution-NomDuDomainService.svc/OData

Au final donc, si vous suivez ces étapes pour enrichir l’application proposée dans l’article précédent, cela nous donnera l’URL suivante :

https://localhost:62206/ClientBin/BookShelf-Web-Services-BookClubService.svc/OData/

Car notre DomainService se trouve hébergé dans le projet BookShelf.Web et dans le répertoire Services. Avec mon hébergement dans Azure, vous pouvez donc jouer avec cette URL publique:

https://bookclub.cloudapp.net/ClientBin/BookShelf-Web-Services-BookClubService.svc/OData/

Ainsi, si vous utilisez l’application Silverlight OData Explorer avec cette URL, vous obtiendrez ce genre de résultat:

BookClubScreen009

Note : il faut exécuter l’application Silverlight OData Explorer en “Out of Browser” Full-Trust pour vous affranchir des limitations des appels cross-domains.

Limitations d’OData dans WCF RIA Services et différences avec WCF Data Services

Mais alors, c’est génial cet OData ! Si j’expose ma couche WCF RIA Services en OData, je suis le roi du pétrole et à moi les clients cités précédemment non ? Mouais…

OData proposé par WCF Data Services est effectivement très complet et répond parfaitement aux scénarios d’ouverture et d'interopérabilité. Mais le endpoint OData de WCF RIA Services est malheureusement beaucoup moins riche. Ainsi, si vous souhaitez contrôler la couche WCF RIA Services de manière complète depuis un client WPF, Windows Phone 7 ou même Android comme on me l’a récemment demandé, je vous conseillerais plutôt de vous y connecter via un endpoint SOAP (comme un WebService classique donc) comme nous le verrons dans l’article suivant.

Alors en quoi c’est moins riche ? Bah tout simplement, le endpoint OData ne supporte que la lecture seule des entités exposées et il ne supporte pas non plus les opérations de recherches (query operations). C’est un point sur lequel nous allons surement travailler dans la prochaine version mais à l’heure actuelle, WCF RIA Services v1.0 sera basé sur ces limitations.

Mais si elles ne sont pas un problème pour vous, c’est parfait! Du coup, cela peut répondre aux besoins suivant:

1 – Le besoin principal est la création d’une application Web RIA et vous devez avoir la productivité maximum sur ce point

2 – Vous devez exposer les mêmes données en lecture seule à d’autres clients sans la nécessité de piloter la source de données pour du tri ou du filtre 

Le 1er besoin est couvert par la chaine SL4 - WCF RIA Services - ASP.NET 4.0. Le 2ème sera couvert par le endpoint OData. Vous pourrez donc y brancher des mobiles de type Windows Phone 7, Android ou iPhone iOS sans problème dessus.

odatasdkforiphomeobjectivec

Le support d’OData dans WCF Data Services

Ok, cependant pour que vous compreniez bien les limitations de notre endpoint, faisons une comparaison entre WCF Data Services et WCF RIA Services sur le support d’OData. Prenons la base de données SQL Server NorthWind disponible publiquement avec un accès OData ici : https://services.odata.org/Northwind/Northwind.svc/

Je peux requêter cette base avec les requêtes suivantes (testez ces URL dans votre navigateur pour voir le résultat ou dans un outil de type Silverlight OData Explorer) :

https://services.odata.org/Northwind/Northwind.svc/Customers/ pour obtenir la liste de tous les clients présents dans la base

https://services.odata.org/Northwind/Northwind.svc/Customers('ALFKI')/ pour obtenir un client particulier en fonction de son Id (ici “ALFKI”)

https://services.odata.org/Northwind/Northwind.svc/Customers('ALFKI')/Orders pour obtenir la liste des commandes associées au client “ALFKI'”.

https://services.odata.org/Northwind/Northwind.svc/Customers('ALFKI')/Orders?$filter=OrderID gt 10700 pour requêter parmi l’ensemble des commandes du client “ALFKI” et retourner uniquement les commandes dont l’OrderID est supérieur à 10700.

Au passage, si vous avez un client .NET (Silverlight, Windows Phone 7 ou WPF) qui se connecte à cette source, vous pouvez profiter du provider LINQ et du proxy WCF côté client. Si on prends la dernière requête HTTP, on peut alors l’exprimer plutôt avec cette requête LINQ :

 var ordersQuery = from o in context.Orders
    where o.Customers.CustomerID == "ALFKI" && o.OrderID > 10700
        select o;

Un arbre d’expressions sera alors construit en mémoire, sera analysé puis transformé en requête HTTP lorsque nous en aurons besoin.

A tout cela s’ajoute je vous le rappelle la possibilité d’ajouter des éléments, en supprimer ou en mettre à jour.

Le support d’OData dans WCF RIA Services

Côté WCF RIA Services, regardons le type de requêtes que l’on peut faire.

https://bookclub.cloudapp.net/ClientBin/BookShelf-Web-Services-BookClubService.svc/OData/BookSet pour obtenir la liste de tous les livres présents dans ma base de données SQL Azure… et puis… c’est tout en fait !

Si vous essayez des requêtes un peu plus complexes comme la sélection d’un élément particulier:

https://bookclub.cloudapp.net/ClientBin/BookShelf-Web-Services-BookClubService.svc/OData/BookSet(39)

ou la mise en place d’un filtre:

https://bookclub.cloudapp.net/ClientBin/BookShelf-Web-Services-BookClubService.svc/OData/BookSet?$filter=BookID gt 39

Vous aurez respectivement les 2 erreurs suivantes: “Requests that attempt to access a single element using key values from a result set are not supported. ” et “Query options are not allowed. ”. Je pense que pour une fois, vous ne pourrez pas nous reprocher d’être assez clair dans le message d’erreur retourné. Clignement d'œil

Note : si vous avez besoin davantage de billes pour comprendre le positionnement de WCF vs WCF Data Services vs WCF RIA Services, vous pouvez regarder le WebCast de notre session Applications d'entreprise avec .Net 4.0 et Silverlight 4 des Microsoft Days 2010 animée par Mitsu Furuta et moi-même. Les 2/3 de cette session s’occupant de montrer chacun des framework et leurs avantages.

Sinon, utilisez les 2 !

Une autre solution consiste tout simplement à utiliser les 2 frameworks ! Quels intérêts ? A nouveau, réfléchissons au scénario suivant :

1 – Vous devez créer une application Web RIA basée sur Silverlight et vous devez avoir une excellente productivité sur ce point. C’est par exemple pour créer une application de back office en interne. 
2 – Vous devez exposer les mêmes données vers l’internet public avec un support de la lecture, de l’écriture et de recherches vers d’autres clients de type iPad/iPhone/Windows Phone 7/Android, etc.

Pour le point 1, c’est toujours Silverlight 4 et WCF RIA Services qui va vous aider. Vous pourrez donc bénéficier du fort couplage .NET 4.0 côté serveur avec Silverlight 4 côté client pour une chaine productive (contrôles, validation, etc.).

Pour le point 2, si vous mettez en place un endpoint SOAP sur vos services RIA Services (comme nous allons le voir sur l’article suivant), vous allez pouvoir facilement obtenir la lecture et l’écriture d’entités depuis les devices indiqués. Par contre, vous n’aurez pas nativement la possibilité d’effectuer des recherches comme nous l’avons vu avec les exemples précédents d’OData. On peut donc imaginer d’injecter le modèle de données consommé par RIA Services vers WCF Data Services.

En effet, si vous êtes partis sur une modélisation avec Entity Framework comme dans la solution Visual Studio 2010 que je vous ai partagée dans l’article précédent, cela se fait en quelques clics de souris. Ajoutez un nouvel élément de type WCF Data Service dans le répertoire “Services” et nommez le “BooksDataService” :

BookClubScreen015

Demandez à WCF Data Service d’introspecter le même modèle BookShelf.Web.Models.BookClubEntities. C’est celui actuellement utilisé par RIA Services. Demandez ensuite à exposer toutes les entités du modèle avec un jeu de droits complet (lecture/écriture) sur l’ensemble. Cela vous donne le code suivant :

 public class BooksDataService : DataService<BookShelf.Web.Models.BookClubEntities>
{
    public static void InitializeService(DataServiceConfiguration config)
    {
        config.SetEntitySetAccessRule(" * ", EntitySetRights.All);
        config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
    }
}

Vous aurez alors une nouvelle URL exposant un point d’accès OData complet cette fois-ci (lecture, écriture, recherches, filtres, etc.). Dans mon cas, cet URL est disponible ici :

https://bookclub.cloudapp.net/Services/BooksDataService.svc/

Et cette fois-ci, vous allez pouvoir effectuer des recherches un peu plus complexes. Si en reprend l’exemple de l’article précédent, c’est à dire rechercher les livres dans la catégorie “Technology” dont l’auteur contient le mot clé “Papa”, on peut la construire avec l’URL suivante :

https://bookclub.cloudapp.net/Services/BooksDataService.svc/Categories(1)/Books?$filter=substringof('Papa', Author) eq true 

Vous pourrez également mettre à jour des données ou les supprimer en fonction du niveau de droits que vous aurez positionné avec la méthode SetEntitySetAccessRule() . Retrouvez les autres opérateurs de recherches ici : https://www.odata.org/developers/protocols/uri-conventions#QueryStringOptions

Les inconvénients : il y a forcément quelques légers inconvénients à mélanger les 2 frameworks en même temps. D’une part, cela impose forcément une maintenance du code des 2 branches d’accès distants. Par ailleurs, cela impose également de gérer la sécurité à 2 niveaux. En effet, si l’on n’autorise la mise à jour de données que pour certaines populations à travers les attributs de WCF RIA Services, il faudra en faire de même sur la couche WCF Data Services. Nous verrons par exemple dans le prochain article l’intérêt de n’avoir qu’une seule couche de services distants sur laquelle on positionne les droits qu’une seule fois pour tous les types de clients.

Consommation du endpoint OData de RIA Services depuis Excel

Bon, après cette longue – mais nécessaire – introduction, voyons maintenant comment consommer notre service RIA depuis Excel.

Note : nous utiliserons donc ici les possibilités natives de RIA Services et son endpoint OData limité. Nous n’utiliserons pas l’accès que nous avons juste créé avec WCF Data Services.

Avant toute chose, il vous faut télécharger l’add-in PowerPivot pour Excel 2010 ici : https://www.powerpivot.com/download.aspx 

Une fois l’add-in installé, vous aurez une nouvelle section nommée PowerPivot dans le ruban d’Excel. Cliquez sur “Fenêtre PowerPivot” :

BookClubScreen010

Ensuite cliquez sur “A partir de flux de données” :

BookClubScreen011

Rentrez l’URL pointant sur les livres de mon endpoint OData hébergé dans Azure : https://bookclub.cloudapp.net/ClientBin/BookShelf-Web-Services-BookClubService.svc/OData/BookSet :

BookClubScreen012

Vous devriez obtenir cette fenêtre indiquant que tout s’est bien passé:

BookClubScreen013

Vous pouvez alors manipuler les données dans Excel pour trier, générer des graphiques, etc. :

BookClubScreen014

On peut déjà envisager de charger un nombre conséquent de données dans le client Excel qui s’occupera ensuite de travailler ces informations en mémoire uniquement sur votre machine.

Consommation du endpoint OData depuis une application écrite avec WebMatrix

Si vous ne connaissez pas encore WebMatrix, je vous invite à découvrir ce nouvel outil gratuit destiné à simplifier les petits développements Web ici : https://msdn.microsoft.com/fr-fr/asp.net/gg232708.aspx où l’on vous propose 18 tutoriaux en français très simple à suivre. Si vous êtes un développeur ASP n’ayant pas fait le pas vers ASP.NET ou si vous êtes développeur PHP souhaitant vous intéresser à autre chose, cet outil devrait vous plaire.

Par ailleurs, je vous conseille vivement aussi de regarder le WebCast d’1h de la session WebMatrix des Microsoft Days 2010 de mon collègue Pierre Couzy : [MS DAYS 2010] Introduction à WebMatrix 

Allez, nous allons ici découvrir qu’avec les Helpers proposés par WebMatrix (que l’on peut d’ailleurs réutiliser dans ASP.NET MVC 3) et la syntaxe Razor, on va construire une petite application Web en 2 temps 3 mouvement affichant nos livres depuis notre endpoint OData.

1 – Lancez WebMatrix et un nouveau site depuis le template “Starter Site” et nommez le “WebMatrixBookClub” :

WebMatrix001

Changez la page maitre “ _SiteLayout.cshtml” pour remplacer la chaine “My ASP.NET Web Page” présente à 3 endroits par “WebMatrix BookClub Application

Changez ensuite la page “Default.cshtml” avec ce contenu :

 @{  
    Layout = "~/_SiteLayout.cshtml";
    Page.Title = "List of available books";
}
<p>
    Display here the list of books from the OData endpoint
</p>

Testez l’ensemble dans votre navigateur en pressant le bouton “Run” ou la touche “F12” :

WebMatrix002

Vous aurez alors cette page ultra simple :

WebMatrix003

2 – Ensuite, pour récupérer nos données depuis le endpoint OData, il va nous falloir un helper qui n’est pas installé par défaut avec WebMatrix mais disponible en ligne et très simple à installer. Naviguez vers la même URL en ajoutant “/_Admin” :

WebMatrix004

Créez-vous un mot de passe et demandez l’affichage des packages en ligne pour télécharger et installer le helper OData :

WebMatrix005

WebMatrix006

Et si cela s’installe bien, vous aurez cela :

WebMatrix007

3 – Maintenant, on va pouvoir coder la partir qui récupère les données. Allez dans la page “Default.cshtml” et remplacez son contenu par celui-ci:

 @{  
 Layout = "~/_SiteLayout.cshtml";
 Page.Title = "List of available books";
    
 var result = OData.Get("https://bookclub.cloudapp.net/ClientBin/BookShelf-Web-Services-BookClubService.svc/OData/BookSet");
 var grid = new WebGrid(source: result,                           
            defaultSort: "Author",                            
            rowsPerPage: 10);            
}

<p>
    @grid.GetHtml(
        columns: grid.Columns(
            grid.Column("Author"),
            grid.Column("Title"),
            grid.Column("ASIN")))
</p>

On utilise ici le helper OData pour lancer la requête vers notre endpoint WCF RIA Services avec l’appel OData.Get() puis ensuite on passe le résultat à un autre helper nommé WebGrid en lui demandant gentiment de trier l’ensemble sur les auteurs et d’afficher les résultat 10 par 10.

Pour finir, plutôt que d’afficher toutes les colonnes, on demande via la méthode GetHtml() de n’afficher que les colonnes “Author”, “Title” et “ASIN”.

Si vous lancez l’ensemble, vous obtenez ce à quoi vous vous attendez :

WebMatrix008

Et voilà, 3 mouvements exactement. Le tout avec une simplicité déconcertante !

Si vous êtes curieux des autres possibilités offertes par les WebPages utilisées par l’outil WebMatrix, rendez-vous ici : https://www.asp.net/webmatrix/tutorials/asp-net-web-pages-api-reference et vous découvrirez d’autres helpers sympas comme celui pour afficher un flux Twitter, indiquer que l’on aime cette page dans FaceBook, etc. Vous pouvez d’ailleurs retrouver cette productivité et cette nouvelle syntaxe Razor pour des projets plus importants à travers ASP.NET MVC 3.

Allez, rendez-vous au prochain article qui va tâcher de vos montrer comment exposer toujours le même service à travers un endpoint SOAP pour le consommer depuis une application WPF et Windows Phone 7.

David