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


Руководство. Создание 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

Ниже описано, как упростить подход к выбору данных и обработке данных. Вы будете использовать GetFromJsonAsync метод расширения, который входит в 📦 пакет NuGet System.Net.Http.Json , чтобы получить и десериализировать результаты JSON в объекты.

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

    public record class Repository(string Name);
    

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

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

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

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

    var repositories = await client.GetFromJsonAsync<List<Repository>>("https://api.github.com/orgs/dotnet/repos");
    

    Обновленный код заменяет GetStringAsync(String) на HttpClientJsonExtensions.GetFromJsonAsync.

    Первым аргументом GetFromJsonAsync метода await является выражение. await выражения могут отображаться практически в любом месте кода, даже если до сих пор вы видели их только как часть инструкции назначения. Следующий параметр requestUri является необязательным и не должен быть указан, если он уже указан при создании client объекта. Вы не предоставили объекту универсальный client код ресурса (URI) для отправки запроса, поэтому вы указали универсальный код ресурса (URI). Последний необязательный параметр CancellationToken опущен в фрагменте кода.

    Метод GetFromJsonAsyncявляется универсальным, что означает, что вы предоставляете аргументы типа для того, какой тип объектов следует создать из полученного текста 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.WriteLine(repo.Name);
    
  4. В верхней части файла должны присутствовать следующие using директивы:

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

    dotnet run
    

    Выходные данные — это список имен репозиториев, которые являются частью .NET Foundation.

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

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

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

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

    var repositories = await client.GetFromJsonAsync<List<Repository>>("https://api.github.com/orgs/dotnet/repos");
    return repositories ?? new();
    

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

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

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

    Выходные данные одинаковы.

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

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

  1. Замените содержимое Repository класса следующим record определением. Обязательно импортируйте System.Text.Json.Serialization пространство имен и примените [JsonPropertyName] атрибут для сопоставления полей JSON с свойствами C# явным образом.

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

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

    JSON часто использует lowercase или snake_case в качестве имен свойств. Такие поля, как html_url и pushed_at не следуют соглашениям об именовании C# PascalCase. Использование [JsonPropertyName] гарантирует правильность привязки этих ключей JSON к соответствующим свойствам C#, даже если их имена отличаются в случае или содержат подчеркивания. Этот подход гарантирует прогнозируемую и стабильную десериализацию, позволяя определение имен свойств в стиле PascalCase в C#. Кроме того, GetFromJsonAsync метод используется case-insensitive при сопоставлении имен свойств, поэтому дальнейшее преобразование не требуется.

  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(
        string Name,
        string Description,
        [property: JsonPropertyName("html_url")] Uri GitHubHomeUrl,
        Uri Homepage,
        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.Net.Http.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)
    {
        var repositories = await client.GetFromJsonAsync<List<Repository>>("https://api.github.com/orgs/dotnet/repos");
        return repositories ?? new List<Repository>();
    }
    
  4. Запустите приложение.

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

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

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

Подробнее о том, как настроить сериализацию JSON, читайте в разделе "Как сериализовать и десериализовать (маршалировать и демаршалировать) JSON в .NET.