C# 文件上传和下载(上传文件到服务端)

C# 文件上传和下载(上传文件到服务端)

ASP.NET Core 服务端 + WinForms / WPF / 控制台客户端,重点大文件上传、断点续传、进度条、安全性等工业现场最关心的问题。


一、整体架构设计

┌─────────────────────────────────────────────────────────────┐
│                    文件上传下载系统架构                      │
├─────────────────────────────────────────────────────────────┤
│  客户端        │  网络传输       │  服务端存储      │  业务层  │
│                │                 │                 │         │
│  • 文件选择   │  • HTTP/HTTPS   │  • 本地磁盘     │  • 权限  │
│  • 分片上传   │  • 断点续传     │  • 临时目录     │  • 审计  │
│  • 进度显示   │  • 校验(MD5)    │  • 数据库记录   │  • 通知  │
│  • 暂停/恢复 │  • 压缩传输     │  • 云存储对接   │  • 日志  │
└─────────────────────────────────────────────────────────────┘

二、方案一:ASP.NET Core 轻量级文件服务器

适合:上位机工具、内网部署、工控系统

2.1 服务端(ASP.NET Core Minimal API)

Program.cs

using Microsoft.AspNetCore.Http.Features;

var builder = WebApplication.CreateBuilder(args);

// 允许大文件上传(500MB)
builder.Services.Configure<IISServerOptions>(options =>
{
    options.MaxRequestBodySize = 524288000;
});
builder.Services.Configure<KestrelServerOptions>(options =>
{
    options.Limits.MaxRequestBodySize = 524288000;
});

var app = builder.Build();

// 上传接口
app.MapPost("/upload", async (HttpContext context) =>
{
    var file = context.Request.Form.Files[0];
    var uploadDir = Path.Combine(Directory.GetCurrentDirectory(), "Uploads");

    if (!Directory.Exists(uploadDir))
        Directory.CreateDirectory(uploadDir);

    var filePath = Path.Combine(uploadDir, file.FileName);

    using var stream = new FileStream(filePath, FileMode.Create);
    await file.CopyToAsync(stream);

    return Results.Ok(new { fileName = file.FileName, size = file.Length });
});

// 下载接口
app.MapGet("/download/{fileName}", (string fileName) =>
{
    var filePath = Path.Combine("Uploads", fileName);
    if (!File.Exists(filePath))
        return Results.NotFound();

    return Results.File(filePath, "application/octet-stream", fileName);
});

app.Run("http://localhost:5000");

三、方案二:企业级断点续传方案

适合:固件升级、大文件、工业现场

3.1 核心思想(分片上传)

客户端:
1. 计算文件 MD5
2. 切割文件(如 5MB / 片)
3. 逐片上传
4. 通知服务端合并

服务端:
1. 接收分片
2. 临时存储
3. 合并文件
4. 校验完整性

3.2 服务端(分片上传接口)

using System.Security.Cryptography;

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapPost("/upload/chunk", async (HttpRequest request) =>
{
    var form = await request.ReadFormAsync();
    var fileId = form["fileId"].ToString();
    var chunkIndex = int.Parse(form["chunkIndex"]);
    var totalChunks = int.Parse(form["totalChunks"]);
    var file = form.Files["file"];

    var tempDir = Path.Combine("Temp", fileId);
    if (!Directory.Exists(tempDir))
        Directory.CreateDirectory(tempDir);

    var chunkPath = Path.Combine(tempDir, chunkIndex.ToString());
    using var stream = new FileStream(chunkPath, FileMode.Create);
    await file.CopyToAsync(stream);

    return Results.Ok();
});

app.MapPost("/upload/merge", async (HttpRequest request) =>
{
    var form = await request.ReadFormAsync();
    var fileId = form["fileId"].ToString();
    var fileName = form["fileName"].ToString();

    var tempDir = Path.Combine("Temp", fileId);
    var finalPath = Path.Combine("Uploads", fileName);

    using var destStream = new FileStream(finalPath, FileMode.Create);

    for (int i = 0; ; i++)
    {
        var chunkPath = Path.Combine(tempDir, i.ToString());
        if (!File.Exists(chunkPath)) break;

        using var chunkStream = new FileStream(chunkPath, FileMode.Open);
        await chunkStream.CopyToAsync(destStream);
    }

    Directory.Delete(tempDir, true);

    return Results.Ok("文件上传完成");
});

app.Run();

四、客户端(WinForms / WPF / 控制台)

4.1 普通上传(简单)

using System.Net.Http.Headers;

static async Task UploadFile(string filePath)
{
    using var client = new HttpClient();
    using var content = new MultipartFormDataContent();

    var fileStream = new FileStream(filePath, FileMode.Open);
    var fileContent = new StreamContent(fileStream);
    fileContent.Headers.ContentType = MediaTypeHeaderValue.Parse("application/octet-stream");

    content.Add(fileContent, "file", Path.GetFileName(filePath));

    var response = await client.PostAsync("http://localhost:5000/upload", content);
    Console.WriteLine(await response.Content.ReadAsStringAsync());
}

4.2 带进度条的上传(工业级)

static async Task UploadWithProgress(string filePath)
{
    var fileInfo = new FileInfo(filePath);
    long totalSize = fileInfo.Length;

    using var client = new HttpClient();
    using var fileStream = new FileStream(filePath, FileMode.Open);

    var content = new ProgressableStreamContent(fileStream, totalSize, progress =>
    {
        Console.WriteLine($"进度: {progress:P}");
    });

    var formData = new MultipartFormDataContent
    {
        { content, "file", Path.GetFileName(filePath) }
    };

    await client.PostAsync("http://localhost:5000/upload", formData);
}

// 进度流包装器
class ProgressableStreamContent : StreamContent
{
    private readonly Stream _content;
    private readonly long _totalSize;
    private readonly Action<double> _progress;

    public ProgressableStreamContent(Stream content, long totalSize, Action<double> progress)
        : base(content)
    {
        _content = content;
        _totalSize = totalSize;
        _progress = progress;
    }

    protected override async Task SerializeToStreamAsync(Stream stream, TransportContext context)
    {
        var buffer = new byte[81920];
        long uploaded = 0;

        while (true)
        {
            int read = await _content.ReadAsync(buffer, 0, buffer.Length);
            if (read == 0) break;

            await stream.WriteAsync(buffer, 0, read);
            uploaded += read;
            _progress(uploaded / (double)_totalSize);
        }
    }
}

五、下载文件(客户端)

static async Task DownloadFile(string fileName, string savePath)
{
    using var client = new HttpClient();
    var response = await client.GetAsync(
        $"http://localhost:5000/download/{fileName}",
        HttpCompletionOption.ResponseHeadersRead);

    using var stream = await response.Content.ReadAsStreamAsync();
    using var fileStream = new FileStream(savePath, FileMode.Create);

    await stream.CopyToAsync(fileStream);
}

参考代码 C# 文件上传和下载(上传文件到服务端) www.youwenfan.com/contentcsu/62408.html

六、安全性与工业现场注意事项

风险 解决方案
文件过大 限制 MaxRequestBodySize
恶意文件 校验扩展名 + MIME
路径穿越 白名单文件名
重复上传 MD5 去重
权限控制 Token / JWT
断电续传 分片上传

 

专注于matlab/simulink,电子电路,编程