web-dev-qa-db-fra.com

Streaming de gros fichiers téléchargés vers ASP.NET MVC

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 l'intégralité de la demande dans RAM avant de commencer à le réparer - ce n'est pas vraiment idéal pour une telle application. Notamment, essayer de contourner le problème via un code tel que Suivant:

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 la mentalité de buffer-the-request-into-RAM. Existe-t-il un moyen simple de contourner ce problème?

59
Benjamin Pollack

Il s'avère que mon code initial était fondamentalement correct; le seul changement requis était de changer

request.ContentLength = clientRequest.InputStream.Length;

à

request.ContentLength = clientRequest.ContentLength;

Le premier flux dans toute la demande pour déterminer la longueur du contenu; ce dernier vérifie simplement le Content-Length en-tête, qui nécessite uniquement que les en-têtes aient été envoyés en entier. Cela permet à IIS de commencer à diffuser la requête presque immédiatement, ce qui élimine complètement le problème d'origine.

25
Benjamin Pollack

Bien sûr, vous pouvez le faire. Voir téléchargement de fichiers RESTful avec HttpWebRequest et 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 qu'il n'y paraît.

En un mot, 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 dans votre web.config, et cela fonctionne comme un charme. À ce stade du cycle de vie de la demande, le fichier entier en cours de téléchargement n'est pas chargé en mémoire, il évite donc parfaitement 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 réellement. C'est juste là pour donner une extension alternative afin que la demande ne soit pas interceptée par le gestionnaire standard. Vous pointez votre formulaire de téléchargement de fichier vers ce chemin, mais votre classe FileUploadHandler entre en action et reçoit réellement le fichier.

Mise à jour: En fait, le code que j'utilise est différent de cet article, et je pense que je suis tombé sur la raison pour laquelle cela fonctionne. 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 demandes, y compris les champs de formulaire et les fichiers téléchargés, sont supérieures à 256 Ko. tamponné sur le disque, plutôt que conservé 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));
        }
    }
}
17
RedFilter