桌面程序把本地文件推到服务器,最常见是 HTTP POST。早期 .NET 用 HttpWebRequest,现在更推荐 HttpClient。下面先指出旧代码的典型坑,再给可维护写法。
旧思路里最危险的一处
很多人会先:
- 打开文件
- 分配
byte[] postData - 还没 Read 就先对 postData 算 MD5
- 再
Read
那校验的是 全零缓冲,不是文件内容。正确顺序永远是:
读完字节 → 再哈希 → 再写入请求体(或边读边写)。
较清晰的 HttpWebRequest 示例
private async Task<string> UploadFileAsync(string url, string filePath)
{
byte[] data = await File.ReadAllBytesAsync(filePath);
string md5 = Convert.ToHexString(
System.Security.Cryptography.MD5.HashData(data)); // 示例;生产可用 SHA-256
var request = (HttpWebRequest)WebRequest.Create(url);
request.Method = "POST";
request.ContentType = "application/octet-stream";
request.Headers["X-File-MD5"] = md5;
request.ContentLength = data.Length;
using (var reqStream = await request.GetRequestStreamAsync())
{
await reqStream.WriteAsync(data, 0, data.Length);
}
using var response = (HttpWebResponse)await request.GetResponseAsync();
using var rs = response.GetResponseStream();
using var reader = new StreamReader(rs);
return await reader.ReadToEndAsync();
}
若服务端要 multipart/form-data(字段 + 文件),不要手写边界除非必要,优先用更高层 API。
推荐:HttpClient + Multipart
using var client = new HttpClient();
using var form = new MultipartFormDataContent();
using var fs = File.OpenRead(filePath);
var streamContent = new StreamContent(fs);
streamContent.Headers.ContentType =
new System.Net.Http.Headers.MediaTypeHeaderValue("application/octet-stream");
form.Add(streamContent, "file", Path.GetFileName(filePath));
form.Add(new StringContent("meta"), "description");
using var resp = await client.PostAsync(url, form);
resp.EnsureSuccessStatusCode();
return await resp.Content.ReadAsStringAsync();
优点:流式发送大文件、自动处理 boundary、易测。
服务端需要什么(客户端要对齐)
| 项 | 说明 |
|---|---|
| 方法/路径 | POST /api/upload 等 |
| Content-Type | raw 流 vs multipart |
| 鉴权 | Bearer / Cookie / 签名头 |
| 完整性 | MD5/SHA 头与 body 一致 |
| 大小限制 | 客户端提前拦,避免无意义上传 |
安全注意
- HTTPS:上传凭证与文件内容必须加密传输。
- 路径:只上传用户选择的文件,禁止把任意服务器路径当上传源。
- 哈希算法:MD5 仅可作「防误传」校验,不可作安全签名。
- 超时与重试:大文件要调
Timeout,失败续传需服务端支持。
UI 线程
WinForms 里不要在按钮事件直接 ReadAllBytes 大文件而不异步:
private async void btnUpload_Click(object sender, EventArgs e)
{
btnUpload.Enabled = false;
try {
var result = await UploadFileAsync(url, path);
MessageBox.Show(result);
} finally {
btnUpload.Enabled = true;
}
}
小结
上传文件的本质是 把字节可靠放到 HTTP 请求体,并带上服务端约定的元数据。先保证读文件与哈希顺序正确,再考虑 multipart 与 HttpClient;旧的 HttpWebRequest 示例可以理解协议,但不建议新项目继续堆。