ASP.NET Core多文件夹文件上传及已上传文件列表持久化方案咨询
解决方案:ASP.NET Core 多批次文件上传并持久化已上传列表
首先得戳破核心问题:HTTP是无状态的,每次你点击提交上传新文件时,之前上传的文件信息默认会丢失。所以我们的思路是:把每次上传的文件先存到服务器临时位置,再把这些文件的元数据(比如文件名、临时路径)存在用户会话里,这样每次回发都能加载并展示已上传的文件,最后统一处理。
下面是具体的实现步骤和代码:
1. 准备自定义模型类存储临时文件信息
先定义一个简单的类,用来记录已上传文件的基本信息,方便后续存储和展示:
public class TempFileInfo { public string FileName { get; set; } // 原始文件名 public string TempFilePath { get; set; } // 服务器上的临时存储路径 }
2. 配置Session支持
因为要在用户会话中保存已上传文件的列表,得先在Program.cs里配置Session:
var builder = WebApplication.CreateBuilder(args); // 添加Session服务,设置超时时间(比如30分钟,避免用户操作超时丢失数据) builder.Services.AddSession(options => { options.IdleTimeout = TimeSpan.FromMinutes(30); options.Cookie.HttpOnly = true; options.Cookie.IsEssential = true; }); // 添加HttpContextAccessor,方便在PageModel里访问Session builder.Services.AddHttpContextAccessor(); builder.Services.AddRazorPages(); // 可选:配置文件上传大小限制,根据你的业务需求调整 builder.Services.Configure<FormOptions>(options => { options.MultipartBodyLengthLimit = 20 * 1024 * 1024; // 20MB }); var app = builder.Build(); // 中间件顺序很重要,UseSession要放在UseRouting之后、MapRazorPages之前 app.UseSession(); // 其他常规中间件配置... app.MapRazorPages(); app.Run();
3. 修改PageModel处理上传逻辑
这是核心部分,负责处理每次上传的文件、存储临时文件、维护已上传列表,以及最终的提交处理:
public class UploadModel : PageModel { private readonly IWebHostEnvironment _webHostEnv; private readonly IHttpContextAccessor _httpContextAccessor; // 构造函数注入依赖 public UploadModel(IWebHostEnvironment webHostEnv, IHttpContextAccessor httpContextAccessor) { _webHostEnv = webHostEnv; _httpContextAccessor = httpContextAccessor; } // 绑定前端上传的文件 [BindProperty] public List<IFormFile> Upload { get; set; } // 页面展示用的已上传文件列表 public List<TempFileInfo> UploadedFiles { get; set; } = new(); // 页面加载时从Session读取已上传列表 public void OnGet() { var savedFiles = _httpContextAccessor.HttpContext.Session.Get<List<TempFileInfo>>("UploadedFiles"); if (savedFiles != null) { UploadedFiles = savedFiles; } } // 处理每次添加文件的请求 public async Task<IActionResult> OnPostAsync() { // 先取出Session里已有的文件列表(如果有的话) var existingFiles = _httpContextAccessor.HttpContext.Session.Get<List<TempFileInfo>>("UploadedFiles") ?? new List<TempFileInfo>(); // 处理本次上传的新文件 if (Upload != null && Upload.Count > 0) { // 创建临时存储目录(如果不存在) var tempDir = Path.Combine(_webHostEnv.WebRootPath, "temp-uploads"); Directory.CreateDirectory(tempDir); foreach (var file in Upload) { if (file.Length > 0) { // 生成唯一的临时文件名,避免冲突和安全问题 var tempFileName = $"{Guid.NewGuid()}_{file.FileName}"; var tempPath = Path.Combine(tempDir, tempFileName); // 将文件复制到临时目录 using (var stream = new FileStream(tempPath, FileMode.Create)) { await file.CopyToAsync(stream); } // 将新文件信息添加到列表 existingFiles.Add(new TempFileInfo { FileName = file.FileName, TempFilePath = tempPath }); } } } // 更新Session里的文件列表 _httpContextAccessor.HttpContext.Session.Set("UploadedFiles", existingFiles); // 赋值给页面展示属性 UploadedFiles = existingFiles; return Page(); } // 最终提交所有文件的处理方法 public async Task<IActionResult> OnPostFinalizeAsync() { var uploadedFiles = _httpContextAccessor.HttpContext.Session.Get<List<TempFileInfo>>("UploadedFiles"); if (uploadedFiles == null || uploadedFiles.Count == 0) { ModelState.AddModelError(string.Empty, "还没有上传任何文件哦"); UploadedFiles = uploadedFiles ?? new List<TempFileInfo>(); return Page(); } // 创建最终存储目录(根据你的业务需求调整路径) var finalDir = Path.Combine(_webHostEnv.WebRootPath, "final-uploads"); Directory.CreateDirectory(finalDir); // 把所有临时文件移动到最终目录 foreach (var file in uploadedFiles) { if (System.IO.File.Exists(file.TempFilePath)) { var finalPath = Path.Combine(finalDir, file.FileName); // 处理文件名重复的情况 if (System.IO.File.Exists(finalPath)) { finalPath = Path.Combine(finalDir, $"{Guid.NewGuid()}_{file.FileName}"); } System.IO.File.Move(file.TempFilePath, finalPath); } } // 清理Session和临时目录 _httpContextAccessor.HttpContext.Session.Remove("UploadedFiles"); var tempDir = Path.Combine(_webHostEnv.WebRootPath, "temp-uploads"); if (Directory.Exists(tempDir)) { Directory.Delete(tempDir, true); } // 跳转到成功页面(你可以自己创建这个页面) return RedirectToPage("UploadSuccess"); } }
4. 前端页面展示和交互
最后是前端Razor页面代码,实现文件选择、添加文件按钮、已上传文件列表展示,以及最终提交按钮:
@page @model UploadModel <h1>多批次文件上传</h1> <!-- 上传新文件的表单 --> <form method="post" enctype="multipart/form-data"> <div class="mb-3"> <label asp-for="Upload" class="form-label">选择要添加的文件</label> <input type="file" asp-for="Upload" multiple class="form-control" /> <span asp-validation-for="Upload" class="text-danger"></span> </div> <button type="submit" class="btn btn-primary">添加文件</button> </form> <!-- 展示已上传的文件列表 --> @if (Model.UploadedFiles.Count > 0) { <hr /> <h3>已上传文件</h3> <ul class="list-group mb-3"> @foreach (var file in Model.UploadedFiles) { <li class="list-group-item">@file.FileName</li> } </ul> <!-- 最终提交按钮的表单 --> <form method="post" asp-page-handler="Finalize"> <button type="submit" class="btn btn-success">完成上传,提交处理</button> </form> }
一些额外注意事项
- 临时文件清理:如果用户中途放弃上传,Session超时后临时文件会留在服务器上,建议加个定时任务(比如用Hangfire)定期清理
temp-uploads目录。 - 安全性:用
Guid生成临时文件名可以避免路径遍历攻击,同时要确保临时目录的权限设置正确,禁止直接访问。 - 文件大小:根据你的业务需求调整
FormOptions里的上传大小限制,避免过大文件导致错误。
内容的提问来源于stack exchange,提问作者Ian Boggs
相关产品推荐
相关产品推荐

