Question

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?

Was it helpful?

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.

OTHER TIPS

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top