49 votes

Créer un fichier Zip à partir d'un flux et le télécharger

J'ai un DataTable que je veux convertir en xml et ensuite le zipper, en utilisant DotNetZip. Finalement l'utilisateur peut le télécharger via une page web Asp.Net. Mon code est le suivant

    dt.TableName = "Declaration";

    MemoryStream stream = new MemoryStream();
    dt.WriteXml(stream);

    ZipFile zipFile = new ZipFile();
    zipFile.AddEntry("Report.xml", "", stream);
    Response.ClearContent();
    Response.ClearHeaders();
    Response.AppendHeader("content-disposition", "attachment; filename=Report.zip");

    zipFile.Save(Response.OutputStream);
    //Response.Write(zipstream);
    zipFile.Dispose();

le fichier xml dans le fichier zip est vide.

3 votes

Alors, quelle partie ne fonctionne pas ? :)

0 votes

Le fichier xml dans le fichier zip est vide.

70voto

Cheeso Points 87022

Deux choses. Premièrement, si vous conservez la conception du code que vous avez, vous devez effectuer un Seek() sur le MemoryStream avant de l'écrire dans l'entrée.

dt.TableName = "Declaration"; 

MemoryStream stream = new MemoryStream(); 
dt.WriteXml(stream); 
stream.Seek(0,SeekOrigin.Begin);   // <-- must do this after writing the stream!

using (ZipFile zipFile = new ZipFile())
{
  zipFile.AddEntry("Report.xml", "", stream); 
  Response.ClearContent(); 
  Response.ClearHeaders(); 
  Response.AppendHeader("content-disposition", "attachment; filename=Report.zip"); 

  zipFile.Save(Response.OutputStream); 
}

Même si vous conservez cette conception, je suggérerais l'utilisation d'une clause using(), comme je l'ai montré, et comme décrit dans tous les documents de la série Exemples de DotNetZip au lieu d'appeler Dispose(). La clause using() est plus fiable face aux échecs.

Maintenant, vous vous demandez peut-être pourquoi il est nécessaire de chercher dans le MemoryStream avant d'appeler AddEntry() ? La raison est que AddEntry() est conçu pour aider les appelants qui passent un flux où la position est importante. Dans ce cas, l'appelant a besoin que les données d'entrée soient lues à partir du flux, en utilisant la position actuelle du flux . AddEntry() supporte cela. Par conséquent, définissez la position dans le flux avant d'appeler AddEntry().

Mais, la meilleure option est de modifier votre code afin d'utiliser la fonction surcharge de AddEntry() qui accepte un WriteDelegate . Il a été conçu spécifiquement pour ajouter des ensembles de données dans des fichiers zip. Votre code original écrit le jeu de données dans un flux de mémoire, puis cherche sur le flux et écrit le contenu du flux dans le zip. C'est plus rapide et plus facile si vous écrivez les données une seule fois, ce que le WriteDelegate vous permet de faire. Le code ressemble à ceci :

dt.TableName = "Declaration"; 
Response.ClearContent(); 
Response.ClearHeaders(); 
Response.ContentType = "application/zip";
Response.AppendHeader("content-disposition", "attachment; filename=Report.zip"); 

using(Ionic.Zip.ZipFile zipFile = new Ionic.Zip.ZipFile())
{
    zipFile.AddEntry("Report.xml", (name,stream) => dt.WriteXml(stream) );
    zipFile.Save(Response.OutputStream); 
}

Ceci écrit le jeu de données directement dans le flux compressé du fichier zip. Très efficace ! Pas de double tampon. Le délégué anonyme est appelé au moment de ZipFile.Save(). Une seule écriture (+compression) est effectuée.

0 votes

@Cheeso : zipFile.AddEntry n'a pas cette surcharge !

3 votes

@MeysamJavadi - C'est le cas, dans la version 1.9 de la bibliothèque DotNetZip. C'est promis. Vérifiez le fichier .chm.

6voto

t0mm13b Points 21031

Pourquoi n'avez-vous pas fermé le MemoryStream, je l'aurais enveloppé dans un using la même chose pourrait être dite pour zipFile ? Aussi dt Je suppose qu'il s'agit d'une DataTable... mettez un contrôle d'erreur pour voir s'il y a des lignes, voir le code ci-dessous...

    dt.TableName = "Declaration"; 

    if (dt.Rows != null && dt.Rows.Count >= 1){
      using (MemoryStream stream = new MemoryStream()){
         dt.WriteXml(stream); 

         // Thanks Cheeso/Mikael
         stream.Seek(0, SeekOrigin.Begin);
         //

         using (ZipFile zipFile = new ZipFile()){
             zipFile.AddEntry("Report.xml", "", stream); 
             Response.ClearContent(); 
             Response.ClearHeaders(); 
             Response.AppendHeader("content-disposition", "attachment; filename=Report.zip"); 

             //zipFile.Save(Response.OutputStream); 
             zipFile.Save(stream);

             // Commented this out
             /\*
               Response.Write(zipstream); // <----- Where did that come from?
             \*/
          }
          Response.Write(stream);
       } 
    }
    // No rows...don't bother...

Edit : Ayant regardé à nouveau, et réalisant que Ionic.Ziplib de Codeplex a été utilisé, j'ai légèrement modifié le code, au lieu de zipFile.Save(Response.OutputStream); J'ai utilisé zipFile.Save(stream); en utilisant le stream instance de la MemoryStream et l'écrire en utilisant Response.Write(stream); .

Edit#2 : Merci à Cheeso + Mikael pour avoir souligné le défaut évident - je l'ai manqué de loin et je n'ai pas compris leur commentaire jusqu'à ce que je réalise que le flux était à la fin...

0 votes

Des conseils utiles, mais le principal point manquant est que le code original doit Seek() au début du MemoryStream, avant d'appeler AddEntry().

0 votes

Je le dis parce que c'est vrai. Le curseur sur le MemoryStream est à EOS quand AddEntry() est appelé dans le code original (qui ne fonctionne pas). Il doit être au début du flux pour que AddEntry() puisse lire avec succès le contenu du flux comme prévu. AddEntry ne cherche pas à revenir en arrière dans le flux avant de le lire. L'appelant doit préparer le flux et placer le curseur si nécessaire.

0 votes

Comme le dit @cheeso. Mettez la position du memstream à 0 avant de l'écrire.

1voto

jmservera Points 3239

Avez-vous essayé de vider le flux avant de le compresser ?

dt.WriteXml(stream);
stream.Flush();
ZipFile zipFile = new ZipFile();

1voto

Ian Points 13892

Ok. Il ne semble pas que nous allions très loin ici, vous devez donc commencer à déboguer un peu plus.

Mettez à jour votre code pour faire ce qui suit :

dt.WriteXml(stream);
stream.Seek(0, SeekOrigin.Begin);
File.WriteAllBytes("c:\test.xml", stream.GetBuffer());

Regardez si vous avez un fichier XML valide. Si c'est le cas, continuez en faisant de même avec votre fichier ZipFile. Enregistrez-le dans un fichier local. Regardez s'il est là, s'il contient votre fichier xml et si votre fichier xml a du contenu.

Si cela fonctionne, essayez de renvoyer uniquement le flux de mémoire comme réponse, et voyez si cela fonctionne.

Vous devriez alors être en mesure de mieux cerner le problème.

0 votes

Vous avez raison ; tout ce qui est nécessaire est un Seek() sur le MemoryStream, entre l'écriture et la lecture.

0voto

Mikael Svenson Points 18243

Ajoutez un en-tête ContentType :

Response.ContentType = "application/zip";

cela permettra aux navigateurs de détecter ce que vous envoyez.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X