ASP.NET Core文件上传实战指南 | 3种主流方式详解与完整C#后端接收代码示例

大家好,我是第八哥,一个有十年.NET开发经验的老兵,今天咱们就来聊聊ASP.NET Core文件上传的几种玩法。

文件上传看似简单,但实际上不同的场景需要采用不同的方案才能高效处理。下面我将手把手教你掌握四种主流方法,并提供完整的后端代码示例,全是实战干货!

基础表单上传(单文件)

这是最常用的方式,适用于普通的表单提交。前端使用 <form enctype="multipart/form-data">,后端采用 IFormFile 进行接收,通过CopyToAsync()保存文件到服务器的指定路径‌。示例代码如下:

前端HTML:

<form method="post" enctype="multipart/form-data" action="/Home/Upload">
    <input type="file" name="file" required>
    <button type="submit">上传</button>
</form>

后端C#:

[HttpPost]
public async Task<IActionResult> Upload(IFormFile file)
{
    var filePath = Path.Combine(_webHostEnvironment.WebRootPath, file.FileName);
    using (var stream = System.IO.File.Create(filePath))
    {
        await file.CopyToAsync(stream);
    }
    return Ok();
}

多文件批量上传

多文件上传和单文件上传类似,只需要将单文件上传的代码简单改动下即可。在前端input标签中添加multiple属性,支持选择多个文件;后端参数使用List<IFormFile>IFormFileCollection进行接收多个文件。示例代码如下:

前端调整:

<form method="post" enctype="multipart/form-data" action="/Home/Uploads">
    <input type="file" name="files" multiple required>
    <button type="submit">上传</button>
</form>

后端升级:

[HttpPost]
public async Task<IActionResult> Uploads(List<IFormFile> files)
{
    foreach (var file in files)
    {
        var filePath = Path.Combine(_webHostEnvironment.WebRootPath, file.FileName);
        using (var stream = System.IO.File.Create(filePath))
        {
            await file.CopyToAsync(stream);
        }
    }
    return Ok();
}

流式大文件上传(2GB+)

在现代Web应用中,大文件上传是一个很常见的需求。如果继续采用传统的表单上传方式,一次性把文件怼进内存,那服务器会直接哭给你看,超时、内存溢出等小脾气一个接一个的甩到你脸上。所以,在处理超大文件时分块+流式才是王道,通过减少单次请求负载、优化内存使用,显著提升上传稳定性和用户体验。示例代码如下:

前端代码

<div>
    <input type="file" name="file" id="fileInput" required>
</div>

<script>
    document.getElementById('fileInput').addEventListener('change'(e) => {
        // 选择文件后,开始上传
        startUpload(e.target.files[0])
    });

    function startUpload(file) {
        if (!file) return;
        const chunkSize = 5 * 1024 * 1024;
        const totalChunks = Math.ceil(file.size / chunkSize);
        let currentChunk = 0;
        let fileId = Date.now() + '-' + Math.random().toString(36).substring(2);

        upload(fileId, file, currentChunk, totalChunks, chunkSize)
    }

    async function upload(fileId, file, currentChunk, totalChunks, chunkSize) {
        while (currentChunk < totalChunks) {
            const start = currentChunk * chunkSize;
            const end = Math.min(start + chunkSize, file.size);
            const chunk = file.slice(start, end);

            const formData = new FormData();
            formData.append('fileId', fileId);
            formData.append('chunkNumber', currentChunk + 1);
            formData.append('totalChunks', totalChunks);
            formData.append('fileName', file.name);
            formData.append('chunk', chunk, file.name);

            try {
                const response = await fetch('/Home/UploadChunk', {
                    method'POST',
                    body: formData
                });
                const result = await response.json();
                console.log(result.message);
                currentChunk++;
            } catch (error) {
                console.error('上传失败:', error);
                break;
            }
        }
    }
</script>

后端代码:

[HttpPost]
public async Task<IActionResult> UploadChunk(UploadFile uploadFile)
{
    var tempDir = Path.Combine(_webHostEnvironment.WebRootPath, "TempUploads", uploadFile.fileId);
    if(!Directory.Exists(tempDir))  Directory.CreateDirectory(tempDir);

    var chunkPath = Path.Combine(tempDir, $"{uploadFile.chunkNumber}.part");
    using (var stream = new FileStream(chunkPath, FileMode.Create))
    {
        await uploadFile.chunk.CopyToAsync(stream);
    }

    if (uploadFile.chunkNumber == uploadFile.totalChunks)
    {
        var finalPath = Path.Combine(_webHostEnvironment.WebRootPath, "Uploads", uploadFile.fileName);
        await CombineChunksAsync(tempDir, finalPath, uploadFile.totalChunks);
        Directory.Delete(tempDir, true);
        return Ok(new { message = "文件合并完成", path = finalPath });
    }

    return Ok(new { message = $"分块 {uploadFile.chunkNumber}/{uploadFile.totalChunks} 上传成功" });
}

private async Task CombineChunksAsync(string tempDir, string finalPath, int totalChunks)
{
    using (var finalStream = new FileStream(finalPath, FileMode.Create))
    {
        for (int i = 1; i <= totalChunks; i++)
        {
            var chunkPath = Path.Combine(tempDir, $"{i}.part");
            using (var chunkStream = new FileStream(chunkPath, FileMode.Open))
            {
                await chunkStream.CopyToAsync(finalStream);
            }
        }
    }
}

public class UploadFile
{
    public string fileId { getset; }
    public int chunkNumber { getset; }
    public int totalChunks { getset; }
    public string fileName { getset; }
    public IFormFile chunk { getset; }
}

上面的代码实现了按每块5MB大小的分块上传,并由服务端自动合并分块。前端使用 Fetch API 进行流式传输,后端则采用异步文件操作以保证高性能。

注意:上面的代码,仅用于上传演示,未对文件格式、大小进行校验以及防病毒扫描。

安全与优化贴士

1.文件类型验证‌:使用白名单机制校验扩展名(如.jpg, .pdf);通过文件头签名检测真实类型(防止伪装攻击)。

‌2.存储安全‌:文件保存至非Web根目录(如/SecureUploads);禁用执行权限(防止恶意脚本运行)。

‌3.输入过滤‌:对文件名进行消毒处理(移除../等路径遍历字符);使用Path.GetRandomFileName()生成随机存储名称。

‌4.访问控制‌:结合[Authorize]限制上传权限;记录操作日志(用户、时间、文件哈希)。

‌5.内容扫描‌:集成ClamAV等引擎检测病毒文件。

这些方案覆盖了90%业务场景,如果您在开发中遇到了其他问题,欢迎留言讨论!

上一篇 资深.NET开发者实战:ASP.NET Core MVC部署到IIS详细步骤与常见问题解决方案 下一篇 ASP.NET Core 新手入门指南:从零搭建你的第一个Web应用 | 快速实战教程

评论

暂不支持评论