2009-06-25 6 views
55

Pour une application sur laquelle je travaille, je dois autoriser l'utilisateur à télécharger des fichiers très volumineux, c'est-à-dire potentiellement plusieurs gigaoctets, via notre site Web. Malheureusement, ASP.NET MVC semble charger la totalité de la requête dans la RAM avant de commencer à la réparer - ce qui n'est pas idéal pour une telle application. Notamment, en essayant de contourner le problème via le code tel que le suivant:Diffusion de gros fichiers en amont sur 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); 
     } 
    } 
} 

ne parvient pas à contourner le tampon-la-demande-en-RAM mentalité. Existe-t-il un moyen facile de contourner ce problème?

+1

Où réside votre code dans votre application? Manette? gestionnaire? module? –

+0

Je sais que cette question est ancienne, mais c'est une très bonne question que j'utilise comme référence pour un projet sur lequel je travaille. Question rapide - quel est le "cb" censé indiquer dans votre variable cbRead? – KSwift87

+0

@ KSwift87 'cb' signifie" Count of Bytes ", qui est la notation standard des applications hongroises à Fog Creek. –

Répondre

23

Il s'avère que mon code initial était fondamentalement correct; la seule modification nécessaire est de changer

request.ContentLength = clientRequest.InputStream.Length; 

à

request.ContentLength = clientRequest.ContentLength; 

Les anciens cours d'eau dans toute la demande pour déterminer la longueur de contenu; ce dernier vérifie simplement l'en-tête Content-Length, qui exige seulement que les en-têtes aient été envoyés dans leur intégralité. Cela permet à IIS de commencer à diffuser la requête presque immédiatement, ce qui élimine complètement le problème d'origine.

+1

Ainsi, le simple fait de toucher simplement la propriété InputStream annule la mise en mémoire tampon de votre disque. Est-il possible d'obtenir un handle de flux dans le fichier protégé par le disque? –

+2

Ce n'est pas ce que j'ai pu déterminer, mais j'ai arrêté sérieusement d'enquêter là-dessus une fois que j'ai obtenu quelque chose qui fonctionnait. –

14

Bien sûr, vous pouvez le faire. Voir RESTful file uploads with HttpWebRequest and IHttpHandler. J'utilise cette méthode depuis quelques années et j'ai un site qui a été testé avec des fichiers d'au moins plusieurs gigaoctets. Essentiellement, vous voulez créer votre propre IHttpHandler, ce qui est plus facile que ça en a l'air. En résumé, vous créez une classe qui implémente l'interface IHttpHandler , ce qui signifie que vous devez prendre en charge la propriété IsReusable et la méthode ProcessRequest. En plus de cela, il y a un changement mineur à votre web.config, et cela fonctionne comme un charme. À ce stade du cycle de vie de la requête, le fichier entier en cours de téléchargement n'est pas chargé en mémoire, ce qui permet d'éviter les problèmes de mémoire.

Notez que dans le web.config,

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

le fichier référencé, DocumentUploadService.upl, n'existe pas en réalité. C'est juste là pour donner une autre extension afin que la requête ne soit pas interceptée par le gestionnaire standard. Vous pointez votre formulaire de téléchargement de fichier sur ce chemin, mais votre classe FileUploadHandler entre et reçoit le fichier.

Mise à jour: En fait, le code que je l'utilise est différent de cet article, et je pense que je suis tombé sur la raison pour laquelle il travaille. J'utilise la classe HttpPostedFile, dans laquelle "Les fichiers sont téléchargés au format MIME multipart/form-data Par défaut, toutes les requêtes, y compris les champs de formulaire et les fichiers téléchargés, supérieures à 256 Ko sont stockées sur le disque plutôt que dans la mémoire du serveur "

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

Votre code semble presque identique au mien; la seule différence est que je l'exécute dans ASP.NET MVC, et que vous l'exécutez directement dans un nouveau IHttpHandler. Savez-vous pourquoi il y a une différence? Est-ce qu'il y a quelque chose dans mon code qui force le flux à être tiré? –

+0

voir ma mise à jour ci-dessus – RedFilter

+0

Comment affichez-vous la progression du téléchargement? –