Appel à un service OData à partir d’un client .NET (C#)
par Mike Wasson
Ce tutoriel montre comment appeler un service OData à partir d’une application cliente C#.
Versions logicielles utilisées dans le tutoriel
- Visual Studio 2013 (fonctionne avec Visual Studio 2012)
- Bibliothèque client services de données WCF
- API web 2. (L’exemple de service OData est généré à l’aide de l’API Web 2, mais l’application cliente ne dépend pas de l’API web.)
Dans ce tutoriel, je vais passer en revue la création d’une application cliente qui appelle un service OData. Le service OData expose les entités suivantes :
Product
Supplier
ProductRating
Les articles suivants décrivent comment implémenter le service OData dans l’API web. (Toutefois, vous n’avez pas besoin de les lire pour comprendre ce tutoriel.)
- Création d’un point de terminaison OData dans l’API web 2
- Relations d’entité OData dans l’API web 2
- Actions OData dans Web API 2
Générer le proxy de service
La première étape consiste à générer un proxy de service. Le proxy de service est une classe .NET qui définit des méthodes pour accéder au service OData. Le proxy traduit les appels de méthode en requêtes HTTP.
Commencez par ouvrir le projet de service OData dans Visual Studio. Appuyez sur Ctrl+F5 pour exécuter le service localement dans IIS Express. Notez l’adresse locale, y compris le numéro de port attribué par Visual Studio. Vous aurez besoin de cette adresse lorsque vous créerez le proxy.
Ensuite, ouvrez un autre instance de Visual Studio et créez un projet d’application console. L’application console sera notre application cliente OData. (Vous pouvez également ajouter le projet à la même solution que le service.)
Notes
Les étapes restantes font référence au projet de console.
Dans Explorateur de solutions, cliquez avec le bouton droit sur Références, puis sélectionnez Ajouter une référence de service.
Dans la boîte de dialogue Ajouter une référence de service , tapez l’adresse du service OData :
http://localhost:port/odata
où port est le numéro de port.
Pour Espace de noms, tapez « ProductService ». Cette option définit l’espace de noms de la classe proxy.
Cliquez sur Atteindre. Visual Studio lit le document de métadonnées OData pour découvrir les entités dans le service.
Cliquez sur OK pour ajouter la classe proxy à votre projet.
Créer une instance de la classe proxy de service
Dans votre Main
méthode, créez un instance de la classe proxy, comme suit :
using System;
using System.Data.Services.Client;
using System.Linq;
namespace Client
{
class Program
{
static void Main(string[] args)
{
Uri uri = new Uri("http://localhost:1234/odata/");
var container = new ProductService.Container(uri);
// ...
}
}
}
Là encore, utilisez le numéro de port réel dans lequel votre service est en cours d’exécution. Lorsque vous déployez votre service, vous utilisez l’URI du service actif. Vous n’avez pas besoin de mettre à jour le proxy.
Le code suivant ajoute un gestionnaire d’événements qui imprime les URI de requête dans la fenêtre de console. Cette étape n’est pas obligatoire, mais il est intéressant de voir les URI pour chaque requête.
container.SendingRequest2 += (s, e) =>
{
Console.WriteLine("{0} {1}", e.RequestMessage.Method, e.RequestMessage.Url);
};
Interroger le service
Le code suivant obtient la liste des produits du service OData.
class Program
{
static void DisplayProduct(ProductService.Product product)
{
Console.WriteLine("{0} {1} {2}", product.Name, product.Price, product.Category);
}
// Get an entire entity set.
static void ListAllProducts(ProductService.Container container)
{
foreach (var p in container.Products)
{
DisplayProduct(p);
}
}
static void Main(string[] args)
{
Uri uri = new Uri("http://localhost:18285/odata/");
var container = new ProductService.Container(uri);
container.SendingRequest2 += (s, e) =>
{
Console.WriteLine("{0} {1}", e.RequestMessage.Method, e.RequestMessage.Url);
};
// Get the list of products
ListAllProducts(container);
}
}
Notez que vous n’avez pas besoin d’écrire de code pour envoyer la requête HTTP ou analyser la réponse. La classe proxy effectue cette opération automatiquement lorsque vous énumérez la Container.Products
collection dans la boucle foreach .
Lorsque vous exécutez l’application, la sortie doit ressembler à ce qui suit :
GET http://localhost:60868/odata/Products
Hat 15.00 Apparel
Scarf 12.00 Apparel
Socks 5.00 Apparel
Yo-yo 4.95 Toys
Puzzle 8.00 Toys
Pour obtenir une entité par ID, utilisez une where
clause .
// Get a single entity.
static void ListProductById(ProductService.Container container, int id)
{
var product = container.Products.Where(p => p.ID == id).SingleOrDefault();
if (product != null)
{
DisplayProduct(product);
}
}
Pour le reste de cette rubrique, je n’affiche pas la fonction entière Main
, mais uniquement le code nécessaire pour appeler le service.
Appliquer les options de requête
OData définit des options de requête qui peuvent être utilisées pour filtrer, trier, pager des données, etc. Dans le proxy de service, vous pouvez appliquer ces options à l’aide de différentes expressions LINQ.
Dans cette section, je vais montrer de brefs exemples. Pour plus d’informations, consultez la rubrique CONSIDÉRATIONS SUR LINQ (WCF Data Services) sur MSDN.
Filtrage ($filter)
Pour filtrer, utilisez une where
clause . L’exemple suivant filtre par catégorie de produit.
// Use the $filter option.
static void ListProductsInCategory(ProductService.Container container, string category)
{
var products =
from p in container.Products
where p.Category == category
select p;
foreach (var p in products)
{
DisplayProduct(p);
}
}
Ce code correspond à la requête OData suivante.
GET http://localhost/odata/Products()?$filter=Category eq 'apparel'
Notez que le proxy convertit la where
clause en expression OData $filter
.
Tri ($orderby)
Pour trier, utilisez une orderby
clause . L’exemple suivant trie par prix, du plus élevé au plus bas.
// Use the $orderby option
static void ListProductsSorted(ProductService.Container container)
{
// Sort by price, highest to lowest.
var products =
from p in container.Products
orderby p.Price descending
select p;
foreach (var p in products)
{
DisplayProduct(p);
}
}
Voici la requête OData correspondante.
GET http://localhost/odata/Products()?$orderby=Price desc
pagination Client-Side ($skip et $top)
Pour les jeux d’entités volumineux, le client peut vouloir limiter le nombre de résultats. Par exemple, un client peut afficher 10 entrées à la fois. C’est ce qu’on appelle la pagination côté client. (Il existe également une pagination côté serveur, où le serveur limite le nombre de résultats.) Pour effectuer la pagination côté client, utilisez les méthodes LINQ Skip et Take . L’exemple suivant ignore les 40 premiers résultats et prend les 10 suivants.
// Use $skip and $top options.
static void ListProductsPaged(ProductService.Container container)
{
var products =
(from p in container.Products
orderby p.Price descending
select p).Skip(40).Take(10);
foreach (var p in products)
{
DisplayProduct(p);
}
}
Voici la requête OData correspondante :
GET http://localhost/odata/Products()?$orderby=Price desc&$skip=40&$top=10
Sélectionner ($select) et Développer ($expand)
Pour inclure des entités associées, utilisez la DataServiceQuery<t>.Expand
méthode . Par exemple, pour inclure le Supplier
pour chaque Product
:
// Use the $expand option.
static void ListProductsAndSupplier(ProductService.Container container)
{
var products = container.Products.Expand(p => p.Supplier);
foreach (var p in products)
{
Console.WriteLine("{0}\t{1}\t{2}", p.Name, p.Price, p.Supplier.Name);
}
}
Voici la requête OData correspondante :
GET http://localhost/odata/Products()?$expand=Supplier
Pour modifier la forme de la réponse, utilisez la clause LINQ select . L’exemple suivant obtient uniquement le nom de chaque produit, sans autres propriétés.
// Use the $select option.
static void ListProductNames(ProductService.Container container)
{
var products = from p in container.Products select new { Name = p.Name };
foreach (var p in products)
{
Console.WriteLine(p.Name);
}
}
Voici la requête OData correspondante :
GET http://localhost/odata/Products()?$select=Name
Une clause select peut inclure des entités associées. Dans ce cas, n’appelez pas Expand ; le proxy inclut automatiquement l’extension dans ce cas. L’exemple suivant obtient le nom et le fournisseur de chaque produit.
// Use $expand and $select options
static void ListProductNameSupplier(ProductService.Container container)
{
var products =
from p in container.Products
select new
{
Name = p.Name,
Supplier = p.Supplier.Name
};
foreach (var p in products)
{
Console.WriteLine("{0}\t{1}", p.Name, p.Supplier);
}
}
Voici la requête OData correspondante. Notez qu’elle inclut l’option $expand .
GET http://localhost/odata/Products()?$expand=Supplier&$select=Name,Supplier/Name
Pour plus d’informations sur $select et $expand, consultez Utilisation de $select, de $expand et de $value dans l’API Web 2.
Ajouter une nouvelle entité
Pour ajouter une nouvelle entité à un jeu d’entités, appelez AddToEntitySet
, où EntitySet est le nom du jeu d’entités. Par exemple, AddToProducts
ajoute un nouveau Product
à l’ensemble d’entités Products
. Lorsque vous générez le proxy, WCF Data Services crée automatiquement ces méthodes AddTo fortement typées.
// Add an entity.
static void AddProduct(ProductService.Container container, ProductService.Product product)
{
container.AddToProducts(product);
var serviceResponse = container.SaveChanges();
foreach (var operationResponse in serviceResponse)
{
Console.WriteLine(operationResponse.StatusCode);
}
}
Pour ajouter un lien entre deux entités, utilisez les méthodes AddLink et SetLink . Le code suivant ajoute un nouveau fournisseur et un nouveau produit, puis crée des liens entre eux.
// Add entities with links.
static void AddProductWithSupplier(ProductService.Container container,
ProductService.Product product, ProductService.Supplier supplier)
{
container.AddToSuppliers(supplier);
container.AddToProducts(product);
container.AddLink(supplier, "Products", product);
container.SetLink(product, "Supplier", supplier);
var serviceResponse = container.SaveChanges();
foreach (var operationResponse in serviceResponse)
{
Console.WriteLine(operationResponse.StatusCode);
}
}
Utilisez AddLink lorsque la propriété de navigation est une collection. Dans cet exemple, nous ajoutons un produit à la Products
collection sur le fournisseur.
Utilisez SetLink lorsque la propriété de navigation est une seule entité. Dans cet exemple, nous définissons la Supplier
propriété sur le produit.
Mise à jour / Correctif
Pour mettre à jour une entité, appelez la méthode UpdateObject .
static void UpdatePrice(ProductService.Container container, int id, decimal price)
{
var product = container.Products.Where(p => p.ID == id).SingleOrDefault();
if (product != null)
{
product.Price = price;
container.UpdateObject(product);
container.SaveChanges(SaveChangesOptions.PatchOnUpdate);
}
}
La mise à jour est effectuée lorsque vous appelez SaveChanges. Par défaut, WCF envoie une requête HTTP MERGE. L’option PatchOnUpdate indique à WCF d’envoyer un CORRECTIF HTTP à la place.
Notes
Pourquoi PATCH et MERGE ? La spécification HTTP 1.1 d’origine (RCF 2616) ne définissait aucune méthode HTTP avec sémantique de « mise à jour partielle ». Pour prendre en charge les mises à jour partielles, la spécification OData a défini la méthode MERGE. En 2010, RFC 5789 a défini la méthode PATCH pour les mises à jour partielles. Vous pouvez lire une partie de l’histoire dans ce billet de blog sur le blog WCF Data Services. Aujourd’hui, PATCH est préféré à MERGE. Le contrôleur OData créé par la génération automatique d’API web prend en charge les deux méthodes.
Si vous souhaitez remplacer l’entité entière (sémantique PUT), spécifiez l’option ReplaceOnUpdate . Wcf envoie alors une requête HTTP PUT.
container.SaveChanges(SaveChangesOptions.ReplaceOnUpdate);
Supprimer une entité
Pour supprimer une entité, appelez DeleteObject.
static void DeleteProduct(ProductService.Container container, int id)
{
var product = container.Products.Where(p => p.ID == id).SingleOrDefault();
if (product != null)
{
container.DeleteObject(product);
container.SaveChanges();
}
}
Appeler une action OData
Dans OData, les actions permettent d’ajouter des comportements côté serveur qui ne sont pas facilement définis en tant qu’opérations CRUD sur des entités.
Bien que le document de métadonnées OData décrive les actions, la classe proxy ne crée pas de méthodes fortement typées pour celles-ci. Vous pouvez toujours appeler une action OData à l’aide de la méthode Execute générique. Toutefois, vous devez connaître les types de données des paramètres et la valeur de retour.
Par exemple, l’action prend le RateProduct
paramètre nommé « Rating » de type Int32
et retourne un double
. Le code suivant montre comment appeler cette action.
int rating = 2;
Uri actionUri = new Uri(uri, "Products(5)/RateProduct");
var averageRating = container.Execute<double>(
actionUri, "POST", true, new BodyOperationParameter("Rating", rating)).First();
Pour plus d’informations, consultezAppel d’opérations et d’actions de service.
Une option consiste à étendre la classe Container pour fournir une méthode fortement typée qui appelle l’action :
namespace ProductServiceClient.ProductService
{
public partial class Container
{
public double RateProduct(int productID, int rating)
{
Uri actionUri = new Uri(this.BaseUri,
String.Format("Products({0})/RateProduct", productID)
);
return this.Execute<double>(actionUri,
"POST", true, new BodyOperationParameter("Rating", rating)).First();
}
}
}