0

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.)

neizan
  • 2,291
  • 2
  • 37
  • 52
  • Did you try this? https://stackoverflow.com/questions/41961496/how-do-i-increase-maximum-download-size-in-an-mvc-application – ArhiChief Oct 23 '17 at 19:45

2 Answers2

1

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.

Joel Coehoorn
  • 399,467
  • 113
  • 570
  • 794
  • Thanks. Please see my update. Are you sure the stream becomes invalid when the `WebClient` object is disposed? If so, what could explain that my code is now working? Either way, it may be a moot point, because I think it would be a good idea to refactor so the client class writes the data to the response inside the using block. I didn't like passing the stream back up the stack, then having to dispose of it anyway. Any suggestions? – neizan Oct 23 '17 at 20:32
0

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

Haitham Shaddad
  • 4,336
  • 2
  • 14
  • 19
  • So you're saying that ASP.NET will just read the bytes into memory if I return the stream inside the `using` block? That would seem to contradict my experience here, because I now have a working example that returns outside the using (higher up the stack as mentioned in the update). – neizan Oct 23 '17 at 20:36
  • @neizan are you sure the selected method OpenRead returns Stream not bytes array? – Haitham Shaddad Oct 24 '17 at 04:07
  • Yes, according to the [documentation](https://msdn.microsoft.com/en-us/library/ms144209(v=vs.110).aspx#Anchor_0). – neizan Oct 24 '17 at 12:02