You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.27 15:02:36