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

如何通过复选框属性实现仅下载已勾选项(C#.NET实现)

多文件批量下载实现方案

整个实现拆分为前端、后端两部分,基于你现有的Razor循环渲染表格逻辑改造即可。

前端实现

  • 改造视图渲染逻辑
    给每一行的复选框绑定对应文件的唯一ID,新增批量下载按钮,可选增加全选复选框提升操作效率,改造后的Razor代码参考:
@* 假设你的视图强类型绑定为文件数据列表 *@
@model List<FileItem>
<table>
<thead>
  <tr>
    <th><input type="checkbox" id="checkAll" /></th>
    <th>文件名</th>
    <th>ID</th>
    <th>数据</th>
  </tr>
</thead>
<tbody>
@foreach(var item in Model)
{
<tr>
   <td><input type="checkbox" name="nameOfCheckBox" class="row-check" value="@item.FileId" /></td>
   <td>@item.Name</td>
   <td>@item.FileId</td> 
   <td>@item.Data</td>
</tr>
}
</tbody>
</table>
<button id="batchDownloadBtn" type="button">批量下载选中文件</button>
  • 编写交互逻辑
    实现选中项收集、合法性校验、请求发送、文件流触发下载的逻辑,原生JS实现参考(不需要额外引入前端框架):
// 可选:全选/取消全选功能
document.getElementById('checkAll').addEventListener('change', function(e){
  const isChecked = e.target.checked;
  document.querySelectorAll('.row-check').forEach(cb => cb.checked = isChecked);
});

// 批量下载逻辑
document.getElementById('batchDownloadBtn').addEventListener('click', async function(){
  // 收集所有选中项的文件ID
  const selectedIds = Array.from(document.querySelectorAll('.row-check:checked'))
    .map(cb => Number(cb.value));

  // 校验选中状态
  if(selectedIds.length === 0){
    alert('请至少选择一个要下载的文件');
    return;
  }

  // 向后端发送下载请求
  const resp = await fetch('/api/File/BatchDownload', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(selectedIds)
  });

  if(!resp.ok){
    const errMsg = await resp.text();
    alert(`下载失败:${errMsg}`);
    return;
  }

  // 解析返回的文件流,触发浏览器下载
  const blob = await resp.blob();
  const contentDisposition = resp.headers.get('Content-Disposition');
  let fileName = '批量下载文件.zip';
  if(contentDisposition){
    const matchRes = contentDisposition.match(/filename="?([^"]+)"?/);
    if(matchRes && matchRes[1]) fileName = decodeURIComponent(matchRes[1]);
  }
  const tempUrl = window.URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = tempUrl;
  a.download = fileName;
  document.body.appendChild(a);
  a.click();
  window.URL.revokeObjectURL(tempUrl);
  a.remove();
});

后端(C#/.NET)实现

后端逻辑核心是接收前端传的文件ID列表,校验权限后将所有文件打包为ZIP流返回,不需要在服务器生成本地临时文件。

  • 前置准备:如果是.NET Framework项目,先通过Nuget安装System.IO.Compression包;.NET Core/.NET 5+版本已内置该类库,无需额外安装。
  • 接口实现参考(ASP.NET Core 示例):
using Microsoft.AspNetCore.Mvc;
using System.IO.Compression;
using System.Web;

[ApiController]
public class FileController : ControllerBase
{
    // 注入你的数据库上下文、当前用户服务,替换成你项目里的实际实现
    private readonly AppDbContext _dbContext;
    private readonly ICurrentUserService _currentUser;

    public FileController(AppDbContext dbContext, ICurrentUserService currentUser)
    {
        _dbContext = dbContext;
        _currentUser = currentUser;
    }

    [HttpPost]
    [Route("api/File/BatchDownload")]
    public async Task<IActionResult> BatchDownload([FromBody] List<long> selectedFileIds)
    {
        // 参数合法性校验
        if (selectedFileIds == null || selectedFileIds.Count == 0)
        {
            return BadRequest("未选择任何下载文件");
        }

        // 查询文件,必须加权限校验,仅返回当前用户有权限访问的文件,防止越权
        var currentUserId = _currentUser.UserId;
        var allowDownloadFiles = await _dbContext.FileInfos
            .Where(f => selectedFileIds.Contains(f.FileId) && f.CreatorUserId == currentUserId)
            .ToListAsync();

        if (allowDownloadFiles.Count == 0)
        {
            return BadRequest("未查询到可下载的有效文件");
        }

        // 内存流打包ZIP,无本地临时文件
        var memoryStream = new MemoryStream();
        using (var zipArchive = new ZipArchive(memoryStream, ZipArchiveMode.Create, leaveOpen: true))
        {
            foreach (var file in allowDownloadFiles)
            {
                // 替换成你项目里的文件流读取逻辑:本地存储就读磁盘、数据库存储就转二进制流、对象存储就拉取OSS流
                await using var fileStream = GetFileStream(file.StoragePath);
                // 处理ZIP内重名文件,自动加序号
                var entryName = GetDuplicateFreeEntryName(zipArchive, file.FileName);
                var zipEntry = zipArchive.CreateEntry(entryName);
                await using var entryStream = zipEntry.Open();
                await fileStream.CopyToAsync(entryStream);
            }
        }
        // 重置流位置到开头,避免返回空内容
        memoryStream.Position = 0;

        // 返回ZIP文件
        var downloadFileName = HttpUtility.UrlEncode($"批量下载_{DateTime.Now:yyyyMMddHHmmss}.zip");
        return File(memoryStream, "application/zip", downloadFileName);
    }

    /// <summary>
    /// 处理ZIP包内重名文件
    /// </summary>
    private string GetDuplicateFreeEntryName(ZipArchive archive, string originFileName)
    {
        var finalName = originFileName;
        var dupIndex = 1;
        while (archive.Entries.Any(e => e.Name == finalName))
        {
            var ext = Path.GetExtension(originFileName);
            var namePart = Path.GetFileNameWithoutExtension(originFileName);
            finalName = $"{namePart}({dupIndex}){ext}";
            dupIndex++;
        }
        return finalName;
    }

    /// <summary>
    /// 读取文件流,替换为你项目的实际存储逻辑
    /// </summary>
    private Stream GetFileStream(string storagePath)
    {
        return System.IO.File.OpenRead(storagePath);
    }
}
  • 注意事项:
    • 如果单文件体积普遍超过100MB、批量下载总大小超过1G,不建议用内存流打包,可以改为写入服务器临时目录,返回文件后自动删除,避免占用过多服务器内存。
    • 必须做文件权限校验,禁止直接根据前端传的ID返回文件,防止恶意用户遍历ID下载他人私密文件。
    • 如果文件数量极多(超过100个),可以在接口增加数量限制,避免打包时间过长导致请求超时。

内容的提问来源于stack exchange,提问作者Numan Azad

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 22:54:17