Потоковая загрузка больших файлов в ASP.NET MVC - PullRequest
58 голосов
/ 26 июня 2009

Для приложения, над которым я работаю, мне нужно разрешить пользователю загружать очень большие файлы - то есть потенциально много гигабайт - через наш веб-сайт. К сожалению, ASP.NET MVC, по-видимому, загружает весь запрос в оперативную память перед началом его обслуживания - не совсем идеально для такого приложения. В частности, пытаясь обойти проблему с помощью кода, такого как следующий:

if (request.Method == "POST")
{
    request.ContentLength = clientRequest.InputStream.Length;
    var rgbBody = new byte[32768];

    using (var requestStream = request.GetRequestStream())
    {
        int cbRead;
        while ((cbRead = clientRequest.InputStream.Read(rgbBody, 0, rgbBody.Length)) > 0)
        {
            fileStream.Write(rgbBody, 0, cbRead);
        }
    }
}

не в состоянии обойти менталитет "буфер-запрос-в-RAM". Есть ли простой способ обойти это поведение?

Ответы [ 2 ]

25 голосов
/ 11 августа 2009

Оказывается, мой исходный код был в основном правильным; единственное изменение, которое требовалось, было изменить

request.ContentLength = clientRequest.InputStream.Length;

до

request.ContentLength = clientRequest.ContentLength;

Прежние потоки во всем запросе для определения длины контента; последний просто проверяет заголовок Content-Length, который требует только того, чтобы заголовки были отправлены полностью. Это позволяет IIS начать потоковую передачу запроса практически сразу, что полностью устраняет исходную проблему.

17 голосов
/ 26 июня 2009

Конечно, вы можете сделать это. См. Загрузка файлов RESTful с помощью HttpWebRequest и IHttpHandler . Я использую этот метод в течение нескольких лет, и у меня есть сайт, который был протестирован с файлами размером не менее нескольких гигабайт. По сути, вы хотите создать свой собственный IHttpHandler, который проще, чем кажется.

В двух словах, вы создаете класс, который реализует IHttpHandler интерфейс, что означает, что вы должны поддерживать свойство IsReusable и метод ProcessRequest. Кроме того, в вашем файле web.config есть небольшое изменение, и оно работает как шарм. На этом этапе жизненного цикла запроса весь загружаемый файл не загружается в память, поэтому он аккуратно обходит проблемы с памятью.

Обратите внимание, что в файле web.config,

<httpHandlers>
 <add verb="*" path="DocumentUploadService.upl" validate="false" type="TestUploadService.FileUploadHandler, TestUploadService"/>
</httpHandlers>

файл, на который ссылается DocumentUploadService.upl, фактически не существует. Это просто для того, чтобы дать альтернативное расширение, чтобы запрос не был перехвачен стандартным обработчиком. Вы указываете форму загрузки файла на этот путь, но затем ваш класс FileUploadHandler запускается и фактически получает файл.

Обновление: На самом деле, код, который я использую, отличается от той статьи, и я думаю, что наткнулся на причину, по которой он работает. Я использую класс HttpPostedFile , в котором файлы "загружаются в формате MIME multipart / form-data. По умолчанию все запросы, включая поля формы и загруженные файлы размером более 256 КБ, буферизуются в диск, а не хранится в памяти сервера. "

if (context.Request.Files.Count > 0)
{
    string tempFile = context.Request.PhysicalApplicationPath;
    for(int i = 0; i < context.Request.Files.Count; i++)
    {
        HttpPostedFile uploadFile = context.Request.Files[i];
        if (uploadFile.ContentLength > 0)
        {
            uploadFile.SaveAs(string.Format("{0}{1}{2}",
              tempFile,"Upload\\", uploadFile.FileName));
        }
    }
}
...