FileResult буферизован в память

Я пытаюсь вернуть большие файлы через контроллер ActionResult и реализовал собственный класс FileResult, как показано ниже.

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

Однако проблема, с которой я столкнулся, заключается в том, что весь файл, кажется, помещается в буфер в памяти. Что мне нужно сделать, чтобы предотвратить это?


person Scott Wilson    schedule 17.09.2012    source источник
comment
Почему бы вам не использовать существующий FileStreamResult?   -  person Erik Funkenbusch    schedule 17.09.2012
comment
Сначала я попытался использовать FileStreamResult, но он также буферизует файл в память.   -  person Scott Wilson    schedule 17.09.2012


Ответы (2)


Вам нужно сбросить ответ, чтобы предотвратить буферизацию. Однако, если вы продолжите буферизацию без установки длины содержимого, пользователь не увидит никакого прогресса. Таким образом, чтобы пользователи могли видеть надлежащий прогресс, IIS буферизует весь контент, вычисляет его длину, применяет сжатие и затем отправляет ответ. Мы приняли следующую процедуру для доставки файлов клиенту с высокой производительностью.

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

Вы можете изменить размер буфера, но 4 КБ является идеальным вариантом, поскольку файловая система нижнего уровня также считывает буфер фрагментами по 4 КБ.

person Akash Kava    schedule 17.09.2012

Акаш Кава частично прав, а частично неправ. Вам НЕ нужно добавлять заголовок Content-Length или выполнять очистку после этого. Но вы ДЕЙСТВИТЕЛЬНО должны периодически промывать response.OutputStream, а затем response. ASP.NET MVC (по крайней мере версия 5) автоматически преобразует это в ответ «Transfer-Encoding: chunked».

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

Я протестировал это, и он работает.

person N73k    schedule 04.04.2016
comment
Без Content-Length браузер не будет показывать прогресс, потому что он не знает, сколько байтов нужно загрузить, фрагментированная кодировка просто сообщает клиенту, что контента еще больше, но не сколько. Так что, если у вас огромный файл, а браузер просто продолжает получать куски, он никогда не покажет прогресс%. - person Akash Kava; 03.06.2016