大家好,我是第八哥,一个有十年.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 { get; set; }
public int chunkNumber { get; set; }
public int totalChunks { get; set; }
public string fileName { get; set; }
public IFormFile chunk { get; set; }
}
上面的代码实现了按每块5MB大小的分块上传,并由服务端自动合并分块。前端使用 Fetch API 进行流式传输,后端则采用异步文件操作以保证高性能。
注意:上面的代码,仅用于上传演示,未对文件格式、大小进行校验以及防病毒扫描。
安全与优化贴士
1.文件类型验证:使用白名单机制校验扩展名(如.jpg, .pdf);通过文件头签名检测真实类型(防止伪装攻击)。
2.存储安全:文件保存至非Web根目录(如/SecureUploads);禁用执行权限(防止恶意脚本运行)。
3.输入过滤:对文件名进行消毒处理(移除../等路径遍历字符);使用Path.GetRandomFileName()
生成随机存储名称。
4.访问控制:结合[Authorize]限制上传权限;记录操作日志(用户、时间、文件哈希)。
5.内容扫描:集成ClamAV等引擎检测病毒文件。
这些方案覆盖了90%业务场景,如果您在开发中遇到了其他问题,欢迎留言讨论!
评论