Chamar um serviço OData em um cliente .NET (C#)
por Mike Wasson
Este tutorial mostra como chamar um serviço OData de um aplicativo cliente C#.
Versões de software usadas no tutorial
- Visual Studio 2013 (funciona com o Visual Studio 2012)
- Biblioteca de cliente do WCF Data Services
- API Web 2. (O serviço OData de exemplo é criado usando a API Web 2, mas o aplicativo cliente não depende da API Web.)
Neste tutorial, percorrerei a criação de um aplicativo cliente que chama um serviço OData. O serviço OData expõe as seguintes entidades:
Product
Supplier
ProductRating
Os artigos a seguir descrevem como implementar o serviço OData na API Web. (No entanto, você não precisa lê-los para entender este tutorial.)
- Criando um ponto de extremidade OData na API Web 2
- Relações de Entidade OData na API Web 2
- Ações de OData na API Web 2
Gerar o Proxy de Serviço
A primeira etapa é gerar um proxy de serviço. O proxy de serviço é uma classe .NET que define métodos para acessar o serviço OData. O proxy converte chamadas de método em solicitações HTTP.
Comece abrindo o projeto de serviço OData no Visual Studio. Pressione CTRL+F5 para executar o serviço localmente em IIS Express. Observe o endereço local, incluindo o número da porta que o Visual Studio atribui. Você precisará desse endereço ao criar o proxy.
Em seguida, abra outra instância do Visual Studio e crie um projeto de aplicativo de console. O aplicativo de console será nosso aplicativo cliente OData. (Você também pode adicionar o projeto à mesma solução que o serviço.)
Observação
As etapas restantes referem-se ao projeto do console.
Em Gerenciador de Soluções, clique com o botão direito do mouse em Referências e selecione Adicionar Referência de Serviço.
Na caixa de diálogo Adicionar Referência de Serviço , digite o endereço do serviço OData:
http://localhost:port/odata
em que porta é o número da porta.
Para Namespace, digite "ProductService". Essa opção define o namespace da classe proxy.
Clique em Ir. O Visual Studio lê o documento de metadados OData para descobrir as entidades no serviço.
Clique em OK para adicionar a classe proxy ao seu projeto.
Criar uma instância da classe proxy de serviço
Main
Dentro do seu método, crie uma nova instância da classe proxy, da seguinte maneira:
using System;
using System.Data.Services.Client;
using System.Linq;
namespace Client
{
class Program
{
static void Main(string[] args)
{
Uri uri = new Uri("http://localhost:1234/odata/");
var container = new ProductService.Container(uri);
// ...
}
}
}
Novamente, use o número da porta real em que o serviço está em execução. Ao implantar seu serviço, você usará o URI do serviço dinâmico. Você não precisa atualizar o proxy.
O código a seguir adiciona um manipulador de eventos que imprime os URIs de solicitação na janela do console. Essa etapa não é necessária, mas é interessante ver os URIs de cada consulta.
container.SendingRequest2 += (s, e) =>
{
Console.WriteLine("{0} {1}", e.RequestMessage.Method, e.RequestMessage.Url);
};
Consultar o serviço
O código a seguir obtém a lista de produtos do serviço OData.
class Program
{
static void DisplayProduct(ProductService.Product product)
{
Console.WriteLine("{0} {1} {2}", product.Name, product.Price, product.Category);
}
// Get an entire entity set.
static void ListAllProducts(ProductService.Container container)
{
foreach (var p in container.Products)
{
DisplayProduct(p);
}
}
static void Main(string[] args)
{
Uri uri = new Uri("http://localhost:18285/odata/");
var container = new ProductService.Container(uri);
container.SendingRequest2 += (s, e) =>
{
Console.WriteLine("{0} {1}", e.RequestMessage.Method, e.RequestMessage.Url);
};
// Get the list of products
ListAllProducts(container);
}
}
Observe que você não precisa escrever nenhum código para enviar a solicitação HTTP ou analisar a resposta. A classe proxy faz isso automaticamente quando você enumera a Container.Products
coleção no loop foreach .
Quando você executa o aplicativo, a saída deve ser semelhante à seguinte:
GET http://localhost:60868/odata/Products
Hat 15.00 Apparel
Scarf 12.00 Apparel
Socks 5.00 Apparel
Yo-yo 4.95 Toys
Puzzle 8.00 Toys
Para obter uma entidade por ID, use uma where
cláusula .
// Get a single entity.
static void ListProductById(ProductService.Container container, int id)
{
var product = container.Products.Where(p => p.ID == id).SingleOrDefault();
if (product != null)
{
DisplayProduct(product);
}
}
Para o restante deste tópico, não mostrarei toda Main
a função, apenas o código necessário para chamar o serviço.
Aplicar opções de consulta
O OData define opções de consulta que podem ser usadas para filtrar, classificar, dados de página e assim por diante. No proxy de serviço, você pode aplicar essas opções usando várias expressões LINQ.
Nesta seção, mostrarei breves exemplos. Para obter mais detalhes, consulte o tópico Considerações sobre LINQ (WCF Data Services) no MSDN.
Filtragem ($filter)
Para filtrar, use uma where
cláusula . O exemplo a seguir filtra por categoria de produto.
// Use the $filter option.
static void ListProductsInCategory(ProductService.Container container, string category)
{
var products =
from p in container.Products
where p.Category == category
select p;
foreach (var p in products)
{
DisplayProduct(p);
}
}
Esse código corresponde à consulta OData a seguir.
GET http://localhost/odata/Products()?$filter=Category eq 'apparel'
Observe que o proxy converte a where
cláusula em uma expressão OData $filter
.
Classificação ($orderby)
Para classificar, use uma orderby
cláusula . O exemplo a seguir classifica por preço, do mais alto para o mais baixo.
// Use the $orderby option
static void ListProductsSorted(ProductService.Container container)
{
// Sort by price, highest to lowest.
var products =
from p in container.Products
orderby p.Price descending
select p;
foreach (var p in products)
{
DisplayProduct(p);
}
}
Aqui está a solicitação OData correspondente.
GET http://localhost/odata/Products()?$orderby=Price desc
Paginação Client-Side ($skip e $top)
Para conjuntos de entidades grandes, talvez o cliente queira limitar o número de resultados. Por exemplo, um cliente pode mostrar 10 entradas por vez. Isso é chamado de paginação do lado do cliente. (Também há paginação do lado do servidor, em que o servidor limita o número de resultados.) Para executar a paginação do lado do cliente, use os métodos LINQ Skip e Take . O exemplo a seguir ignora os primeiros 40 resultados e usa os próximos 10.
// Use $skip and $top options.
static void ListProductsPaged(ProductService.Container container)
{
var products =
(from p in container.Products
orderby p.Price descending
select p).Skip(40).Take(10);
foreach (var p in products)
{
DisplayProduct(p);
}
}
Esta é a solicitação OData correspondente:
GET http://localhost/odata/Products()?$orderby=Price desc&$skip=40&$top=10
Selecione ($select) e Expandir ($expand)
Para incluir entidades relacionadas, use o DataServiceQuery<t>.Expand
método . Por exemplo, para incluir o Supplier
para cada Product
:
// Use the $expand option.
static void ListProductsAndSupplier(ProductService.Container container)
{
var products = container.Products.Expand(p => p.Supplier);
foreach (var p in products)
{
Console.WriteLine("{0}\t{1}\t{2}", p.Name, p.Price, p.Supplier.Name);
}
}
Esta é a solicitação OData correspondente:
GET http://localhost/odata/Products()?$expand=Supplier
Para alterar a forma da resposta, use a cláusula de seleção LINQ. O exemplo a seguir obtém apenas o nome de cada produto, sem outras propriedades.
// Use the $select option.
static void ListProductNames(ProductService.Container container)
{
var products = from p in container.Products select new { Name = p.Name };
foreach (var p in products)
{
Console.WriteLine(p.Name);
}
}
Esta é a solicitação OData correspondente:
GET http://localhost/odata/Products()?$select=Name
Uma cláusula select pode incluir entidades relacionadas. Nesse caso, não chame Expandir; O proxy inclui automaticamente a expansão nesse caso. O exemplo a seguir obtém o nome e o fornecedor de cada produto.
// Use $expand and $select options
static void ListProductNameSupplier(ProductService.Container container)
{
var products =
from p in container.Products
select new
{
Name = p.Name,
Supplier = p.Supplier.Name
};
foreach (var p in products)
{
Console.WriteLine("{0}\t{1}", p.Name, p.Supplier);
}
}
Aqui está a solicitação OData correspondente. Observe que ele inclui a opção $expand .
GET http://localhost/odata/Products()?$expand=Supplier&$select=Name,Supplier/Name
Para obter mais informações sobre $select e $expand, consulte Usando $select, $expand e $value na API Web 2.
Adicionar uma nova entidade
Para adicionar uma nova entidade a um conjunto de entidades, chame AddToEntitySet
, em que EntitySet é o nome do conjunto de entidades. Por exemplo, AddToProducts
adiciona um novo Product
ao Products
conjunto de entidades. Quando você gera o proxy, WCF Data Services cria automaticamente esses métodos AddTo fortemente tipados.
// Add an entity.
static void AddProduct(ProductService.Container container, ProductService.Product product)
{
container.AddToProducts(product);
var serviceResponse = container.SaveChanges();
foreach (var operationResponse in serviceResponse)
{
Console.WriteLine(operationResponse.StatusCode);
}
}
Para adicionar um link entre duas entidades, use os métodos AddLink e SetLink . O código a seguir adiciona um novo fornecedor e um novo produto e cria links entre eles.
// Add entities with links.
static void AddProductWithSupplier(ProductService.Container container,
ProductService.Product product, ProductService.Supplier supplier)
{
container.AddToSuppliers(supplier);
container.AddToProducts(product);
container.AddLink(supplier, "Products", product);
container.SetLink(product, "Supplier", supplier);
var serviceResponse = container.SaveChanges();
foreach (var operationResponse in serviceResponse)
{
Console.WriteLine(operationResponse.StatusCode);
}
}
Use AddLink quando a propriedade de navegação for uma coleção. Neste exemplo, estamos adicionando um produto à Products
coleção no fornecedor.
Use SetLink quando a propriedade de navegação for uma única entidade. Neste exemplo, estamos definindo a Supplier
propriedade no produto.
Atualização/Patch
Para atualizar uma entidade, chame o método UpdateObject .
static void UpdatePrice(ProductService.Container container, int id, decimal price)
{
var product = container.Products.Where(p => p.ID == id).SingleOrDefault();
if (product != null)
{
product.Price = price;
container.UpdateObject(product);
container.SaveChanges(SaveChangesOptions.PatchOnUpdate);
}
}
A atualização é executada quando você chama SaveChanges. Por padrão, o WCF envia uma solicitação HTTP MERGE. A opção PatchOnUpdate informa ao WCF para enviar um PATCH HTTP.
Observação
Por que PATCH versus MERGE? A especificação HTTP 1.1 original (RCF 2616) não definiu nenhum método HTTP com semântica de "atualização parcial". Para dar suporte a atualizações parciais, a especificação OData definiu o método MERGE. Em 2010, o RFC 5789 definiu o método PATCH para atualizações parciais. Você pode ler parte do histórico nesta postagem no blog do WCF Data Services. Hoje, PATCH é preferencial em vez de MERGE. O controlador OData criado pelo scaffolding da API Web dá suporte a ambos os métodos.
Se você quiser substituir a entidade inteira (semântica PUT), especifique a opção ReplaceOnUpdate . Isso faz com que o WCF envie uma solicitação HTTP PUT.
container.SaveChanges(SaveChangesOptions.ReplaceOnUpdate);
Excluir uma entidade
Para excluir uma entidade, chame DeleteObject.
static void DeleteProduct(ProductService.Container container, int id)
{
var product = container.Products.Where(p => p.ID == id).SingleOrDefault();
if (product != null)
{
container.DeleteObject(product);
container.SaveChanges();
}
}
Invocar uma ação OData
No OData, as ações são uma maneira de adicionar comportamentos do lado do servidor que não são facilmente definidos como operações CRUD em entidades.
Embora o documento de metadados OData descreva as ações, a classe proxy não cria métodos fortemente tipados para elas. Você ainda pode invocar uma ação OData usando o método Execute genérico. No entanto, você precisará conhecer os tipos de dados dos parâmetros e o valor retornado.
Por exemplo, a ação usa o RateProduct
parâmetro chamado "Rating" do tipo Int32
e retorna um double
. O código a seguir mostra como invocar essa ação.
int rating = 2;
Uri actionUri = new Uri(uri, "Products(5)/RateProduct");
var averageRating = container.Execute<double>(
actionUri, "POST", true, new BodyOperationParameter("Rating", rating)).First();
Para obter mais informações, consulteOperações e ações do serviço de chamada.
Uma opção é estender a classe Container para fornecer um método fortemente tipado que invoca a ação:
namespace ProductServiceClient.ProductService
{
public partial class Container
{
public double RateProduct(int productID, int rating)
{
Uri actionUri = new Uri(this.BaseUri,
String.Format("Products({0})/RateProduct", productID)
);
return this.Execute<double>(actionUri,
"POST", true, new BodyOperationParameter("Rating", rating)).First();
}
}
}