Поделиться через


Руководство. Создание HTTP-запросов в консольном приложении .NET с помощью C#

В этом руководстве создается приложение, которое выдает HTTP-запросы к службе REST на GitHub. Приложение считывает сведения в формате JSON и преобразует 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#. Замените вызов GetStringAsync(String) в методе ProcessRepositoriesAsync следующими строками:

    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>. Аргумент типа — это запись формата 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. Запустите приложение.

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

Добавьте свойство даты

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

2016-02-08T21:27:00Z

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

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

  1. В Repository.cs добавьте свойство для представления даты и времени в формате UTC и свойство readonly 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.