Используя HttpClient.GetFromJsonAsync(), как обрабатывать HttpRequestException на основе HttpStatusCode без дополнительных вызовов SendAsync?

Методы расширения System.Net.Http.JsonHttpClient, такие как GetFromJsonAsync(), значительно упрощают рутинные коды для извлечения объектов json из веб-API. Это удовольствие использовать.

Но из-за того, как он спроектирован (возврат десериализованных объектов напрямую), он не создает никаких HttpResponseMessage для проверки, что позволяет мне выполнять пользовательские действия на основе HttpStatusCode.

Вместо этого коды состояния неуспешного выполнения приводят к HttpRequestException, который, по-видимому, не предлагает никаких свойств, раскрывающих строго типизированный HttpStatusCode. Вместо этого код состояния включается в саму строку Message исключения.

Обновлено: в .NET 5.0 добавлено свойство HttpRequestException.StatusCode, поэтому теперь его можно проверить при вызове GetFromJsonAsync.

//старый пост ниже

Итак, я делал что-то вроде этого:

try
{
  var cars = await httpClient.GetFromJsonAsync<List<Car>>("/api/cars");
  //...
}
            
catch (HttpRequestException ex)
{
   if (ex.Message.Contains(HttpStatusCode.Unauthorized.ToString()))
   {
     //Show unauthorized error page...
   }
   //...
}

Это кажется немного хакерским. Благодаря старому способу создания HttpRequestMessage и вызова SendAsync у нас, естественно, появилась возможность проверить ответ HttpResponseMessage.StatusCode. Добавление некоторых из этих кодов обратно лишило бы удобную цель использования однострочников в System.Net.Http.Json.

Любые предложения здесь будут очень признательны.

Я обнаружил, что .GetFromJsonAsync<T> выдает исключение на основе StatusCode: System.Net.Http.HttpRequestException Response status code does not indicate success: 400 (Bad Request). at System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode(‌​)

gawkface 07.12.2021 06:58

судя по моему предыдущему комментарию, нашел такой подход: HttpClientExtensions.PostAsJsonAsync(HttpClientInstance, requestUri, body, cancellationToken);

gawkface 07.12.2021 07:41
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
31
2
41 707
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Вы можете использовать:

// return HttpResponseMessage
var res= await httpClient.GetAsync<List<Car>>("/api/cars")

if (res.IsSuccessStatusCode)
   var cars = res.Content.ReadFromJsonAsync<List<Car>>();
else
   // deal with the HttpResponseMessage directly as you used to

Я использую такой базовый класс:

using System;
using System.Net.Http;
using System.Net.Http.Json;
using System.Threading.Tasks;


namespace MyProject.ClientAPI
{
    public abstract class ClientAPI
    {
        protected readonly HttpClient Http;
        private readonly string BaseRoute;

        protected ClientAPI(string baseRoute, HttpClient http)
        {
            BaseRoute = baseRoute;
            Http = http;
        }

        protected async Task<TReturn> GetAsync<TReturn>(string relativeUri)
        {
            HttpResponseMessage res = await Http.GetAsync($"{BaseRoute}/{relativeUri}");
            if (res.IsSuccessStatusCode)
            {
                return await res.Content.ReadFromJsonAsync<TReturn>();
            }
            else
            {
                string msg = await res.Content.ReadAsStringAsync();
                Console.WriteLine(msg);
                throw new Exception(msg);
            }
        }
        
        protected async Task<TReturn> PostAsync<TReturn, TRequest>(string relativeUri, TRequest request)
        {
            HttpResponseMessage res = await Http.PostAsJsonAsync<TRequest>($"{BaseRoute}/{relativeUri}", request);
            if (res.IsSuccessStatusCode)
            {
                return await res.Content.ReadFromJsonAsync<TReturn>();
            }
            else
            {
                string msg = await res.Content.ReadAsStringAsync();
                Console.WriteLine(msg);
                throw new Exception(msg);
            }
        }
    }
}

а затем из производного класса мы вернулись к однострочному

public class MySpecificAPI : ClientAPI
{
    public MySpecificAPI(HttpClient http) : base("api/myspecificapi", http) {}
    
    public async Task<IEnumerable<MyClass>> GetMyClassAsync(int ownerId)
    {
        try
        {
            return GetAsync<IEnumerable<MyClass>>($"apiMethodName?ownerId = {ownerId}");
        }
        catch (Exception e)
        {
            // Deal with exception
        }
    }
    
    // repeat for post
}

ОБНОВЛЕНИЕ: ОБРАБОТКА ВОЗВРАТА NULL

Столкнувшись с допустимым сценарием, когда WebAPI возвращает значение null, строка:

return await res.Content.ReadFromJsonAsync<TReturn>();

выдаст ошибку десериализации Json.

Чтобы решить эту проблему, нам нужно обнаружить ответ NoContent (204) и соответствующим образом обработать:

if (res.StatusCode == HttpStatusCode.NoContent)
    return default(TReturn);
else if (res.IsSuccessStatusCode)
    return await res.Content.ReadFromJsonAsync<TReturn>();

На самом деле, я также только что узнал, что в .NET 5.0 добавлено свойство HttpRequestException.StatusCode. Я использовал .NET Core 3.1, поэтому не знал.

thankyoussd 21.12.2020 20:10
GetAsync не принимает аргумент типа в приложении .NET 5 Blazor, поэтому предложенный код мне не подходит.
Ted 25.04.2021 09:07

@Ted ... Обратите внимание, что метод GetAsync, вызываемый из MySpecificAPI (где я использую тип), не является HttpClient.GetAsync, это метод GetAsync в моем базовом классе ClientAPI. В свою очередь ClientAPI вызывает метод HttpClient.GetAsync (который не поддерживает аргумент типа). Приведенный выше код определенно работает, или в моем приложении происходит какая-то странная магия ;-)

Neil W 25.04.2021 13:04

@Ted Хороший улов в проблеме с обработкой нулей, вы спасли мой день!

MissRaphie 18.11.2021 17:33

Я только что узнал, что .NET 5.0 действительно добавил свойство StatusCode к классу HttpRequestException!

https://github.com/dotnet/runtime/pull/32455

https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httprequestexception.statuscode?view=net-5.0

Другие вопросы по теме