为何HttpClient调用SendAsync时,端点返回错误却得到空内容?
服务端在出现错误时会返回自定义的Error Details JSON对象,异常中间件实现如下;客户端使用HttpClient调用该接口,但获取错误响应时ReadAsStringAsync()返回空字符串——Swagger调用能正常显示错误JSON,Nginx返回的503错误HTML也能正常读取。使用环境为.NET Core 6和ASP.NET Core。
服务端代码
private static Task HandleExceptionAsync(HttpContext context, Exception exception) { var errorDetail = new ErrorDetail() { Title = "Some Title", Detail = exception.Message // 其他字段 }; context.Response.ContentType = "application/json"; context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; var json = JsonSerializer.Serialize(errorDetail); return context.Response.WriteAsync(json); }
客户端代码
protected async Task<string> PostAsync(string path, string json) { var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, new Uri(path, UriKind.Relative)); var postContent = new StringContent(json, Encoding.UTF8, "application/json"); httpRequestMessage.Content = postContent; var httpResponseMessage = await _httpClient.SendAsync(httpRequestMessage); // 预期获取错误详情JSON,但返回空字符串 var stringContent = await httpResponseMessage.Content.ReadAsStringAsync(); return stringContent; }
以下是可能的原因及对应解决方法:
1. ErrorDetail类序列化配置问题
System.Text.Json(.NET Core 6默认序列化器)仅会序列化带有公共getter的属性。如果ErrorDetail的属性只有setter、访问权限非公共,或未适配System.Text.Json的序列化规则,会导致序列化后的JSON为空或缺失字段。
解决:
确保ErrorDetail类的属性为公共且包含getter:
public class ErrorDetail { public string Title { get; set; } public string Detail { get; set; } // 其他属性需同步添加公共getter }
若需序列化私有成员,可配置JsonSerializerOptions:
var options = new JsonSerializerOptions { IncludeFields = true, // 序列化字段 PropertyNameCaseInsensitive = true }; var json = JsonSerializer.Serialize(errorDetail, options);
2. 异常中间件未确保响应完整发送
在.NET Core中,仅调用WriteAsync可能无法保证响应被完整提交,后续中间件可能修改或清空响应内容。需显式调用CompleteAsync终止响应流。
解决:
修改异常中间件代码,在写入后完成响应:
private static async Task HandleExceptionAsync(HttpContext context, Exception exception) { var errorDetail = new ErrorDetail() { Title = "Some Title", Detail = exception.Message // 其他字段 }; // 指定UTF-8编码,避免客户端读取编码不匹配 context.Response.ContentType = "application/json; charset=utf-8"; context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; var json = JsonSerializer.Serialize(errorDetail); await context.Response.WriteAsync(json); // 确保响应流完成,阻止后续中间件干扰 await context.Response.CompleteAsync(); }
3. HttpClient读取配置问题
若SendAsync指定了HttpCompletionOption.ResponseHeadersRead,会在仅读取响应头后就返回,此时响应内容可能尚未完全写入,导致ReadAsStringAsync读取为空。
解决:
显式指定等待完整响应内容(默认即为该值,可明确写出以避免歧义):
var httpResponseMessage = await _httpClient.SendAsync( httpRequestMessage, HttpCompletionOption.ResponseContentRead );
同时注意不要多次调用ReadAsStringAsync——第一次调用会耗尽响应流,后续调用将返回空字符串。
4. 响应编码未明确指定
服务端未在ContentType中指定UTF-8编码时,HttpClient可能使用默认的ISO-8859-1读取,若响应内容包含特殊字符可能引发读取异常,极端情况下可能返回空。
解决:
在服务端设置ContentType时明确指定编码:
context.Response.ContentType = "application/json; charset=utf-8";
内容的提问来源于stack exchange,提问作者handles

