使用 HttpClient.GetFromJsonAsync(),如何处理基于 HttpStatusCode 的 HttpRequestException 而无需额外的 SendAsync 调用?

Posted

技术标签:

【中文标题】使用 HttpClient.GetFromJsonAsync(),如何处理基于 HttpStatusCode 的 HttpRequestException 而无需额外的 SendAsync 调用?【英文标题】:Using HttpClient.GetFromJsonAsync(), how to handle HttpRequestException based on HttpStatusCode without extra SendAsync calls? 【发布时间】:2020-12-20 18:13:06 【问题描述】:

System.Net.Http.JsonHttpClient 扩展方法如GetFromJsonAsync() 大大简化了从Web API 检索json 对象的例程代码。使用起来很愉快。

但由于它的设计方式(直接返回反序列化的对象),它不会产生任何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...
   
   //...

这感觉有点hacky。使用创建HttpRequestMessage 和调用SendAsync 的老派方式,我们自然有机会检查回复的HttpResponseMessage.StatusCode。添加其中一些代码会破坏使用System.Net.Http.Json 中的单行代码的便利目的。

如有任何建议,我们将不胜感激。

【问题讨论】:

我发现 .GetFromJsonAsync 根据 StatusCode 抛出异常:System.Net.Http.HttpRequestException Response status code does not indicate success: 400 (Bad Request). at System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode() 写了我之前的评论,找到了这种方法: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

更新:处理 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,所以我不知道。 GetAsync 在 .NET 5 Blazor 应用程序中不接受类型参数,因此建议的代码对我不起作用。 @Ted ... 请注意,从 MySpecificAPI(我使用类型)调用的 GetAsync 方法不是 HttpClient.GetAsync,它是我的基类 ClientAPI 中的 GetAsync 方法。反过来 ClientAPI 调用 HttpClient.GetAsync 方法(不支持类型参数)。上面的代码肯定有效,或者我的应用程序中有一些奇怪的魔法;-) @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

【讨论】:

以上是关于使用 HttpClient.GetFromJsonAsync(),如何处理基于 HttpStatusCode 的 HttpRequestException 而无需额外的 SendAsync 调用?的主要内容,如果未能解决你的问题,请参考以下文章

在使用加载数据流步骤的猪中,使用(使用 PigStorage)和不使用它有啥区别?

今目标使用教程 今目标任务使用篇

Qt静态编译时使用OpenSSL有三种方式(不使用,动态使用,静态使用,默认是动态使用)

MySQL db 在按日期排序时使用“使用位置;使用临时;使用文件排序”

使用“使用严格”作为“使用强”的备份

Kettle java脚本组件的使用说明(简单使用升级使用)