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

#c# #asp.net-core #asp.net-web-api #dotnet-httpclient

#c# #asp.net-core #asp.net-web-api #dotnet-httpclient

Вопрос:

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

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

Вместо этого коды состояния неуспешности приводят к a 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 .

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

Комментарии:

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

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

Ответ №1:

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

 // 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
}
 

ОБНОВЛЕНИЕ: ОБРАБОТКА НУЛЕВЫХ ВОЗВРАТОВ

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

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

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

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

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

Комментарии:

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

2. GetAsync не принимает аргумент типа в приложении .NET 5 Blazor, поэтому предлагаемый код для меня не работает.

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

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

Ответ №2:

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

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

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