Dot.Blog

C#, XAML, Xamarin, UWP/Android/iOS

WinRT réinvente les Ria Services (les nouveaux WCF Data Services)

Les Ria Services, cette merveille de sophistication et de simplicité qui permet sous Silverlight d’écrire des applications orientées données en un claquement de doigts… WinRT en C#/Xaml si proche de Silverlight… Finalement la fusion se fait : tout ce qu’il y avait de bon dans Silverlight se retrouve dans WinRT, même les Ria Services, sous le nom de Data Services. Un exemple vous en dira plus long…

Le tooling

La première des choses à considérer c’est le tooling. Voici notre trousse à outils pour accomplir le miracle :

 

Grâce à cet ensemble nous allons pouvoir travailler presque comme avec les Ria Services sous Silverlight, et si c’est un petit pas pour le développeur c’est un pas de géant pour l’avenir des applications LOB sous Windows 8, et pour Windows 8 lui-même peut-être !

En passant, trouvez-vous aussi un endroit calme pour travailler (je viens de refaire mon bureau, ça aide à la concentration ! un petit aperçu d’une partie des installations que je profite d’exhiber avec fierté tant que c’est rangé :-) ).

2012-09-29 16.09.04 traité

Construire l’exemple

Plaisanter est une chose, faire quelque chose d’utile avec cet attirail de geek en est une autre, donc au boulot !

Créer le projet Windows Store

Première étape de cet exemple dont le seul but est de montrer que tout cela fonctionne d’ores et déjà (même si le manque de docs et d’exemples rend la tâche plus ardue) : créer un nouveau projet C#/Xaml pour Windows Store.

Seconde étape : personnaliser le manifeste de l’application pour lui donner les droits d’accès réseau. C’est un détail à ne pas oublier ou rien ne marchera.

J’ai choisi de déboguer en mode simulateur plutôt qu’en mode machine locale, cela évite de polluer la machine (et puis il existe des petits bugs liés à des handles de fichiers non relâchés qui empêchent parfois le déploiement, sur le simulateur il suffit de tout fermer et de rouvrir, en local il faut faire un reboot plus contraignant).

Vous pouvez aussi préférer travailler dans une machine virtuelle, mais Windows 8 est aujourd’hui parfaitement utilisable en production et un vrai setup est toujours plus efficace qu’une émulation. Si vous préférez une VM je vous conseille Virtual Box, ça marche très bien avec Windows 8.

Pour l’instant nous allons laisser ce projet en l’état, nous n’y avons pas beaucoup touché mais sans les données nous ne pouvons pas avancer plus.

Créer le projet Web

Dans la solution qui a été ajoutée automatiquement autour du précédent projet nous ajoutons maintenant un projet Web application, le plus simple possible sans rien dedans.

Les données

Il faut disposer d’un serveur et d’une base de données. Vous pouvez prendre ce que vous voulez pour les tests si vous avez déjà une base dédiée à ce genre de travail. Sinon installez la base exemple indiquée dans les liens en début de billet.

Comme tout fonctionne pour le moment comme sous Ria Services, je ne vais pas entrer dans les détails.

Il faut bien entendu ajouter au projet Web un modèle Entity Framework. Je n’ai utilisé qu’une seule table de la base de données.

Ensuite il faut ajouter un WCF Data Service.

Le squelette créé par défaut mérite une petite adaptation pour que notre exemple fonctionne : exposer le Dataset “Employees” du modèle EF :

namespace WebApplication1
{
    public class WcfDataService1 : DataService< AdventureWorks2012Entities >
    {
        // This method is called only once to initialize service-wide policies.
        public static void InitializeService(DataServiceConfiguration config)
        {
            config.SetEntitySetAccessRule("Employees", EntitySetRights.AllRead);
            config.DataServiceBehavior.MaxProtocolVersion =
                 DataServiceProtocolVersion.V3;
        }
    }
}

 

C’est peu de choses, mais c’est essentiel pour exposer les données en OData. Vous remarquerez qu’ici je n’ai donné que les droits en lecture sur la table.

Il est possible de lancer en mode debug le projet Web pour tester le service (clic droit sur le projet puis exécuter en debug).

Le navigateur affichera une erreur 403 car le répertoire n’est pas browsable et que nous n’avons aucune page par défaut dans ce projet. Cela permet toutefois de noter l’adresse du service (par exemple localhost:65373) ce qui nous servira pour l’application cliente.

On peut aussi vérifier que l’accès à la ressource déclarée est possible.

Dans l’exemple qui utilise des noms par défaut, l’accès se fait par l’adresse suivante :

http://localhost:65373/WcfDataService1.svc/Employees

“Employees” étant le dataset unique du modèle EF.

S’agissant d’un flux OData, le navigateur le prend pour un flux RSS et passe dans un mode d’affichage peu pratique :

image

Cela suffit pour voir que le service répond, qu’il renvoie bien un flux OData reconnu comme tel, et on peut même voir qu’il y a 290 enregistrements retournés.

En demandant l’affichage du source de la page on peut s’assurer que toutes les données sont bien renvoyées (ici en xml) :

image

C’est loin d’être lisible mais on y retrouve les données réclamées.

La partie cliente

La mise en page d’une application Modern UI se rapproche beaucoup de Silverlight en plus “calibré” et plus simple (si on suit les directives de MS). Je vais donc faire très simple pour l’exemple : une Listbox avec une petit DataTemplate pour afficher la liste des enregistrements et une poignée de TextBlock et TextBox pour le détail de l’enregistrement sélectionné. Un bouton “Load” permettra de charger les données.

Les informations de détail sont placées dans une Grid dont le DataContext est lié à l’item sélectionné de la ListBox. Tout le reste n’est que Binding rudimentaire sur lequel je passerai.

Une fois terminé cet écran ressemble à cela :

screenshot_09292012_150049

On notera l’observance la plus stricte du minimalisme cher à Modern UI ! (En réalité on peut faire de très belles choses en Modern UI, à condition de ne pas respecter toutes les règles édictées par MS, c’est donc un choix à faire…).

Mais l’essentiel de notre affaire n’est pas dans la mise en page…

Il nous manque le principal : les données.

Ajouter le service

C’est là que les outils indiqués en début de billet vont produire leur effet magique… Notamment les tools pour WCF Data Services 5.

On procède en réalité de la même façon que pour ajouter un service Web sous Silverlight : dans les références on ajoute un service, ce qui amène un dialogue classique pour cette fonction. En cliquant sur le bouton de découverte VS va trouver le service exposée par le projet Web qui se trouve dans la même solution et va nous permettre de l’importer.

Une fois le service référencée, “y’a plus k’a !”.

C’est là que ça se corse, car en absence d’exemples fiables et d’une documentation explicite, c’est un peu sportif : le principe est le même que les Ria Services mais avec suffisamment de différences pour que rien de ce qu’on essaye ne fonctionne :-)

Il faut donc retrousser ses manches et plonger dans le code, investiguer les types retournées ici et là, tenter d’interpréter les exceptions. Bref le travail de débroussaillage classique du pionnier qui essuie les plâtres. J’ai l’habitude, c’est un avantage certain.

Je vais passer sur ces recherches et sur le tâtonnement ainsi que sur les explications complètes du pourquoi du comment. Je publierai d’autres billets qui détailleront les mécanismes en jeu une fois que j’aurai la certitude d’avoir tout compris et de n’avoir rien loupé.  Ce billet est un avant goût, une preuve fonctionnelle de faisabilité, pas un article sur les détails de WCF Data Services 5. Mais cela viendra !

Accéder aux données

Donc, comme nous l’avons vu sur la mise en page, il y a un bouton “Load”. Ce bouton va déclencher le chargement des données.

Tout est asynchrone sous Windows 8, et si nous étions déjà habitué à ce mode de fonctionnement avec Silverlight et les Ria Services, c’est dans la façon de mettre en œuvre cet asynchronisme que ce trouve les pièges de WinRT…

Au début c’est facile, il suffit de créer un contexte, exactement comme avec les Ria Services.

Mais en fait ça se complique tout de suite un peu puisqu’il faut passer une URI.

C’est là qu’il est intéressant d’avoir testé le service (voir un peu plus haut) et d’avoir noter l’adresse…

Pour créer le contexte on procédera donc comme suit :

 if (context == null) context = 
      new AdventureWorks2012Entities(
           new Uri("http://localhost:65373/WcfDataService1.svc/", UriKind.Absolute));

 

 

La variable “context” a été déclarée de cette façon :

private ServiceReference1.AdventureWorks2012Entities context;

 

Le premier appui sur le bouton Load créera le contexte, les appuis suivants, s’il y en a, récupéreront le contexte déjà créé.

Tout va résider maintenant dans la façon d’envoyer la requête au server et d’interpréter le résultat.

async private void Button_Click_1(object sender, RoutedEventArgs e)
        {
            try
            {
                if (context == null) context = 
                 new AdventureWorks2012Entities(
                    new Uri("http://localhost:65373/WcfDataService1.svc/",
                           UriKind.Absolute));

                var query = 
                 (DataServiceQuery<Employee>)
                     (from ee in context.Employees 
                      orderby ee.BirthDate select ee);

                var data = await query.ExecuteAsync();

                lb1.ItemsSource = data;
            }
            catch (DataServiceQueryException ex)
            {
                throw new Exception("Erreur: " + ex.Message);
            }
        }

On pense au départ qu’une requête Linq fera l’affaire… C’est vrai, mais ce n’est pas suffisant.

Comme on le remarque dans le code ci-dessus, la requête Linq est transtypée en “DataServiceQuery<Employee>”. Resharper m’indique qu’il s’agit d’un transtypage un peu étrange car il ne trouve pas ce qu’il y a de commun entre un résultat de requête Linq et le type DataServiceQuery.

J’avoue que je reste aussi perplexe que lui pour l’instant faute d’avoir vraiment bien compris le jeu entre ces différentes classes. Mais cela viendra et je vous dirai tout !

L’avantage de ce transtypage est de pouvoir accéder à certaines fonctions qui permettent d’exécuter la requête en mode asynchrone.

Toutefois je voulais me servir des nouveaux modes de C# 5 (await et async) plutôt que de programmer un Callback à “l’ancienne”.

C’est pourquoi la méthode Button_Click est précédée du marqueur async car je vais utiliser await dans son corps.

La variable “data” est ainsi assignée via un await sur l’opération d’exécution de la requête.

En réalité, ExecuteAsync() est une extension que j’ai ajoutée. Elle permet d’appeler l’exécution de la requête asynchrone sans se soucier des détails et d’obtenir un code lisible grâce à await.

Une fois la requête exécutée, la variable “data” est affectée tout simplement à l’ItemsSource de la Listbox.

L’affichage se produit alors :

screenshot_09292012_150055

Mon DataTemplate de la ListBox est minimaliste, je reprends pour chaque ligne uniquement la date de naissance (puisque la requête réclame un tri sur ce champ) et l’ID de l’employé.

Lorsqu’on clique sur un item de la liste, la partie détail à droite se synchronise :

screenshot_09292012_150108

 

Je n’ai pas exposé les données en mode écriture, même si TextBox le laisse croire, on ne peut donc  pas modifier les données. Si on le fait, ces modifications sont purement locales en mémoire. Il suffirait bien entendu d’ajouter un autre bouton avec un appel SaveChanges pour rendre l’ensemble de données modifiable (à condition d’ajouter ce droit lors de son exposition par le service).

Cool non ?

Ah oui… Il reste un détail, le code de la fameuse extension qui permet d’utiliser await :

public static class WcfDataServicesExtensions
    {
        public static async Task<IEnumerable<TResult>>
                                    ExecuteAsync<TResult>
                                              (this DataServiceQuery<TResult> query)
        {
            var queryTask = 
                 Task.Factory.FromAsync<IEnumerable<TResult>>
                        (query.BeginExecute(null, null), (asResult) =>
            {
                var result = query.EndExecute(asResult).ToList();
                return result;
            });

            return await queryTask;
        }
    }

 

Ce n’est pas bien compliqué mais ça simplifie l’écriture du code comme nous l’avons vu.

Conclusion

Je ne vous joint pas le projet au billet car il ne comporte rien de spécial en dehors de ce qui est publié ici, le reste n’est que Binding ou plomberie habituelle. De plus l’exemple fonctionne avec une base de données que vous n’aurez peut-être pas envie d’installer, ou pas dans la même version de SQL Server, etc…

Ce qui comptait dans ce billet était de montrer qu’alors même que nous nous approchons à grands pas de la date de sortie officielle de Windows 8, le tooling se met aussi en ligne sur ce qu’il y avait de mieux dans Silverlight et que dès maintenant il est possible d’intégrer WinRT dans une logique LOB.

Cela est vraiment crucial tellement Windows 8 a été présenté par MS et d’autres, à tord ou à raison, comme un produit grand public.

Il n’en est rien, Windows 8 et WinRT forment une plateforme sérieuse, presque mature alors que le produit n’est pas encore officiellement vendu.

Silverlight n’est pas mort, il a juste changé de nom. Toute sa puissance, tant pour les UI que pour l’accès aux données est toujours vivante et peut-être encore plus qu’avant.

Essayez de reproduire l’exemple de ce billet, vous serez convaincu j’en suis certain.

C’est promis, je reviendrai sur tout cela avec plus d’explications dans un moment.

Entre temps, amusez-vous bien avec WinRT

… Et Stay Tuned !

blog comments powered by Disqus