Chamar um serviço OData em um cliente .NET (C#)

por Mike Wasson

Baixar Projeto Concluído

Este tutorial mostra como chamar um serviço OData de um aplicativo cliente C#.

Versões de software usadas no tutorial

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

Diagrama mostrando as entidades do serviço de Dados O e uma lista de suas propriedades, com setas de conexão para mostrar como cada uma se relaciona ou trabalha em conjunto.

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

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.

Diagrama mostrando as chamadas de solicitação H T TP do proxy de serviço em execução do aplicativo, por meio do proxy de serviço e do serviço de Dados O.

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.

Captura de tela da janela do gerenciador de soluções, mostrando o menu em 'referências' para adicionar uma nova 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.

Captura de tela da janela

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.

Captura de tela da caixa de diálogo

Clique em OK para adicionar a classe proxy ao seu projeto.

Captura de tela da caixa de diálogo gerenciador de soluções, mostrando o menu no 'cliente do serviço de produto' e realçando a opção de 'Serviço de Produto'.

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