JSON представляет один из наиболее популярных форматов передачи данных от клиента серверу и обратно. Посмотрим, как получать от сервера данные в формате JSON.
Для начала определим сервер, который и будет посылать данные в формате JSON. Для этого Для этого создадим проект ASP.NET по типу ASP.NET Core Empty:
К примеру, в моем случае проект называется HttpClientTestApp. Откроем в этом проекте в файл Program.cs
и определим в нем следующий код:
var builder = WebApplication.CreateBuilder(); var app = builder.Build(); app.MapGet("/", () => new Person("Tom", 38)); app.Run(); record Person(string Name, int Age);
Для создания веб-приложения на ASP.NET Core вначале нам необходимо создать объект WebApplicationBuilder:
var builder = WebApplication.CreateBuilder();
Затем с помощью его метода Build создаем объект WebApplication, который собственно представляет приложение ASP.NET:
var app = builder.Build();
С помощью метода app.MapGet()
определяем конечную точку - обработчик запросов по определенному маршруту:
app.MapGet("/", () => new Person("Tom", 38));
В данном случае при обращении по адресу "/" (то есть к корню веб-приложения) обработчик возвращает объект Person("Tom", 38)
, который при отправке автоматически сериализуется
в формат JSON.
Затем запускаем приложение:
app.Run();
Запустим проект, и браузер нам отобразит отправленный объект в формате JSON:
Сразу обратим внимание на адрес, по которому запущено приложение ASP.NET, чтобы затем подключиться к нему из HttpClient. Так, в моем случае это "https://localhost:7094/".
Непосредственно класс HttpClient не определяет удобной функциональности для автоматического получения данных в JSON. Однако в пространстве имен System.Net.Http.Json определены методы расширения для HttpClient специально для работы с Json:
GetFromJsonAsync(): отправляет запрос GET и возвращает десериализованные объекты из JSON
DeleteFromJsonAsync(): отправляет запрос DELETE и возвращает десериализованные объекты из JSON
Оба метода практически идентичны, кроме типа отправляемого запроса. Нетипизированная версия получает как минимум два параметра - адрес запроса и тип, в который надо десериализовать данные:
using System.Net.Http.Json; // пространство имен метода GetFromJsonAsync class Program { static HttpClient httpClient = new HttpClient(); static async Task Main() { object? data = await httpClient.GetFromJsonAsync("https://localhost:7094/", typeof(Person)); if(data is Person person) { Console.WriteLine($"Name: {person.Name} Age: {person.Age}"); } } } record Person(string Name, int Age);
Нетипизированная версия возвращает данные в виде объекта object?
, который надо затем приводить к нужному типу.
Так, в данном случае мы пытаемся десериализовать данные в объект Person, который соответствует отправляемым на сервере данным. В итоге мы получим следующий консольный вывод
Name: Tom Age: 38
При типизированной версии метод тизируется классом, в который надо десериализовать данные. А в качестве параметров достаточно передать адрес запроса:
using System.Net.Http.Json; class Program { static HttpClient httpClient = new HttpClient(); static async Task Main() { Person? person = await httpClient.GetFromJsonAsync<Person>("https://localhost:7094/"); Console.WriteLine($"Name: {person?.Name} Age: {person?.Age}"); } } record Person(string Name, int Age);
В качестве результата возвращается объект типа, которым типизируется метод и который допускает значение null.
В обоих случае если десериализация данных в объект указанного типа завершится неудачно, в этом случае результат метода будет равен null
Хотя примеры выше в принципе нормально работают, но в некоторых ситуациях мы можем столкнуться с ограничениями. Так, что есть мы хотим считать статус ответа, какие-то заголовки? Например, изменим код сервера следующим образом:
var builder = WebApplication.CreateBuilder(); var app = builder.Build(); app.MapGet("/{id?}", (int? id) => { if (id is null) return Results.BadRequest(new { Message = "Некорректные данные в запросе" }); else if (id != 1) return Results.NotFound(new { Message = $"Объект с id={id} не существует" }); else return Results.Json(new Person("Bob", 42)); }); app.Run(); record Person(string Name, int Age);
Теперь метод app.MapGet
прикреплен к запросам, которые соответствуют шаблону "/{id?}". В данном случае пользователь после слеша может указать некоторое значение, и это значение будет передано в параметр id
.
Вопросительный знак ? после названия параметра id указывает, что этот параметр необязательный. То есть шаблон "/{id?}" будет соответствовать запросам "/" (в этом случае id равен null)
или "/5" (в этом случае id равен 5). Причем если после слеша указано значение, оно должно представлять число.
В обработчике этого маршрута проверяем значение параметра id
. Так, если оно равно null, то посылаем клиенту ошибку 400 с одним сообщением:
return Results.BadRequest(new { Message = "Некорректные данные в запросе" });
Если id указан, но он не равен 1, посылаем ошибку 404 с другим сообщением:
return Results.NotFound(new { Message = $"Объект с id={id} не существует" });
В остальных случаях посылаем объект Person:
return Results.Json(new Person("Bob", 42));
Для отправки данных здесь применяются методы класса Results
, но все они сериализуют переданные в них объекты в Json. Это довольно обычная ситуация, потому что нередко ответ сервера
вовлекает статусные коды, сообщения об ошибках и прочую информацию, которая может потребоваться на клиенте.
Как в этом случае мы можем обработать все эти возможные варианты? В этом случае мы можем вместо получения непосредственно отправленных данных
получать ответ сервера в виде HttpMessageResponse, который даст нам доступ ко всей сопутствующей информации как заголовки, статус и т.д. Тогда мы можем получить из HttpMessageResponse ответ в виде
объекта HttpContent
, у которого затем вызывается метод ReadFromJsonAsyn(). Этот метод определен в пространстве имен System.Net.Http.Json.
Он типизируется типом, в который надо десериализовать данные:
using System.Net; using System.Net.Http.Json; class Program { static HttpClient httpClient = new HttpClient(); static async Task Main() { using var response = await httpClient.GetAsync("https://localhost:7094/1"); if (response.StatusCode == HttpStatusCode.BadRequest || response.StatusCode == HttpStatusCode.NotFound) { // получаем информацию об ошибке Error? error = await response.Content.ReadFromJsonAsync<Error>(); Console.WriteLine(response.StatusCode); Console.WriteLine(error?.Message); } else { // если запрос завершился успешно, получаем объект Person Person? person = await response.Content.ReadFromJsonAsync<Person>(); Console.WriteLine($"Name: {person?.Name} Age: {person?.Age}"); } } } // для успешного ответа record Person(string Name, int Age); // для ошибок record Error(string Message);
В зависимости от статуса ответа считываем ответ с помощью метода ReadFromJsonAsync()
объекта HttpContent в один из типов - Person или Error.