web-dev-qa-db-fra.com

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

J'ai un DataTable que je veux le convertir au xml et ensuite le compresser, en utilisant DotNetZip. Enfin, l'utilisateur peut le télécharger via la page Web Asp.Net . Mon code ci-dessous 

    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.

45
Navid Farhadi

2 choses. Premièrement, si vous conservez la conception de 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 gardez cette structure, je suggérerais une clause using (), comme je l’ai montré et décrite dans tous les 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 en est que AddEntry () est conçu pour prendre en charge les appelants qui transmettent un flux où la position est importante. Dans ce cas, l'appelant a besoin que les données d'entrée soient lues dans le 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 consiste à modifier votre code pour utiliser la surcharge de AddEntry () qui accepte un WriteDelegate . Il a été conçu spécifiquement pour ajouter des jeux de données dans des fichiers Zip. Votre code d'origine écrit le jeu de données dans un flux de mémoire, puis cherche dans le flux et écrit le contenu du flux dans le fichier Zip. C’est plus rapide et plus facile si vous écrivez les données une fois, comme le permet WriteDelegate. 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); 
}

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

69
Cheeso

Pourquoi n'avez-vous pas fermé MemoryStream, je voudrais envelopper cela dans une clause using, on pourrait en dire autant de zipFile? De plus, je suppose que dt est un DataTable ... lors de la vérification des erreurs pour voir s'il y a des lignes, voir le code ci-dessous ...

 dt.TableName = "Déclaration"; 

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

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

 using (ZipFile zipFile = new ZipFile ()) {
 zipFile.AddEntry ("Report.xml", "", flux); 
 Response.ClearContent (); 
 Response.ClearHeaders (); 
 Response.AppendHeader ("content-disposition", "pièce jointe; nom du fichier = Report.Zip"); 

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

 // a commenté ceci 
 /*
 Response.Write (zipstream); // <----- D'où cela vient-il? 
 */
 } 
 Response.Write (flux); 
 } 
 } 
 // Pas de lignes ... ne vous embêtez pas ...

Edit: Après avoir examiné cela à nouveau, et réalisé que Ionic.Ziplib de Codeplex était utilisé, j'ai légèrement modifié le code, au lieu de zipFile.Save(Response.OutputStream);, j'ai utilisé zipFile.Save(stream); en utilisant l'instance stream de la classe MemoryStream en utilisant Response.Write(stream);.

Edit # 2: Merci à Cheeso + Mikael pour avoir signalé le défaut évident - je l'ai raté à un kilomètre et je n'ai pas compris. leur commentaire jusqu'à ce que je réalise que le flux était à la fin ...

6
t0mm13b

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

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

D'accord. Il ne semble pas que nous allions trop loin ici, vous devez donc commencer à le déboguer un peu plus. 

Mettez à jour votre code pour effectuer les opérations suivantes:

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

Voyez si vous avez un fichier XML valide. Si vous le faites, faites de même avec votre ZipFile. Enregistrez-le dans un fichier local. Voir si c'est là, a votre fichier XML et votre fichier XML a un contenu en elle.

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

Vous devriez alors pouvoir suivre le problème plus loin. 

1
Ian

Vérifiez de nouveau le flux que vous retournez aussi. Dans votre exemple ci-dessous

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

Vous enregistrez le fichier zip dans votre flux de réponses à l'aide de la méthode Save, mais vous appelez également Response.Write () avec une variable zipstream. Qu'est-ce que zipstream? Vérifiez que ce n'est pas un flux vide aussi.

0
Ian

Ce code vous aidera à télécharger un fichier depuis stream.

using (var outStream = new MemoryStream())
{
    using (var archive = new ZipArchive(outStream, ZipArchiveMode.Create, true))
    {
        var fileInArchive = archive.CreateEntry("FileName.pdf", CompressionLevel.Optimal);
        using (var entryStream = fileInArchive.Open())
        using (WebResponse response = req.GetResponse())
        {
            using (var fileToCompressStream = response.GetResponseStream())
            {
                fileToCompressStream.CopyTo(entryStream);
            }
        }                       
    }
    using (var fileStream = new FileStream(@"D:\test.Zip", FileMode.Create))
    {
        outStream.Seek(0, SeekOrigin.Begin);
        outStream.CopyTo(fileStream);
    }
}

Espaces de noms nécessaires:

using System.IO.Compression;
using System.IO.Compression.ZipArchive;
0
Skull

Ajoutez un en-tête ContentType:

Response.ContentType = "application/Zip";

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

0
Mikael Svenson

Créer un fichier Zip à partir d'un flux et le télécharger. Ci-dessous le code. 

FileStream stream=File.OpenRead(@"D:\FileDownLoad\DeskTop\1.txt");
MemoryStream MS=new MemoryStream();

ZipOutputStream zipOutputStream = new ZipOutputStream(MS);
zipOutputStream.SetLevel(9);
ZipEntry entry = new ZipEntry("1.txt");
zipOutputStream.PutNextEntry(entry);

byte[] buffer = new byte[stream.Length];
int byteRead = 0;

while ((byteRead = stream.Read(buffer, 0, buffer.Length)) > 0) 
    zipOutputStream.Write(buffer, 0, byteRead);

    zipOutputStream.IsStreamOwner = false;
    stream.Close();
    zipOutputStream.Close();
    MS.Position = 0;

    Response.ContentType = "application/application/octet-stream";
    Response.AppendHeader("content-disposition", "attachment; filename=\"Download.Zip\"");
    Response.BinaryWrite(MS.ToArray());
0
Sheo Dayal Singh