neizan
neizan

Reputation: 2321

OutOfMemoryException when returning FileResult with Stream

In a ASP.NET MVC app, I am reading a file from SharePoint Online via the REST API and displaying it in the current browser window for the MVC app. I have code that works for smaller files, but for "large files" (I don't know the exact breakpoint) I get an OutOfMemoryException. Here's the controller code:

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);
}

I have looked around SO and the internet, but I cannot find a solution.

I started out reading all the data into a byte[] via method WebClient.DownloadData. When I got the OutOfMemoryException, I thought replacing with a Stream, via method WebClient.OpenRead, would fix it, but I'm still getting the exception for large files. For smaller files, the behavior is just as I want it. How can I fix the exception?

UPDATE
The example code above is not the real code. The real code has a lower level client class retrieves the stream and passes it up the call stack to, eventually, the controller. The client class does use a using statement as shown in the example, though. If the stream is closed when the method returns, and the code exits the using block, then why did it work for smaller files? Furthermore, I actually got my code working by processing the stream manually with a buffer and writing to the response. My current code is closing the stream in a finally block (in the controller action method--still retrieving the stream from lower level client class). (Note I came back to post my new code as the answer and close this question, but now I'm a little confused.)

Upvotes: 0

Views: 461

Answers (2)

Haitham Shaddad
Haitham Shaddad

Reputation: 4456

You should return from within the using statement.

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);
    }
}

What happens is that the stream is read inside the using statement and after it, the whole bulk of bytes is read in the memory.

When you return before the end of the using statement, it can return the results streamed to the caller

Upvotes: 0

Joel Coehoorn
Joel Coehoorn

Reputation: 415665

Not sure about the OutOfMemory issue, but the you have this:

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

That stream becomes invalid when webClient is disposed, unless you have something else we can't see that first reads the entire contents into memory... and that could easily explain the OutOfMemory issue.

Upvotes: 1

Related Questions