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
yPress
, el valor del atributoOpenType
es true. Los tiposCustomer
yAddress
no tienen este atributo. - El tipo de entidad
Book
tiene tres propiedades declaradas: ISBN, Title y Press. Los metadatos de OData no incluyen la propiedadBook.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 propiedadPress.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
.