Pontos de dados

Uma nova opção para criar OData: API da Web

Julie Lerman

Baixar o código de exemplo

Julie Lerman
Os desenvolvedores do Microsoft .NET conseguiam criar feeds OData até mesmo antes de existir uma especificação OData. Usando o WCF Data Services, era possível expor um Modelo de Dados de Entidade (EDM) na Web usando o serviço REST (Representational State Transfer). Em outras palavras, era possível consumir esses serviços por meio de chamadas HTTP: GET, PUT, DELETE e assim por diante. À medida que a estrutura para a criação desses serviços evoluiu (e foi renomeada algumas vezes durante esse tempo), o resultado também evoluiu e, posteriormente, foi encapsulado na especificação OData (odata.org). Agora, há uma grande variedade de APIs de cliente para consumir OData — .NET, PHP, JavaScript e de muitos outros clientes. Entretanto, até recentemente, a única maneira fácil de criar um serviço era usando o WCF Data Services.

O WCF Data Services é uma tecnologia .NET que simplesmente encapsula seu EDM (.edmx, ou um modelo definido via Code First) e, em seguida, expõe esse modelo para consulta e atualização por meio de HTTP. Como as chamadas são URIs (tal como http://mysite.com/mydataservice/­Clients(34)), é possível até mesmo fazer uma consulta em um navegador da Web ou em uma ferramenta, como o Fiddler. Para criar um WCF Data Service, o Visual Studio fornece um modelo de item para gerar um serviço de dados usando um conjunto de APIs.

Agora, existe outra maneira de criar feeds OData — usando uma API da Web do ASP.NET. Neste artigo, quero fornecer uma visão detalhada de algumas diferenças entre essas duas abordagens e os motivos para escolher cada uma delas. Também analisarei as diferenças entre criar uma API de OData e uma API da Web.

API vs. Data Service em detalhes

Um WCF Data Service é um System.Data.Services.DataService que encapsula um ObjectContext ou um DbContext que você já definiu. Quando você declara a classe de serviço, é um DataService genérico de seu contexto (ou seja, DataService<MyDbContext>). Como ele começa completamente bloqueado, você define permissões de acesso no construtor para o DbSets em seu contexto que deseja que o serviço exponha. Isso é tudo o que você precisa fazer. A API do DataService subjacente cuida do resto: interagindo diretamente com seu contexto, consultando e atualizando o banco de dados em resposta às solicitações HTTP para o serviço de seu aplicativo cliente. Também é possível adicionar algumas personalizações ao serviço, substituindo algumas de suas consultas ou lógicas de atualização. Mas, em grande parte, o objetivo é deixar que o DataService cuide da maioria das interações com o contexto.

Com uma API da Web, por outro lado, você define a interação do contexto em resposta às solicitações HTTP (PUT, GET e similares). A API expõe os métodos e você define a lógica deles. Você não precisa necessariamente interagir com o Entity Framework ou nem mesmo com um banco de dados. É possível ter objetos na memória que o cliente está solicitando ou enviando. Os pontos de acesso não serão criados magicamente, assim como são criados com o WCF Data Service; em vez disso, você controla o que está acontecendo em resposta a essas chamadas. Esse é o fator que define a escolha de um serviço em vez de uma API para expor seu OData. Se grande parte do que você deseja expor for operações CRUD (Create, Read, Update, Delete) simples sem a necessidade de muitas personalizações, um serviço de dados será a melhor escolha. Se você precisará personalizar uma boa parte do comportamento, faz mais sentido escolher uma API da Web.

Gosto da maneira como Matt Milner, MVP de integração da Microsoft, descreveu isso em uma reunião recente: “O WCF Data Services é indicado para quando você começar com os dados e o modelo e apenas quiser expô-los. A API da Web é melhor para quando você começar com a API e quiser definir o que deve ser exposto”.

Preparando o terreno com uma API da Web padrão

Para aqueles que tiverem pouca experiência com a API da Web, antes de observar o novo suporte a OData, achei que seria útil mostrar as noções básicas de API da Web e, em seguida, ver como elas estão relacionadas com a criação de uma API da Web que expõe OData. Farei isso aqui — primeiro, criando uma API da Web simples que usa o Entity Framework como sua camada de dados e, em seguida, convertendo-a para fornecer os resultados como OData.

Como uma das opções, uma API da Web pode ser usada como uma alternativa para um controlador padrão em um aplicativo MVC (Model-View-Controller), e você pode criá-la como parte de um projeto do ASP.NET MVC 4. Caso não queira o front-end, pode começar com um aplicativo Web ASP.NET vazio e adicionar controladores da API da Web. No entanto, para os iniciantes, começarei com um modelo do ASP.NET MVC 4, pois ele fornece o scaffolding que gerará alguns códigos iniciais. Até que você entenda como todas as peças se complementam, iniciar com um projeto vazio é a melhor opção.

Portanto, criarei um aplicativo do ASP.NET MVC 4 e, seguida, quando for solicitado, escolherei o modelo vazio (não o modelo de API da Web, que é destinado a um aplicativo mais robusto que utiliza exibições e é exagerado para os meus objetivos). Isso resulta em um projeto estruturado para um aplicativo MVC com pastas vazias para Modelos, Exibições e Controladores. A Figura 1 compara os resultados do modelo vazio com o modelo de API da Web. Você pode ver que um modelo vazio resulta em uma estrutura muito mais simples. Tudo o que preciso fazer é excluir algumas pastas.

Projetos ASP.NET MVC 4 do modelo vazio e do modelo de API da Web
Figura 1 Projetos ASP.NET MVC 4 do modelo vazio e do modelo de API da Web

Também não precisarei da pasta Modelos, pois estou usando um conjunto existente de classes de domínio e uma DbContext em projetos separados para fornecer o modelo. Em seguida, usarei as ferramentas do Visual Studio para criar meu primeiro controlador, que será um controlador da API da Web para interagir com minha DbContext e com as classes de domínio que referenciei de meu projeto MVC. Meu modelo contém classes para Airline, Passengers, Flights e alguns tipos adicionais para dados relacionados à companhia aérea.

Como usei o modelo vazio, precisarei adicionar algumas referências para chamar para a DbContext — uma para System.Data.Entity.dll e outra para EntityFramework.dll. Você pode adicionar essas duas referências instalando o pacote NuGet EntityFramework.

Você pode criar um controlador da API da Web da mesma maneira que faria para criar um controlador MVC padrão: clique com o botão direito do mouse na pasta Controladores na solução e, em seguida, escolha Adicionar e Controlador. Como pode ser visto na Figura 2, agora você tem um modelo para criar um controlador da API com ações de leitura e gravação do EF. Também há um controlador da API vazio. Vamos começar com as ações de leitura e gravação do EF para uma comparação com o controlador que desejamos para OData que também usará o Entity Framework.

Um modelo para criar um controlador da API com ações previamente preenchidas
Figura 2 Um modelo para criar um controlador da API com ações previamente preenchidas

Se você já tiver criado controladores MVC antes, verá que a classe resultante é semelhante, mas em vez de um conjunto de métodos de ação relacionado com a exibição, como Index, Add e Edit, esse controlador tem um conjunto de ações HTTP.

Por exemplo, há dois métodos Get, como mostra a Figura 3. O primeiro, Get­Airlines, possui uma assinatura que não aceita parâmetros e usa uma instância do AirlineContext (que o scaffolding do modelo nomeou como db) para retornar um conjunto de instâncias Airline em um Enumerable. O outro, GetAirline, aceita um número inteiro e o utiliza para encontrar e retornar uma companhia aérea específica.

Figura 3 Alguns dos métodos do controlador da API da Web criados pelo scaffolding MVC

public class AirlineController : ApiController
  {
    private AirlineContext db = new AirlineContext2();
    // GET api/Airline
    public IEnumerable<Airline> GetAirlines()
    {
      return db.Airlines.AsEnumerable();
    }
    // GET api/Airline/5
    public Airline GetAirline(int id)
    {
      Airline airline = db.Airlines.Find(id);
      if (airline == null)
      {
        throw new HttpResponseException
          (Request.CreateResponse(HttpStatusCode.NotFound));
      }
      return airline;
    }

O modelo adiciona comentários para demonstrar como esses métodos podem ser usados.

Depois de fornecer algumas configurações para a minha API da Web, posso testá-la diretamente em um navegador usando a sintaxe de exemplo na porta que meu aplicativo atribuiu: http://localhost:1702/api/Airline. Essa é a chamada HTTP GET padrão e, portanto, é roteada pelo aplicativo para executar o método GetAirlines. A API da Web usa a negociação de conteúdo para determinar como o conjunto de resultados deve ser formatado. Estou usando o Google Chrome como meu navegador padrão, que acionou os resultados para serem formatados como XML. A solicitação do cliente controla o formato dos resultados. O Internet Explorer, por exemplo, não envia informações de cabeçalho específicas com relação ao formato que aceita, portanto, a API da Web retornará JSON por padrão. Meus resultados XML são mostrados na Figura 4.

Figura 4 Resposta de companhia aérea da API da Web para GET, exibida como XML em meu navegador

<ArrayOfAirline xmlns:i=http://www.w3.org/2001/XMLSchema-instance 
  xmlns="http://schemas.datacontract.org/2004/07/DomainClasses">
    <Airline>
      <Id>1</Id>
      <Legs/>
      <ModifiedDate>2013-02-22T00:00:00</ModifiedDate>
      <Name>Vermont Balloon Transporters</Name>
    </Airline>
    <Airline>
      <Id>2</Id>
      <Legs/>
      <ModifiedDate>2013-02-22T00:00:00</ModifiedDate>
      <Name>Olympic Airways</Name>
    </Airline>
    <Airline>
      <Id>3</Id>
      <Legs/>
      <ModifiedDate>2013-02-22T00:00:00</ModifiedDate>
      <Name>Salt Lake Flyer</Name>
    </Airline>
</ArrayOfAirline>

Se, seguindo as diretrizes do método GetAirline, eu adicionasse um parâmetro numérico inteiro à solicitação — http://localhost:1702/api/Airline/3 —, apenas a companhia aérea cuja chave (Id) fosse igual a 3 seria retornada:

<Airline xmlns:i=http://www.w3.org/2001/XMLSchema-instance
  xmlns="http://schemas.datacontract.org/2004/07/DomainClasses">
    <Id>3</Id>
    <Legs/>
    <ModifiedDate>2013-02-22T00:00:00</ModifiedDate>
    <Name>Salt Lake Flyer</Name>
</Airline>

Se eu usasse o Internet Explorer, ou uma ferramenta como o Fiddler, onde pudesse controlar explicitamente a solicitação para a API para garantir que receberei JSON, o resultado da solicitação para companhia aérea com a Id 3 seria retornado como JSON:

{"Id":3,
  "Name":"Salt Lake Flyer",
  "Legs":[],
  "ModifiedDate":"2013-03-17T00:00:00"
}

Essas respostas contêm representações simples do tipo de companhia aérea com elementos para cada propriedade: Id, Legs, ModifiedDate e Name.

O controlador também contém um método PutAirline que a API da Web chamará em resposta à solicitação PUT HTTP. PutAirline contém códigos para usar AirlineContext para atualizar uma companhia aérea. Também há um método PostAirline para inserção e um método DeleteAirline para exclusão. Eles não podem ser demonstrados em uma URL do navegador, mas você pode encontrar muitos conteúdos introdutórios para a API da Web no MSDN, na Pluralsight e em outros locais, por isso, passarei para a conversão para obter o resultado de acordo com a especificação OData.

Transformando sua API da Web em um provedor de OData

Agora que você tem algumas noções básicas sobre como a API da Web pode ser usada para expor dados usando o Entity Framework, vamos analisar o uso específico da API da Web para criar um provedor de OData a partir de seu modelo de dados. Você pode forçar sua API da Web a retornar dados formatados como OData transformando seu controlador em um controlador de OData — usando uma classe que está disponível no ASP.NET e no pacote Web Tools 2012.2 — e, em seguida, substituindo seus métodos específicos de OData. Com esse novo tipo de controlador, você nem precisará dos métodos criados pelo modelo. Na verdade, um caminho mais eficiente para criar um controlador de OData é escolher o modelo de scaffolding vazio de API da Web, em vez daquele que criou as operações CRUD. 

Precisarei executar quatro etapas para realizar essa transição:

  1. Tornar o controlador um tipo de ODataController e implementar seus métodos HTTP. Usarei um atalho para isso.
  2. Definir os EntitySets disponíveis no arquivo WebAPIConfig do projeto.
  3. Configurar o roteamento no WebAPIConfig.
  4. Pluralizar o nome da classe de controlador para alinhar com as convenções do OData.

Criando um ODataController Em vez de herdar do ODataController diretamente, usarei o EntitySetController, que deriva do ODataController e fornece suporte de nível superior por meio de uma série de métodos CRUD virtuais. Usei o NuGet para instalar o pacote Web API OData do Microsoft ASP.NET para os assemblies adequados que contêm essas duas classes do controlador.

Veja o início de minha classe, agora herdando do EntitySetController e especificando que o controlador é para o tipo Airline:

public class AirlinesController : EntitySetController<Airline,int>
{
  private AirlineContext db = new AirlineContext();
  public override IQueryable<Airline> Get()
  {
    return db.Airlines;
  }

Preenchi a substituição para o método Get, que retornará db.Airlines. Observe que não estou chamando ToList ou AsEnumerable no DbSet Airlines. O método Get precisa retornar um IQueryable de Airline e é isso que o db.Airlines faz. Dessa maneira, o consumidor do OData pode definir consultas nesse conjunto, que depois serão executadas no banco de dados, em vez de colocar todas as companhias aéreas na memória e, em seguida, fazer consultas nelas.

Os métodos HTTP que podem ser substituídos e aos quais lógica pode ser adicionada são GET, POST (para inserções), PUT (para atualizações), PATCH (para mesclagem de atualizações) e DELETE. No entanto, para atualizações, você usará o método virtual CreateEntity para substituir a lógica chamada para um POST, o UpdateEntity para a lógica invocada com PUT e PatchEntity para a lógica necessária para a chamada HTTP PATCH. Métodos virtuais adicionais que podem fazer parte desse provedor de OData são: CreateLink, DeleteLink e GetEntityByKey.

No WCF Data Services, você controla quais ações CRUD são permitidas por EntitySet configurando SetEntitySetAccessRule. No entanto, com a API da Web, basta adicionar os métodos aos quais deseja oferecer suporte e omitir os métodos que não deseja que os consumidores acessem.

Especificando EntitySets para a API A API da Web precisa saber quais EntitySets devem estar disponíveis para os consumidores. A princípio, isso me confundiu. Esperei que essas informações fossem descobertas lendo o AirlineContext. Porém, depois de pensar um pouco mais sobre isso, percebi que é parecido com o uso de SetEntitySetAccessRule no WCF Data Services. No WCF Data Services, você define quais operações CRUD são permitidas ao mesmo tempo em que você expõe um conjunto específico. Com a API da Web, porém, você começa modificando o método WebApiConfig.Register para especificar quais conjuntos farão parte da API e, em seguida, usa os métodos no controlador para expor as operações CRUD específicas. Os conjuntos são especificados usando o ODataModelBuilder — semelhante ao DbContext.ModelBuilder que você talvez tenha usado com o Code First. Veja o código no método Register do arquivo WebApiConfig para permitir que meu feed OData exponha Airlines e Legs:

ODataModelBuilder modelBuilder = new ODataConventionModelBuilder();
                  modelBuilder.EntitySet<Airline>("Airlines");
                  modelBuilder.EntitySet<FlightLeg>("Legs");

Definindo uma rota para encontrar o OData Em seguida, o método Register precisa de uma rota que aponta para esse modelo, de modo que quando você chamar a API da Web, ele fornecerá acesso aos EntitySets que você definiu:

Microsoft.Data.Edm.IEdmModel model = modelBuilder.GetEdmModel();
config.Routes.MapODataRoute("ODataRoute", "odata", model);

Você verá que muitas demonstrações usam “odata” para o parâmetro RoutePrefix, que define o prefixo da URL para seus métodos da API. Embora esse seja um bom padrão, você pode nomeá-lo como desejar.

Portanto, irei alterá-lo para mostrar que é possível:

config.Routes.MapODataRoute("ODataRoute", "oairlinedata", model);

Renomeando a classe de controlador O modelo de aplicativo gera um código que usa uma convenção de nomenclatura singular para controladores, como AirlineController e LegController. No entanto, o foco do OData são os EntitySets, que geralmente são nomeados usando o nome da entidade no plural. Como meus EntitySets já estão no plural, preciso alterar o nome de minha classe de controlador para AirlinesController para alinhar com o EntitySet Airlines.

Consumindo o OData

Agora, posso trabalhar com a API usando sintaxe de consulta familiar do OData. Começarei solicitando uma lista do que está disponível usando a solicitação: http://localhost:1702/oairlinedata/. Os resultados são mostrados na Figura 5.

Figura 5. Solicitando uma lista dos dados disponíveis

http://localhost:1702/oairlinedata/
<service xmlns="http://www.w3.org/2007/app" xmlns:atom=
  "http://www.w3.org/2005/Atom" 
  xml:base="http://localhost:1702/oairlinedata /">
    <workspace>
      <atom:title type="text">Default</atom:title>
      <collection href="Airlines">
        <atom:title type="text">Airlines</atom:title>
      </collection>
      <collection href="Legs">
        <atom:title type="text">Legs</atom:title>
      </collection>
    </workspace>
</service>

Os resultados mostram que o serviço expõe Airlines e Legs. Em seguida, pedirei uma lista de Airlines como OData com http://localhost:1702/oairlinedata/Airlines. O OData pode ser retornado como XML ou JSON. O padrão para os resultados da API da Web é o formato JSON:

{
  "odata.metadata":
    "http://localhost:1702/oairlinedata/$metadata#Airlines","value":[
    {
      "Id":1,"Name":"Vermont Balloons","ModifiedDate":"2013-02-26T00:00:00"
    },{
      "Id":2,"Name":"Olympic Airways","ModifiedDate":"2013-02-26T00:00:00"
    },{
      "Id":3,"Name":"Salt Lake Flyer","ModifiedDate":"2013-02-26T00:00:00"
    }
  ]
}

Um dos muitos recursos de URI do OData é a consulta. Por padrão, a API da Web não permite consultas, pois isso gera uma carga extra no servidor. Portanto, você só poderá usar esses recursos de consulta com sua API da Web depois de adicionar a anotação Queryable nos métodos adequados. Por exemplo, adicionei Queryable ao método Get:

[Queryable]
public override IQueryable<Airline> Get()
{
  return db.Airlines;
}

Agora, você pode usar os métodos $filter, $inlinecount, $orderby, $sort e $top. Veja uma consulta usando o método de filtro do OData:

http://localhost:1702/oairlinedata/Airlines?$filter=startswith(Name,'Vermont')

O ODataController permite restringir as pesquisas, para que os consumidores não causem problemas de desempenho em seu servidor. Por exemplo, você pode limitar o número de registros que são retornados em uma única resposta. Veja o artigo “Orientações de segurança do OData”, específico sobre API da Web, em bit.ly/X0hyv3 para obter mais detalhes.

Apenas uma pequena amostra

Analisei apenas uma parte dos recursos de consulta que você pode fornecer com o suporte do Web API OData. Você também pode usar os métodos virtuais do EntitySetController para permitir a atualização no banco de dados. Uma adição interessante ao PUT, POST e DELETE é PATCH, que permite enviar uma solicitação explícita e eficiente para uma atualização quando apenas um pequeno número de campos tiverem sido alterados, em vez de enviar a entidade inteira para um POST. Mas a lógica em seu método PATCH precisa ter uma atualização correta, o que, se você estiver usando o Entity Framework, provavelmente significa recuperar o objeto atual do banco de dados e atualizá-lo com os novos valores. Para escolher a maneira como essa lógica é implementada, é preciso saber em que ponto do fluxo de trabalho você está disposto a pagar o preço de enviar dados pela rede. Também é importante estar ciente de que essa versão (com o ASP.NET e o pacote Web Tools 2012.2) oferece suporte apenas a um subconjunto de recursos do OData. Isso significa que nem todas as chamadas de API que você pode fazer para um feed OData funcionarão com um provedor de OData criado com a API da Web. As notas de versão para o ASP.NET e o pacote Web Tools 2012.2 listam quais recursos têm suporte.

Há muito mais para aprender do que é possível compartilhar no espaço limitado desta coluna. Recomendo a série excelente de Mike Wasson sobre OData na documentação oficial de API da Web em bit.ly/14cfHIm. Você aprenderá a criar todos os métodos CRUD, usar o PATCH e até mesmo usar anotações para limitar quais tipos de filtragem são permitidos em suas APIs de OData e trabalhar com relacionamentos. Lembre-se de que muitos dos outros recursos da API da Web são aplicados à API de OData, tal como o uso da autorização para limitar quem pode acessar quais operações. Além disso, o blog de desenvolvimento para a Web e ferramentas de .NET (blogs.msdn.com/webdev) possui uma série de postagens detalhadas sobre o suporte ao OData na API da Web.

Julie Lerman é uma Microsoft MVP, mentora e consultora do .NET, que reside nas colinas de Vermont. Você pode encontrá-la fazendo apresentações sobre acesso a dados e outros tópicos do Microsoft .NET em grupos de usuários e conferências em todo o mundo. Seu blog está em thedatafarm.com/blog e ela é autora do livro “Programming Entity Framework”, da O’Reilly Media, e de vários cursos online no Pluralsight.com. Siga-a no Twitter, em twitter.com/julielerman.

Agradecemos aos seguintes especialistas técnicos pela revisão deste artigo: Jon Galloway (Microsoft) e Mike Wasson (Microsoft)
Jon Galloway (Jon.Galloway@microsoft.com) é um divulgador técnico na equipe de divulgação do Windows Azure, com foco em ASP.NET MVC e ASP.NET Web API. Ele participa como palestrante em conferências e em web camps internacionais, de Istambul a Bangalore e Buenos Aires. Ele é coautor da série de livros Wrox Professional ASP.NET MVC e coapresentador do podcast Herding Code.
Mike Wasson (mwasson@microsoft.com) trabalha como programador e redator na Microsoft. Por muitos anos, documentou as APIs multimídia do Win32. Atualmente, escreve sobre o ASP.NET, com foco em API da Web.