使用MVC下载大文件

时间:2018-07-27 12:57:21

标签: c# asp.net-mvc

我有一个带有实体框架的MVC 5应用程序。我正在实现导出功能以导出用户数据。用户可以选择记录并单击“导出”,然后期望以HTML的形式下载文件。

现在,我已经使该过程适用于少量记录,但是随着文件的增长,该过程将不起作用。我相信应用程序必须有一种方法来流式传输结果,而不是以我的方式构造它。我解释:

在视图中,我执行以下操作:

@using (Html.BeginForm("ExportRecords", "UserData", FormMethod.Post))
{
    @Html.AntiForgeryToken()
    @Html.Hidden("selectedRecordIds", "1;2;3;4")
    <input id="submit" name="submit" type="submit" />
}

在控制器中,我从DB + Azure获取记录,并生成HTML并开始下载:

[ValidateAntiForgeryToken]
public async Task<ActionResult> ExportRecords(string selectedRecordIds)
{
    var selectedRecordsIdList = selectedRecordIds.Split(";".ToCharArray()).ToList();

    var records = await GetRecords(selectedRecordsIdList);

    var recordsAsHtml = await Helper.GenerateHtml(records);

    return File(Encoding.ASCII.GetBytes(recordsAsHtml),System.Net.Mime.MediaTypeNames.Application.Octet, "ExportedFile.html");}

现在,GenerateHtml方法的简化版本如下:

public static async Task<string> GenerateHtml(List<Records> records)
{
    var stringWriter = new StringWriter();
    using (var writer = new HtmlTextWriter(stringWriter))
    {
        foreach (var record in records)
        {
           writer.Indent++; 
           writer.Write("<tr>");
           writer.WriteLine();
            ......
        }
    }

    // This could cause "Out of Memory Exception"
    var htmlContent = stringWriter.ToString();

    return htmlContent;
}

因此,使用上面的代码,我在foreach周围放置了一些for循环,以模拟有成千上万条记录的情况,很快我就遇到了内存不足异常。

那么导出大量数据的最佳方法是什么?

谢谢您的帮助。

1 个答案:

答案 0 :(得分:0)

好几个小时的试用后,我认为我有一个合理的解决方案。基本上,我要解决的问题是将稳定的字符流写入要作为文件下载到用户计算机/浏览器的流。现在,我要做的就是将我的字符串当时以块的形式写入MemoryStream,然后将MemoryStream写入Response,然后刷新MemoryStream。我在一个阶段模拟了超过2Gig的文件,并且RAM并没有增加并且一直稳定到最后。下面的代码显示了我的意思:

    public ActionResult Download()
    {
        Response.Clear();
        Response.BufferOutput = false;
        Response.ContentType = "text/html";
        Response.AddHeader("Content-Disposition", "attachment; filename=records.html");

        var subString = "";
        for (var i = 0; i < 500000000; i++)
        {
            subString += "<sometag></sometag>";

            if (subString.Length > 100)
            {
                var byteArray = Encoding.ASCII.GetBytes(subString);
                var stream = new MemoryStream(byteArray);

                Response.BinaryWrite(stream.ToArray());

                stream.Flush();
                stream.Close();
                stream.Dispose();

                subString = string.Empty;
            }
        }

        Response.Flush();
        Response.Close();
        Response.End();

        return null;
    }

这可能会帮助其他人。