Appel à un service OData à partir d’un client .NET (C#)

par Mike Wasson

Télécharger le projet terminé

Ce tutoriel montre comment appeler un service OData à partir d’une application cliente C#.

Versions logicielles utilisées dans le tutoriel

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

Diagramme montrant les entités du service O Data et une liste de leurs propriétés, avec des flèches de connexion pour montrer comment chacune d’elles se lie ou fonctionne ensemble.

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.)

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.

Diagramme montrant les appels de requête HT P du proxy de service s’exécutant dans les deux sens à partir de l’application, via le proxy de service et vers le service O Data.

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.

Capture d’écran de la fenêtre de l’Explorateur de solutions, montrant le menu sous « références » afin d’ajouter une nouvelle 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

port est le numéro de port.

Capture d’écran de la fenêtre « ajouter une référence de service », qui montre le numéro de port dans le champ d’adresse URL et un champ pour ajouter un espace nom.

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.

Capture d’écran de la boîte de dialogue « Ajouter une référence de service », mettant en surbrillance le service de conteneur, pour afficher les opérations en cours d’exécution.

Cliquez sur OK pour ajouter la classe proxy à votre projet.

Capture d’écran de la boîte de dialogue de l’Explorateur de solutions, montrant le menu sous « client de service de produit » et mettant en surbrillance l’option « Service produit ».

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();
        }
    }
}