我们将使用 HttpClient 来处理文件上传,这是目前最现代和推荐的方式,服务器端我们将创建一个简单的 ASP.NET Web API 来接收文件。

整体流程
-
客户端 (WinForms):
- 用户点击按钮,打开一个文件选择对话框 (
OpenFileDialog)。 - 用户选择一张图片。
- 客户端使用
HttpClient将图片文件以multipart/form-data格式发送到预先定义好的服务器 API 端点。
- 用户点击按钮,打开一个文件选择对话框 (
-
服务器端 (ASP.NET Web API):
- 创建一个 Web API 控制器,并定义一个
HttpPost方法。 - 该方法接收
HttpRequestMessage或IFormFile对象。 - 服务器接收文件流,并将其保存到服务器的指定文件夹中。
- 返回一个响应,告知客户端上传成功或失败。
- 创建一个 Web API 控制器,并定义一个
第一步:创建服务器端 (ASP.NET Web API)
你需要一个接收文件的服务器,这里我们用 Visual Studio 创建一个简单的 ASP.NET Web API 项目。
创建项目
- 打开 Visual Studio。
- 选择 "创建新项目"。
- 搜索并选择 "ASP.NET Core Web API",然后点击 "下一步"。
- 给项目命名,
ImageUploadServer。 - 在 "其他信息" 页面,确保框架选择的是你需要的版本(如 .NET 8.0 或 .NET 6.0),并取消勾选 "使用控制器"(因为我们稍后会手动添加一个更简单的控制器)。
- 点击 "创建"。
创建上传文件夹
在项目根目录下,创建一个名为 Uploads 的文件夹,这个文件夹将用来存储上传的图片,为了确保 Web 应用有权限写入此文件夹,右键点击 Uploads 文件夹 -> 属性 -> 安全 -> 编辑 -> 添加,输入 IIS_IUSRS 或 NETWORK SERVICE 并赋予其 "修改" 权限。

创建控制器
在 Controllers 文件夹中,添加一个新的控制器类,命名为 ImagesController.cs。
// ImagesController.cs
using Microsoft.AspNetCore.Mvc;
using System.IO;
using Microsoft.AspNetCore.Hosting; // 需要注入 IWebHostEnvironment
namespace ImageUploadServer.Controllers
{
[ApiController]
[Route("api/[controller]")] // 路由将是 /api/images
public class ImagesController : ControllerBase
{
private readonly IWebHostEnvironment _hostingEnvironment;
// 通过构造函数注入 IWebHostEnvironment 来获取服务器路径
public ImagesController(IWebHostEnvironment hostingEnvironment)
{
_hostingEnvironment = hostingEnvironment;
}
[HttpPost("upload")]
public async Task<IActionResult> UploadImage(IFormFile file)
{
// 1. 检查是否有文件上传
if (file == null || file.Length == 0)
{
return BadRequest(new { message = "没有选择文件或文件为空。" });
}
// 2. 检查文件类型(可选,但推荐)
var allowedExtensions = new[] { ".jpg", ".jpeg", ".png", ".gif" };
var fileExtension = Path.GetExtension(file.FileName).ToLowerInvariant();
if (!allowedExtensions.Contains(fileExtension))
{
return BadRequest(new { message = "只允许上传 JPG, JPEG, PNG, GIF 格式的图片。" });
}
// 3. 构建文件保存路径
// _hostingEnvironment.WebRootPath 指向 wwwroot 文件夹
// 我们想保存在 wwwroot/uploads 下
var uploadsFolderPath = Path.Combine(_hostingEnvironment.WebRootPath, "uploads");
// 如果文件夹不存在,则创建
if (!Directory.Exists(uploadsFolderPath))
{
Directory.CreateDirectory(uploadsFolderPath);
}
// 4. 生成唯一的文件名,防止文件名冲突
var uniqueFileName = Guid.NewGuid().ToString() + "_" + file.FileName;
var filePath = Path.Combine(uploadsFolderPath, uniqueFileName);
// 5. 将文件保存到服务器
using (var stream = new FileStream(filePath, FileMode.Create))
{
await file.CopyToAsync(stream);
}
// 6. 返回成功响应,可以包含文件的访问路径
var fileUrl = $"/uploads/{uniqueFileName}";
return Ok(new {
message = "文件上传成功!",
url = fileUrl,
fileName = uniqueFileName
});
}
}
}
配置和运行
-
确保
Program.cs中已经注册了 controllers,如果你之前取消了 "使用控制器",需要手动添加:// Program.cs var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddControllers(); var app = builder.Build(); // Configure the HTTP request pipeline. app.UseHttpsRedirection(); app.UseAuthorization(); app.MapControllers(); // 这行是关键 app.Run();
-
按
F5运行服务器,你的 API 现在正在监听https://localhost:xxxx/api/images/upload。
第二步:创建客户端 (WinForms)
我们来创建一个可以上传图片的 WinForms 应用程序。

创建项目
- 在另一个 Visual Studio 实例中,创建一个新项目。
- 选择 "Windows Forms App (.NET Framework)" 或 "Windows Forms App" (.NET 6/8/9)。
- 给项目命名,
ImageUploadClient。
设计窗体
打开 Form1.cs [Design],从工具箱中拖拽以下控件到窗体上:
- 一个
Button,将其Name属性设为btnSelectImage,Text属性设为 "选择图片"。 - 一个
PictureBox,将其Name属性设为pictureBox1。 - 一个
Button,将其Name属性设为btnUpload,Text属性设为 "上传图片"。 - 一个
Label,将其Name属性设为lblStatus,Text属性留空。
编写代码
双击 "选择图片" 按钮,为其 Click 事件生成代码,然后切换到代码视图,编写完整的上传逻辑。
// Form1.cs
using System;
using System.Drawing;
using System.IO;
using System.Windows.Forms;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
namespace ImageUploadClient
{
public partial class Form1 : Form
{
// 创建一个静态的 HttpClient 实例,避免重复创建,提高性能
private static readonly HttpClient client = new HttpClient();
private string selectedImagePath = null;
public Form1()
{
InitializeComponent();
}
private void btnSelectImage_Click(object sender, EventArgs e)
{
using (OpenFileDialog openFileDialog = new OpenFileDialog())
{
openFileDialog.InitialDirectory = "c:\\";
openFileDialog.Filter = "图片文件|*.jpg;*.jpeg;*.png;*.gif|所有文件 (*.*)|*.*";
openFileDialog.FilterIndex = 1;
openFileDialog.RestoreDirectory = true;
if (openFileDialog.ShowDialog() == DialogResult.OK)
{
// 获取选中的文件路径
selectedImagePath = openFileDialog.FileName;
// 在 PictureBox 中显示图片
try
{
pictureBox1.ImageLocation = selectedImagePath;
lblStatus.Text = "已选择图片: " + Path.GetFileName(selectedImagePath);
}
catch (Exception ex)
{
MessageBox.Show("无法加载图片: " + ex.Message);
}
}
}
}
private async void btnUpload_Click(object sender, EventArgs e)
{
// 检查是否已选择图片
if (string.IsNullOrEmpty(selectedImagePath))
{
MessageBox.Show("请先选择一张图片!");
return;
}
// 禁用上传按钮,防止重复点击
btnUpload.Enabled = false;
lblStatus.Text = "正在上传...";
try
{
// 创建一个 MultipartFormDataContent 对象
using (var multipartFormContent = new MultipartFormDataContent())
{
// 1. 将图片文件添加到内容中
// 使用 FileStream 读取文件
var fileStream = File.OpenRead(selectedImagePath);
var fileName = Path.GetFileName(selectedImagePath);
// Add the file content
// "file" 必须与服务器端 IFormFile file 参数名匹配
multipartFormContent.Add(new StreamContent(fileStream), "file", fileName);
// 2. 设置服务器 API 的 URL
// !!! 请将 YOUR_SERVER_IP 替换为你的服务器实际IP地址或域名
string apiUrl = "http://YOUR_SERVER_IP:5000/api/images/upload";
// 如果你的服务器是 https,并且有证书,则使用 https
// string apiUrl = "https://YOUR_SERVER_DOMAIN/api/images/upload";
// 3. 发送 POST 请求
var response = await client.PostAsync(apiUrl, multipartFormContent);
// 4. 检查响应是否成功
if (response.IsSuccessStatusCode)
{
// 读取并显示服务器返回的 JSON 响应
string responseBody = await response.Content.ReadAsStringAsync();
MessageBox.Show("上传成功!\n服务器响应: " + responseBody);
lblStatus.Text = "上传成功!";
}
else
{
// 如果请求失败,显示错误信息
string errorResponse = await response.Content.ReadAsStringAsync();
MessageBox.Show($"上传失败: {response.StatusCode}\n详情: {errorResponse}");
lblStatus.Text = "上传失败。";
}
}
}
catch (Exception ex)
{
MessageBox.Show("上传过程中发生错误: " + ex.Message);
lblStatus.Text = "上传出错。";
}
finally
{
// 重新启用上传按钮
btnUpload.Enabled = true;
}
}
}
}
运行和测试
- 启动服务器: 确保
ImageUploadServer项目正在运行。 - 启动客户端: 启动
ImageUploadClient项目。 - 选择图片: 在客户端窗体上点击 "选择图片",选择一张本地图片。
- 上传图片: 点击 "上传图片" 按钮。
- 检查结果:
- 客户端应该会弹出 "上传成功" 的消息框。
- 服务器端的
Uploads文件夹中会出现你上传的图片。 - 客户端的
lblStatus标签会更新状态。
重要注意事项和最佳实践
-
服务器地址: 客户端代码中的
apiUrl必须替换为你服务器的真实 IP 地址或域名,如果你在本地调试,服务器和客户端都在同一台电脑上,可以使用http://localhost:5000或http://127.0.0.1:5000。 -
跨域问题: 如果你的客户端和服务器不在同一个域下(客户端是
http://client.com,服务器是http://server.com),浏览器会因为同源策略而阻止请求,在 ASP.NET Core 中,你可以在Program.cs中添加 CORS 策略来解决:// Program.cs builder.Services.AddCors(options => { options.AddPolicy("AllowMyClient", builder => { // 允许的客户端源 builder.WithOrigins("http://localhost:12345") // 替换为你的客户端地址 .AllowAnyHeader() .AllowAnyMethod(); }); }); // ... app.UseCors("AllowMyClient"); -
异常处理: 服务器端和客户端的代码都包含了基本的异常处理,这对于生产环境至关重要。
-
HttpClient的生命周期: 在上面的示例中,我们使用了static readonly HttpClient,这是一个好习惯,因为它可以重用 TCP 连接,提高性能,对于简单的 WinForms 这是足够的,对于更复杂的应用,可以考虑使用IHttpClientFactory。 -
安全性:
- 文件名: 服务器端我们使用
Guid生成唯一文件名,可以防止恶意用户通过文件名(如../../../malicious.txt)进行路径遍历攻击。 - 文件类型: 服务器端对文件扩展名进行了校验,这是一个很好的第一步,但不是万无一失的,更安全的做法是检查文件的真实内容(MIME type 或文件头),而不是仅仅依赖扩展名。
- 文件大小: 你可以在服务器端添加对文件大小的限制,防止用户上传过大的文件导致服务器磁盘空间被占满,可以在
Startup.cs(ASP.NET Core) 中配置:builder.Services.Configure<FormOptions>(options => { options.MultipartBodyLengthLimit = 104857600; // 100MB });
- 文件名: 服务器端我们使用
这个完整的示例涵盖了从服务器搭建到客户端实现的全过程,并考虑了实际开发中的一些常见问题和最佳实践,希望对你有帮助!
