Search code examples
c#asp.net-mvcfileresult

FileResult buffered to memory


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?


Solution

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