我们将使用 ASP.NET Web API 作为后端服务器,因为它非常适合处理这类 RESTful 请求,客户端使用 HttpClient 发送文件流。

目录
- 服务器端实现: 创建一个 Web API 项目来接收文件。
- 客户端实现: 创建一个 WinForms 项目来选择文件并发送到服务器。
- 高级功能:
- 显示上传进度
- 添加错误处理
- 添加文件校验(如文件类型、大小)
- 总结与最佳实践
第1步:服务器端实现 (ASP.NET Web API)
我们需要一个服务器来接收文件,我们将创建一个简单的 ASP.NET Web API 项目。
1. 创建 Web API 项目
在 Visual Studio 中,创建一个新项目,选择 "ASP.NET Web 应用程序",在模板中,选择 "Web API"。
2. 创建上传控制器
在 Controllers 文件夹上右键 -> 添加 -> 控制器,选择 "Web API 2 - 空控制器",命名为 UploadController.cs。
3. 编写上传 Action
打开 UploadController.cs,添加以下代码,这段代码定义了一个 POST 方法,用于处理文件上传。

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web;
using System.Web.Http;
namespace ServerSide.Controllers
{
public class UploadController : ApiController
{
// 定义允许上传的文件类型和最大大小 ( 10MB)
private static readonly string[] AllowedExtensions = { ".jpg", ".jpeg", ".png", ".gif", ".pdf", ".doc", ".docx", ".txt" };
private const long MaxFileSize = 10 * 1024 * 1024; // 10MB
[HttpPost]
[Route("api/upload")] // 定义路由
[RequestSizeLimit(long.MaxValue)] // 允许大文件上传 (注意: 在生产环境中需要谨慎使用)
public async Task<IHttpActionResult> UploadFile()
{
try
{
// 检查是否是 multipart/form-data 请求
if (!Request.Content.IsMimeMultipartContent())
{
return BadRequest("请求内容不是 multipart/form-data 格式。");
}
// 创建一个提供程序来解析 multipart 请求
var provider = new MultipartMemoryStreamProvider();
// 异步读取请求内容到 provider
await Request.Content.ReadAsMultipartAsync(provider);
// 确保服务器上有上传目录
string uploadFolderPath = HttpContext.Current.Server.MapPath("~/Uploads");
if (!Directory.Exists(uploadFolderPath))
{
Directory.CreateDirectory(uploadFolderPath);
}
foreach (var file in provider.Contents)
{
// 检查是否有文件名
if (file.Headers.ContentDisposition.FileName == null || file.Headers.ContentDisposition.FileName.Trim() == "\"\"")
{
continue; // 跳过没有文件名的部分
}
// 获取文件名
string fileName = file.Headers.ContentDisposition.FileName.Trim('"');
// 获取文件扩展名
string fileExtension = Path.GetExtension(fileName).ToLowerInvariant();
// 1. 校验文件扩展名
if (!AllowedExtensions.Contains(fileExtension))
{
return BadRequest($"不支持的文件类型: {fileExtension},允许的类型: {string.Join(", ", AllowedExtensions)}");
}
// 2. 校验文件大小
var buffer = await file.ReadAsByteArrayAsync();
if (buffer.Length > MaxFileSize)
{
return BadRequest($"文件大小超过限制,最大允许: {MaxFileSize / (1024 * 1024)}MB。");
}
// 生成唯一文件名,防止覆盖
string uniqueFileName = Guid.NewGuid().ToString() + fileExtension;
string filePath = Path.Combine(uploadFolderPath, uniqueFileName);
// 将文件保存到服务器
File.WriteAllBytes(filePath, buffer);
}
return Ok(new { message = "文件上传成功!" });
}
catch (Exception ex)
{
// 记录错误日志 (这里简单打印到控制台)
Console.WriteLine($"上传文件时发生错误: {ex.Message}");
return InternalServerError(ex);
}
}
}
}
代码解释:
[HttpPost]和[Route("api/upload")]: 定义这是一个 HTTP POST 请求,访问路径为/api/upload。MultipartMemoryStreamProvider: 用于处理包含多个部分的表单数据,特别是文件上传。HttpContext.Current.Server.MapPath("~/Uploads"): 获取网站根目录下Uploads文件夹的物理路径,如果文件夹不存在,我们会创建它。File.WriteAllBytes: 将字节数组写入文件系统。- 校验: 我们添加了文件类型和文件大小的校验,这是非常重要的安全措施。
4. 配置 Web.config
为了支持大文件上传,你需要修改 Web.config 文件。
<system.web>
<httpRuntime targetFramework="4.8" executionTimeout="3600" maxRequestLength="10485760" />
</system.web>
<!-- executionTimeout: 脚本运行超时时间(秒) -->
<!-- maxRequestLength: 最大请求大小(KB),这里是 10MB -->
服务器端已经准备好了,你可以运行这个 Web API 项目。
第2步:客户端实现 (WinForms)
现在我们来创建 WinForms 客户端,它可以选择文件并发送到我们刚刚创建的 API。

1. 创建 WinForms 项目
创建一个新的 "Windows 窗体应用 (.NET Framework)" 项目。
2. 设计窗体
打开 Form1.cs [Design],从工具箱中拖拽以下控件到窗体上:
Button(命名为btnSelectFile,Text="选择文件")Button(命名为btnUpload,Text="上传")TextBox(命名为txtFilePath)ProgressBar(命名为progressBarUpload)Label(命名为lblStatus)
3. 编写上传代码
双击 btnSelectFile 和 btnUpload,为它们添加事件处理程序。
Form1.cs 的完整代码:
using System;
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WinFormsUploader
{
public partial class Form1 : Form
{
// 使用 HttpClient 的最佳实践是创建一次实例并重复使用
private static readonly HttpClient client = new HttpClient();
public Form1()
{
InitializeComponent();
// 设置服务器地址 (请替换为你的实际服务器地址)
client.BaseAddress = new Uri("http://localhost:XXXX/"); // XXXX 是你的端口号
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
private void btnSelectFile_Click(object sender, EventArgs e)
{
using (OpenFileDialog openFileDialog = new OpenFileDialog())
{
openFileDialog.InitialDirectory = "c:\\";
openFileDialog.Filter = "所有文件 (*.*)|*.*";
openFileDialog.FilterIndex = 1;
openFileDialog.RestoreDirectory = true;
if (openFileDialog.ShowDialog() == DialogResult.OK)
{
// 获取选中的文件路径
txtFilePath.Text = openFileDialog.FileName;
}
}
}
private async void btnUpload_Click(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(txtFilePath.Text))
{
MessageBox.Show("请先选择一个文件。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
string filePath = txtFilePath.Text;
if (!File.Exists(filePath))
{
MessageBox.Show("文件不存在。", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
// 禁用上传按钮,防止重复点击
btnUpload.Enabled = false;
lblStatus.Text = "准备上传...";
progressBarUpload.Value = 0;
try
{
var fileInfo = new FileInfo(filePath);
long totalBytes = fileInfo.Length;
// 使用 HttpClient 发送 multipart/form-data 请求
using (var multipartFormDataContent = new MultipartFormDataContent())
{
// 创建流内容,并设置进度报告
var fileContent = new StreamContent(new FileStream(filePath, FileMode.Open, FileAccess.Read))
{
Headers =
{
ContentLength = totalBytes,
ContentType = new MediaTypeHeaderValue("application/octet-stream")
}
};
// 添加文件到 multipart 内容中
// "file" 是服务器端读取的键名,与 Request.Files["file"] 对应
multipartFormDataContent.Add(fileContent, "file", Path.GetFileName(filePath));
// 设置 HttpClient 超时时间
client.Timeout = TimeSpan.FromMinutes(10);
// 发送 POST 请求
HttpResponseMessage response = await client 