凌峰创科服务平台

WinForm如何实现图片上传到服务器?

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

WinForm如何实现图片上传到服务器?-图1
(图片来源网络,侵删)

整体流程

  1. 客户端 (WinForms):

    • 用户点击按钮,打开一个文件选择对话框 (OpenFileDialog)。
    • 用户选择一张图片。
    • 客户端使用 HttpClient 将图片文件以 multipart/form-data 格式发送到预先定义好的服务器 API 端点。
  2. 服务器端 (ASP.NET Web API):

    • 创建一个 Web API 控制器,并定义一个 HttpPost 方法。
    • 该方法接收 HttpRequestMessageIFormFile 对象。
    • 服务器接收文件流,并将其保存到服务器的指定文件夹中。
    • 返回一个响应,告知客户端上传成功或失败。

第一步:创建服务器端 (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_IUSRSNETWORK SERVICE 并赋予其 "修改" 权限。

WinForm如何实现图片上传到服务器?-图2
(图片来源网络,侵删)

创建控制器

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 应用程序。

WinForm如何实现图片上传到服务器?-图3
(图片来源网络,侵删)

创建项目

  • 在另一个 Visual Studio 实例中,创建一个新项目。
  • 选择 "Windows Forms App (.NET Framework)""Windows Forms App" (.NET 6/8/9)。
  • 给项目命名,ImageUploadClient

设计窗体

打开 Form1.cs [Design],从工具箱中拖拽以下控件到窗体上:

  • 一个 Button,将其 Name 属性设为 btnSelectImageText 属性设为 "选择图片"。
  • 一个 PictureBox,将其 Name 属性设为 pictureBox1
  • 一个 Button,将其 Name 属性设为 btnUploadText 属性设为 "上传图片"。
  • 一个 Label,将其 Name 属性设为 lblStatusText 属性留空。

编写代码

双击 "选择图片" 按钮,为其 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;
            }
        }
    }
}

运行和测试

  1. 启动服务器: 确保 ImageUploadServer 项目正在运行。
  2. 启动客户端: 启动 ImageUploadClient 项目。
  3. 选择图片: 在客户端窗体上点击 "选择图片",选择一张本地图片。
  4. 上传图片: 点击 "上传图片" 按钮。
  5. 检查结果:
    • 客户端应该会弹出 "上传成功" 的消息框。
    • 服务器端的 Uploads 文件夹中会出现你上传的图片。
    • 客户端的 lblStatus 标签会更新状态。

重要注意事项和最佳实践

  1. 服务器地址: 客户端代码中的 apiUrl 必须替换为你服务器的真实 IP 地址或域名,如果你在本地调试,服务器和客户端都在同一台电脑上,可以使用 http://localhost:5000http://127.0.0.1:5000

  2. 跨域问题: 如果你的客户端和服务器不在同一个域下(客户端是 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");
  3. 异常处理: 服务器端和客户端的代码都包含了基本的异常处理,这对于生产环境至关重要。

  4. HttpClient 的生命周期: 在上面的示例中,我们使用了 static readonly HttpClient,这是一个好习惯,因为它可以重用 TCP 连接,提高性能,对于简单的 WinForms 这是足够的,对于更复杂的应用,可以考虑使用 IHttpClientFactory

  5. 安全性:

    • 文件名: 服务器端我们使用 Guid 生成唯一文件名,可以防止恶意用户通过文件名(如 ../../../malicious.txt)进行路径遍历攻击。
    • 文件类型: 服务器端对文件扩展名进行了校验,这是一个很好的第一步,但不是万无一失的,更安全的做法是检查文件的真实内容(MIME type 或文件头),而不是仅仅依赖扩展名。
    • 文件大小: 你可以在服务器端添加对文件大小的限制,防止用户上传过大的文件导致服务器磁盘空间被占满,可以在 Startup.cs (ASP.NET Core) 中配置:
      builder.Services.Configure<FormOptions>(options =>
      {
          options.MultipartBodyLengthLimit = 104857600; // 100MB
      });

这个完整的示例涵盖了从服务器搭建到客户端实现的全过程,并考虑了实际开发中的一些常见问题和最佳实践,希望对你有帮助!

分享:
扫描分享到社交APP
上一篇
下一篇