Chamar uma API Web de um cliente .NET (C#)

Esse conteúdo é para uma versão anterior do .NET. O novo desenvolvimento deve usar ASP.NET Core. Para obter mais informações sobre como usar ASP.NET Core API Web, consulte:

Baixar Projeto Concluído. Baixar instruções.

Este tutorial mostra como chamar uma API Web de um aplicativo .NET usando System.Net.Http.HttpClient.

Neste tutorial, um aplicativo cliente é escrito que consome a seguinte API Web:

Ação Método HTTP URI relativo
Obter um produto por ID GET /api/products/id
Criar um novo produto POST /api/products
Atualizar um produto PUT /api/products/id
Excluir um produto Delete (excluir) /api/products/id

Para saber como implementar essa API com ASP.NET Web API, consulte Como criar uma API Web compatível com operações CRUD.

Para simplificar, o aplicativo cliente neste tutorial é um aplicativo de console do Windows. HttpClient também tem suporte para aplicativos Windows Phone e da Windows Store. Para obter mais informações, consulte Escrever código de cliente da API Web para várias plataformas usando bibliotecas portáteis

NOTA: Se você passar URLs base e URIs relativas como valores codificados em código, fique atento às regras para utilizar a HttpClient API. A HttpClient.BaseAddress propriedade deve ser definida como um endereço com uma barra à direita (/). Por exemplo, ao passar URIs de recurso codificados em código para o HttpClient.GetAsync método, não inclua uma barra de avanço à esquerda. Para obter uma Product ID por:

  1. Defina client.BaseAddress = new Uri("https://localhost:5001/");
  2. Solicitar um Product. Por exemplo, client.GetAsync<Product>("api/products/4");.

Criar o aplicativo de console

No Visual Studio, crie um novo aplicativo de console do Windows chamado HttpClientSample e cole no seguinte código:

using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;

namespace HttpClientSample
{
    public class Product
    {
        public string Id { get; set; }
        public string Name { get; set; }
        public decimal Price { get; set; }
        public string Category { get; set; }
    }

    class Program
    {
        static HttpClient client = new HttpClient();

        static void ShowProduct(Product product)
        {
            Console.WriteLine($"Name: {product.Name}\tPrice: " +
                $"{product.Price}\tCategory: {product.Category}");
        }

        static async Task<Uri> CreateProductAsync(Product product)
        {
            HttpResponseMessage response = await client.PostAsJsonAsync(
                "api/products", product);
            response.EnsureSuccessStatusCode();

            // return URI of the created resource.
            return response.Headers.Location;
        }

        static async Task<Product> GetProductAsync(string path)
        {
            Product product = null;
            HttpResponseMessage response = await client.GetAsync(path);
            if (response.IsSuccessStatusCode)
            {
                product = await response.Content.ReadAsAsync<Product>();
            }
            return product;
        }

        static async Task<Product> UpdateProductAsync(Product product)
        {
            HttpResponseMessage response = await client.PutAsJsonAsync(
                $"api/products/{product.Id}", product);
            response.EnsureSuccessStatusCode();

            // Deserialize the updated product from the response body.
            product = await response.Content.ReadAsAsync<Product>();
            return product;
        }

        static async Task<HttpStatusCode> DeleteProductAsync(string id)
        {
            HttpResponseMessage response = await client.DeleteAsync(
                $"api/products/{id}");
            return response.StatusCode;
        }

        static void Main()
        {
            RunAsync().GetAwaiter().GetResult();
        }

        static async Task RunAsync()
        {
            // Update port # in the following line.
            client.BaseAddress = new Uri("http://localhost:64195/");
            client.DefaultRequestHeaders.Accept.Clear();
            client.DefaultRequestHeaders.Accept.Add(
                new MediaTypeWithQualityHeaderValue("application/json"));

            try
            {
                // Create a new product
                Product product = new Product
                {
                    Name = "Gizmo",
                    Price = 100,
                    Category = "Widgets"
                };

                var url = await CreateProductAsync(product);
                Console.WriteLine($"Created at {url}");

                // Get the product
                product = await GetProductAsync(url.PathAndQuery);
                ShowProduct(product);

                // Update the product
                Console.WriteLine("Updating price...");
                product.Price = 80;
                await UpdateProductAsync(product);

                // Get the updated product
                product = await GetProductAsync(url.PathAndQuery);
                ShowProduct(product);

                // Delete the product
                var statusCode = await DeleteProductAsync(product.Id);
                Console.WriteLine($"Deleted (HTTP Status = {(int)statusCode})");

            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }

            Console.ReadLine();
        }
    }
}

O código anterior é o aplicativo cliente completo.

RunAsync executa e bloqueia até que ele seja concluído. A maioria dos métodos HttpClient é assíncrona, pois eles executam E/S de rede. Todas as tarefas assíncronas são feitas dentro RunAsync. Normalmente, um aplicativo não bloqueia o thread principal, mas esse aplicativo não permite nenhuma interação.

static async Task RunAsync()
{
    // Update port # in the following line.
    client.BaseAddress = new Uri("http://localhost:64195/");
    client.DefaultRequestHeaders.Accept.Clear();
    client.DefaultRequestHeaders.Accept.Add(
        new MediaTypeWithQualityHeaderValue("application/json"));

    try
    {
        // Create a new product
        Product product = new Product
        {
            Name = "Gizmo",
            Price = 100,
            Category = "Widgets"
        };

        var url = await CreateProductAsync(product);
        Console.WriteLine($"Created at {url}");

        // Get the product
        product = await GetProductAsync(url.PathAndQuery);
        ShowProduct(product);

        // Update the product
        Console.WriteLine("Updating price...");
        product.Price = 80;
        await UpdateProductAsync(product);

        // Get the updated product
        product = await GetProductAsync(url.PathAndQuery);
        ShowProduct(product);

        // Delete the product
        var statusCode = await DeleteProductAsync(product.Id);
        Console.WriteLine($"Deleted (HTTP Status = {(int)statusCode})");

    }
    catch (Exception e)
    {
        Console.WriteLine(e.Message);
    }

    Console.ReadLine();
}

Instalar as bibliotecas de clientes da API Web

Use o Gerenciador de Pacotes do NuGet para instalar o pacote bibliotecas de clientes da API Web.

No menu Ferramentas selecione Gerenciador de Pacotes NuGet>Console do Gerenciador de Pacotes. No PMC (Console do Gerenciador de Pacotes), digite o seguinte comando:

Install-Package Microsoft.AspNet.WebApi.Client

O comando anterior adiciona os seguintes pacotes NuGet ao projeto:

  • Microsoft.AspNet.WebApi.Client
  • Newtonsoft.Json

Newtonsoft.Json (também conhecido como Json.NET) é uma estrutura JSON popular de alto desempenho para .NET.

Adicionar uma classe de modelo

Examine a classe Product:

public class Product
{
    public string Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
    public string Category { get; set; }
}

Essa classe corresponde ao modelo de dados usado pela API Web. Um aplicativo pode usar HttpClient para ler uma Product instância de uma resposta HTTP. O aplicativo não precisa escrever nenhum código de desserialização.

Criar e inicializar HttpClient

Examine a propriedade HttpClient estática:

static HttpClient client = new HttpClient();

HttpClient destina-se a ser instanciado uma vez e reutilizado ao longo da vida útil de um aplicativo. As seguintes condições podem resultar em erros SocketException :

  • Criando uma nova instância httpClient por solicitação.
  • Servidor sob carga pesada.

A criação de uma nova instância httpClient por solicitação pode esgotar os soquetes disponíveis.

O código a seguir inicializa a instância httpClient :

static async Task RunAsync()
{
    // Update port # in the following line.
    client.BaseAddress = new Uri("http://localhost:64195/");
    client.DefaultRequestHeaders.Accept.Clear();
    client.DefaultRequestHeaders.Accept.Add(
        new MediaTypeWithQualityHeaderValue("application/json"));

O código anterior:

  • Define o URI base para solicitações HTTP. Altere o número da porta para a porta usada no aplicativo do servidor. O aplicativo não funcionará a menos que a porta do aplicativo de servidor seja usada.
  • Define o cabeçalho Accept como "application/json". Definir esse cabeçalho informa ao servidor para enviar dados no formato JSON.

Enviar uma solicitação GET para recuperar um recurso

O código a seguir envia uma solicitação GET para um produto:

static async Task<Product> GetProductAsync(string path)
{
    Product product = null;
    HttpResponseMessage response = await client.GetAsync(path);
    if (response.IsSuccessStatusCode)
    {
        product = await response.Content.ReadAsAsync<Product>();
    }
    return product;
}

O método GetAsync envia a solicitação HTTP GET. Quando o método for concluído, ele retornará um HttpResponseMessage que contém a resposta HTTP. Se o código de status na resposta for um código de sucesso, o corpo da resposta conterá a representação JSON de um produto. Chame ReadAsAsync para desserializar o conteúdo JSON para uma Product instância. O método ReadAsAsync é assíncrono porque o corpo da resposta pode ser arbitrariamente grande.

HttpClient não gera uma exceção quando a resposta HTTP contém um código de erro. Em vez disso, a propriedade IsSuccessStatusCode será falsa se o status for um código de erro. Se preferir tratar códigos de erro HTTP como exceções, chame HttpResponseMessage.EnsureSuccessStatusCode no objeto de resposta. EnsureSuccessStatusCode gera uma exceção se o código de status estiver fora do intervalo de 200 a 299. Observe que o HttpClient pode gerar exceções por outros motivos — por exemplo, se a solicitação atingir o tempo limite.

Media-Type formatores a desserializar

Quando ReadAsAsync é chamado sem parâmetros, ele usa um conjunto padrão de formatores de mídia para ler o corpo da resposta. Os formatadores padrão dão suporte a dados codificados em JSON, XML e Form-url.

Em vez de usar os formatadores padrão, você pode fornecer uma lista de formatadores para o método ReadAsAsync . Usar uma lista de formatadores será útil se você tiver um formatador de tipo de mídia personalizado:

var formatters = new List<MediaTypeFormatter>() {
    new MyCustomFormatter(),
    new JsonMediaTypeFormatter(),
    new XmlMediaTypeFormatter()
};
resp.Content.ReadAsAsync<IEnumerable<Product>>(formatters);

Para obter mais informações, consulte Formatores de Mídia no ASP.NET Web API 2

Enviando uma solicitação POST para criar um recurso

O código a seguir envia uma solicitação POST que contém uma Product instância no formato JSON:

static async Task<Uri> CreateProductAsync(Product product)
{
    HttpResponseMessage response = await client.PostAsJsonAsync(
        "api/products", product);
    response.EnsureSuccessStatusCode();

    // return URI of the created resource.
    return response.Headers.Location;
}

O método PostAsJsonAsync :

  • Serializa um objeto para JSON.
  • Envia o conteúdo JSON em uma solicitação POST.

Se a solicitação for bem-sucedida:

  • Ele deve retornar uma resposta 201 (Criado).
  • A resposta deve incluir a URL dos recursos criados no cabeçalho Local.

Enviando uma solicitação PUT para atualizar um recurso

O código a seguir envia uma solicitação PUT para atualizar um produto:

static async Task<Product> UpdateProductAsync(Product product)
{
    HttpResponseMessage response = await client.PutAsJsonAsync(
        $"api/products/{product.Id}", product);
    response.EnsureSuccessStatusCode();

    // Deserialize the updated product from the response body.
    product = await response.Content.ReadAsAsync<Product>();
    return product;
}

O método PutAsJsonAsync funciona como PostAsJsonAsync, exceto que ele envia uma solicitação PUT em vez de POST.

Enviando uma solicitação DELETE para excluir um recurso

O código a seguir envia uma solicitação DELETE para excluir um produto:

static async Task<HttpStatusCode> DeleteProductAsync(string id)
{
    HttpResponseMessage response = await client.DeleteAsync(
        $"api/products/{id}");
    return response.StatusCode;
}

Assim como GET, uma solicitação DELETE não tem um corpo de solicitação. Você não precisa especificar o formato JSON ou XML com DELETE.

O exemplo de teste

Para testar o aplicativo cliente:

  1. Baixe e execute o aplicativo de servidor. Baixe as instruções. Verifique se o aplicativo de servidor está funcionando. Por exemplo, http://localhost:64195/api/products deve retornar uma lista de produtos.

  2. Defina o URI base para solicitações HTTP. Altere o número da porta para a porta usada no aplicativo do servidor.

    static async Task RunAsync()
    {
        // Update port # in the following line.
        client.BaseAddress = new Uri("http://localhost:64195/");
        client.DefaultRequestHeaders.Accept.Clear();
        client.DefaultRequestHeaders.Accept.Add(
            new MediaTypeWithQualityHeaderValue("application/json"));
    
  3. Execute o aplicativo cliente. A seguinte saída é produzida:

    Created at http://localhost:64195/api/products/4
    Name: Gizmo     Price: 100.0    Category: Widgets
    Updating price...
    Name: Gizmo     Price: 80.0     Category: Widgets
    Deleted (HTTP Status = 204)