Вызов веб-API из клиента .NET (C#)

Это содержимое предназначено для предыдущей версии .NET. Новая разработка должна использовать ASP.NET Core. Дополнительные сведения об использовании веб-API ASP.NET Core см. в следующей статье:

Скачайте завершенный проект.

В этом руководстве показано, как вызвать веб-API из приложения .NET с помощью System.Net.Http.HttpClient.

В этой инструкции создается клиентское приложение, которое использует следующий web API.

Действие Метод HTTP Относительный универсальный код ресурса (URI)
Получение сведений о продукте по идентификатору GET /api/products/id
Создание нового продукта ПОСТ /api/products
Обновление продукта ЗАПИСАТЬ /api/products/id
Удаление продукта DELETE /api/products/id

Сведения о реализации этого API с помощью веб-API ASP.NET см. в статье "Создание веб-API, поддерживающего операции CRUD".

Для простоты клиентское приложение в этом руководстве является консольным приложением Windows. HttpClient также поддерживается для приложений Windows Phone и Магазина Windows. Дополнительные сведения см. в статье Написание клиентского кода веб-API для нескольких платформ с помощью переносимых библиотек

ПРИМЕЧАНИЕ: Если вы передаете базовые URL-адреса и относительные URI в виде жестко закодированных значений, помните о правилах использования HttpClient API. Свойство HttpClient.BaseAddress должно иметь адрес, оканчивающийся на косую черту (/). Например, при передаче жёстко закодированных URI ресурсов в метод HttpClient.GetAsync, не включайте начальную косую черту. Чтобы получить Product по идентификатору:

  1. Задайте значение client.BaseAddress = new Uri("https://localhost:5001/");.
  2. Запросите Product. Например: client.GetAsync<Product>("api/products/4");.

Создание консольного приложения

В Visual Studio создайте новое консольное приложение Windows с именем HttpClientSample и вставьте в следующий код:

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

Приведенный выше код является полным клиентским приложением.

RunAsync выполняется и блокируется до завершения. Большинство методов HttpClient являются асинхронными, так как они выполняют сетевые операции ввода-вывода. Все асинхронные задачи выполняются внутри RunAsync. Как правило, приложение не блокирует основной поток, но это приложение не позволяет взаимодействовать.

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

Установка клиентских библиотек веб-API

Используйте диспетчер пакетов NuGet для установки пакета клиентских библиотек веб-API.

В меню Tools выберите NuGet Package Manager>Package Manager Console. В консоли диспетчера пакетов (PMC) введите следующую команду:

Install-Package Microsoft.AspNet.WebApi.Client

Предыдущая команда добавляет в проект следующие пакеты NuGet:

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

Newtonsoft.Json (также известный как Json.NET) — это популярная высокопроизводительная платформа JSON для .NET.

Добавление класса модели

Product Проверьте класс:

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

Этот класс соответствует модели данных, используемой веб-API. Приложение может использовать HttpClient для чтения Product экземпляра из HTTP-ответа. Приложению не нужно писать код десериализации.

Создание и инициализация HttpClient

Проверьте статическое свойство HttpClient :

static HttpClient client = new HttpClient();

HttpClient предназначен для создания экземпляра один раз и повторного использования в течение всего времени существования приложения. Следующие условия могут привести к ошибкам SocketException :

  • Создание нового экземпляра HttpClient для каждого запроса.
  • Сервер под тяжелой нагрузкой.

Создание нового экземпляра HttpClient для каждого запроса может исчерпать доступные сокеты.

Следующий код инициализирует экземпляр 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"));

Предыдущий код:

  • Задает базовый универсальный код ресурса (URI) для HTTP-запросов. Измените номер порта на порт, используемый в серверном приложении. Приложение не будет работать, если порт для серверного приложения не используется.
  • Устанавливает заголовок Accept со значением "application/json". Если задать этот заголовок, сервер отправляет данные в формате JSON.

Отправка запроса GET для получения ресурса

Следующий код отправляет запрос GET для продукта:

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

Метод GetAsync отправляет HTTP-запрос GET. По завершении метода возвращается httpResponseMessage , содержащий http-ответ. Если код состояния в ответе является кодом успешного выполнения, текст ответа содержит представление JSON продукта. Вызовите ReadAsAsync для десериализации данных JSON в Product экземпляр. Метод ReadAsAsync является асинхронным, так как тело отклика может быть произвольно большим.

HttpClient не создает исключение, если http-ответ содержит код ошибки. Вместо этого свойство IsSuccessStatusCodeимеет значение false , если состояние является кодом ошибки. Если вы предпочитаете рассматривать коды ошибок HTTP в качестве исключений, вызовите httpResponseMessage.EnsureSuccessStatusCode в объекте ответа. EnsureSuccessStatusCode вызывает исключение, если код состояния выходит за пределы диапазона 200–299. Обратите внимание, что HttpClient может вызывать исключения по другим причинам, например, если время ожидания запроса истекает.

Форматеры типа media для десериализации

При вызове ReadAsAsync без параметров используется набор форматировщиков мультимедиа по умолчанию для чтения текста ответа. Форматировщики по умолчанию поддерживают данные в кодировке JSON, XML и Form-url.

Вместо использования средств форматирования по умолчанию можно указать список средств форматирования методу ReadAsAsync . Использование списка средств форматирования полезно, если у вас есть настраиваемый модуль форматирования типа мультимедиа:

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

Дополнительные сведения см. в разделе "Формататоры мультимедиа" в веб-API ASP.NET 2

Отправка запроса POST для создания ресурса

Следующий код отправляет запрос POST, содержащий Product экземпляр в формате 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;
}

Метод PostAsJsonAsync :

  • Сериализует объект в JSON.
  • Отправляет полезные данные JSON в запросе POST.

Если запрос выполнен успешно:

  • Он должен возвращать ответ 201 (Создано).
  • Ответ должен содержать URL-адрес созданных ресурсов в заголовке location.

Отправка запроса PUT для обновления ресурса

Следующий код отправляет запрос PUT для обновления продукта:

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

Метод PutAsJsonAsync работает как PostAsJsonAsync, за исключением того, что он отправляет запрос PUT вместо POST.

Отправка запроса DELETE для удаления ресурса

Следующий код отправляет запрос DELETE для удаления продукта:

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

Как и GET, запрос DELETE не имеет текста запроса. Вам не нужно указывать формат JSON или XML с помощью DELETE.

Тестирование примера

Чтобы протестировать клиентское приложение, выполните следующие действия.

  1. Скачайте и запустите серверные приложения. Проверка работы серверного приложения. Например, http://localhost:64195/api/products должен возвращать список продуктов.

  2. Задайте базовый универсальный код ресурса (URI) для HTTP-запросов. Измените номер порта на порт, используемый в серверном приложении.

    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. Запустите клиентское приложение. Выводятся следующие результаты:

    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)