HttpRequestMessage仅是请求容器,不发送请求;必须由HttpClient.SendAsync执行,否则无响应且可能抛ObjectDisposedException。

HttpRequestMessage 不是“发请求的工具”,它只是个容器;真正干活的是 HttpClient.SendAsync。没调用它,就等于写了封信但没寄出去——程序不会报错,但也不会有响应,甚至可能后续抛 ObjectDisposedException。
HttpRequestMessage 必须配 HttpClient.SendAsync 才生效
很多人写完 var req = new HttpRequestMessage(HttpMethod.Get, "https://api.example.com") 就停了,以为设置好 RequestUri 和 Headers 就能发请求。其实这只是在组装信封,没邮局(HttpClient)和寄送动作(SendAsync),它永远到不了服务器。
-
HttpClient实例应复用,别每次 new —— 它是线程安全且带连接池的,new 太多会耗尽 socket - 必须显式调用
await client.SendAsync(req),否则req一直闲置,后续若又用了已释放的client,就会触发ObjectDisposedException - 记得加
response.EnsureSuccessStatusCode():默认情况下HttpClient对 4xx/5xx 不抛异常,容易漏掉服务端错误
POST JSON 时 Content-Type 容易设失效
用 StringContent 构造 body 时传了 "application/json",不代表请求头真生效了。某些 .NET Framework 版本或中间层会忽略构造参数,导致服务器返回 415 Unsupported Media Type。
- 安全写法是两步走:
var content = new StringContent(json, Encoding.UTF8, "application/json"),再补一句content.Headers.ContentType = new MediaTypeHeaderValue("application/json") - 别依赖
StringContent构造函数自动同步 header;显式赋值才能确保Content-Type出现在最终请求中 - 如果用
JsonContent(.NET 5+),它内部已处理好,可省略手动设 ContentType
Authorization 头该加在哪?DefaultRequestHeaders 还是 RequestMessage.Headers?
答案取决于 token 是否随请求变化。如果每个请求用不同 token(比如用户级 bearer token),必须加在 req.Headers 上;加在 client.DefaultRequestHeaders 会导致所有后续请求都带上同一个 token,高并发下极易错乱。
-
client.DefaultRequestHeaders.Authorization只适合全局固定 token(如服务间通信的 static API key) - 动态 token 场景下,用
req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token) - 避免用
Add("Authorization", "Bearer xxx")——AuthenticationHeaderValue会自动处理空格、引号等格式细节,手拼容易出401 Unauthorized
想拦截/改写请求?别碰 HttpClientHandler,用 DelegatingHandler
试图重写 HttpClientHandler.SendAsync 是徒劳的——它不是虚方法,编译都不过。真正可插手请求/响应链路的,只有 DelegatingHandler。
- 继承
DelegatingHandler,构造时传入new HttpClientHandler()作为 inner handler - 在
SendAsync中修改request.RequestUri或request.Headers是安全的,但 Host 等只读头得用TryAddWithoutValidation - 改 body 要特别小心:
request.Content.ReadAsStreamAsync()后 stream 位置已移到末尾,下游读不到内容;需用ReadAsByteArrayAsync()+new ByteArrayContent(...)重建
最常被忽略的一点:HttpRequestMessage 的生命周期完全绑定于 SendAsync 调用。一旦响应返回,它的 Content Stream 就不可再读;任何试图二次消费 body 的逻辑,都会静默失败或抛异常。


















