Llamar a una API web desde un cliente .NET (C#)

Este contenido corresponde a una versión anterior de .NET. El nuevo desarrollo debería usar ASP.NET Core. Para más información sobre el uso de ASP.NET Core Web API, consulte:

Descargar proyecto finalizado.

Este tutorial muestra cómo llamar a una API web desde una aplicación .NET, usando System.Net.Http.HttpClient.

En este tutorial, se escribe una aplicación cliente que consume la siguiente API web:

Action Método HTTP Dirección URL relativa
Obtener un producto según el id. OBTENER /api/products/id
Crear un nuevo producto PUBLICAR /api/products
actualización de un producto PUT /api/products/id
Eliminar un producto Delete /api/products/id

Para aprender a implementar esta API con ASP.NET Web API, consulte Creación de una API web compatible con operaciones CRUD.

Para simplificar, la aplicación cliente de este tutorial es una aplicación de consola de Windows. HttpClient también es compatible con las aplicaciones de Windows Phone y Windows Store. Para más información, consulte Escribir código de cliente de API web para múltiples plataformas usando bibliotecas portátiles

NOTA: Si se pasan URL base y URI relativos como valores codificados, tenga en cuenta las reglas para usar la API HttpClient. La propiedad HttpClient.BaseAddress debe estar establecida en una dirección con una barra oblicua final (/). Por ejemplo, cuando se pasen URI de recursos codificados al método HttpClient.GetAsync, no incluya una barra oblicua inicial. Para obtener un Product por identificador:

  1. Establezca client.BaseAddress = new Uri("https://localhost:5001/");
  2. Solicite un Product. Por ejemplo, client.GetAsync<Product>("api/products/4");.

Creación de la aplicación de consola

En Visual Studio, cree una nueva aplicación de consola de Windows llamada HttpClientSample y pegue el siguiente 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();
        }
    }
}

El código anterior es la aplicación cliente completa.

RunAsync se ejecuta y se bloquea hasta su finalización. La mayoría de los métodos HttpClient son asincrónicos, porque realizan E/S de red. Todas las tareas asincrónicas se realizan dentro de RunAsync. Normalmente, una aplicación no bloquea el subproceso principal, pero esta aplicación no permite ninguna interacción.

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

Instalación de las bibliotecas cliente de API web

Use el administrador de paquetes NuGet para instalar el paquete Bibliotecas cliente de API web.

En el menú Herramientas, seleccione Administrador de paquetes NuGet>Consola del Administrador de paquetes. En la Consola del administrador de paquetes (PMC), escriba el siguiente comando:

Install-Package Microsoft.AspNet.WebApi.Client

El comando anterior agrega los siguientes paquetes NuGet al proyecto:

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

Newtonsoft.Json (también conocido como Json.NET) es un popular marco JSON de alto rendimiento para .NET.

Agregar una clase modelo

Examine la clase Product:

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

Esta clase coincide con el modelo de datos usado por API web. Una aplicación puede usar HttpClient para leer una instancia de Product de una respuesta HTTP. La aplicación no tiene que escribir ningún código de deserialización.

Creación e inicialización de HttpClient

Examine la propiedad estática HttpClient:

static HttpClient client = new HttpClient();

HttpClient está pensado para ser instanciado una vez y reutilizado a lo largo de la vida de una aplicación. Las condiciones siguientes pueden dar lugar a errores de SocketException:

  • Creación de una nueva instancia de HttpClient por solicitud.
  • Servidor bajo carga pesada.

La creación de una nueva instancia de HttpClient por solicitud puede agotar los sockets disponibles.

El siguiente código inicializa la instancia de 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"));

El código anterior:

  • Establece el URI base para las solicitudes HTTP. Cambie el número de puerto por el puerto usado en la aplicación del servidor. La aplicación no funcionará a menos que se use el puerto para la aplicación del servidor.
  • Establece el encabezado Aceptar en "application/json". Al establecer este encabezado se indica al servidor que envíe los datos en formato JSON.

Envío de una solicitud GET para recuperar un recurso

El siguiente código envía una solicitud GET para un producto:

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

El método GetAsync envía la solicitud HTTP GET. Cuando el método finaliza, devuelve un HttpResponseMessage que contiene la respuesta HTTP. Si el código de estado de la respuesta es un código de éxito, el cuerpo de la respuesta contiene la representación JSON de un producto. Llame a ReadAsAsync para deserializar la carga JSON en una instancia de Product. El método ReadAsAsync es asincrónico porque el cuerpo de la respuesta puede ser arbitrariamente grande.

HttpClient no lanza una excepción cuando la respuesta HTTP contiene un código de error. En cambio, la propiedad IsSuccessStatusCode es false si el estado es un código de error. Si prefiere tratar los códigos de error HTTP como excepciones, llame a HttpResponseMessage.EnsureSuccessStatusCode en el objeto de respuesta. EnsureSuccessStatusCode lanza una excepción si el código de estado cae fuera del rango 200-299. Tenga en cuenta que HttpClient puede lanzar excepciones por otros motivos, por ejemplo, si la solicitud agota el tiempo de espera.

Formateadores de tipo multimedia para deserializar

Cuando ReadAsAsync se llama sin parámetros, usa un conjunto predeterminado de formateadores multimedia para leer el cuerpo de la respuesta. Los formateadores predeterminados son compatibles con datos en JSON, XML y Form-url-encoded.

En lugar de usar los formateadores predeterminados, puede proporcionar una lista de formateadores al método ReadAsAsync. El uso de una lista de formateadores es útil si tiene un formateador de tipo multimedia personalizado:

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

Para más información, consulte Formateadores multimedia en ASP.NET Web API 2

Envío de una solicitud POST para crear un recurso

El siguiente código envía una solicitud POST que contiene una instancia de Product en 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;
}

El método PostAsJsonAsync:

  • Serializa un objeto a JSON.
  • Envía la carga JSON en una solicitud POST.

Si la solicitud tiene éxito:

  • Debería devolver una respuesta 201 (Creado).
  • La respuesta debería incluir la URL de los recursos creados en el encabezado Ubicación.

Envío de una solicitud PUT para actualizar un recurso

El siguiente código envía una solicitud PUT para actualizar un producto:

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

El método PutAsJsonAsync funciona como PostAsJsonAsync, salvo que envía una solicitud PUT en lugar de POST.

Envío de una solicitud DELETE para eliminar un recurso

El siguiente código envía una solicitud DELETE para eliminar un producto:

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

Al igual que GET, una solicitud DELETE no tiene cuerpo de solicitud. No es necesario especificar el formato JSON o XML con DELETE.

Pruebe el ejemplo

Para probar la aplicación cliente:

  1. Descargue y ejecute la aplicación del servidor. Compruebe que la aplicación del servidor funciona. Por ejemplo, http://localhost:64195/api/products debería devolver una lista de productos.

  2. Establezca el URI base para las solicitudes HTTP. Cambie el número de puerto por el puerto usado en la aplicación del 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. Ejecute la aplicación cliente. Se produce el siguiente resultado:

    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)