Учебник по созданию HTTP-запросов в консольном приложении .NET на C#
В этом учебнике рассказывается, как создать приложение, которое отправляет HTTP-запросы к службе REST на GitHub. Приложение считывает информацию в формате JSON и преобразовывает ее в объекты C#. Преобразование из данных JSON в объекты C# называется десериализацией.
В этом учебнике вы научитесь следующему:
- отправлять HTTP-запросы;
- десериализовать JSON-ответы;
- настраивать десериализацию с использованием атрибутов.
Если вы хотите поработать с примером окончательного кода по этому учебнику, вы можете загрузить такой пример. Инструкции по загрузке см. в разделе Просмотр и скачивание примеров.
Предварительные требования
- Пакет SDK для .NET 6.0 или более поздней версии
- Редактор кода, например [Visual Studio Code (кроссплатформенный редактор с открытым кодом). Пример приложения можно запускать в ОС Windows, Linux, macOS или в контейнере Docker.
Создание клиентского приложения
Откройте командную строку и создайте каталог для приложения. Перейдите в этот каталог.
Введите следующую команду в окне консоли:
dotnet new console --name WebAPIClient
Эта команда создает начальный набор файлов для базового приложения Hello World. Имя проекта — "WebAPIClient".
Перейдите в каталог 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.
Откройте файл в каталоге
Program.cs
проекта и замените его содержимое следующим:await ProcessRepositoriesAsync(); static async Task ProcessRepositoriesAsync(HttpClient client) { }
Этот код выполняет следующие действия:
- заменяет оператор
Console.WriteLine
вызовомProcessRepositoriesAsync
, в котором используется ключевое словоawait
. - Определяет пустой
ProcessRepositoriesAsync
метод.
- заменяет оператор
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;
- задает заголовки HTTP для всех запросов, а именно
В методе
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
ответа выводится в консоль.
Выполните сборку приложения и запустите его.
dotnet run
Предупреждение о сборке отсутствует, так как
ProcessRepositoriesAsync
теперь содержит операторawait
. Выходные данные являются длинным текстом JSON.
Десериализация результата JSON
Шаги ниже позволяют преобразовать ответ в формате JSON в объекты C#. Используйте класс System.Text.Json.JsonSerializer, чтобы десериализовать данные JSON в объекты.
Создайте файл с именем Repository.cs и добавьте следующий код:
public record class Repository(string name);
Код выше определяет класс, представляющий объект JSON, возвращаемый из API GitHub. Этот класс будет использоваться для вывода списка имен репозиториев.
Данные JSON для объекта репозитория содержат десятки свойств, но только свойство
name
будет десериализовано. Сериализатор автоматически игнорирует свойства JSON, для которых нет совпадения в целевом классе. Эта функция упрощает создание типов, работающих только с подмножеством полей из пакета JSON.Соглашение C# должно сделать прописными первые буквы имен свойств, но свойство
name
здесь начинается со строчной буквы, так как точно соответствует свойству в JSON. Далее вы узнаете, как использовать имена свойств на C#, которые не соответствуют именам свойств JSON.Используйте сериализатор для преобразования пакета 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 представляет коллекцию объектов репозитория.Добавьте код для вывода имени каждого репозитория. Удалите вот эти строки кода:
Console.Write(json);
на новый код:
foreach (var repo in repositories ?? Enumerable.Empty<Repository>()) Console.Write(repo.name);
В верхней части файла должны присутствовать следующие
using
директивы:using System.Net.Http.Headers; using System.Text.Json;
Запустите приложение.
dotnet run
Оно выведет список имен всех репозиториев, которые принадлежат к .NET Foundation.
Настройка десериализации
В файле Repository.cs замените содержимое файла следующим кодом C#.
using System.Text.Json.Serialization; public record class Repository( [property: JsonPropertyName("name")] string Name);
Этот код выполняет следующие действия:
- изменяет имя свойства
name
наName
; - Добавляет , JsonPropertyNameAttribute чтобы указать, как это свойство отображается в JSON.
- изменяет имя свойства
В файле Program.cs обновите код, чтобы использовать свойство
Name
с прописной буквы:foreach (var repo in repositories) Console.Write(repo.Name);
Запустите приложение.
Результат тот же самый.
Рефакторинг кода
Метод ProcessRepositoriesAsync
может выполнять работу в асинхронном режиме и возвращает коллекцию репозиториев. Измените этот метод, чтобы вернуть Task<List<Repository>>
и переместите код, который записывает данные в консоль рядом с вызывающим.
Измените сигнатуру
ProcessRepositoriesAsync
, чтобы этот метод возвращал задачу, результатом которой является список объектовRepository
:static async Task<List<Repository>> ProcessRepositoriesAsync(HttpClient client)
Верните репозитории после обработки 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
.Измените файл Program.cs , заменив вызов
ProcessRepositoriesAsync
следующим, чтобы записать результаты и записать имена каждого репозитория в консоль.var repositories = await ProcessRepositoriesAsync(client); foreach (var repo in repositories) Console.Write(repo.Name);
Запустите приложение.
Результат тот же самый.
Десериализация дополнительных свойств
Следующие шаги добавляют код для обработки дополнительных свойств в полученном пакете JSON. Вы, вероятно, не будете обрабатывать каждое свойство, но добавление еще нескольких свойств позволит продемонстрировать другие возможности C#.
Замените содержимое
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 содержит данные, которые не преобразуются в целевой тип, действие сериализации создает исключение.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(); }
Запустите приложение.
Теперь список содержит дополнительные свойства.
Добавление свойства даты
Дата последней операции push-уведомления в ответе JSON имеет следующий формат:
2016-02-08T21:27:00Z
Этот формат предназначен для времени в формате UTC, поэтому результатом десериализации является значение DateTime, свойство Kind которого равно Utc.
Чтобы получить дату и время в вашем часовом поясе, вам необходимо написать пользовательский метод преобразования.
В файле 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# даже свойства только для записи, но для них трудно найти применение.)Добавьте еще одну инструкцию вывода данных в файл Program.cs:
Console.WriteLine($"Last push: {repo.LastPush}");
Полное приложение должно выглядеть так, как в следующем файле 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(); }
Запустите приложение.
Выходные данные включают дату и время последней отправки в каждый репозиторий.
Дальнейшие действия
В этом руководстве вы создали приложение, которое выполняет веб-запросы и анализирует результаты. Теперь версия вашего приложения должна совпадать с полной версией примера.
Дополнительные сведения о настройке сериализации JSON см. в статье Сериализация и десериализация (маршалирование и демаршалирование) JSON в .NET.