Comment développer des applications d’entreprises orientées données avec Silverlight 3 : introduction à .NET RIA Services (2/4)

Article mis à jour : juillet 2009 pour la sortie de Silverlight 3 RTW et .NET RIA Services Juillet 2009 

J’espère vous avoir donné l’eau à la bouche avec la précédente vidéo du 1er billet. Nous allons maintenant créer une petite application de bout en bout et introduire quelques concepts principaux au fur et à mesure. Pour cela, nous allons utiliser la célèbre base de données d’exemple NorthWind. Vous pouvez la télécharger ici. Vous pouvez également voir le résultat final en action dans la vidéo contenue dans le dernier billet.

Note : les captures d'écrans ont été réalisés avec le thème par défaut d'une application "Business Application" avec Silverlight 3 Beta et .NET RIA Services Mai 2009. Vous n'aurez pas le même template d'affichage avec Silverlight 3 RTW et .NET RIA Services Juillet 2009 mais j'ai bien mis à jour l'ensemble des 4 articles pour la RTW.

Création d’une petite application étape par étape

Création du projet Silverlight 3

Lancez Visual Studio 2008 et créer un nouveau projet Silverlight 3 de type « Silverlight Business Application » cette fois-ci et nommez le « GestionClientsRIA ».

RIAImage1

Une fois le projet créé, si vous vous rendez dans les propriétés du projet Silverlight, vous noterez l’apparition d’une nouvelle option :3573455193

Cela vous permettra de lier le projet ASP.NET au projet Silverlight 3. C’est la fameuse glue dont je vous parlais plus haut. Vous allez mieux comprendre au fur et à mesure. On peut éventuellement établir cette liaison plus tard sur un projet existant par exemple.

Découverte de l’application par défaut

Vous pouvez dors et déjà lancer l’application Silverlight 3 pour tester ce que l’on vous propose « Out of the box » comme disent les Américains :

RIAImage3

Note : dans la version Silverlight 3 RTW, vous aurez plutôt un écran ressemblant à cela:

Template Business Application SL3 RTW

Essayez par exemple de cliquer sur « about » et de revenir ensuite sur « home ». Vous verrez alors 2 choses intéressantes :

1 – les URLs changent derrière le symbole # pour viser la bonne vue : https://localhost:port/BusinessApplicationTestPage.aspx#/Home pour la page d’accueil par exemple et https://localhost:port/BusinessApplicationTestPage.aspx#/About pour la page « A propos ».
2 – Cela permet alors de naviguer dans votre application Silverlight 3 avec la gestion de l’historique du navigateur :

3573455503

Le tout sans avoir encore rien codé. Sympa non ? :) Silverlight 3 embarque en effet un framework de navigation. Vous pouvez alors modifier les URLs pour masquer le nom des vues et avoir des choses plus explicites comme https://localhost:port/votrepage.aspx#Accueil par exemple. Mais ne nous attarderons pas sur cette notion ici, j’y reviendrais probablement dans un futur billet.

Continuons de jouer avec l’application fournie par défaut et cliquez sur « login » :

RIAImage5

Depuis la mise à jour de Mai 2009 de .NET RIA Services, on vous propose une gestion intégrée de l’authentification des utilisateurs. Cliquez sur « Register now » et créez-vous un utilisateur.

Vous notez alors déjà certaines fonctionnalités sympathiques :

RIAImage6

En effet, une validation coté client Silverlight est proposée grâce à .NET RIA Services après avoir défini la règle coté serveur. L’expérience utilisateur est également relativement agréable. Nous allons voir comment mettre en place cette validation un peu plus loin.

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

Récupérez la base de données NorthWind et glissez/déplacez le fichier NORTHWND.MDF dans le dossier « App_Data » de votre projet ASP.NET.

RIAImage7

Ajoutez un nouvel élément à votre projet ASP.NET de type ADO.NET Entity Data Model et nommez le CustomersModel

RIAImage8

Choisissez « Generate from database » et faites « Next » jusqu’à cet écran :

RIAImage9

Cliquez sur la table « Customers » et nommez le modèle « CustomersModel » puis « Finish ».

Un modèle Entity Framework sera donc généré automatiquement pour vous. Considérons qu’il représente votre couche d’accès aux données. Le code généré automatiquement se trouve dans « CustomersModel.Designer.cs ». Avant tout chose, compilez votre solution. Cela est nécessaire pour que le modèle Entity soit visible dans l’étape suivante.

Il faut désormais rendre accessible cette couche d’accès aux données au client Silverlight qui se trouve de l’autre coté du tuyau ou de l’autre coté du nuage.

Pour cela, ajoutez un élément de type « Domain Service Class » à votre projet ASP.NET et nommez le CustomersService.cs

RIAImage10

Cliquez sur « Add », vous arrivez sur cette fenêtre :

RIAImage11

Cochez toutes les cases. Pour faire simple, avant de rentrer un peu plus dans le détail :

- « Enable Client access » permet d’exposer votre logique métier vers le client Silverlight
- « Enable editing » va permettre de générer automatiquement les méthodes pour mettre à jour, ajouter, supprimer des enregistrements.
- « Generate associated classes for metadata » va nous permettre de spécifier des règles de validation coté serveur sur des champs via des attributs. Ces règles seront alors automatiquement répliquées et appliquées coté client comme on a pu le voir avec le contrôle de création d’un utilisateur plus haut. Le fait de cocher cette case va nous prémâcher le travail en générant à nouveau du code pour nous mais on aurait tout à fait pu le faire à la main plus tard.

Le service qui sera exposé au client se trouve alors dans le fichier « CustomersService.cs ». 4 méthodes ont été générées pour nous :

public IQueryable<Customers> GetCustomers()
public void InsertCustomers(Customers customers)
public void UpdateCustomers(Customers currentCustomers, Customers originalCustomers)
public void DeleteCustomers(Customers customers)

Permettant respectivement de récupérer la liste des clients, insérer un nouveau client, mettre à jour et supprimer un client dans la base. Si nous n’avions pas coché la case « Enable editing », nous n’aurions eu que la 1ère méthode. D’ailleurs notez que cette dernière retourne un IQueryable. Nous allons donc pouvoir lancer une requête LINQ coté client pour ne récupérer que les enregistrements qui nous intéressent.

Compilez la solution à nouveau. C’est obligatoire pour que le client Silverlight puisse voir ce service.

Utilisation du service .NET RIA coté client

Rendez-vous dans le projet Silverlight et dans la vue Home.xaml.

Identifiez le contenu du StackPanel et retirer le. Glissez/déposez le contrôle DataGrid dedans et nommez la grille « GrilleClients ». Voici le XAML correspondant au container Grid :

<Grid x:Name="LayoutRoot">

<ScrollViewer x:Name="PageScrollViewer" Style="{StaticResource PageScrollViewerStyle}" >

<StackPanel x:Name="ContentStackPanel" Style="{StaticResource ContentStackPanelStyle}">

<TextBlock x:Name="HeaderText" Style="{StaticResource HeaderTextStyle}"

Text="Grille Clients"/>

<data:DataGrid x:Name="GrilleClients"></data:DataGrid>

</StackPanel>

</ScrollViewer>

</Grid>

Rendez-vous dans le code behind dans Home.xaml.cs et ajoutez ce « using » :

using GestionClientsRIA.Web;

Surprenant n’est ce pas ? On demande à utiliser du code défini sur le serveur d’une certaine manière. Déclarez cette variable membre :

CustomersContext contexte = new CustomersContext();

Cela correspond à la projection coté client de notre service CustomersService présent sur le serveur Web. Ajoutez alors ce code dans le constructeur en dessous de InitializeComponent() :

// On définit la source de données pour le binding

GrilleClients.ItemsSource = contexte.Customers;

// On charge les données depuis le serveur en asynchrone

contexte.Load(contexte.GetCustomersQuery());

 

On précise l’entité à associer à la grille (on aurait pu en effet choisir plusieurs tables lors de la conception de notre modèle avec Entity Framework) et on lance le téléchargement des données. Le téléchargement se fait ensuite automatiquement de manière asynchrone et de manière transparente. En effet, vous ne voyez ici aucun abonnement au moindre évènement et la moindre utilisation de délégué. Cette complexité est par défaut masquée. Par contre, ne vous étonnez pas si vous ne voyez pas les données apparaître de suite lorsque vous application Silverlight commence à s’afficher. Il faut en effet attendre que la 2ème phase du chargement asynchrone soit déclenchée pour que les données soit injectées dans la grille.

Allez, lançons l’ensemble pour voir à quoi cela ressemble !

RIAImage12

Et voilà, j’ai déjà l’ensemble des clients de la base dans mon contrôle DataGrid en ayant écrit que 3 lignes de code. Pas mal non ? A noter que vous pouvez déjà cliquer sur les colonnes pour effectuer un tri sur celles-ci et même changer une valeur en cliquant dans l’une des cases. Cependant, la modification ne sera effectuée qu’en mémoire coté client vu que nous n’avons pas encore implémenté la sauvegarde vers la base.

Utilisons un peu de LINQ

Vous noterez que la méthode Load() prend en paramètre une requête. Pour l’instant, nous avons utilisé une requête retournant l’ensemble des clients. Filtrons un peu plus les données. Pour cela, commencez par ajouter ce using :

using System.Windows.Ria.Data;

 Et chargez désormais les données à l’aide de ces lignes de code :

//Création du filtre à envoyer au serveur

var query = from clients in contexte.GetCustomersQuery()

where clients.Country.Equals("France")

select clients;

// On charge les données depuis le serveur en asynchrone

contexte.Load(query);

On indique donc que l’on souhaite récupérer uniquement les clients habitant en France. Il est très important de comprendre que cette requête LINQ n’est pas exécutée coté client après avoir récupéré l’ensemble des enregistrements depuis le serveur. Cela n’aurait aucun intérêt et engorgerait le réseau inutilement. Non, cette requête LINQ est sérialisée et envoyée au serveur Web qui effectue lui-même la requête au près du serveur de base de données et le résultat est alors renvoyé du serveur Web vers le client Silverlight ! La plomberie a bien été entièrement masquée et on a traversé les différents tiers sans aucune difficulté.

On peut également filtrer en amont sur le service exposé au client Silverlight. Pour cela, rendez-vous dans « CustomersService.cs » du projet ASP.NET et ajoutez cette méthode :

public IQueryable<Customers> GetCustomersByCountry(string country)
{
    var customersInThisCountry =
        from c in this.Context.Customers
        where c.Country == country
        select c;

    return customersInThisCountry;
}

Compilez votre solution. Retournez dans le code du client Silverlight et vous verrez qu’une nouvelle méthode nommée GetCustomersByCountryQuery() est apparue. Remplacez l’appel à GetCustomersQuery() par cette ligne de code :

contexte.Load(contexte.GetCustomersByCountryQuery("France"));

Relancez l’ensemble : on obtient bien le même résultat sauf que cette fois-ci, on expose directement une collection limitée d’enregistrements depuis notre serveur Web. Nous avons donc la liberté de filtrer les informations remontées à chaque tiers.

Explication d’une partie de la magie cachée derrière

J’imagine votre curiosité sur l’implémentation sous-jacente de cette magie. Faisons un parallèle avec des notions déjà connues, prenons l’exemple d’un service Web. Ce dernier expose ses méthodes disponibles à travers un contrat en WSDL et la sérialisation des informations envoyées entre le service et le client consommant les méthodes se fait en SOAP/XML. On peut donc ensuite imaginer écrire un client consommant ce service en écrivant du code soi-même envoyant des requêtes HTTP vers le service Web, gérant soi-même l’encapsulation en XML/SOAP des données, etc. C’est ce que l’on appelle écrire un proxy client. Mais que c’est fastidieux de faire tout cela à la main ! Heureusement, Visual Studio nous propose depuis longtemps de faire simplement un « Add Web Reference » et on lui donne l’URL où se trouve exposé le WSDL. Il se débrouille alors comme un grand pour fabriquer le proxy client. Il ne nous reste plus alors qu’à ajouter le bon « using » coté client et on a l’impression de pouvoir appeler des méthodes en local alors que ces dernières sont bien exécutées de l’autre coté. Productivité et complexité masquée.

On peut tout à fait appliquer cette même idée de génération de proxy client avec .NET RIA Services. Vous définissez un service coté ASP.NET vous permettant de charger des enregistrements, des les mettre à jour. Vous définissez également les règles de validation à appliquer sur les champs des objets associés aux enregistrements. Vous pourriez alors écrire vous-même un client consommant ce service en réécrivant du code pour faire l’appel au service (via WCF) et s’occupant de valider les champs de saisie (duplication du code). Heureusement, .NET RIA Services s’occupe de cela pour vous.

Le début de la magie commence par cet attribut :

[EnableClientAccess()]

Cela va indiquer à Visual Studio d’analyser les méthodes présentes dans votre classe service dérivant dans notre cas de LinqToEntitiesDomainService. Alors où est caché le proxy client résultant coté Silverlight ? Pour le voir, cliquez sur le bouton « Show All Files » sur votre projet Silverlight.

RIAImage13

Un dossier apparaît nommé « Generated_Code » et vous découvrez à l’intérieur un fichier « GestionClientsRIA.Web.g.cs » (.g pour generated). C’est le proxy client ! On y découvre nos différentes méthodes Load notamment et on peut voir dans le constructeur par défaut vers quel type d’URL va se faire la requête HTTP sous-jacente :

public CustomersContext() :
                base(new HttpDomainClient(new Uri("DataService.axd/GestionClientsRIA-Web-CustomersService/", System.UriKind.Relative)))
        {
        }

Si l’on y ajoute un petit coup de Fiddler pour analyser le trafic réseau et si l’on charge les enregistrements avec cette ligne de code :

contexte.Load(contexte.GetCustomersByCountryQuery("Italy"));

On voit partir cette requête :

# Result Protocol Host URL Body Caching Content-Type Process Comments Custom
17 200 HTTP 127.0.0.1:52878 /ClientBin/DataService.axd/GestionClientsRIA-Web-CustomersService/GetCustomersByCountry?country=Italy 1 194 no-cache Expires: -1 text/json; charset=utf-8 iexplore:6648

Cela ressemble donc méchamment à l’utilisation de REST et JSON derrière. :) Voici d’ailleurs le retour :

{"__type":"DataServiceResult:DomainServices","IsDomainServiceException":false,"Results":[{"__type":"Customers:https://schemas.datacontract.org/2004/07/GestionClientsRIA.Web","Address":"Via Monte Bianco 34","City":"Torino","CompanyName":"Franchi S.p.A.","ContactName":"Paolo Accorti","ContactTitle":"Sales Representative","Country":"Italy","CustomerID":"FRANS","Fax":"011-4988261","Phone":"011-4988260","PostalCode":"10100","Region":null},{"__type":"Customers:https://schemas.datacontract.org/2004/07/GestionClientsRIA.Web","Address":"Via Ludovico il Moro 22","City":"Bergamo","CompanyName":"Magazzini Alimentari Riuniti","ContactName":"Giovanni Rovelli","ContactTitle":"Marketing Manager","Country":"Italy","CustomerID":"MAGAA","Fax":"035-640231","Phone":"035-640230","PostalCode":"24100","Region":null},{"__type":"Customers:https://schemas.datacontract.org/2004/07/GestionClientsRIA.Web","Address":"Strada Provinciale 124","City":"Reggio Emilia","CompanyName":"Reggiani Caseifici","ContactName":"Maurizio Moroni","ContactTitle":"Sales Associate","Country":"Italy","CustomerID":"REGGC","Fax":"0522-556722","Phone":"0522-556721","PostalCode":"42100","Region":null}],"TotalCount":-2,"ResultCount":3}

Grâce à l’utilisation de ces standards, vous pourriez donc tout à fait faire appel au service .NET RIA exposé coté ASP.NET depuis AJAX ou PHP.

Astuce : par défaut Fiddler ne voit pas le trafic sur localhost. Changez alors l’URL pour accéder à votre application Web de https://localhost:port/BusinessApplicationTestPage.aspx#/Home vers https://ipv4.fiddler:port/BusinessApplicationTestPage.aspx#/Home

Nous allons maintenant voir comment utiliser de nouveaux contrôles orientés données de Silverlight 3 et comment spécifier des règles de validation pour améliorer notre application dans ce 3ème billet.