Compartir a través de


Tipos abiertos en OData v4 con ASP.NET Web API

Por Microsoft

En OData v4, un tipo abierto es un tipo estructurado que contiene propiedades dinámicas, además de las propiedades declaradas en la definición de tipo. Los tipos abiertos le permiten agregar flexibilidad a los modelos de datos. En este tutorial se muestra cómo usar tipos abiertos en OData en ASP.NET Web API.

En este tutorial se supone que ya sabe cómo crear un punto de conexión de OData en ASP.NET Web API. Si no es así, lea primero Creación de un punto de conexión de OData v4.

Versiones de software usadas en el tutorial

  • Web API OData 5.3
  • OData v4

En primer lugar, alguna terminología de OData:

  • Tipo de entidad: tipo estructurado con una clave.
  • Tipo complejo: tipo estructurado sin una clave.
  • Tipo abierto: tipo con propiedades dinámicas. Tanto los tipos de entidad como los tipos complejos pueden ser abiertos.

El valor de una propiedad dinámica puede ser un tipo primitivo, un tipo complejo o un tipo de enumeración; o una colección de cualquiera de esos tipos. Para más información sobre los tipos abiertos, consulte la especificación de OData v4.

Instalación de las bibliotecas Web OData

Use el Administrador de paquetes NuGet para instalar las bibliotecas más recientes de Web API OData. En la ventana Consola del Administrador de paquetes:

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

Definición de los tipos CLR

Comience definiendo los modelos EDM como tipos 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; }
}

Cuando se crea el modelo de datos de entidad (EDM),

  • Category es un tipo de enumeración.
  • Address es un tipo complejo. (No tiene una clave, por lo que no es un tipo de entidad).
  • Customer es un tipo de entidad. (Tiene una clave).
  • Press es un tipo complejo abierto.
  • Book es un tipo de entidad abierta.

Para crear un tipo abierto, el tipo CLR debe tener una propiedad de tipo IDictionary<string, object>, que contiene las propiedades dinámicas.

Compilación del modelo EDM

Si usa ODataConventionModelBuilder para crear el EDM, Press y Book se agregan automáticamente como tipos abiertos, si existe una propiedad IDictionary<string, object>.

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

    }
}

También puede compilar el EDM explícitamente mediante 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();

Adición de un controlador OData

A continuación, agregue un controlador OData. En este tutorial, usaremos un controlador simplificado que solo admite solicitudes GET y POST y usa una lista en memoria para almacenar entidades.

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

Observe que la primera instancia de Book no tiene propiedades dinámicas. La segunda instancia de Book tiene las siguientes propiedades dinámicas:

  • "Published": tipo primitivo
  • "Authors": colección de tipos primitivos
  • "OtherCategories": colección de tipos de enumeración.

Además, la propiedad Press de esa instancia de Book tiene las siguientes propiedades dinámicas:

  • "Blog": tipo primitivo
  • "Address": tipo complejo

Consulta de los metadatos

Para obtener el documento de metadatos OData, envíe una solicitud GET a ~/$metadata. El cuerpo de la respuesta debe tener un aspecto similar al siguiente:

<?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>

En el documento de metadatos, puede ver lo siguiente:

  • En los tipos Book y Press, el valor del atributo OpenType es true. Los tipos Customer y Address no tienen este atributo.
  • El tipo de entidad Book tiene tres propiedades declaradas: ISBN, Title y Press. Los metadatos de OData no incluyen la propiedad Book.Properties de la clase CLR.
  • Del mismo modo, el tipo complejo Press solo tiene dos propiedades declaradas: Name y Category. Los metadatos no incluyen la propiedad Press.DynamicProperties de la clase CLR.

Consulta de una entidad

Para obtener el libro con un ISBN "978-0-7356-7942-9", envíe una solicitud GET a ~/Books('978-0-7356-7942-9'). El cuerpo de la respuesta debe tener un aspecto similar al siguiente. (Se ha aplicado sangría para que sea más legible).

{
  "@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"
  ]
}

Observe que las propiedades dinámicas se incluyen alineadas con las propiedades declaradas.

Uso de POST con una entidad

Para agregar una entidad Book, envíe una solicitud POST a ~/Books. El cliente puede establecer propiedades dinámicas en la carga de la solicitud.

Esta es una solicitud de ejemplo. Observe las propiedades "Price" y "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 establece un punto de interrupción en el método de controlador, puede ver que Web API agregó estas propiedades al diccionario Properties.

Screenshot of the code that sends a POST request, highlighting the 'add books' part of the code to show the properties added by the Web A P I.

Recursos adicionales

Ejemplo de tipo abierto de OData