web-dev-qa-db-fra.com

Pourquoi les fichiers .docx sont-ils corrompus lors du téléchargement à partir d'une page ASP.NET?

J'ai ce code suivant pour apporter des pièces jointes de page à l'utilisateur:

private void GetFile(string package, string filename)
{
    var stream = new MemoryStream();

    try
    {
        using (ZipFile Zip = ZipFile.Read(package))
        {
            Zip[filename].Extract(stream);
        }
    }
    catch (System.Exception ex)
    {
        throw new Exception("Resources_FileNotFound", ex);
    }

    Response.ClearContent();
    Response.ClearHeaders();
    Response.ContentType = "application/unknown";

    if (filename.EndsWith(".docx"))
    {
        Response.ContentType = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
    }

    Response.AddHeader("Content-Disposition", "attachment;filename=\"" + filename + "\"");
    Response.BinaryWrite(stream.GetBuffer());
    stream.Dispose();
    Response.Flush();
    HttpContext.Current.ApplicationInstance.CompleteRequest();
}

Le problème est que tous les fichiers pris en charge fonctionnent correctement (jpg, gif, png, pdf, doc, etc.), mais les fichiers .docx, une fois téléchargés, sont corrompus et doivent être corrigés par Office pour pouvoir être ouverts.

Au début, je ne savais pas si le problème consistait à décompresser le fichier Zip contenant le fichier .docx. Au lieu de ne placer le fichier de sortie que dans la réponse, je l'ai d'abord sauvegardé et le fichier s'est ouvert correctement. Je connais donc le problème. devrait être à la réponse par écrit.

Savez-vous ce qui peut arriver?

24
Victor Rodrigues

J'ai aussi rencontré ce problème et trouvé la réponse ici: http://www.aspmessageboard.com/showthread.php?t=230778

Il se trouve que le format docx doit avoir Response.End () juste après Response.BinaryWrite.

31
Geoff Tanaka

Lorsque vous stockez un fichier binaire dans SQL Server, gardez à l'esprit qu'un fichier est rempli à la limite de Word la plus proche. Vous pouvez donc éventuellement ajouter un octet supplémentaire à un fichier. La solution consiste à stocker la taille du fichier d'origine dans la base de données lorsque vous stockez le fichier et à l'utiliser pour la longueur devant être transmise à la fonction d'écriture de l'objet Stream. "Stream.Write (bytes (), 0, longueur)". C’est le SEUL moyen fiable d’obtenir la taille de fichier correcte, ce qui est très important pour Office 2007 et les fichiers ultérieurs, qui ne permettent pas l’insertion de caractères supplémentaires à la fin (la plupart des autres types de fichiers comme jpg ne s’en soucient pas).

4
Randall Spychalla

Pour ce que cela vaut, j'ai également rencontré le même problème que celui énuméré ici. Pour moi, le problème était en fait avec le code de téléchargement, pas le code de téléchargement:

    Public Sub ImportStream(FileStream As Stream)
        'Use this method with FileUpload.PostedFile.InputStream as a parameter, for example.
        Dim arrBuffer(FileStream.Length) As Byte
        FileStream.Seek(0, SeekOrigin.Begin)
        FileStream.Read(arrBuffer, 0, FileStream.Length)
        Me.FileImage = arrBuffer
    End Sub

Dans cet exemple, le problème est que je déclare le tableau d'octets arrBuffer avec un octet de taille trop grand. Cet octet nul est ensuite enregistré avec l'image du fichier dans la base de données et reproduit lors du téléchargement. Le code corrigé serait:

        Dim arrBuffer(FileStream.Length - 1) As Byte

Également pour référence, mon code HttpResponse est le suivant:

                context.Response.Clear()
                context.Response.ClearHeaders()
                'SetContentType() is a function which looks up the correct mime type
                'and also adds and informational header about the lookup process...
                context.Response.ContentType = SetContentType(objPostedFile.FileName, context.Response)
                context.Response.AddHeader("content-disposition", "attachment;filename=" & HttpUtility.UrlPathEncode(objPostedFile.FileName))
                'For reference: Public Property FileImage As Byte()
                context.Response.BinaryWrite(objPostedFile.FileImage)
                context.Response.Flush()
2
pseudocoder

Vous ne devez pas utiliser stream.GetBuffer() car il renvoie le tableau de mémoire tampon pouvant contenir des octets inutilisés. Utilisez stream.ToArray() à la place. Aussi, avez-vous essayé d'appeler stream.Seek(0, SeekOrigin.Begin) avant d'écrire quoi que ce soit?

Meilleures salutations,
Oliver Hanappi

2
Oliver Hanappi

Si vous utilisez l'approche ci-dessus qui utilise response.Close (), les gestionnaires de téléchargement tels que IE10 diront «impossible de télécharger le fichier» car les longueurs d'octets ne correspondent pas aux en-têtes. Voir la documentation. NE PAS utiliser response.Close. EVER. Toutefois, l’utilisation du verbe CompeteRequest n’arrête pas l’écriture d’octets dans le flux de sortie, de sorte que les applications basées sur XML, telles que Word 2007, verront le document comme corrompu. Dans ce cas, enfreignez la règle pour NE JAMAIS utiliser Response.End. Le code suivant résout les deux problèmes. Vos résultats peuvent varier.

        '*** transfer package file memory buffer to output stream
        Response.ClearContent()
        Response.ClearHeaders()
        Response.AddHeader("content-disposition", "attachment; filename=" + NewDocFileName)
        Me.Response.ContentType = "application/vnd.ms-Word.document.12"
        Response.ContentEncoding = System.Text.Encoding.UTF8
        strDocument.Position = 0
        strDocument.WriteTo(Response.OutputStream)
        strDocument.Close()
        Response.Flush()
        'See documentation at http://blogs.msdn.com/b/aspnetue/archive/2010/05/25/response-end-response-close-and-how-customer-feedback-helps-us-improve-msdn-documentation.aspx
        HttpContext.Current.ApplicationInstance.CompleteRequest() 'This is the preferred method
        'Response.Close() 'BAD pattern. Do not use this approach, will cause 'cannot download file' in IE10 and other download managers that compare content-Header to actual byte count
        Response.End() 'BAD Pattern as well. However, CompleteRequest does not terminate sending bytes, so Word or other XML based appns will see the file as corrupted. So use this to solve it.

@Cesar: vous utilisez response.Close -> pouvez-vous l'essayer avec IE 10? je parie que ça ne marche pas (le nombre d'octets ne correspond pas)

1
PhilM

J'ai eu le même problème pendant que j'essayais d'ouvrir des documents .docx et .xlsx. Je résous le problème en définissant la capacité de mise en cache de ServerAndPrivate au lieu de NoCache

il y a ma méthode pour appeler le document:

public void ProcessRequest(HttpContext context)

 {


       var fi = new FileInfo(context.Request.Path);
        var mediaId = ResolveMediaIdFromName(fi.Name);
        if (mediaId == null) return;

        int mediaContentId;
        if (!int.TryParse(mediaId, out mediaContentId)) return;

        var media = _repository.GetPublicationMediaById(mediaContentId);
        if (media == null) return;

        var fileNameFull = string.Format("{0}{1}", media.Name, media.Extension);
        context.Response.Clear();
        context.Response.AddHeader("content-disposition", string.Format("attachment;filename={0}", fileNameFull));            
        context.Response.Charset = "";
        context.Response.Cache.SetCacheability(HttpCacheability.ServerAndPrivate);
        context.Response.ContentType = media.ContentType;
        context.Response.BinaryWrite(media.Content);
        context.Response.Flush();          
        context.Response.End();          
    }
0
Stephane

Tout semble bien. Ma seule idée est d'essayer d'appeler Dispose sur votre flux après avoir appelé Response.Flush plutôt qu'avant, juste au cas où les octets ne seraient pas entièrement écrits avant le vidage.

0
Ray

Jetez un oeil à ceci: Écriture de MemoryStream dans Response Object

J'ai eu le même problème et la seule solution qui a fonctionné pour moi était:

    Response.Clear();
    Response.ContentType = "Application/msword";
    Response.AddHeader("Content-Disposition", "attachment; filename=myfile.docx");
    Response.BinaryWrite(myMemoryStream.ToArray());
    // myMemoryStream.WriteTo(Response.OutputStream); //works too
    Response.Flush();
    Response.Close();
    Response.End();
0
playful