FileResult buffered to memory
Asked Answered
S

2

5

I'm trying to return large files via a controller ActionResult and have implemented a custom FileResult class like the following.

    public class StreamedFileResult : FileResult
{
    private string _FilePath;

    public StreamedFileResult(string filePath, string contentType)
        : base(contentType)
    {
        _FilePath = filePath;
    }

    protected override void WriteFile(System.Web.HttpResponseBase response)
    {
        using (FileStream fs = new FileStream(_FilePath, FileMode.Open, FileAccess.Read))
        {
            int bufferLength = 65536;
            byte[] buffer = new byte[bufferLength];
            int bytesRead = 0;

            while (true)
            {
                bytesRead = fs.Read(buffer, 0, bufferLength);

                if (bytesRead == 0)
                {
                    break;
                }

                response.OutputStream.Write(buffer, 0, bytesRead);
            }
        }
    }
}

However the problem I am having is that entire file appears to be buffered into memory. What would I need to do to prevent this?

Schrader answered 17/9, 2012 at 17:24 Comment(2)
Why don't you use the existing FileStreamResult?Inconsistent
I initially tried using the FileStreamResult but it also buffers the file into memory.Schrader
A
9

You need to flush the response in order to prevent buffering. However if you keep on buffering without setting content-length, user will not see any progress. So in order for users to see proper progress, IIS buffers entire content, calculates content-length, applies compression and then sends the response. We have adopted following procedure to deliver files to client with high performance.

FileInfo path = new FileInfo(filePath);

// user will not see a progress if content-length is not specified
response.AddHeader("Content-Length", path.Length.ToString());
response.Flush();// do not add anymore headers after this...


byte[] buffer = new byte[ 4 * 1024 ]; // 4kb is a good for network chunk

using(FileStream fs = path.OpenRead()){
   int count = 0;
   while( (count = fs.Read(buffer,0,buffer.Length)) >0 ){
      if(!response.IsClientConnected) 
      {
          // network connection broke for some reason..
          break;
      }
      response.OutputStream.Write(buffer,0,count);
      response.Flush(); // this will prevent buffering...
   }
}

You can change buffer size, but 4kb is ideal as lower level file system also reads buffer in chunks of 4kb.

Aldehyde answered 17/9, 2012 at 17:47 Comment(0)
C
0

Akash Kava is partly right and partly wrong. You DO NOT need to add the Content-Length header or do the flush afterward. But you DO, need to periodically flush response.OutputStream and then response. ASP.NET MVC (at least version 5) will automatically convert this into a "Transfer-Encoding: chunked" response.

byte[] buffer = new byte[ 4 * 1024 ]; // 4kb is a good for network chunk

using(FileStream fs = path.OpenRead()){
   int count = 0;
   while( (count = fs.Read(buffer,0,buffer.Length)) >0 ){
      if(!response.IsClientConnected) 
      {
          // network connection broke for some reason..
          break;
      }
      response.OutputStream.Write(buffer,0,count);
      response.OutputStream.Flush();
      response.Flush(); // this will prevent buffering...
   }
}

I tested it and it works.

Cinderellacindi answered 4/4, 2016 at 18:32 Comment(1)
Without Content-Length, browser will not show progress because it does not know how many bytes are there to download, chunked encoding just tells client that there is still more content, but not how much. So if you have huge file, and browser just keeps on receiving chunks, it will never show progress %.Aldehyde

© 2022 - 2024 — McMap. All rights reserved.