使用HttpClient SendAsync调用API触发两次的问题及解决咨询
我编写了使用HttpClient的SendAsync方法的代码,测试时发现API接口被触发了两次。相关代码如下:
控制器代码
[HttpGet("GetAllStores")] public async Task<bookstores> GetAllStores() { Console.WriteLine("Trigger Stores"); return await dbContext.Set<bookstores>().ToListAsync(); }
HttpClient代码
public async Task<IEnumerable<bookstores>> FetchAllStores() { var requestContent = new HttpRequestMessage { Method = HttpMethod.Get }; var response = await httpClient.SendAsync("http://127.0.0.1:5000/GetAllStores", HttpCompletionOption.ResponseHeadersRead); response.EnsureSuccessStatusCode(); return JsonSerializer.Deserialize<bookstores>(await response.Content.ReadAsStringAsync(), defaultJsonSerializerOptions); }
测试代码
public async Task GetSettingProfile_AsExpected() { var sut = new ApiClient( new HttpClient() { BaseAddress=new Uri("http://localhost:40331") }); await sut.FetchAllStores(); }
控制台输出显示“Trigger Stores”两次,请问如何让API仅被调用一次?
1. 修正BaseAddress与请求URL的冲突
你的HttpClient实例设置了BaseAddress = new Uri("http://localhost:40331"),但调用SendAsync时又传入了完整URL"http://127.0.0.1:5000/GetAllStores",这种混合使用的方式可能导致HttpClient内部URL解析异常,触发重复请求。
修复方案:
选择一种URL使用方式:
- 保留BaseAddress,只传入相对路径:
var response = await httpClient.SendAsync("GetAllStores", HttpCompletionOption.ResponseHeadersRead); - 移除BaseAddress,直接使用完整URL,避免路径拼接冲突。
2. 调整HttpCompletionOption参数
使用HttpCompletionOption.ResponseHeadersRead时,HttpClient会在读取响应头后就标记请求完成,但后续读取响应内容时如果出现异常,可能触发底层的重试逻辑。部分服务器或中间件也可能对这种模式的请求做出重复响应。
修复方案:
使用默认的HttpCompletionOption.ResponseContentRead(可省略该参数),确保请求在完整读取响应内容后才完成:
var response = await httpClient.SendAsync("http://127.0.0.1:5000/GetAllStores");
3. 修正序列化类型不匹配问题
你的方法返回值声明为IEnumerable<bookstores>,但序列化时使用了JsonSerializer.Deserialize<bookstores>(...),类型不匹配会导致序列化失败。如果HttpClient配置了自动重试策略,失败的请求会被重新发送。
修复方案:
将泛型类型改为与返回值一致的集合类型:
return JsonSerializer.Deserialize<IEnumerable<bookstores>>(await response.Content.ReadAsStringAsync(), defaultJsonSerializerOptions);
4. 排查测试环境的重复执行可能
部分测试框架(如xUnit)如果检测到异步测试存在异常,可能会自动重试测试方法;或者测试类中存在多个测试方法调用了同一个接口,导致重复触发。
排查方向:
- 确认测试类中只有
GetSettingProfile_AsExpected这一个测试方法调用FetchAllStores - 检查测试框架的配置,是否开启了测试重试机制
内容的提问来源于stack exchange,提问作者Carrie Kaski

