桌面程序把本地文件推到服务器,最常见是 HTTP POST。早期 .NET 用 HttpWebRequest,现在更推荐 HttpClient。下面先指出旧代码的典型坑,再给可维护写法。

旧思路里最危险的一处

很多人会先:

  1. 打开文件
  2. 分配 byte[] postData
  3. 还没 Read 就先对 postData 算 MD5
  4. 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 一致
大小限制 客户端提前拦,避免无意义上传

安全注意

  1. HTTPS:上传凭证与文件内容必须加密传输。
  2. 路径:只上传用户选择的文件,禁止把任意服务器路径当上传源。
  3. 哈希算法:MD5 仅可作「防误传」校验,不可作安全签名。
  4. 超时与重试:大文件要调 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 示例可以理解协议,但不建议新项目继续堆。