Учебник по созданию HTTP-запросов в консольном приложении .NET на C#

В этом учебнике рассказывается, как создать приложение, которое отправляет HTTP-запросы к службе REST на GitHub. Приложение считывает информацию в формате JSON и преобразовывает ее в объекты C#. Преобразование из данных JSON в объекты C# называется десериализацией.

В этом учебнике вы научитесь следующему:

  • отправлять HTTP-запросы;
  • десериализовать JSON-ответы;
  • настраивать десериализацию с использованием атрибутов.

Если вы хотите поработать с примером окончательного кода по этому учебнику, вы можете загрузить такой пример. Инструкции по загрузке см. в разделе Просмотр и скачивание примеров.

Предварительные требования

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

  1. Откройте командную строку и создайте каталог для приложения. Перейдите в этот каталог.

  2. Введите следующую команду в окне консоли:

    dotnet new console --name WebAPIClient
    

    Эта команда создает начальный набор файлов для базового приложения Hello World. Имя проекта — "WebAPIClient".

  3. Перейдите в каталог WebAPIClient и запустите приложение.

    cd WebAPIClient
    
    dotnet run
    

    dotnet run автоматически запускает dotnet restore, чтобы восстановить все зависимости, необходимые приложению. Эта команда также запускает dotnet build при необходимости. Вы должны увидеть выходные данные "Hello, World!"приложения . В терминале нажмите клавиши CTRL+C , чтобы остановить приложение.

Создание HTTP-запросов

Это приложение вызывает API GitHub для получения сведений о проектах под зонтичным брендом .NET Foundation. Конечная точка имеет значение https://api.github.com/orgs/dotnet/repos. Для получения сведений создается HTTP-запрос get. Браузеры также используют HTTP-запросы get, поэтому вы можете указать этот URL-адрес в адресной строке браузера и увидеть, какие сведения вы будете получать и обрабатывать.

Используйте класс HttpClient, чтобы выполнять HTTP-запросы. HttpClient поддерживает только асинхронные методы для длительно выполняющихся API-интерфейсов. Следующие шаги позволяют создать асинхронный метод и вызывают его из метода Main.

  1. Откройте файл в каталоге Program.cs проекта и замените его содержимое следующим:

    await ProcessRepositoriesAsync();
    
    static async Task ProcessRepositoriesAsync(HttpClient client)
    {
    }
    

    Этот код выполняет следующие действия:

    • заменяет оператор Console.WriteLine вызовом ProcessRepositoriesAsync, в котором используется ключевое слово await.
    • Определяет пустой ProcessRepositoriesAsync метод.
  2. Program В классе используйте HttpClient для обработки запросов и ответов, заменив содержимое следующим C#.

    using System.Net.Http.Headers;
    
    using HttpClient client = new();
    client.DefaultRequestHeaders.Accept.Clear();
    client.DefaultRequestHeaders.Accept.Add(
        new MediaTypeWithQualityHeaderValue("application/vnd.github.v3+json"));
    client.DefaultRequestHeaders.Add("User-Agent", ".NET Foundation Repository Reporter");
    
    await ProcessRepositoriesAsync(client);
    
    static async Task ProcessRepositoriesAsync(HttpClient client)
    {
    }
    

    Этот код выполняет следующие действия:

    • задает заголовки HTTP для всех запросов, а именно
      • Accept для приема ответов в формате JSON;
      • Название объекта User-Agent. Код сервера GitHub проверяет эти заголовки, они необходимы для извлечения сведений из GitHub;
  3. В методе ProcessRepositoriesAsync вызовите конечную точку GitHub, которая возвращает список всех репозиториев .NET Foundation:

     static async Task ProcessRepositoriesAsync(HttpClient client)
     {
         var json = await client.GetStringAsync(
             "https://api.github.com/orgs/dotnet/repos");
    
         Console.Write(json);
     }
    

    Этот код выполняет следующие действия:

    • Ожидает задачу, возвращенную вызовом HttpClient.GetStringAsync(String) метода . Этот метод отправляет HTTP-запрос GET на указанный URI. В качестве текста ответа возвращаются данные типа String. Они доступны по завершении задачи;
    • Строка json ответа выводится в консоль.
  4. Выполните сборку приложения и запустите его.

    dotnet run
    

    Предупреждение о сборке отсутствует, так как ProcessRepositoriesAsync теперь содержит оператор await. Выходные данные являются длинным текстом JSON.

Десериализация результата JSON

Шаги ниже позволяют преобразовать ответ в формате JSON в объекты C#. Используйте класс System.Text.Json.JsonSerializer, чтобы десериализовать данные JSON в объекты.

  1. Создайте файл с именем Repository.cs и добавьте следующий код:

    public record class Repository(string name);
    

    Код выше определяет класс, представляющий объект JSON, возвращаемый из API GitHub. Этот класс будет использоваться для вывода списка имен репозиториев.

    Данные JSON для объекта репозитория содержат десятки свойств, но только свойство name будет десериализовано. Сериализатор автоматически игнорирует свойства JSON, для которых нет совпадения в целевом классе. Эта функция упрощает создание типов, работающих только с подмножеством полей из пакета JSON.

    Соглашение C# должно сделать прописными первые буквы имен свойств, но свойство name здесь начинается со строчной буквы, так как точно соответствует свойству в JSON. Далее вы узнаете, как использовать имена свойств на C#, которые не соответствуют именам свойств JSON.

  2. Используйте сериализатор для преобразования пакета JSON в объекты C#. В методе ProcessRepositoriesAsync замените вызов GetStringAsync(String) следующими строками:

    await using Stream stream =
        await client.GetStreamAsync("https://api.github.com/orgs/dotnet/repos");
    var repositories =
        await JsonSerializer.DeserializeAsync<List<Repository>>(stream);
    

    В обновленном коде GetStringAsync(String) заменится на GetStreamAsync(String). Метод сериализатора использует в качестве источника поток, а не строку.

    Первый аргумент JsonSerializer.DeserializeAsync<TValue>(Stream, JsonSerializerOptions, CancellationToken) является выражением await. Выражения await могут использоваться почти в любом месте кода, хотя пока мы их применяли только в операторе назначения. Два других параметра, JsonSerializerOptions и CancellationToken, необязательны и не включены во фрагмент кода.

    Метод DeserializeAsync является общим, что означает, что необходимо предоставить аргументы типа согласно типу объектов, создаваемых на основе текста JSON. В этом примере выполняется десериализация в объект List<Repository>, который является еще одним общим объектом, System.Collections.Generic.List<T>. Класс List<T> хранит коллекцию объектов. Аргумент типа определяет тип объектов, хранящихся в List<T>. Аргумент type — это запись Repository , так как текст JSON представляет коллекцию объектов репозитория.

  3. Добавьте код для вывода имени каждого репозитория. Удалите вот эти строки кода:

    Console.Write(json);
    

    на новый код:

    foreach (var repo in repositories ?? Enumerable.Empty<Repository>())
        Console.Write(repo.name);
    
  4. В верхней части файла должны присутствовать следующие using директивы:

    using System.Net.Http.Headers;
    using System.Text.Json;
    
  5. Запустите приложение.

    dotnet run
    

    Оно выведет список имен всех репозиториев, которые принадлежат к .NET Foundation.

Настройка десериализации

  1. В файле Repository.cs замените содержимое файла следующим кодом C#.

    using System.Text.Json.Serialization;
    
    public record class Repository(
        [property: JsonPropertyName("name")] string Name);
    

    Этот код выполняет следующие действия:

    • изменяет имя свойства name на Name;
    • Добавляет , JsonPropertyNameAttribute чтобы указать, как это свойство отображается в JSON.
  2. В файле Program.cs обновите код, чтобы использовать свойство Name с прописной буквы:

    foreach (var repo in repositories)
       Console.Write(repo.Name);
    
  3. Запустите приложение.

    Результат тот же самый.

Рефакторинг кода

Метод ProcessRepositoriesAsync может выполнять работу в асинхронном режиме и возвращает коллекцию репозиториев. Измените этот метод, чтобы вернуть Task<List<Repository>>и переместите код, который записывает данные в консоль рядом с вызывающим.

  1. Измените сигнатуру ProcessRepositoriesAsync, чтобы этот метод возвращал задачу, результатом которой является список объектов Repository:

    static async Task<List<Repository>> ProcessRepositoriesAsync(HttpClient client)
    
  2. Верните репозитории после обработки JSON-ответа:

    await using Stream stream =
        await client.GetStreamAsync("https://api.github.com/orgs/dotnet/repos");
    var repositories =
        await JsonSerializer.DeserializeAsync<List<Repository>>(stream);
    return repositories ?? new();
    

    Компилятор создает объект Task<T> в качестве выходных данных, так как этот метод обозначен ключевым словом async.

  3. Измените файл Program.cs , заменив вызов ProcessRepositoriesAsync следующим, чтобы записать результаты и записать имена каждого репозитория в консоль.

    var repositories = await ProcessRepositoriesAsync(client);
    
    foreach (var repo in repositories)
        Console.Write(repo.Name);
    
  4. Запустите приложение.

    Результат тот же самый.

Десериализация дополнительных свойств

Следующие шаги добавляют код для обработки дополнительных свойств в полученном пакете JSON. Вы, вероятно, не будете обрабатывать каждое свойство, но добавление еще нескольких свойств позволит продемонстрировать другие возможности C#.

  1. Замените содержимое Repository класса следующим record определением:

    using System.Text.Json.Serialization;
    
    public record class Repository(
        [property: JsonPropertyName("name")] string Name,
        [property: JsonPropertyName("description")] string Description,
        [property: JsonPropertyName("html_url")] Uri GitHubHomeUrl,
        [property: JsonPropertyName("homepage")] Uri Homepage,
        [property: JsonPropertyName("watchers")] int Watchers);
    

    Типы Uri и int имеют встроенные функции для преобразования в строковое представление и из него. Для десериализации из строкового формата JSON в эти целевые типы не требуется дополнительного кода. Если пакет JSON содержит данные, которые не преобразуются в целевой тип, действие сериализации создает исключение.

  2. foreach Обновите цикл в файле Program.cs, чтобы отобразить значения свойств:

    foreach (var repo in repositories)
    {
        Console.WriteLine($"Name: {repo.Name}");
        Console.WriteLine($"Homepage: {repo.Homepage}");
        Console.WriteLine($"GitHub: {repo.GitHubHomeUrl}");
        Console.WriteLine($"Description: {repo.Description}");
        Console.WriteLine($"Watchers: {repo.Watchers:#,0}");
        Console.WriteLine();
    }
    
  3. Запустите приложение.

    Теперь список содержит дополнительные свойства.

Добавление свойства даты

Дата последней операции push-уведомления в ответе JSON имеет следующий формат:

2016-02-08T21:27:00Z

Этот формат предназначен для времени в формате UTC, поэтому результатом десериализации является значение DateTime, свойство Kind которого равно Utc.

Чтобы получить дату и время в вашем часовом поясе, вам необходимо написать пользовательский метод преобразования.

  1. В файле Repository.cs добавьте свойство для представления даты и времени в формате UTC и свойство только LastPush для чтения, которое возвращает дату, преобразованную в местное время. Файл должен выглядеть следующим образом:

    using System.Text.Json.Serialization;
    
    public record class Repository(
        [property: JsonPropertyName("name")] string Name,
        [property: JsonPropertyName("description")] string Description,
        [property: JsonPropertyName("html_url")] Uri GitHubHomeUrl,
        [property: JsonPropertyName("homepage")] Uri Homepage,
        [property: JsonPropertyName("watchers")] int Watchers,
        [property: JsonPropertyName("pushed_at")] DateTime LastPushUtc)
    {
        public DateTime LastPush => LastPushUtc.ToLocalTime();
    }
    

    Свойство LastPush определяется с помощью члена, заданного выражением для метода доступа get. Метод доступа set отсутствует. Пропуск set метода доступа — это один из способов определения свойства , доступного только для чтения , в C#. (Да, вы можете создать в C# даже свойства только для записи, но для них трудно найти применение.)

  2. Добавьте еще одну инструкцию вывода данных в файл Program.cs:

    Console.WriteLine($"Last push: {repo.LastPush}");
    
  3. Полное приложение должно выглядеть так, как в следующем файле Program.cs :

    using System.Net.Http.Headers;
    using System.Text.Json;
    
    using HttpClient client = new();
    client.DefaultRequestHeaders.Accept.Clear();
    client.DefaultRequestHeaders.Accept.Add(
        new MediaTypeWithQualityHeaderValue("application/vnd.github.v3+json"));
    client.DefaultRequestHeaders.Add("User-Agent", ".NET Foundation Repository Reporter");
    
    var repositories = await ProcessRepositoriesAsync(client);
    
    foreach (var repo in repositories)
    {
        Console.WriteLine($"Name: {repo.Name}");
        Console.WriteLine($"Homepage: {repo.Homepage}");
        Console.WriteLine($"GitHub: {repo.GitHubHomeUrl}");
        Console.WriteLine($"Description: {repo.Description}");
        Console.WriteLine($"Watchers: {repo.Watchers:#,0}");
        Console.WriteLine($"{repo.LastPush}");
        Console.WriteLine();
    }
    
    static async Task<List<Repository>> ProcessRepositoriesAsync(HttpClient client)
    {
        await using Stream stream =
            await client.GetStreamAsync("https://api.github.com/orgs/dotnet/repos");
        var repositories =
            await JsonSerializer.DeserializeAsync<List<Repository>>(stream);
        return repositories ?? new();
    }
    
  4. Запустите приложение.

    Выходные данные включают дату и время последней отправки в каждый репозиторий.

Дальнейшие действия

В этом руководстве вы создали приложение, которое выполняет веб-запросы и анализирует результаты. Теперь версия вашего приложения должна совпадать с полной версией примера.

Дополнительные сведения о настройке сериализации JSON см. в статье Сериализация и десериализация (маршалирование и демаршалирование) JSON в .NET.