2009-06-25 37 views
55

Para una aplicación en la que estoy trabajando, debo permitir que el usuario cargue archivos muy grandes, es decir, potencialmente muchos gigabytes, a través de nuestro sitio web. Desafortunadamente, ASP.NET MVC parece cargar toda la solicitud en la memoria RAM antes de comenzar a repararla, lo cual no es exactamente lo ideal para una aplicación de este tipo. En particular, tratando de eludir el problema a través de un código como el siguiente:Transmisión de archivos de gran tamaño a 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); 
     } 
    } 
} 

falla para eludir el buffer-la-petición-en-RAM mentalidad. ¿Hay una manera fácil de evitar este comportamiento?

+1

¿Dónde su código residen en su aplicación? ¿Controlador? ¿entrenador de animales? ¿módulo? –

+0

Sé que esta pregunta es antigua, pero es una muy buena pregunta que estoy usando como referencia para un proyecto en el que estoy trabajando. Pregunta rápida: ¿qué se supone que denotará "cb" en su variable cbRead? – KSwift87

+0

@ KSwift87 'cb' significa" Count of Bytes ", que es la notación húngara estándar de Apps en Fog Creek. –

Respuesta

23

Resulta que mi código inicial era básicamente correcto; el único cambio necesario era cambiar

request.ContentLength = clientRequest.InputStream.Length; 

a

request.ContentLength = clientRequest.ContentLength; 

Los antiguos arroyos en toda la solicitud para determinar la longitud del contenido; este último simplemente verifica el encabezado Content-Length, que solo requiere que los encabezados se hayan enviado completos. Esto permite a IIS comenzar a transmitir la solicitud casi de inmediato, lo que elimina por completo el problema original.

+1

Entonces, básicamente, simplemente tocar la propiedad InputStream vence el almacenamiento en búfer de tu disco. ¿Es posible obtener un identificador de flujo para el archivo de buffer de disco? –

+2

No he podido determinarlo, pero dejé de investigar seriamente esto una vez que obtuve algo que funcionó. –

14

Claro, puedes hacer esto. Ver RESTful file uploads with HttpWebRequest and IHttpHandler. He estado usando este método por algunos años y tengo un sitio que ha sido probado con archivos de al menos varios gigabytes. Básicamente, desea crear su propio IHttpHandler, que es más fácil de lo que parece.

En pocas palabras, se crea una clase que implementa la interfaz IHttpHandler , lo que significa que tiene que soportar la propiedad IsReusable y el método ProcessRequest. Además de eso, hay un pequeño cambio en su web.config, y funciona como un encanto. En esta etapa del ciclo de vida de la solicitud, el archivo completo que se está cargando no se carga en la memoria, por lo que ordena cuidadosamente los problemas de memoria.

Tenga en cuenta que en el web.config,

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

el archivo referenciado, DocumentUploadService.upl, en realidad no existe. Solo está ahí para dar una extensión alternativa para que la solicitud no sea interceptada por el controlador estándar. Dirige su formulario de carga de archivos a esa ruta, pero luego su clase FileUploadHandler se activa y realmente recibe el archivo.

Actualización: En realidad, el código que uso es diferente de ese artículo, y creo que tropecé con el motivo por el que funciona. Uso la clase HttpPostedFile, en la cual "Los archivos se cargan en formato MIME multipart/form-data. De forma predeterminada, todas las solicitudes, incluidos los campos de formulario y los archivos cargados, mayores de 256 KB se guardan en el disco, en lugar de en la memoria del servidor "

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

Tu código se ve casi idéntico al mío; la única diferencia es que lo estoy ejecutando en ASP.NET MVC, y lo está ejecutando directamente en un nuevo IHttpHandler. ¿Sabes por qué hay una diferencia? ¿Hay algo en mi código que fuerce la transmisión? –

+0

ver mi actualización, arriba – RedFilter

+0

¿Cómo está mostrando el progreso de carga? –

Cuestiones relacionadas