Créer un point de terminaison OData v4 à l’aide de API Web ASP.NET

Le protocole OData (Open Data Protocol) est un protocole d’accès aux données pour le web. OData fournit un moyen uniforme d’interroger et de manipuler des jeux de données par le biais d’opérations CRUD (créer, lire, mettre à jour et supprimer).

API Web ASP.NET prend en charge les versions v3 et v4 du protocole. Vous pouvez même avoir un point de terminaison v4 qui s’exécute côte à côte avec un point de terminaison v3.

Ce tutoriel montre comment créer un point de terminaison OData v4 qui prend en charge les opérations CRUD.

Versions logicielles utilisées dans le tutoriel

  • API web 5.2
  • OData v4
  • Visual Studio 2017 (téléchargez Visual Studio 2017 ici)
  • Entity Framework 6
  • .NET 4.7.2

Versions du didacticiel

Pour la version 3 d’OData, consultez Création d’un point de terminaison OData v3.

Créer le projet Visual Studio

Dans Visual Studio, dans le menu Fichier , sélectionnez Nouveau>projet.

DéveloppezVisual C#>Webinstallé>, puis sélectionnez le modèle application web ASP.NET (.NET Framework). Nommez le projet « ProductService ».

Capture d’écran de la fenêtre nouveau projet visual studio, montrant les options de menu permettant de créer une application web A S P P dot NET avec dot NET Framework.

Sélectionnez OK.

Capture d’écran de l’application web A SP dot NET, montrant les modèles disponibles pour créer l’application avec un dossier d’API web et une référence principale.

Sélectionnez le modèle Vide. Sous Ajouter des dossiers et des références principales pour :, sélectionnez API web. Sélectionnez OK.

Installer les packages OData

Dans le menu Outils, sélectionnez Gestionnaire de package NuGet>Console du Gestionnaire de package. Dans la fenêtre Console du Gestionnaire de package, tapez :

Install-Package Microsoft.AspNet.Odata

Cette commande installe les derniers packages NuGet OData.

Ajouter une classe de modèle

Un modèle est un objet qui représente une entité de données dans votre application.

Dans l’Explorateur de solutions, cliquez avec le bouton droit sur le dossier Modèles. Dans le menu contextuel, sélectionnez Ajouter une>classe.

Capture d’écran de la fenêtre de l’Explorateur de solutions, mettant en surbrillance le chemin d’accès permettant d’ajouter un objet de classe de modèle au projet.

Notes

Par convention, les classes de modèle sont placées dans le dossier Models, mais vous n’avez pas besoin de suivre cette convention dans vos propres projets.

Nommez la classe Product. Dans le fichier Product.cs, remplacez le code réutilisable par ce qui suit :

namespace ProductService.Models
{
    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public decimal Price { get; set; }
        public string Category { get; set; }
    }
}

La Id propriété est la clé d’entité. Les clients peuvent interroger des entités par clé. Par exemple, pour obtenir le produit avec l’ID 5, l’URI est /Products(5). La Id propriété sera également la clé primaire dans la base de données principale.

Activer Entity Framework

Pour ce tutoriel, nous allons utiliser Entity Framework (EF) Code First pour créer la base de données principale.

Notes

L’API web OData ne nécessite pas EF. Utilisez n’importe quelle couche d’accès aux données qui peut traduire des entités de base de données en modèles.

Tout d’abord, installez le package NuGet pour EF. Dans le menu Outils, sélectionnez Gestionnaire de package NuGet>Console du Gestionnaire de package. Dans la fenêtre Console du Gestionnaire de package, tapez :

Install-Package EntityFramework

Ouvrez le fichier Web.config et ajoutez la section suivante à l’intérieur de l’élément de configuration , après l’élément configSections .

<configuration>
  <configSections>
    <!-- ... -->
  </configSections>

  <!-- Add this: -->
  <connectionStrings>
    <add name="ProductsContext" connectionString="Data Source=(localdb)\mssqllocaldb; 
        Initial Catalog=ProductsContext; Integrated Security=True; MultipleActiveResultSets=True; 
        AttachDbFilename=|DataDirectory|ProductsContext.mdf"
      providerName="System.Data.SqlClient" />
  </connectionStrings>

Ce paramètre ajoute une chaîne de connexion pour une base de données LocalDB. Cette base de données sera utilisée lorsque vous exécuterez l’application localement.

Ensuite, ajoutez une classe nommée ProductsContext au dossier Models :

using System.Data.Entity;
namespace ProductService.Models
{
    public class ProductsContext : DbContext
    {
        public ProductsContext() 
                : base("name=ProductsContext")
        {
        }
        public DbSet<Product> Products { get; set; }
    }
}

Dans le constructeur, "name=ProductsContext" donne le nom de la chaîne de connexion.

Configurer le point de terminaison OData

Ouvrez le fichier App_Start/WebApiConfig.cs. Ajoutez les instructions using suivantes :

using ProductService.Models;
using Microsoft.AspNet.OData.Builder;
using Microsoft.AspNet.OData.Extensions;

Ajoutez ensuite le code suivant à la méthode Register :

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // New code:
        ODataModelBuilder builder = new ODataConventionModelBuilder();
        builder.EntitySet<Product>("Products");
        config.MapODataServiceRoute(
            routeName: "ODataRoute",
            routePrefix: null,
            model: builder.GetEdmModel());
    }
}

Ce code effectue deux opérations :

  • Crée un modèle de données d’entité (EDM).
  • Ajoute un itinéraire.

Un EDM est un modèle abstrait des données. L’EDM est utilisé pour créer le document de métadonnées de service. La classe ODataConventionModelBuilder crée un EDM à l’aide des conventions d’affectation de noms par défaut. Cette approche nécessite le moins de code. Si vous souhaitez mieux contrôler l’EDM, vous pouvez utiliser la classe ODataModelBuilder pour créer l’EDM en ajoutant explicitement des propriétés, des clés et des propriétés de navigation.

Un itinéraire indique à l’API web comment acheminer les requêtes HTTP vers le point de terminaison. Pour créer un itinéraire OData v4, appelez la méthode d’extension MapODataServiceRoute .

Si votre application a plusieurs points de terminaison OData, créez un itinéraire distinct pour chacun d’eux. Donnez à chaque itinéraire un nom et un préfixe uniques.

Ajouter le contrôleur OData

Un contrôleur est une classe qui gère les requêtes HTTP. Vous créez un contrôleur distinct pour chaque ensemble d’entités dans votre service OData. Dans ce tutoriel, vous allez créer un contrôleur pour l’entité Product .

Dans Explorateur de solutions, cliquez avec le bouton droit sur le dossier Contrôleurs et sélectionnez Ajouter une>classe. Nommez la classe ProductsController.

Notes

La version de ce tutoriel pour OData v3 utilise la génération automatique Ajouter un contrôleur . Actuellement, il n’existe pas de génération automatique pour OData v4.

Remplacez le code réutilisable dans ProductsController.cs par ce qui suit.

using ProductService.Models;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.OData;
namespace ProductService.Controllers
{
    public class ProductsController : ODataController
    {
        ProductsContext db = new ProductsContext();
        private bool ProductExists(int key)
        {
            return db.Products.Any(p => p.Id == key);
        } 
        protected override void Dispose(bool disposing)
        {
            db.Dispose();
            base.Dispose(disposing);
        }
    }
}

Le contrôleur utilise la ProductsContext classe pour accéder à la base de données à l’aide d’EF. Notez que le contrôleur remplace la méthode Dispose pour supprimer le ProductsContext.

Il s’agit du point de départ du contrôleur. Ensuite, nous allons ajouter des méthodes pour toutes les opérations CRUD.

Interroger le jeu d’entités

Ajoutez les méthodes suivantes à ProductsController.

[EnableQuery]
public IQueryable<Product> Get()
{
    return db.Products;
}
[EnableQuery]
public SingleResult<Product> Get([FromODataUri] int key)
{
    IQueryable<Product> result = db.Products.Where(p => p.Id == key);
    return SingleResult.Create(result);
}

La version sans paramètre de la Get méthode retourne l’intégralité de la collection Products. La Get méthode avec un paramètre de clé recherche un produit par sa clé (dans ce cas, la Id propriété ).

L’attribut [EnableQuery] permet aux clients de modifier la requête à l’aide d’options de requête telles que $filter, $sort et $page. Pour plus d’informations, consultez Prise en charge des options de requête OData.

Ajouter une entité au jeu d’entités

Pour permettre aux clients d’ajouter un nouveau produit à la base de données, ajoutez la méthode suivante à ProductsController.

public async Task<IHttpActionResult> Post(Product product)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }
    db.Products.Add(product);
    await db.SaveChangesAsync();
    return Created(product);
}

Mise à jour d'une entité

OData prend en charge deux sémantiques différentes pour la mise à jour d’une entité , PATCH et PUT.

  • PATCH effectue une mise à jour partielle. Le client spécifie uniquement les propriétés à mettre à jour.
  • PUT remplace l’entité entière.

L’inconvénient de PUT est que le client doit envoyer des valeurs pour toutes les propriétés de l’entité, y compris les valeurs qui ne changent pas. La spécification OData indique que PATCH est préféré.

Dans tous les cas, voici le code des méthodes PATCH et PUT :

public async Task<IHttpActionResult> Patch([FromODataUri] int key, Delta<Product> product)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }
    var entity = await db.Products.FindAsync(key);
    if (entity == null)
    {
        return NotFound();
    }
    product.Patch(entity);
    try
    {
        await db.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!ProductExists(key))
        {
            return NotFound();
        }
        else
        {
            throw;
        }
    }
    return Updated(entity);
}
public async Task<IHttpActionResult> Put([FromODataUri] int key, Product update)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }
    if (key != update.Id)
    {
        return BadRequest();
    }
    db.Entry(update).State = EntityState.Modified;
    try
    {
        await db.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!ProductExists(key))
        {
            return NotFound();
        }
        else
        {
            throw;
        }
    }
    return Updated(update);
}

Dans le cas de PATCH, le contrôleur utilise le type Delta<T> pour suivre les modifications.

Suppression d’une entité

Pour permettre aux clients de supprimer un produit de la base de données, ajoutez la méthode suivante à ProductsController.

public async Task<IHttpActionResult> Delete([FromODataUri] int key)
{
    var product = await db.Products.FindAsync(key);
    if (product == null)
    {
        return NotFound();
    }
    db.Products.Remove(product);
    await db.SaveChangesAsync();
    return StatusCode(HttpStatusCode.NoContent);
}