最稳方式是 HttpClient + MultipartFormDataContent,需用 StreamContent 包装 FileStream 并显式设置 ContentDisposition,禁用 File.ReadAllBytes,注意 boundary、超时、取消令牌及字段名一致性。

用 HttpClient 发送二进制文件最稳
直接用 HttpClient + MultipartFormDataContent 是当前 C# 最可靠的方式,WebClient 已过时且不支持异步流式上传,HttpWebRequest 写法冗长还容易漏关流。
关键点在于:必须把文件流包装进 StreamContent,再塞进 MultipartFormDataContent,不能直接传 byte[] —— 否则服务端可能收不到文件名或 MIME 类型。
-
FileStream要用FileMode.Open和FileAccess.Read,别用File.ReadAllBytes()加载大文件,会爆内存 - 每个表单项要用
Add()显式命名,比如content.Add(fileContent, "file", "report.pdf"),第三个参数是服务端识别的原始文件名 - 记得设
client.Timeout,上传大文件时默认 100 秒超时经常不够用
遇到 400 Bad Request 或空文件,先查 Content-Type 和边界符
常见现象是服务端返回 400 却没具体错误,或者后端收到空文件、无文件名。大概率是 multipart 的边界(boundary)被破坏,或 Content-Type 没由 MultipartFormDataContent 自动设置好。
- 不要手动改
content.Headers.ContentType,MultipartFormDataContent构造时已生成合法 boundary 并设好 header - 如果服务端要求固定 boundary(极少见),得换用
StringContent手拼,但兼容性风险高,优先跟后端确认是否真需要 - 用 Fiddler 或 Wireshark 抓包,看请求体里是否有完整的
--{boundary}分隔块,以及最后一行是不是--{boundary}--
StreamContent 的 Headers.ContentDisposition 必须显式设置
很多示例代码漏了这步,导致 .NET 默认只写 form-data,不带 name 和 filename,后端框架(如 ASP.NET Core 的 IFormFile)就解析不出文件字段。
- 正确写法:
var fileContent = new StreamContent(fileStream); fileContent.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("form-data") {Name = "file", FileName = "data.zip"}; -
Name要和服务端接收字段名一致(比如后端用[FromForm] IFormFile file,这里就得填"file") -
FileName不能为空字符串,也不能含路径(如"./upload/data.zip"),否则某些服务端会拒绝
大文件上传要开流式读取 + 取消令牌
上传几百 MB 文件时,卡死、超时、OOM 都是因为没控制读取节奏和生命周期。
- 用
FileStream的leaveOpen: false参数,别让StreamContent关闭底层流——你得自己确保流在发送完才释放 - 给
PostAsync()传cancellationToken,UI 线程可随时取消,否则HttpClient会一直阻塞到底层 TCP 超时 - 别用
await client.PostAsync(……).ConfigureAwait(false)在 WinForms/WPF 里,容易丢上下文;直接用await让 UI 线程等结果更安全
实际上传逻辑里最易忽略的是:服务端对 multipart/form-data 的字段顺序、大小写、换行符(CRLF 还是 LF)有隐式要求,本地测试通不代表线上通。上线前务必用真实服务端响应做回归,别只信 Mock。