如何在JavaScript+C# ASP.NET中实现批量PDF文件下载?
批量生成PDF无法触发下载,仅保存到服务器目录
我能成功生成并下载单个PDF,但批量生成多个PDF时出问题。用的是单个下载时的相同代码,现在PDF只存到指定位置,不会触发浏览器下载。
前端JavaScript代码
function GeneratePackingSlipAction() { var checkedVals = GetSelectedOrders(); if (checkedVals.length > 0) { $.ajax({ url: '/Orders/PackingSlipBulkOrders', type: 'POST', data: { selectedOrderIds: checkedVals } }).done(function (data) { console.log(data); alert("was heree:" + data.status); if (data.status == true) { var msg = "Generated Packing Slip " + data.successCount + " order(s)"; alert(msg); jQuery(this).removeClass("checkedall"); jQuery(".SelectOrder").prop("checked", false); } }); } }
后端C#控制器代码
public ActionResult PackingSlipBulkOrders(long[] selectedOrderIds) { int successCount = 0; int failedCount = 0; if (selectedOrderIds.Count() > 0) { foreach (var id in selectedOrderIds) { try { Order order = _orderMgr.GetOrder(id); var result = GenerateExcelPDFBulk(order); successCount++; } catch { failedCount++; } } } return Json(new { status = true, successCount = successCount, failedCount = failedCount }); } public bool GenerateExcelPDFBulk(Order order) { PDFHelperPackingSlip _pdfHelper = new PDFHelperPackingSlip(); string footerimgpath = Server.MapPath("~/Content/images/dark-logo.png"); //Create Excel File and Save string FileName = "PackingSlip.pdf"; string fileLocation = Server.MapPath("~/Content/Imports/") + FileName; if (System.IO.File.Exists(fileLocation)) { System.IO.File.Delete(fileLocation); } string filename = _pdfHelper.GeneratePDF(order, fileLocation, footerimgpath); Response.ContentType = "application/pdf"; Response.AppendHeader("Content-Disposition", "attachment; filename=" + Path.GetFileName(fileLocation)); Response.TransmitFile(fileLocation); Response.Flush(); Response.Close(); Response.End(); return true; }
问题原因及修复方案
核心问题
- AJAX无法触发浏览器下载:浏览器出于安全限制,不会自动处理AJAX响应的文件流,所以后端设置的下载头无法生效。
- 文件名重复导致覆盖:循环生成PDF时使用固定文件名
PackingSlip.pdf,后续文件会直接覆盖之前的,最终仅保留最后一个PDF。 Response.End()中断请求:该方法会直接终止当前HTTP请求,导致循环提前结束,且AJAX请求被强制中断。
修复步骤
1. 后端修改:生成ZIP压缩包(批量下载最优方案)
把所有生成的PDF打包成ZIP文件返回,既避免多次下载请求,也解决文件覆盖问题。
修改后的C#代码:
public ActionResult PackingSlipBulkOrders(long[] selectedOrderIds) { int successCount = 0; int failedCount = 0; var pdfFiles = new List<string>(); string tempDir = Server.MapPath("~/Content/Imports/TempBulkPDFs/"); // 创建临时目录存储批量PDF if (!Directory.Exists(tempDir)) { Directory.CreateDirectory(tempDir); } if (selectedOrderIds.Any()) { foreach (var id in selectedOrderIds) { try { Order order = _orderMgr.GetOrder(id); // 用订单ID做文件名后缀,保证唯一性 string fileName = $"PackingSlip_Order{id}.pdf"; string fileLocation = Path.Combine(tempDir, fileName); PDFHelperPackingSlip _pdfHelper = new PDFHelperPackingSlip(); string footerimgpath = Server.MapPath("~/Content/images/dark-logo.png"); if (System.IO.File.Exists(fileLocation)) { System.IO.File.Delete(fileLocation); } _pdfHelper.GeneratePDF(order, fileLocation, footerimgpath); pdfFiles.Add(fileLocation); successCount++; } catch { failedCount++; } } } // 生成ZIP包并返回 if (pdfFiles.Any()) { string zipFileName = $"BulkPackingSlips_{DateTime.Now:yyyyMMddHHmmss}.zip"; string zipPath = Path.Combine(tempDir, zipFileName); using (var zipArchive = ZipFile.Open(zipPath, ZipArchiveMode.Create)) { foreach (var pdfFile in pdfFiles) { zipArchive.CreateEntryFromFile(pdfFile, Path.GetFileName(pdfFile)); } } // 设置下载响应头 Response.ContentType = "application/zip"; Response.AppendHeader("Content-Disposition", $"attachment; filename={zipFileName}"); Response.TransmitFile(zipPath); Response.Flush(); // 清理临时文件(可选,也可定时任务清理) foreach (var file in pdfFiles) { System.IO.File.Delete(file); } System.IO.File.Delete(zipPath); Directory.Delete(tempDir, true); return new EmptyResult(); } // 无PDF生成时返回状态 return Json(new { status = successCount > 0, successCount = successCount, failedCount = failedCount }); } // 修改生成方法,仅负责生成PDF文件,不处理响应 public bool GenerateExcelPDFBulk(Order order, string fileLocation, string footerimgpath) { PDFHelperPackingSlip _pdfHelper = new PDFHelperPackingSlip(); if (System.IO.File.Exists(fileLocation)) { System.IO.File.Delete(fileLocation); } _pdfHelper.GeneratePDF(order, fileLocation, footerimgpath); return true; }
2. 前端修改:替换AJAX为表单提交
AJAX无法触发下载,改用表单提交让浏览器直接处理后端返回的文件流:
function GeneratePackingSlipAction() { var checkedVals = GetSelectedOrders(); if (checkedVals.length > 0) { // 创建临时表单 var form = $('<form>', { 'method': 'POST', 'action': '/Orders/PackingSlipBulkOrders' }); // 添加选中的订单ID为隐藏字段 checkedVals.forEach(function(id) { $('<input>', { 'type': 'hidden', 'name': 'selectedOrderIds', 'value': id }).appendTo(form); }); // 提交表单后移除 form.appendTo('body').submit().remove(); } }
额外注意事项
- 临时文件清理:建议添加服务器定时任务,定期清理临时目录,避免磁盘空间占用。
- 错误处理:可在后端捕获具体异常类型,返回更详细的错误信息给前端提示用户。
- 文件名唯一性:始终保证生成的文件名称唯一,避免文件覆盖丢失数据。
内容的提问来源于stack exchange,提问作者Jen143
相关产品推荐
相关产品推荐

