Partilhar via


Abrir tipos no OData v4 com ASP.NET Web API

pela Microsoft

No OData v4, um tipo aberto é um tipo estruturado que contém propriedades dinâmicas, além de quaisquer propriedades declaradas na definição de tipo. Os tipos abertos permitem que você adicione flexibilidade aos seus modelos de dados. Este tutorial mostra como usar tipos abertos no ASP.NET Web API OData.

Este tutorial pressupõe que você já saiba como criar um ponto de extremidade OData no ASP.NET Web API. Caso contrário, comece lendo Criar um ponto de extremidade OData v4 primeiro.

Versões de software usadas no tutorial

  • OData da API Web 5.3
  • OData v4

Primeiro, algumas terminologias do OData:

  • Tipo de entidade: um tipo estruturado com uma chave.
  • Tipo complexo: um tipo estruturado sem uma chave.
  • Tipo aberto: um tipo com propriedades dinâmicas. Os tipos de entidade e os tipos complexos podem ser abertos.

O valor de uma propriedade dinâmica pode ser um tipo primitivo, um tipo complexo ou um tipo de enumeração; ou uma coleção de qualquer um desses tipos. Para obter mais informações sobre tipos abertos, consulte a especificação OData v4.

Instalar as bibliotecas Web OData

Use o Gerenciador de Pacotes NuGet para instalar as bibliotecas OData da API Web mais recentes. Da janela Console do Gerenciador de Pacotes:

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

Definir os tipos CLR

Comece definindo os 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; }
}

Quando o EDM (Modelo de Dados de Entidade) é criado,

  • Category é um tipo de enumeração.
  • Address é um tipo complexo. (Ele não tem uma chave, portanto, não é um tipo de entidade.)
  • Customer é um tipo de entidade. (Ele tem uma chave.)
  • Press é um tipo complexo aberto.
  • Book é um tipo de entidade aberta.

Para criar um tipo aberto, o tipo CLR deve ter uma propriedade do tipo IDictionary<string, object>, que contém as propriedades dinâmicas.

Criar o modelo EDM

Se você usar ODataConventionModelBuilder para criar o EDM Press e Book for adicionado automaticamente como tipos abertos, com base na presença de uma IDictionary<string, object> propriedade.

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

    }
}

Você também pode criar o EDM explicitamente, usando 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();

Adicionar um controlador OData

Em seguida, adicione um controlador OData. Para este tutorial, usaremos um controlador simplificado que dá suporte apenas a solicitações GET e POST e usa uma lista na memória para armazenar 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 a primeira Book instância não tem propriedades dinâmicas. A segunda Book instância tem as seguintes propriedades dinâmicas:

  • "Publicado": tipo primitivo
  • "Authors": coleção de tipos primitivos
  • "OtherCategories": coleção de tipos de enumeração.

Além disso, a Press propriedade dessa Book instância tem as seguintes propriedades dinâmicas:

  • "Blog": tipo primitivo
  • "Address": tipo complexo

Consultar os metadados

Para obter o documento de metadados OData, envie uma solicitação GET para ~/$metadata. O corpo da resposta deve ser semelhante a este:

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

No documento de metadados, você pode ver que:

  • Para os Book tipos e Press , o valor do OpenType atributo é true. Os Customer tipos e Address não têm esse atributo.
  • O Book tipo de entidade tem três propriedades declaradas: ISBN, Title e Press. Os metadados OData não incluem a Book.Properties propriedade da classe CLR.
  • Da mesma forma, o Press tipo complexo tem apenas duas propriedades declaradas: Nome e Categoria. Os metadados não incluem a Press.DynamicProperties propriedade da classe CLR.

Consultar uma entidade

Para obter o livro com ISBN igual a "978-0-7356-7942-9", envie uma solicitação GET para ~/Books('978-0-7356-7942-9'). O corpo da resposta deve ser semelhante ao seguinte. (Recuado para torná-lo mais legível.)

{
  "@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 as propriedades dinâmicas são incluídas embutidas com as propriedades declaradas.

POST an Entity

Para adicionar uma entidade Book, envie uma solicitação POST para ~/Books. O cliente pode definir propriedades dinâmicas no conteúdo da solicitação.

Aqui está um exemplo de solicitação. Observe as propriedades "Price" e "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"
}

Se você definir um ponto de interrupção no método do controlador, poderá ver que a API Web adicionou essas propriedades ao Properties dicionário.

Captura de tela do código que envia uma solicitação POST, realçando a parte

Recursos adicionais

Exemplo de tipo aberto OData