如何通过复选框属性实现仅下载已勾选项(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
相关产品推荐
相关产品推荐

