使用 HttpContext.Current.Response 导出会导致问题

Exporting Using HttpContext.Current.Response is Causing Issues

我正在使用 asp.net/C#/HTML5/bootstrap 开发网站。其中一项要求是将文档导出为 Excel and/or PDF。我能够使用以下代码段(这是 Excel 代码段)导出(成功):

        HttpContext.Current.Response.ContentType = "application/octet-stream";
        HttpContext.Current.Response.ContentEncoding = System.Text.Encoding.UTF8;
        HttpContext.Current.Response.AddHeader("content-disposition", "attachment; filename=" + filename);
        HttpContext.Current.Response.BinaryWrite(xlsBytes);
        HttpContext.Current.Response.Flush();
        HttpContext.Current.Response.End();

我遇到的问题是,在此运行之后,它似乎停止了页面生命周期的轨道。例如,用户单击导出按钮,调用 javascript 抛出 "Please wait" 模式对话框并提交表单:

<script src="../Scripts/waitingFor.js"></script>

<script type="text/javascript">
    function pleaseWait() {
        waitingDialog.show("Building File<br/>...this could take a minute", { dialogSize: "sm", progressType: "warning" });

        form = document.getElementById("frm_contentMaster");
        form.submit();
    }
</script>

javascript 包含文件:

/**
 * Module for displaying "Waiting for..." dialog using Bootstrap
 *
 * @author Eugene Maslovich <ehpc@em42.ru>
 */

(function (root, factory) {
    'use strict';

    if (typeof define === 'function' && define.amd) {
        define(['jquery'], function ($) {
            return (root.waitingDialog = factory($));
        });
    }
    else {
        root.waitingDialog = root.waitingDialog || factory(root.jQuery);
    }
}(this, function ($) {

'use strict';

/**
 * Dialog DOM constructor
 */
function constructDialog($dialog) {
    // Deleting previous incarnation of the dialog
    if ($dialog) {
        $dialog.remove();
    }
    return $(
        '<div id="waitingFor" class="modal fade" data-backdrop="static" data-keyboard="false" tabindex="-1" role="dialog" aria-hidden="true" style="padding-top:15%; overflow-y:visible;">' +
            '<div class="modal-dialog modal-m">' +
                '<div class="modal-content">' +
                    '<div class="modal-header" style="display: none;"></div>' +
                    '<div class="modal-body">' +
                        '<div class="progress progress-striped active" style="margin-bottom:0;">' +
                            '<div class="progress-bar" style="width: 100%"></div>' +
                        '</div>' +
                    '</div>' +
                '</div>' +
            '</div>' +
        '</div>'
    );
}

// Dialog object
var $dialog;

return {
    /**
     * Opens our dialog
     * @param message Custom message
     * @param options Custom options:
     *   options.headerText - if the option is set to boolean false, 
     *     it will hide the header and "message" will be set in a paragraph above the progress bar.
     *     When headerText is a not-empty string, "message" becomes a content 
     *     above the progress bar and headerText string will be set as a text inside the H3;
     *   options.headerSize - this will generate a heading corresponding to the size number. Like <h1>, <h2>, <h3> etc;
     *   options.headerClass - extra class(es) for the header tag;
     *   options.dialogSize - bootstrap postfix for dialog size, e.g. "sm", "m";
     *   options.progressType - bootstrap postfix for progress bar type, e.g. "success", "warning";
     *   options.contentElement - determines the tag of the content element. 
     *     Defaults to "p", which will generate a <p> tag;
     *   options.contentClass - extra class(es) for the content tag.
     */
    show: function (message, options) {
        // Assigning defaults
        if (typeof options === 'undefined') {
            options = {};
        }
        if (typeof message === 'undefined') {
            message = 'Loading';
        }
        var settings = $.extend({
            headerText: '',
            headerSize: 3,
            headerClass: '',
            dialogSize: 'm',
            progressType: '',
            contentElement: 'p',
            contentClass: 'content',
            onHide: null // This callback runs after the dialog was hidden
        }, options),
        $headerTag, $contentTag;

        $dialog = constructDialog($dialog);

        // Configuring dialog
        $dialog.find('.modal-dialog').attr('class', 'modal-dialog').addClass('modal-' + settings.dialogSize);
        $dialog.find('.progress-bar').attr('class', 'progress-bar');
        if (settings.progressType) {
            $dialog.find('.progress-bar').addClass('progress-bar-' + settings.progressType);
        }

        // Generate header tag
        $headerTag = $('<h' + settings.headerSize + ' />');
        $headerTag.css({ 'margin': 0 });
        if (settings.headerClass) {
            $headerTag.addClass(settings.headerClass);
        }

        // Generate content tag
        $contentTag = $('<' + settings.contentElement + ' />');
        if (settings.contentClass) {
            $contentTag.addClass(settings.contentClass);
        }

        if (settings.headerText === false) {
            $contentTag.html(message);
            $dialog.find('.modal-body').prepend($contentTag);
        }
        else if (settings.headerText) {
            $headerTag.html(settings.headerText);
            $dialog.find('.modal-header').html($headerTag).show();

            $contentTag.html(message);
            $dialog.find('.modal-body').prepend($contentTag);
        }
        else {
            $headerTag.html(message);
            $dialog.find('.modal-header').html($headerTag).show();
        }

        // Adding callbacks
        if (typeof settings.onHide === 'function') {
            $dialog.off('hidden.bs.modal').on('hidden.bs.modal', function () {
                settings.onHide.call($dialog);
            });
        }
        // Opening dialog
        $dialog.modal();
    },
    /**
     * Closes dialog
 */
    hide: function () {
        if (typeof $dialog !== 'undefined') {
            $dialog.modal('hide');
        }
    }
};
}));

我正在使用 NPOI 在代码隐藏(简化功能)中创建 Excel 文件:

using NPOI.SS.UserModel;
using NPOI.XSSF.UserModel;

protected void exportExcel()
{
    XSSFWorkbook wb = new XSSFWorkbook();

    XSSFSheet sh = (XSSFSheet)wb.CreateSheet("Legend");

    //*****************************************
    //* Workbook Download & Cleanup
    //*****************************************
    MemoryStream stream = new MemoryStream();
    wb.Write(stream);
    stream.Dispose();

    var xlsBytes = stream.ToArray();
    string filename = "Behavior Stats YTD.xlsx";

    MemoryStream newStream = new MemoryStream(xlsBytes);

    HttpContext.Current.Response.ContentType = "application/octet-stream";
    HttpContext.Current.Response.ContentEncoding = System.Text.Encoding.UTF8;
    HttpContext.Current.Response.AddHeader("content-disposition", "attachment; filename=" + filename);
    HttpContext.Current.Response.BinaryWrite(xlsBytes);
    HttpContext.Current.Response.Flush();
    HttpContext.Current.Response.End();
}

这会创建 Excel 文件并将其推送给用户,但后面代码的生命周期不会继续——它会在 End 命令后立即停止。如果我注释掉 HttpContext 行,显然 Excel sheet 不会被创建,但页面的生命周期仍在继续——后面的其余代码运行、页面刷新和模态请稍候对话框消失。

我是不是用错了?我见过的大多数关于如何导出的示例都使用此方法。有没有其他更干净 and/or 更安全的导出方式?我需要对此做一个简单的调整,让生命周期继续下去吗?谁创造了液体肥皂,为什么?

如果您能提供任何帮助,我们将不胜感激。

一切正常。您告诉浏览器响应是一个文件。响应一次只能是一件事。您不能在同一响应中包含页面内容和文件。

您可以通过在 IFrame 中下载文件来将两者分开。首先将您的文件下载 C# 代码放在其自己的页面中。然后使用 JavaScript 函数从 IFrame 调用该页面。

function DownloadExcel() {
    var downloadFrame = document.createElement("IFRAME");

    if (downloadFrame != null) {
        downloadFrame.setAttribute("src", '/DownloadExcel.aspx');
        downloadFrame.style.width = "0px";
        downloadFrame.style.height = "0px";
        document.body.appendChild(downloadFrame);
    }
}