Usando $select, $expand e $value no OData ASP.NET Web API 2

por Mike Wasson

Visão geral e exemplos de código para as opções de $expand, $select e $value na API Web do OData 2 para ASP.NET 4.x. Essas opções permitem que um cliente controle a representação que ele obtém do servidor.

  • $expand faz com que entidades relacionadas sejam incluídas embutidas na resposta.
  • $select seleciona um subconjunto de propriedades a serem incluídas na resposta.
  • $value obtém o valor bruto de uma propriedade.

Esquema de exemplo

Neste artigo, usarei um serviço OData que define três entidades: Produto, Fornecedor e Categoria. Cada produto tem uma categoria e um fornecedor.

Diagrama que mostra um esquema de exemplo para o serviço de Dados O, definindo produtos, fornecedores e categorias como suas entidades.

Aqui estão as classes C# que definem os modelos de entidade:

public class Supplier
{
    [Key]
    public string Key {get; set; }
    public string Name { get; set; }
}
public class Category
{
    public int ID { get; set; }
    public string Name { get; set; }
    public virtual ICollection<Product> Products { get; set; }
}

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

    [ForeignKey("Category")]
    public int CategoryId { get; set; }
    public Category Category { get; set; }

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

Observe que a Product classe define as propriedades de navegação para e CategorySupplier . A Category classe define uma propriedade de navegação para os produtos em cada categoria.

Para criar um ponto de extremidade OData para esse esquema, use o Visual Studio 2013 scaffolding, conforme descrito em Criando um ponto de extremidade OData no ASP.NET Web API. Adicione controladores separados para Produto, Categoria e Fornecedor.

Habilitando $expand e $select

Em Visual Studio 2013, o scaffolding OData da API Web cria um controlador que dá suporte automaticamente a $expand e $select. Para referência, aqui estão os requisitos para dar suporte a $expand e $select em um controlador.

Para coleções, o método do Get controlador deve retornar um IQueryable.

[Queryable]
public IQueryable<Category> GetCategories()
{
    return db.Categories;
}

Para entidades individuais, retorne um SingleResult<T>, em que T é um IQueryable que contém zero ou uma entidade.

[Queryable]
public SingleResult<Category> GetCategory([FromODataUri] int key)
{
    return SingleResult.Create(db.Categories.Where(c => c.ID == key));
}

Além disso, decore seus Get métodos com o atributo [Queryable] , conforme mostrado nos snippets de código anteriores. Como alternativa, chame EnableQuerySupport no objeto HttpConfiguration na inicialização. (Para obter mais informações, consulte Habilitando opções de consulta OData.)

Usando $expand

Quando você consulta uma entidade ou coleção OData, a resposta padrão não inclui entidades relacionadas. Por exemplo, aqui está a resposta padrão para o conjunto de entidades Categories:

{
  "odata.metadata":"http://localhost/odata/$metadata#Categories",
  "value":[
    {"ID":1,"Name":"Apparel"},
    {"ID":2,"Name":"Toys"}
  ]
}

Como você pode ver, a resposta não inclui nenhum produto, mesmo que a entidade Categoria tenha um link de navegação Produtos. No entanto, o cliente pode usar $expand para obter a lista de produtos para cada categoria. A opção $expand vai na cadeia de caracteres de consulta da solicitação:

GET http://localhost/odata/Categories?$expand=Products

Agora, o servidor incluirá os produtos para cada categoria, em linha com as categorias. Aqui está o conteúdo da resposta:

{
  "odata.metadata":"http://localhost/odata/$metadata#Categories",
  "value":[
    {
      "Products":[
        {"ID":1,"Name":"Hat","Price":"15.00","CategoryId":1,"SupplierId":"CTSO"},
        {"ID":2,"Name":"Scarf","Price":"12.00","CategoryId":1,"SupplierId":"CTSO"},
        {"ID":3,"Name":"Socks","Price":"5.00","CategoryId":1,"SupplierId":"FBRK"}
      ],
      "ID":1,
      "Name":"Apparel"
    },
    {
      "Products":[
        {"ID":4,"Name":"Yo-yo","Price":"4.95","CategoryId":2,"SupplierId":"WING"},
        {"ID":5,"Name":"Puzzle","Price":"8.00","CategoryId":2,"SupplierId":"WING"}
      ],
      "ID":2,
      "Name":"Toys"
    }
  ]
}

Observe que cada entrada na matriz "valor" contém uma lista Produtos.

A opção $expand usa uma lista separada por vírgulas de propriedades de navegação para expandir. A solicitação a seguir expande a categoria e o fornecedor de um produto.

GET http://localhost/odata/Products(1)?$expand=Category,Supplier

Este é o corpo da resposta:

{
  "odata.metadata":"http://localhost/odata/$metadata#Products/@Element",
  "Category": {"ID":1,"Name":"Apparel"},
  "Supplier":{"Key":"CTSO","Name":"Contoso, Ltd."},
  "ID":1,
  "Name":"Hat",
  "Price":"15.00",
  "CategoryId":1,
  "SupplierId":"CTSO"
}

Você pode expandir mais de um nível de propriedade de navegação. O exemplo a seguir inclui todos os produtos para uma categoria e também o fornecedor de cada produto.

GET http://localhost/odata/Categories(1)?$expand=Products/Supplier

Este é o corpo da resposta:

{
  "odata.metadata":"http://localhost/odata/$metadata#Categories/@Element",
  "Products":[
    {
      "Supplier":{"Key":"CTSO","Name":"Contoso, Ltd."},
      "ID":1,"Name":"Hat","Price":"15.00","CategoryId":1,"SupplierId":"CTSO"
    },
    {
      "Supplier":{"Key":"CTSO","Name":"Contoso, Ltd."},
      "ID":2,"Name":"Scarf","Price":"12.00","CategoryId":1,"SupplierId":"CTSO"
    },{
      "Supplier":{
        "Key":"FBRK","Name":"Fabrikam, Inc."
      },"ID":3,"Name":"Socks","Price":"5.00","CategoryId":1,"SupplierId":"FBRK"
    }
  ],"ID":1,"Name":"Apparel"
}

Por padrão, a API Web limita a profundidade máxima de expansão a 2. Isso impede que o cliente envie solicitações complexas como $expand=Orders/OrderDetails/Product/Supplier/Region, o que pode ser ineficiente para consultar e criar respostas grandes. Para substituir o padrão, defina a propriedade MaxExpansionDepth no atributo [Queryable] .

[Queryable(MaxExpansionDepth=4)]
public IQueryable<Category> GetCategories()
{
    return db.Categories;
}

Para obter mais informações sobre a opção $expand, consulte Expandir opção de consulta do sistema ($expand) na documentação oficial do OData.

Usando $select

A opção $select especifica um subconjunto de propriedades a serem incluídas no corpo da resposta. Por exemplo, para obter apenas o nome e o preço de cada produto, use a seguinte consulta:

GET http://localhost/odata/Products?$select=Price,Name

Este é o corpo da resposta:

{
  "odata.metadata":"http://localhost/odata/$metadata#Products&$select=Price,Name",
  "value":[
    {"Price":"15.00","Name":"Hat"},
    {"Price":"12.00","Name":"Scarf"},
    {"Price":"5.00","Name":"Socks"},
    {"Price":"4.95","Name":"Yo-yo"},
    {"Price":"8.00","Name":"Puzzle"}
  ]
}

Você pode combinar $select e $expand na mesma consulta. Inclua a propriedade expandida na opção $select. Por exemplo, a solicitação a seguir obtém o nome do produto e o fornecedor.

GET http://localhost/odata/Products?$select=Name,Supplier&$expand=Supplier

Este é o corpo da resposta:

{
  "odata.metadata":"http://localhost/odata/$metadata#Products&$select=Name,Supplier",
  "value":[
    {
      "Supplier":{"Key":"CTSO","Name":"Contoso, Ltd."},
      "Name":"Hat"
    },
    {
      "Supplier":{"Key":"CTSO","Name":"Contoso, Ltd."},
      "Name":"Scarf"
    },
    {
      "Supplier":{"Key":"FBRK","Name":"Fabrikam, Inc."},
      "Name":"Socks"
    },
    {
      "Supplier":{"Key":"WING","Name":"Wingtip Toys"},
      "Name":"Yo-yo"
    },
    {
      "Supplier":{"Key":"WING","Name":"Wingtip Toys"},
      "Name":"Puzzle"
   }
  ]
}

Você também pode selecionar as propriedades dentro de uma propriedade expandida. A solicitação a seguir expande Produtos e seleciona o nome da categoria mais o nome do produto.

GET http://localhost/odata/Categories?$expand=Products&$select=Name,Products/Name

Este é o corpo da resposta:

{
  "odata.metadata":"http://localhost/odata/$metadata#Categories&$select=Name,Products/Name",
  "value":[ 
    {
      "Products":[ {"Name":"Hat"},{"Name":"Scarf"},{"Name":"Socks"} ],
      "Name":"Apparel"
    },
    {
      "Products":[ {"Name":"Yo-yo"},{"Name":"Puzzle"} ],
      "Name":"Toys"
    }
  ]
}

Para obter mais informações sobre a opção $select, consulte Selecionar opção de consulta do sistema ($select) na documentação oficial do OData.

Obtendo propriedades individuais de uma entidade ($value)

Há duas maneiras de um cliente OData obter uma propriedade individual de uma entidade. O cliente pode obter o valor no formato OData ou obter o valor bruto da propriedade.

A solicitação a seguir obtém uma propriedade no formato OData.

GET http://localhost/odata/Products(1)/Name

Aqui está um exemplo de resposta no formato JSON:

HTTP/1.1 200 OK
Content-Type: application/json; odata=minimalmetadata; streaming=true; charset=utf-8
DataServiceVersion: 3.0
Content-Length: 90

{
  "odata.metadata":"http://localhost:14239/odata/$metadata#Edm.String",
  "value":"Hat"
}

Para obter o valor bruto da propriedade , acrescente $value ao URI:

GET http://localhost/odata/Products(1)/Name/$value

Aqui está a resposta. Observe que o tipo de conteúdo é "texto/sem formatação", não JSON.

HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
DataServiceVersion: 3.0
Content-Length: 3

Hat

Para dar suporte a essas consultas no controlador OData, adicione um método chamado GetProperty, em que Property é o nome da propriedade. Por exemplo, o método para obter a propriedade Name seria nomeado GetName. O método deve retornar o valor dessa propriedade:

public async Task<IHttpActionResult> GetName(int key)
{
    Product product = await db.Products.FindAsync(key);
    if (product == null)
    {
        return NotFound();
    }
    return Ok(product.Name);
}