Criar um ponto de extremidade do OData v4 usando ASP.NET Web API

O Protocolo Open Data (OData) é um protocolo de acesso a dados para a Web. O OData fornece uma maneira uniforme de consultar e manipular conjuntos de dados por meio de operações CRUD (criar, ler, atualizar e excluir).

O ASP.NET Web API dá suporte a V3 e v4 do protocolo. Você pode até mesmo ter um ponto de extremidade v4 que é executado lado a lado com um ponto de extremidade v3.

Este tutorial mostra como criar um ponto de extremidade do OData v4 que oferece suporte a operações CRUD.

Versões de software usadas no tutorial

  • API Web 5,2
  • OData v4
  • Visual Studio 2017 (Baixe o Visual Studio 2017 aqui)
  • Entity Framework 6
  • .NET 4.7.2

Versões do tutorial

Para o OData versão 3, consulte criando um ponto de extremidade OData v3.

Criar o projeto do Visual Studio

No Visual Studio, no menu arquivo , selecione novo projetode >.

Expanda instalado > C# Visual > Webe selecione o modelo aplicativo Web do ASP.net (.NET Framework) . Nomeie o projeto "ProductService".

Selecione OK.

Selecione o modelo Vazio. Em Adicionar pastas e referências principais para: , selecione API Web. Selecione OK.

Instalar os pacotes OData

No menu ferramentas , selecione Gerenciador de pacotes NuGet > console do Gerenciador de pacotes. Na janela do console do Gerenciador de pacotes, digite:

Install-Package Microsoft.AspNet.Odata

Esse comando instala os pacotes do NuGet do OData mais recentes.

Adicionar uma classe de modelo

Um modelo é um objeto que representa uma entidade de dados em seu aplicativo.

No Gerenciador de Soluções, clique com o botão direito do mouse na pasta Modelos. No menu de contexto, selecione adicionar > classe.

Note

Por convenção, as classes de modelo são colocadas na pasta modelos, mas você não precisa seguir essa convenção em seus próprios projetos.

Nome da classe Product. No arquivo Product.cs, substitua o código clichê pelo seguinte:

namespace ProductService.Models
{
    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public decimal Price { get; set; }
        public string Category { get; set; }
    }
}

A propriedade Id é a chave de entidade. Os clientes podem consultar entidades por chave. Por exemplo, para obter o produto com a ID 5, o URI é /Products(5). A propriedade Id também será a chave primária no banco de dados back-end.

Habilitar Entity Framework

Para este tutorial, usaremos Entity Framework (EF) Code First para criar o banco de dados back-end.

Note

O OData da API Web não requer o EF. Use qualquer camada de acesso a dados que possa converter entidades de banco de dado em modelos.

Primeiro, instale o pacote NuGet para o EF. No menu ferramentas , selecione Gerenciador de pacotes NuGet > console do Gerenciador de pacotes. Na janela do console do Gerenciador de pacotes, digite:

Install-Package EntityFramework

Abra o arquivo Web. config e adicione a seção a seguir dentro do elemento de configuração , após o elemento configSections .

<configuration>
  <configSections>
    <!-- ... -->
  </configSections>

  <!-- Add this: -->
  <connectionStrings>
    <add name="ProductsContext" connectionString="Data Source=(localdb)\mssqllocaldb; 
        Initial Catalog=ProductsContext; Integrated Security=True; MultipleActiveResultSets=True; 
        AttachDbFilename=|DataDirectory|ProductsContext.mdf"
      providerName="System.Data.SqlClient" />
  </connectionStrings>

Essa configuração adiciona uma cadeia de conexão para um banco de dados LocalDB. Esse banco de dados será usado quando você executar o aplicativo localmente.

Em seguida, adicione uma classe chamada ProductsContext à pasta modelos:

using System.Data.Entity;
namespace ProductService.Models
{
    public class ProductsContext : DbContext
    {
        public ProductsContext() 
                : base("name=ProductsContext")
        {
        }
        public DbSet<Product> Products { get; set; }
    }
}

No construtor, "name=ProductsContext" fornece o nome da cadeia de conexão.

Configurar o ponto de extremidade OData

Abra o aplicativo de arquivo_Start/WebApiConfig. cs. Adicione as seguintes instruções using :

using ProductService.Models;
using Microsoft.AspNet.OData.Builder;
using Microsoft.AspNet.OData.Extensions;

Em seguida, adicione o seguinte código ao método Register :

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // New code:
        ODataModelBuilder builder = new ODataConventionModelBuilder();
        builder.EntitySet<Product>("Products");
        config.MapODataServiceRoute(
            routeName: "ODataRoute",
            routePrefix: null,
            model: builder.GetEdmModel());
    }
}

Esse código faz duas coisas:

  • Cria um Modelo de Dados de Entidade (EDM).
  • Adiciona uma rota.

Um EDM é um modelo abstrato dos dados. O EDM é usado para criar o documento de metadados de serviço. A classe ODataConventionModelBuilder cria um EDM usando as convenções de nomenclatura padrão. Essa abordagem requer o mínimo de código. Se você quiser mais controle sobre o EDM, poderá usar a classe ODataModelBuilder para criar o EDM adicionando Propriedades, chaves e propriedades de navegação explicitamente.

Uma rota informa à API da Web como rotear solicitações HTTP para o ponto de extremidade. Para criar uma rota v4 do OData, chame o método de extensão MapODataServiceRoute .

Se seu aplicativo tiver vários pontos de extremidade OData, crie uma rota separada para cada um. Dê a cada rota um nome e prefixo de rota exclusivos.

Adicionar o controlador OData

Um controlador é uma classe que MANIPULA solicitações HTTP. Você cria um controlador separado para cada conjunto de entidades em seu serviço OData. Neste tutorial, você criará um controlador para a entidade de Product.

Em Gerenciador de Soluções, clique com o botão direito do mouse na pasta controladores e selecione adicionar > classe. Nome da classe ProductsController.

Note

A versão deste tutorial para OData v3 usa o Add Controller scaffolding. Atualmente, não há nenhum scaffolding para o OData v4.

Substitua o código clichê em ProductsController.cs pelo seguinte.

using ProductService.Models;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.OData;
namespace ProductService.Controllers
{
    public class ProductsController : ODataController
    {
        ProductsContext db = new ProductsContext();
        private bool ProductExists(int key)
        {
            return db.Products.Any(p => p.Id == key);
        } 
        protected override void Dispose(bool disposing)
        {
            db.Dispose();
            base.Dispose(disposing);
        }
    }
}

O controlador usa a classe ProductsContext para acessar o banco de dados usando o EF. Observe que o controlador substitui o método Dispose para descartar o ProductsContext.

Este é o ponto de partida para o controlador. Em seguida, adicionaremos métodos para todas as operações CRUD.

Consultar o conjunto de entidades

Adicione os métodos a seguir para ProductsController.

[EnableQuery]
public IQueryable<Product> Get()
{
    return db.Products;
}
[EnableQuery]
public SingleResult<Product> Get([FromODataUri] int key)
{
    IQueryable<Product> result = db.Products.Where(p => p.Id == key);
    return SingleResult.Create(result);
}

A versão sem parâmetros do método Get retorna a coleção de produtos inteira. O método Get com um parâmetro de chave pesquisa um produto por sua chave (nesse caso, a propriedade Id).

O atributo [EnableQuery] permite que os clientes modifiquem a consulta usando opções de consulta, como $filter, $sort e $Page. Para obter mais informações, consulte Opções de consulta do OData de suporte.

Adicionar uma entidade ao conjunto de entidades

Para permitir que os clientes adicionem um novo produto ao banco de dados, adicione o método a seguir para ProductsController.

public async Task<IHttpActionResult> Post(Product product)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }
    db.Products.Add(product);
    await db.SaveChangesAsync();
    return Created(product);
}

Atualizar uma entidade

O OData dá suporte a duas semânticas diferentes para atualizar uma entidade, um PATCH e um PUT.

  • O PATCH executa uma atualização parcial. O cliente especifica apenas as propriedades a serem atualizadas.
  • PUT substitui a entidade inteira.

A desvantagem de PUT é que o cliente deve enviar valores para todas as propriedades na entidade, incluindo valores que não estão sendo alterados. A especificação do OData declara que o patch é preferencial.

Em qualquer caso, aqui está o código para os métodos PATCH e PUT:

public async Task<IHttpActionResult> Patch([FromODataUri] int key, Delta<Product> product)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }
    var entity = await db.Products.FindAsync(key);
    if (entity == null)
    {
        return NotFound();
    }
    product.Patch(entity);
    try
    {
        await db.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!ProductExists(key))
        {
            return NotFound();
        }
        else
        {
            throw;
        }
    }
    return Updated(entity);
}
public async Task<IHttpActionResult> Put([FromODataUri] int key, Product update)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }
    if (key != update.Id)
    {
        return BadRequest();
    }
    db.Entry(update).State = EntityState.Modified;
    try
    {
        await db.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!ProductExists(key))
        {
            return NotFound();
        }
        else
        {
            throw;
        }
    }
    return Updated(update);
}

No caso do PATCH, o controlador usa o tipo de >de<de Delta para controlar as alterações.

Excluir uma entidade

Para permitir que os clientes excluam um produto do banco de dados, adicione o seguinte método a ProductsController.

public async Task<IHttpActionResult> Delete([FromODataUri] int key)
{
    var product = await db.Products.FindAsync(key);
    if (product == null)
    {
        return NotFound();
    }
    db.Products.Remove(product);
    await db.SaveChangesAsync();
    return StatusCode(HttpStatusCode.NoContent);
}