Archive

Posts Tagged ‘Silvelright’

Partie 2 : WCF Data Services – Création de notre service de données OData

31 juillet 2011 1 commentaire

Dans la partie 1 , nous avons parlé du protocole OData que chaque technologie (.Net, Java etc) se doit de respecter les spécifications pour mettre en place un service exposant des données suivant ce protocole.

Dans le cas de .Net nous avons WCF Data Services. WCF Data Services anciennement appelé ADO.Net Data Services fournit un ensemble d’APIs clients et serveurs pour permettre respectivement de consommer et créer un service de données OData. Ces bibliothèques sont incluses dans le Framework .Net. Le schéma suivant résume globalement comment fonctionne ces bibliothèques WCF Data Services :

Schéma expliquant de façon globale le fonctionnement de WCF Data Services (image récupérée sur le site MSDN)

Schéma expliquant de façon globale le fonctionnement de WCF Data Services (image récupérée sur le site MSDN)

Comme on le voit sur le schéma le runtime Data Services se base des fournisseurs (Data Services Providers) pour exposer les données. Ces fournisseurs de services de données sont de 3 types :

Entity Framework : ce fournisseur se base sur ADO.Net Entity Framework pour permettre d’exposer nos données issues d’une base de données relationnelle.
Reflection : se base sur la réflexion pour exposer nos données en se basant sur des classes implémentant IQueryable.
Custom : dans ce cas il s’agira d’un fournisseur de données que vous auriez codé en respectant les règles définies par le Framework .Net.

Dans le présent article nous créerons un service qui se basera sur le fournisseur de type Entity Framework qui me semble être le plus facile à manier.

Dans tout ce qui suit, nous utiliserons les outils suivants :
• Visual Studio 2010
• .Net 4.0
• Silverlight 4.0 SDK
• Silverlight Tools pour Visual Studio 2010
• La base de données AdventureWorks : vous pouvez la base de données ici

1. Ajout des fichiers utiles
Nous allons mettre en place notre premier service de données pour cela créons un projet type Application Silverlight et donnez lui le nom de SilverlightAndWCFDataServices et choisissez le répertoire de destination comme dans l’image ci-dessous :

Création de la solution

Après avoir cliqué sur OK, une boîte de dialogue s’affiche, configurez-la comme définit dans l’image suivante :

Paramétrage de l'application Silverlight

Après avoir cliqué sur le bouton OK, Visual Studio nous crée 2 projets :
SilverlightAndWCFDataServices : qui représente notre application Silverlight.
SilverlightAndWCFDataServices.Web : qui consiste en une application web ASP.Net qui aura pour objectif non seulement d’héberger notre application Silverlight mais aussi héberger notre service de données créé avec WCF Data Services.

Nous allons suivre les étapes suivantes pour ajouter le modèle Entity Framework :

Ajout du fichier EDM


Assistant ajout d'un fichier EDM


Assistant ajout fichier EDM


Choix des tables à mapper

Nous venons de créer notre modèle de données Entity Framework. Pour finaliser renommons les propriétés ProductCateegory1 et ProductCategory2 de l’entité ProductCategory respectivement en SubCategories et ParentCategory. Au final notre modèle ressemblera à ce qui suit :

Modèle final EF

Nous allons poursuivre en ajoutant, toujours dans notre projet web, un nouveau fichier Service de données WCF et nommez ce fichier AdventureWorksDataService.svc comme dans l’image ci-dessous :

Ajout du fichier de Services de données

Une fois le fichier ajouté à notre projet web, vous pouvez accéder au fichier AdventureWorksDataService.svc.cs et visualiser le code ci-dessous :

Contenu du fichier AdventureWorksDataService.svc.cs

On remarque des commentaires avec des TODO qui représentent les deux tâches à effectuer pour que notre service de données soit fonctionnel. Nous devons spécifier le fournisseur de données que le service devra utiliser : pour cela nous devons définir le type de notre contexte de données Entity Framework comme étant le type générique de la classe de base DataService dont dérive notre service de données soit DataService. Reste maintenant à définir les jeux d’entités et les opérations qui seront exposés en configurant les règles d’accès et de modifications.

2. Configuration
Les règles d’accès se configurent dans la méthode statique InitializeService de notre service de données. Cette méthode reçoit un paramètre appelé config qui est une instance de la classe DataServiceConfiguration. Cette classe expose 2 méthodes nous permettant de configurer les règles d’accès de nos jeux d’entités (grâce à la méthode SetEntityAccessRule) et opérations (grâce à la méthode SetOperationAccessRule). Dans notre cas d’utilisation nous n’exposons pas d’opérations alors nous n’allons pas configurer de règles pour celles-ci mais il faut noter qu’elles se configurent avec le même principe que pour les jeux d’entités.
La méthode SetEntityAccessRule reçoit en paramètres :

• Une chaîne de caractère représentant le nom du jeu d’entités Entity Framework dont on veut configurer les règles d’accès. N.B : On peut spécifier le caractère « * » pour spécifier que la configuration s’applique sur tous les jeux d’entités.
• Une énumération qui est du type EntitySetRights (ServiceOperationRights pour les opérations). Ce paramètre définit les droits d’accès sur la ou les ressources concernées. Les valeurs possibles d’EntitySetRights sont :

  • None : Aucun droit d’accès n’est accordé. Alors les clients ne pourront pas accéder à la ressource encore moins la modifier ou la supprimer.
  • ReadSingle : On n’aura pas le droit d’exécuter une requête renvoyant plusieurs éléments d’un jeu d’entités mais uniquement un seul élément de ce jeu.
  • ReadMultiple :On a le droit d’exécuter une requête renvoyant un groupe de données provenant d’un jeu d’entités
  • WriteAppend : Permettre la création d’éléments dans un groupe de données
  • WriteReplace : On pourra remplacer des éléments
  • WriteDelete : On pourra supprimer des éléments
  • WriteMerge : On pourra fusionner les données d’un élément
  • AllRead : Autorisation de lecture des données
  • AllWrite : Autorisation d’écriture de données
  • All : Tous les droits (lecture, écriture, modification et suppression) sont permis

Il faut noter que l’enum EntitySetRights est décoré avec l’attribut FlagsAttribute ce qui nous permet de combiner les différentes valeurs.

Mis à part la configuration des droits d’accès et de modifications sur les jeux d’entités, nous pouvons aussi limiter le nombre d’éléments qui peuvent être inclus dans la réponse d’une requête grâce à la méthode SetEntityPageSize. Cette méthode reçoit en paramètre le nom du jeu d’entités concerné et le nombre d’éléments maximum qu’on pourra récupérer.
Dans notre cas d’utilisation nous autoriserons tous les droits sur les jeux d’entités sans limite sur le nombre d’éléments. Notre code ressemblera à ceci :

public class AdventureWorksDataService : DataService<AdventureWorksEntities>
{
	public static void InitializeService(DataServiceConfiguration config)
	{
		config.SetEntitySetAccessRule("*", EntitySetRights.AllRead);
		config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
	}
}

Nous pouvons maintenant tester notre service de données sans passer par un client mais juste en utilisant un navigateur Web. Pour cela faîtes un clic droit sur le fichier AdventureWorksDataService.svc et puis cliquez sur Afficher dans le navigateur dans le menu contextuel. Dans le navigateur nous avons le Service Document qui s’affiche nous montrant la liste des jeux d’entités accessibles. Testons les requêtes suivantes :

3. Aller plus loin dans la configuration : les intercepteurs
Dans la configuration de notre service de données OData nous avons autorisé tous les droits sur le jeu d’entités Products donc on pourra exécuter une requête d’ajout, de modification et de suppression d’un produit. Nous voudrions ne pas renvoyer la liste des produits en arrêt de commercialisation et aussi ne valider la modification ou l’ajout d’un produit à la catégorie Clothing (Vêtement) que si la taille du vêtement est renseignée. Pour mettre en place ces deux logiques nous devons intercepter les demandes du client.

WCF Data Services intègre ce qu’on appelle des intercepteurs. Les intercepteurs sont définis par entité et dans notre cas d’utilisations ils seront définis sur l’entité Products. Pour définir un intercepteur nous avons à notre disposition deux attributs et chacun reçoit comme paramètre de son constructeur le nom du jeu d’entité concerné :

  • QueryInterceptorAttribute : Cet attribut permettra d’intercepter toutes les requêtes utilisant le verbe http GET et agissant sur le jeu d’entité donné. Ce type d’intercepteur nous permettra d’implémenter notre propre logique de récupération des produits. Cet attribut doit être défini sur une méthode qui ne reçoit aucun paramètre et doit avoir comme type de retour une expression lambda comme le montre le code ci-dessous :
    [QueryInterceptor("Products")]
    public Expression<Func<Product, bool>> OnGetProducts()
    {
         return p => !p.SellEndDate.HasValue;
    }
    
  • ChangeInterceptorAttribute : Cet intercepteur nous permettra de décider si on valide ou non les modifications sur un jeu d’entités. La méthode sur laquelle s’applique cet attribut doit renvoyer aucune valeur et doit recevoir en premier paramètre un objet du même type que les données du jeu d’entités et en deuxième paramètre un enumérateur de type UpdateOptions qui contient les infos sur les modifications qui seront appliquées sur notre donnée et les valeurs possibles sont : Add, Change, Delete et None qui se passent de commentaires. La méthode sera appelée par toute requête utilisant les verbes HTTP autre que GET.
    [ChangeInterceptor("Products")]
    public void OnChangeProducts(Product product, UpdateOperations operations)
    {
    	if (operations == UpdateOperations.Add || operations == UpdateOperations.Change)
    	{
    		// La catégorie est obligatoire sinon nous générons une exception
    		if (!product.ProductCategoryID.HasValue)
    		{
    			// DataServiceException est le type d'exception à lancer lorsqu'on travaille avec WCF Data Services.
    			// Nous utilisons ici la surcharge du constructeur qui reçoit en paramètre le code de l'erreur et
    			// la description du message qui sera lu côté client.
    			throw new DataServiceException(400, "La catégorie du produit doit être renseignée");
    		}
    
    		// Nous avons accès à notre contexte de données grâce à la propriété CurrentDataSource de notre service de données
    		ProductCategory category = this.CurrentDataSource.ProductCategories
    										.Include("ParentCategory")
    										.Where(x => x.ProductCategoryID == product.ProductCategoryID && x.ParentCategory.ProductCategoryID == 3).FirstOrDefault();
    		
    		if (category != null && string.IsNullOrWhiteSpace(product.Size))
    		{
    			throw new DataServiceException(400, 
    				"Tout produit dans la catégorie parent Clothing doit avoir une taille");
    		}
    	}
    	else if (operations == UpdateOperations.Delete)
    	{
    		// On ne peut supprimer un produit mais mettre à jour la date de fin de commercialisation ou 
    		throw new DataServiceException(400, 
    			"Un produit ne peut être supprimé mais à la place mettez à jour la date de fin de commercialisation ou spécifiez qu'il est en rupture de stock.");
    	}
    }
    

    Nous pouvons tester que l’intercepteur de requête marche en utilisant la requête http://localhost:3000/AdventureWorksDataService.svc/Products et nous pouvons constater que pas mal de produits ne sont pas présents dans la liste renvoyée parce qu’ils ne sont plus commercialisés. Pour l’intercepteur de changement nous le testerons une fois que notre application client sera mise en place.

    Nous venons de créer notre service de données en utilisant la bibliothèque serveur fournit par WCF Data Services. Nous allons poursuivre dans la partie 3 en mettant en place une application web Silverlight pour consommer ce service.

%d blogueurs aiment cette page :