【问题标题】:OutOfMemoryException when returning FileResult with Stream使用 Stream 返回 FileResult 时出现 OutOfMemoryException
【发布时间】:2018-03-25 23:46:28
【问题描述】:

在 ASP.NET MVC 应用程序中,我通过 REST API 从 SharePoint Online 读取文件,并将其显示在 MVC 应用程序的当前浏览器窗口中。我有适用于较小文件的代码,但对于“大文件”(我不知道确切的断点)我得到一个OutOfMemoryException。这是控制器代码:

public ActionResult Index() {
    System.IO.Stream stream = null;
    string fileName = "whatever.ext";
    string restUri = $"https://myhost.sharepoint.com/sites/mysite/_api/Web/GetFileByServerRelativePath('mypath/{fileName}')/openbinarystream";
    string mimeType = MimeMapping.GetMimeMapping(fileName);

    using (var webClient = new WebClient()) {
        webClient.Credentials = myCredentials;
        webClient.Headers.Add("X-FORMS_BASED_AUTH_ACCEPTED", "f");
        var uri = new Uri(restUri);
        stream = webClient.OpenRead(uri);
    }

    Response.AppendHeader("Content-Disposition", "inline; filename=" + fileName);

    return File(stream, mimeType);
}

我查看了 SO 和互联网,但找不到解决方案。

我开始通过方法WebClient.DownloadData 将所有数据读入byte[]。当我得到OutOfMemoryException 时,我认为通过方法WebClient.OpenRead 替换为Stream 会修复它,但我仍然遇到大文件的例外情况。对于较小的文件,行为正是我想要的。如何修复异常?

更新
上面的示例代码不是真正的代码。真正的代码有一个较低级别的客户端类检索流并将其向上传递到调用堆栈,最终传递给控制器​​。但是,客户端类确实使用了using 语句,如示例中所示。如果在方法返回时流关闭,并且代码退出 using 块,那么为什么它适用于较小的文件?此外,我实际上是通过使用缓冲区手动处理流并写入响应来使我的代码工作的。我当前的代码正在关闭finally 块中的流(在控制器操作方法中——仍然从较低级别的客户端类中检索流)。 (注意我回来发布我的新代码作为答案并关闭这个问题,但现在我有点困惑。)

【问题讨论】:

标签: asp.net-mvc


【解决方案1】:

不确定 OutOfMemory 问题,但你有这个:

using (var webClient = new WebClient())
{
    stream = webClient.OpenRead(...);
}
return File(stream, mimeType);

webClient 被释放时,stream 变得无效,除非你有其他我们看不到的东西,它首先将整个内容读入内存......这很容易解释 OutOfMemory 问题。

【讨论】:

  • 谢谢。请看我的更新。当WebClient 对象被释放时,您确定流变为无效吗?如果是这样,什么可以解释我的代码现在可以工作?无论哪种方式,这都可能是一个有争议的问题,因为我认为重构是一个好主意,以便客户端类将数据写入 using 块内的响应。我不喜欢将流传回堆栈,然后无论如何都要处理它。有什么建议吗?
【解决方案2】:

您应该从using 语句中返回。

public ActionResult Index() {
    System.IO.Stream stream = null;
    string fileName = "whatever.ext";
    string restUri = $"https://myhost.sharepoint.com/sites/mysite/_api/Web/GetFileByServerRelativePath('mypath/{fileName}')/openbinarystream";
    string mimeType = MimeMapping.GetMimeMapping(fileName);
 Response.AppendHeader("Content-Disposition", "inline; filename=" + fileName);

    using (var webClient = new WebClient()) {
        webClient.Credentials = myCredentials;
        webClient.Headers.Add("X-FORMS_BASED_AUTH_ACCEPTED", "f");
        var uri = new Uri(restUri);
        stream = webClient.OpenRead(uri);
        return File(stream, mimeType);
    }
}

发生的情况是在 using 语句中读取流,然后在内存中读取整个字节。

在 using 语句结束前返回时,可以将结果流式返回给调用者

【讨论】:

  • 所以你是说如果我返回using 块内的流,ASP.NET 只会将字节读入内存?这似乎与我在这里的经验相矛盾,因为我现在有一个在 using 之外返回的工作示例(更新中提到的更高的堆栈)。
  • @neizan 你确定 OpenRead 选择的方法返回的是 Stream 而不是字节数组吗?
  • 是的,根据documentation
猜你喜欢
  • 1970-01-01
  • 2019-09-29
  • 2012-08-09
  • 2018-03-17
  • 2013-06-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-10-05
相关资源
最近更新 更多