Prise en charge des relations d’entité dans OData v3 avec l’API web 2

par Mike Wasson

Télécharger le projet terminé

La plupart des jeux de données définissent les relations entre les entités : les clients ont des commandes ; les livres ont des auteurs; les produits ont des fournisseurs. À l’aide d’OData, les clients peuvent naviguer sur les relations d’entité. En fonction d’un produit, vous pouvez trouver le fournisseur. Vous pouvez également créer ou supprimer des relations. Par exemple, vous pouvez définir le fournisseur d’un produit.

Ce tutoriel montre comment prendre en charge ces opérations dans API Web ASP.NET. Le tutoriel s’appuie sur le didacticiel Création d’un point de terminaison OData v3 avec l’API web 2.

Versions logicielles utilisées dans le tutoriel

  • API web 2
  • OData version 3
  • Entity Framework 6

Ajouter une entité fournisseur

Tout d’abord, nous devons ajouter un nouveau type d’entité à notre flux OData. Nous allons ajouter une Supplier classe.

using System.ComponentModel.DataAnnotations;

namespace ProductService.Models
{
    public class Supplier
    {
        [Key]
        public string Key { get; set; }
        public string Name { get; set; }
    }
}

Cette classe utilise une chaîne pour la clé d’entité. Dans la pratique, cela peut être moins courant que l’utilisation d’une clé entière. Mais il est utile de voir comment OData gère d’autres types de clés en plus des entiers.

Ensuite, nous allons créer une relation en ajoutant une Supplier propriété à la Product classe :

public class Product
{
    public int ID { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
    public string Category { get; set; }

    // New code
    [ForeignKey("Supplier")]
    public string SupplierId { get; set; }
    public virtual Supplier Supplier { get; set; }
}

Ajoutez un nouveau DbSet à la ProductServiceContext classe, afin qu’Entity Framework inclue la Supplier table dans la base de données.

public class ProductServiceContext : DbContext
{
    public ProductServiceContext() : base("name=ProductServiceContext")
    {
    }

    public System.Data.Entity.DbSet<ProductService.Models.Product> Products { get; set; }
    // New code:
    public System.Data.Entity.DbSet<ProductService.Models.Supplier> Suppliers { get; set; }
}

Dans WebApiConfig.cs, ajoutez une entité « Suppliers » au modèle EDM :

ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Product>("Products");
// New code:
builder.EntitySet<Supplier>("Suppliers");

Pour obtenir le fournisseur d’un produit, le client envoie une demande GET :

GET /Products(1)/Supplier

Ici, « Fournisseur » est une propriété de navigation sur le Product type. Dans ce cas, Supplier fait référence à un élément unique, mais une propriété de navigation peut également retourner une collection (relation un-à-plusieurs ou plusieurs-à-plusieurs).

Pour prendre en charge cette demande, ajoutez la méthode suivante à la ProductsController classe :

// GET /Products(1)/Supplier
public Supplier GetSupplier([FromODataUri] int key)
{
    Product product = _context.Products.FirstOrDefault(p => p.ID == key);
    if (product == null)
    {
        throw new HttpResponseException(HttpStatusCode.NotFound);
    }
    return product.Supplier;
}

Le paramètre key est la clé du produit. La méthode retourne l’entité associée, dans ce cas, une Supplier instance. Le nom de la méthode et le nom du paramètre sont tous deux importants. En général, si la propriété de navigation est nommée « X », vous devez ajouter une méthode nommée « GetX ». La méthode doit prendre un paramètre nommé « key » qui correspond au type de données de la clé parente.

Il est également important d’inclure l’attribut [FromOdataUri] dans le paramètre key . Cet attribut indique à l’API web d’utiliser des règles de syntaxe OData lorsqu’elle analyse la clé à partir de l’URI de requête.

OData prend en charge la création ou la suppression de relations entre deux entités. Dans la terminologie OData, la relation est un « lien ». Chaque lien a un URI avec le formulaire entité/$links/entité. Par exemple, le lien entre le produit et le fournisseur se présente comme suit :

/Products(1)/$links/Supplier

Pour créer un lien, le client envoie une requête POST à l’URI de lien. Le corps de la requête est l’URI de l’entité cible. Par exemple, supposons qu’il existe un fournisseur avec la clé « CTSO ». Pour créer un lien de « Product(1) » vers « Supplier('CTSO') », le client envoie une requête comme suit :

POST http://localhost/odata/Products(1)/$links/Supplier
Content-Type: application/json
Content-Length: 50

{"url":"http://localhost/odata/Suppliers('CTSO')"}

Pour supprimer un lien, le client envoie une demande DELETE à l’URI de lien.

Création de liens

Pour permettre à un client de créer des liens produit-fournisseur, ajoutez le code suivant à la ProductsController classe :

[AcceptVerbs("POST", "PUT")]
public async Task<IHttpActionResult> CreateLink([FromODataUri] int key, string navigationProperty, [FromBody] Uri link)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }
            
    Product product = await db.Products.FindAsync(key);
    if (product == null)
    {
        return NotFound();
    }
            
    switch (navigationProperty)
    {
        case "Supplier":
            string supplierKey = GetKeyFromLinkUri<string>(link);
            Supplier supplier = await db.Suppliers.FindAsync(supplierKey);
            if (supplier == null)
            {
                return NotFound();
            }
            product.Supplier = supplier;
            await db.SaveChangesAsync();
            return StatusCode(HttpStatusCode.NoContent);

        default:
            return NotFound();
    }
}

Cette méthode accepte trois paramètres :

  • key : clé de l’entité parente (le produit)
  • navigationProperty : nom de la propriété de navigation. Dans cet exemple, la seule propriété de navigation valide est « Fournisseur ».
  • link : URI OData de l’entité associée. Cette valeur est extraite du corps de la requête. Par exemple, l’URI de lien peut être «http://localhost/odata/Suppliers('CTSO') , ce qui signifie que le fournisseur avec l’ID = 'CTSO'.

La méthode utilise le lien pour rechercher le fournisseur. Si le fournisseur correspondant est trouvé, la méthode définit la Product.Supplier propriété et enregistre le résultat dans la base de données.

La partie la plus difficile consiste à analyser l’URI de lien. Fondamentalement, vous devez simuler le résultat de l’envoi d’une requête GET à cet URI. La méthode d’assistance suivante montre comment procéder. La méthode appelle le processus de routage de l’API web et récupère un instance ODataPath qui représente le chemin OData analysé. Pour un URI de lien, l’un des segments doit être la clé d’entité. (Si ce n’est pas le cas, le client a envoyé un URI incorrect.)

// Helper method to extract the key from an OData link URI.
private TKey GetKeyFromLinkUri<TKey>(Uri link)
{
    TKey key = default(TKey);

    // Get the route that was used for this request.
    IHttpRoute route = Request.GetRouteData().Route;

    // Create an equivalent self-hosted route. 
    IHttpRoute newRoute = new HttpRoute(route.RouteTemplate, 
        new HttpRouteValueDictionary(route.Defaults), 
        new HttpRouteValueDictionary(route.Constraints),
        new HttpRouteValueDictionary(route.DataTokens), route.Handler);

    // Create a fake GET request for the link URI.
    var tmpRequest = new HttpRequestMessage(HttpMethod.Get, link);

    // Send this request through the routing process.
    var routeData = newRoute.GetRouteData(
        Request.GetConfiguration().VirtualPathRoot, tmpRequest);

    // If the GET request matches the route, use the path segments to find the key.
    if (routeData != null)
    {
        ODataPath path = tmpRequest.GetODataPath();
        var segment = path.Segments.OfType<KeyValuePathSegment>().FirstOrDefault();
        if (segment != null)
        {
            // Convert the segment into the key type.
            key = (TKey)ODataUriUtils.ConvertFromUriLiteral(
                segment.Value, ODataVersion.V3);
        }
    }
    return key;
}

Suppression de liens

Pour supprimer un lien, ajoutez le code suivant à la ProductsController classe :

public async Task<IHttpActionResult> DeleteLink([FromODataUri] int key, string navigationProperty)
{
    Product product = await db.Products.FindAsync(key);
    if (product == null)
    {
        return NotFound();
    }

    switch (navigationProperty)
    {
        case "Supplier":
            product.Supplier = null;
            await db.SaveChangesAsync();
            return StatusCode(HttpStatusCode.NoContent);

        default:
            return NotFound();

    }
}

Dans cet exemple, la propriété de navigation est une entité unique Supplier . Si la propriété de navigation est une collection, l’URI permettant de supprimer un lien doit inclure une clé pour l’entité associée. Par exemple :

DELETE /odata/Customers(1)/$links/Orders(1)

Cette demande supprime la commande 1 du client 1. Dans ce cas, la méthode DeleteLink aura la signature suivante :

void DeleteLink([FromODataUri] int key, string relatedKey, string navigationProperty);

Le paramètre relatedKey donne la clé pour l’entité associée. Par conséquent, dans votre DeleteLink méthode, recherchez l’entité primaire par le paramètre key , recherchez l’entité associée par le paramètre relatedKey , puis supprimez l’association. Selon votre modèle de données, vous devrez peut-être implémenter les deux versions de DeleteLink. L’API web appelle la version correcte en fonction de l’URI de la demande.