应长期复用HttpClient实例(如static或IHttpClientFactory注入),避免每次请求new导致端口耗尽;POST JSON用StringContent,表单用FormUrlEncodedContent,传文件用MultipartFormDataContent,并严格匹配Content-Type。

用 HttpClient 发 POST 请求最稳妥
别碰 WebClient 或过时的 HttpWebRequest,它们要么已弃用,要么默认不支持异步、连接池弱。.NET Core 2.0+ 和 .NET 5+ 都该用 HttpClient,它复用 TCP 连接、线程安全、天然支持 async/await。
关键点:HttpClient 实例应长期复用(比如声明为 static 或注入 IHttpClientFactory),不能每次请求都 new —— 否则会快速耗尽端口,触发 SocketException: Address already in use。
常见错误:直接 new HttpClient() 在方法里,尤其在高并发循环中,几分钟就崩。
示例(JSON POST):
var client = new HttpClient(); // 实际应从 DI 获取或复用
var json = JsonSerializer.Serialize(new { username = "alice", password = "123" });
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await client.PostAsync("https://api.example.com/login", content);
var result = await response.Content.ReadAsStringAsync();
POST 表单数据要用 FormUrlEncodedContent
不是所有 API 都接受 JSON;很多老系统或 OAuth 登录接口只认 application/x-www-form-urlencoded。硬塞 JSON 字符串过去,服务器大概率返回 400 或 415,且响应体可能只写 “invalid request”,不告诉你哪错了。
正确做法是用 FormUrlEncodedContent 包装键值对,它会自动做 URL 编码和设置 Content-Type。
- 字段含空格、中文、特殊符号?
FormUrlEncodedContent自动处理,不用手写Uri.EscapeDataString - 传文件?不能用这个,得换
MultipartFormDataContent - Content-Type 不匹配?检查服务器文档,有些接口强制要求
text/plain或application/xml,那就得用StringContent并手动设类型
示例(表单登录):
var data = new Dictionary<string, string>
{
["grant_type"] = "password",
["username"] = "test",
["password"] = "p@ss"
};
var content = new FormUrlEncodedContent(data);
var response = await client.PostAsync("https://api.example.com/token", content);
遇到 401/403 就查 Authorization 头和 Token 有效期
POST 成功但返回 401(Unauthorized)或 403(Forbidden),90% 是认证头没加、加错位置,或 Token 已过期。别急着重发请求,先确认三件事:
- Token 是 Bearer 还是 Basic?
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token)是标准写法;Basic 则要 base64 编码username:password - Header 是加在
DefaultRequestHeaders上,还是每次PostAsync前临时加?后者更灵活,适合多租户场景 - Token 是不是刚获取就立刻用了?某些 OAuth 接口返回的
expires_in是秒级,本地时间不准或网络延迟会导致“拿到就失效”
调试建议:用 Fiddler 或 VS 的 “Network” 工具栏看发出的请求头,比盲猜快十倍。
超时、重试、异常捕获不能靠 try-catch 兜底
HttpRequestException 是通用异常,但具体原因千差万别:DNS 失败、连接拒绝、SSL 握手失败、响应超时……全塞一个 catch 块里只会掩盖问题。
必须区分处理:
- 连接级失败(如
SocketException、HttpRequestException.InnerException is WebException)—— 可考虑重试 - HTTP 状态码错误(如 400、422)—— 属于业务逻辑问题,该解析响应体看具体错误字段
-
TaskCanceledException且Message含 “timeout” —— 调大HttpClient.Timeout,注意它是整个请求周期(DNS + 连接 + 发送 + 接收),不是仅等待响应
别把 Timeout 设成 TimeSpan.MaxValue,那等于放弃控制;也别设太短(如 100ms),公网 API 往往首字节响应就要 200–500ms。
复杂点在于:真正的生产调用必须组合 Polly 做熔断和指数退避,但那是另一层事了——至少先让单次请求不静默失败。


















