Ouvrir des types dans OData v4 avec API Web ASP.NET

par Microsoft

Dans OData v4, un type ouvert est un type structuré qui contient des propriétés dynamiques, en plus des propriétés déclarées dans la définition de type. Les types ouverts vous permettent d’ajouter de la flexibilité à vos modèles de données. Ce tutoriel montre comment utiliser des types ouverts dans API Web ASP.NET OData.

Ce tutoriel part du principe que vous savez déjà comment créer un point de terminaison OData dans API Web ASP.NET. Si ce n’est pas le cas, commencez par lire Créer un point de terminaison OData v4 .

Versions logicielles utilisées dans le tutoriel

  • API web OData 5.3
  • OData v4

Tout d’abord, certaines terminologies OData :

  • Type d’entité : type structuré avec une clé.
  • Type complexe : type structuré sans clé.
  • Type ouvert : type avec des propriétés dynamiques. Les types d’entité et les types complexes peuvent être ouverts.

La valeur d’une propriété dynamique peut être un type primitif, un type complexe ou un type d’énumération ; ou une collection de l’un de ces types. Pour plus d’informations sur les types ouverts, consultez la spécification OData v4.

Installer les bibliothèques Web OData

Utilisez le Gestionnaire de package NuGet pour installer les bibliothèques OData de l’API web les plus récentes. À partir de la fenêtre Console du Gestionnaire de package :

Install-Package Microsoft.AspNet.OData
Install-Package Microsoft.AspNet.WebApi.OData

Définir les types CLR

Commencez par définir les modèles EDM en tant que types CLR.

public enum Category
{
    Book,
    Magazine,
    EBook
}

public class Address
{
    public string City { get; set; }
    public string Street { get; set; }
}

public class Customer
{
    public int Id { get; set; }
    public string Name { get; set; }
    public Address Address { get; set; }
}

public class Press
{
    public string Name { get; set; }
    public string Email { get; set; }
    public Category Category { get; set; }
    public IDictionary<string, object> DynamicProperties { get; set; }
}

public class Book
{
    [Key]
    public string ISBN { get; set; }
    public string Title { get; set; }
    public Press Press { get; set; }
    public IDictionary<string, object> Properties { get; set; }
}

Lorsque le modèle de données d’entité (EDM) est créé,

  • Category est un type d’énumération.
  • Address est un type complexe. (Il n’a pas de clé, il ne s’agit donc pas d’un type d’entité.)
  • Customer est un type d’entité. (Il a une clé.)
  • Press est un type complexe ouvert.
  • Book est un type d’entité ouvert.

Pour créer un type ouvert, le type CLR doit avoir une propriété de type IDictionary<string, object>, qui contient les propriétés dynamiques.

Générer le modèle EDM

Si vous utilisez ODataConventionModelBuilder pour créer l’EDM et PressBook sont automatiquement ajoutés en tant que types ouverts, en fonction de la présence d’une IDictionary<string, object> propriété.

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
        builder.EntitySet<Book>("Books");
        builder.EntitySet<Customer>("Customers");
        var model = builder.GetEdmModel();

        config.MapODataServiceRoute(
            routeName: "ODataRoute",
            routePrefix: null,
            model: model);

    }
}

Vous pouvez également générer explicitement l’EDM à l’aide d’ODataModelBuilder.

ODataModelBuilder builder = new ODataModelBuilder();

ComplexTypeConfiguration<Press> pressType = builder.ComplexType<Press>();
pressType.Property(c => c.Name);
// ...
pressType.HasDynamicProperties(c => c.DynamicProperties);

EntityTypeConfiguration<Book> bookType = builder.EntityType<Book>();
bookType.HasKey(c => c.ISBN);
bookType.Property(c => c.Title);
// ...
bookType.ComplexProperty(c => c.Press);
bookType.HasDynamicProperties(c => c.Properties);

// ...
builder.EntitySet<Book>("Books");
IEdmModel model = builder.GetEdmModel();

Ajouter un contrôleur OData

Ensuite, ajoutez un contrôleur OData. Pour ce tutoriel, nous allons utiliser un contrôleur simplifié qui prend uniquement en charge les requêtes GET et POST, et utilise une liste en mémoire pour stocker des entités.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Http;
using System.Web.OData;

namespace MyApp.Controllers
{
    public class BooksController : ODataController
    {
        private IList<Book> _books = new List<Book>
        {
            new Book
            {
                ISBN = "978-0-7356-8383-9",
                Title = "SignalR Programming in Microsoft ASP.NET",
                Press = new Press
                {
                    Name = "Microsoft Press",
                    Category = Category.Book
                }
            },

            new Book
            {
                ISBN = "978-0-7356-7942-9",
                Title = "Microsoft Azure SQL Database Step by Step",
                Press = new Press
                {
                    Name = "Microsoft Press",
                    Category = Category.EBook,
                    DynamicProperties = new Dictionary<string, object>
                    {
                        { "Blog", "https://blogs.msdn.com/b/microsoft_press/" },
                        { "Address", new Address { 
                              City = "Redmond", Street = "One Microsoft Way" }
                        }
                    }
                },
                Properties = new Dictionary<string, object>
                {
                    { "Published", new DateTimeOffset(2014, 7, 3, 0, 0, 0, 0, new TimeSpan(0))},
                    { "Authors", new [] { "Leonard G. Lobel", "Eric D. Boyd" }},
                    { "OtherCategories", new [] {Category.Book, Category.Magazine}}
                }
            }
        };

        [EnableQuery]
        public IQueryable<Book> Get()
        {
            return _books.AsQueryable();
        }

        public IHttpActionResult Get([FromODataUri]string key)
        {
            Book book = _books.FirstOrDefault(e => e.ISBN == key);
            if (book == null)
            {
                return NotFound();
            }

            return Ok(book);
        }

        public IHttpActionResult Post(Book book)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            } 
            // For this sample, we aren't enforcing unique keys.
            _books.Add(book);
            return Created(book);
        }
    }
}

Notez que la première Book instance n’a pas de propriétés dynamiques. Le deuxième Book instance a les propriétés dynamiques suivantes :

  • « Publié » : type primitif
  • « Authors » : Collection de types primitifs
  • « OtherCategories » : collection de types d’énumération.

En outre, la Press propriété de cette Book instance a les propriétés dynamiques suivantes :

  • « Blog » : type primitif
  • « Adresse » : type complexe

Interroger les métadonnées

Pour obtenir le document de métadonnées OData, envoyez une requête GET à ~/$metadata. Le corps de la réponse doit ressembler à ceci :

<?xml version="1.0" encoding="utf-8"?>
<edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx">
  <edmx:DataServices>
    <Schema Namespace="MyApp.Models" xmlns="http://docs.oasis-open.org/odata/ns/edm">
      <EntityType Name="Book" OpenType="true">
        <Key>
          <PropertyRef Name="ISBN" />
        </Key>
        <Property Name="ISBN" Type="Edm.String" Nullable="false" />
        <Property Name="Title" Type="Edm.String" />
        <Property Name="Press" Type="MyApp.Models.Press" />
      </EntityType>
      <EntityType Name="Customer">
        <Key>
          <PropertyRef Name="Id" />
        </Key>
        <Property Name="Id" Type="Edm.Int32" Nullable="false" />
        <Property Name="Name" Type="Edm.String" />
        <Property Name="Address" Type="MyApp.Models.Address" />
      </EntityType>
      <ComplexType Name="Press" OpenType="true">
        <Property Name="Name" Type="Edm.String" />
        <Property Name="Category" Type="MyApp.Models.Category" Nullable="false" />
      </ComplexType>
      <ComplexType Name="Address">
        <Property Name="City" Type="Edm.String" />
        <Property Name="Street" Type="Edm.String" />
      </ComplexType>
      <EnumType Name="Category">
        <Member Name="Book" Value="0" />
        <Member Name="Magazine" Value="1" />
        <Member Name="EBook" Value="2" />
      </EnumType>
    </Schema>
    <Schema Namespace="Default" xmlns="http://docs.oasis-open.org/odata/ns/edm">
      <EntityContainer Name="Container">
        <EntitySet Name="Books" EntityType="MyApp.Models.Book" />
        <EntitySet Name="Customers" EntityType="MyApp.Models.Customer" />
      </EntityContainer>
    </Schema>
  </edmx:DataServices>
</edmx:Edmx>

Dans le document de métadonnées, vous pouvez voir ce qui suit :

  • Pour les Book types et Press , la valeur de l’attribut OpenType est true. Les Customer types et Address n’ont pas cet attribut.
  • Le Book type d’entité a trois propriétés déclarées : ISBN, Title et Press. Les métadonnées OData n’incluent pas la Book.Properties propriété de la classe CLR.
  • De même, le Press type complexe a seulement deux propriétés déclarées : Name et Category. Les métadonnées n’incluent pas la Press.DynamicProperties propriété de la classe CLR.

Interroger une entité

Pour obtenir le livre avec ISBN égal à « 978-0-7356-7942-9 », envoyez une requête GET à ~/Books('978-0-7356-7942-9'). Le corps de la réponse doit ressembler à ce qui suit. (Mise en retrait pour la rendre plus lisible.)

{
  "@odata.context":"http://localhost:37141/$metadata#Books/$entity",
    "ISBN":"978-0-7356-7942-9",
    "Title":"Microsoft Azure SQL Database Step by Step",
    "Press":{
      "Name":"Microsoft Press",
      "Category":"EBook",
      "Blog":"https://blogs.msdn.com/b/microsoft_press/",
      "Address":{
        "@odata.type":"#MyApp.Models.Address",
        "City":"Redmond",
        "Street":"One Microsoft Way"
      }
  },
  "Published":"2014-07-03T00:00:00Z",
  "Authors@odata.type":"#Collection(String)",
  "Authors":[
    "Leonard G. Lobel","Eric D. Boyd"
  ],
  "OtherCategories@odata.type":"#Collection(MyApp.Models.Category)",
  "OtherCategories":[
    "Book","Magazine"
  ]
}

Notez que les propriétés dynamiques sont incluses dans les propriétés déclarées.

POST d’une entité

Pour ajouter une entité Book, envoyez une requête POST à ~/Books. Le client peut définir des propriétés dynamiques dans la charge utile de la requête.

Voici un exemple de demande. Notez les propriétés « Price » et « Published ».

POST http://localhost:37141/Books HTTP/1.1
User-Agent: Fiddler
Host: localhost:37141
Content-Type: application/json
Content-Length: 191

{
  "ISBN":"978-0-7356-8383-9","Title":"Programming Microsoft ASP.NET MVC","Press":{
  "Name":"Microsoft Press","Category":"Book"
   }, "Price": 49.99, "Published":"2014-02-15T00:00:00Z"
}

Si vous définissez un point d’arrêt dans la méthode du contrôleur, vous pouvez voir que l’API web a ajouté ces propriétés au Properties dictionnaire.

Capture d’écran du code qui envoie une demande POST, mettant en évidence la partie « ajouter des livres » du code pour afficher les propriétés ajoutées par l’API web.

Ressources supplémentaires

OData Open Type Sample