DNN模块中GemBox生成导出文件后的代码中断及模块刷新问题咨询
Alright, let's break down your problem and walk through practical solutions step by step. I've dealt with similar DNN + file download scenarios before, so here's what I recommend:
1. Why Your RegisterStartupScript Isn't Working
The core issue is context: your standalone .aspx file sends a file download response (Excel/PDF/CSV) to the browser. When the browser receives this, it focuses entirely on saving the file—it won’t parse or execute any HTML/JavaScript embedded in that response. Even if your script was output correctly, the browser treats the response as a binary file, not a web page. Plus, DNN’s module scope is tied to the parent page (your .ascx), so the standalone page can’t access the parent’s JavaScript environment anyway.
2. Fixing the Loading Spinner + Module Reload
You have two solid options, depending on how you want to structure your code:
Option A: AJAX + Blob (Cleaner, Full Control)
This approach keeps everything in the parent page’s context, so you can easily manage the spinner and module reload without relying on separate pages:
Frontend (jQuery)
$("#exportButton").click(function() { // Show loading spinner immediately $("#loadingSpinner").show(); // Get user's selected export format var exportFormat = $("#formatDropdown").val(); // AJAX call to generate the file $.ajax({ url: "/DesktopModules/YourModule/ExportHandler.ashx", type: "POST", data: { format: exportFormat }, xhrFields: { responseType: "blob" }, // Critical: tells jQuery to expect binary data success: function(blob) { // Create a temporary download link var url = window.URL.createObjectURL(blob); var downloadLink = $("<a>") .attr("href", url) .attr("download", `PriceList.${exportFormat.toLowerCase()}`) .appendTo("body"); // Trigger the download downloadLink[0].click(); // Clean up window.URL.revokeObjectURL(url); downloadLink.remove(); }, error: function(xhr) { alert(`Export failed: ${xhr.responseText}`); }, complete: function() { // Hide spinner AND reload the DNN module $("#loadingSpinner").hide(); reloadModule(); } }); }); // Reload DNN module without full page refresh function reloadModule() { var moduleId = <%= ModuleId %>; // Pull module ID from code-behind // Option 1: Use DNN's built-in module reload API (preferred) if (typeof dnn !== "undefined" && dnn.modules) { dnn.modules.reload(moduleId); } // Option 2: Manual AJAX fallback if DNN API isn't available else { $.get(`/DesktopModules/YourModule/View.ascx?moduleId=${moduleId}`, function(html) { // Replace module content (adjust selector to match your module's container) $(`#dnn_ctr${moduleId}_Content`).html(html); }); } }
Backend (ASHX Handler)
Create an ExportHandler.ashx file to handle file generation without interrupting the parent page:
public class ExportHandler : IHttpHandler { public void ProcessRequest(HttpContext context) { string format = context.Request.Form["format"]; // 1. Fetch price list from your ERP system var priceList = GetPriceListFromERP(); // 2. Generate file with GemBox using var stream = new MemoryStream(); switch (format.ToUpper()) { case "EXCEL": var workbook = new ExcelFile(); // Populate workbook with priceList data... workbook.Save(stream, SaveOptions.XlsxDefault); context.Response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; context.Response.AddHeader("Content-Disposition", "attachment; filename=PriceList.xlsx"); break; case "CSV": // Generate CSV (use GemBox or direct string writing) // ... context.Response.ContentType = "text/csv"; context.Response.AddHeader("Content-Disposition", "attachment; filename=PriceList.csv"); break; case "PDF": var document = new PdfDocument(); // Populate PDF with priceList data... document.Save(stream); context.Response.ContentType = "application/pdf"; context.Response.AddHeader("Content-Disposition", "attachment; filename=PriceList.pdf"); break; } // Send the file to the client stream.Position = 0; stream.CopyTo(context.Response.OutputStream); // No Response.End() needed here—we're just writing the stream, no thread abort } private List<PriceListItem> GetPriceListFromERP() { // Your ERP integration logic here return new List<PriceListItem>(); } public bool IsReusable => false; }
Option B: Hidden iFrame (Simpler, Uses Your Existing .aspx)
If you want to keep using your standalone .aspx file, use a hidden iframe to trigger the download, then listen for when it finishes loading to clean up:
Frontend
$("#exportButton").click(function() { $("#loadingSpinner").show(); var exportFormat = $("#formatDropdown").val(); // Create hidden iframe to trigger download var iframe = $("<iframe>") .attr("src", `/DesktopModules/YourModule/ExportPage.aspx?format=${exportFormat}`) .css("display", "none") .appendTo("body"); // Listen for iframe load (fires when download completes) iframe.on("load", function() { $("#loadingSpinner").hide(); reloadModule(); // Clean up the iframe $(this).remove(); }); }); // Same reloadModule function as Option A function reloadModule() { var moduleId = <%= ModuleId %>; if (dnn && dnn.modules) { dnn.modules.reload(moduleId); } else { $.get(`/DesktopModules/YourModule/View.ascx?moduleId=${moduleId}`, function(html) { $(`#dnn_ctr${moduleId}_Content`).html(html); }); } }
3. Why the Original PostBack Broke
ASP.NET’s Response.End() (often called implicitly during file downloads) triggers a ThreadAbortException that cuts off the rest of the PostBack lifecycle. Even with GemBox’s workaround, DNN’s partial rendering logic gets confused because the response is hijacked for the file download. Using a separate handler/iframe avoids this entirely, as it doesn’t interfere with the parent page’s PostBack flow.
Final Notes
- Web Workers: You don’t need them here—either AJAX/Blob or iframe will handle the scenario without blocking the UI.
- DNN Module Context: When reloading, make sure your
View.ascxcan handle the GET request with themoduleIdparameter and returns the correct module HTML. - Cross-Browser Testing: Verify behavior across Chrome, Firefox, and Edge to ensure consistent download and reload functionality.
内容的提问来源于stack exchange,提问作者Tig7r

