50

I have a DataTable that i want to convert it to xml and then zip it, using DotNetZip. finally user can download it via Asp.Net webpage. My code in below

    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();

the xml file in zip file is empty.

Navid Farhadi
  • 3,397
  • 2
  • 28
  • 34

8 Answers8

71

2 things. First, if you keep the code design you have, you need to perform a Seek() on the MemoryStream before writing it into the entry.

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); 
}

Even if you keep this design, I would suggest a using() clause, as I have shown, and as described in all the DotNetZip examples, in lieu of calling Dispose(). The using() clause is more reliable in the face of failures.

Now you may wonder, why is it necessary to seek in the MemoryStream before calling AddEntry()? The reason is, AddEntry() is designed to support those callers who pass a stream where the position is important. In that case, the caller needs the entry data to be read from the stream, using the current position of the stream. AddEntry() supports that. Therefore, set the position in the stream before calling AddEntry().

But, the better option is to modify your code to use the overload of AddEntry() that accepts a WriteDelegate. It was designed specifically for adding datasets into zip files. Your original code writes the dataset into a memory stream, then seeks on the stream, and writes the content of the stream into the zip. It's faster and easier if you write the data once, which is what the WriteDelegate allows you to do. The code looks like this:

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); 
}

This writes the dataset directly into the compressed stream in the zipfile. Very efficient! No double-buffering. The anonymous delegate is called at the time of ZipFile.Save(). Only one write (+compress) is performed.

Grhm
  • 6,726
  • 4
  • 40
  • 64
Cheeso
  • 189,189
  • 101
  • 473
  • 713
6

Why did you not close the MemoryStream, I would wrap that in a using clause, the same could be said for zipFile? Also dt I presume is a DataTable...put in error checking to see if there's rows, see the code below...

    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: Having looked at this again, and realizing that Ionic.Ziplib from Codeplex was used, I changed the code slightly, instead of zipFile.Save(Response.OutputStream); I used zipFile.Save(stream); using the stream instance of the MemoryStream class and write it out using Response.Write(stream);.

Edit#2: Thanks to Cheeso + Mikael for pointing out the obvious flaw - I missed it a mile off and did not understood their comment until I realized that the stream was at the end...

t0mm13b
  • 34,087
  • 8
  • 78
  • 110
  • Useful hints, but the key missing point is that the original code needs to Seek() to the beginning of the MemoryStream, before calling AddEntry(). – Cheeso Feb 15 '10 at 17:40
  • I say it because it's true. The cursor on the MemoryStream is at EOS when AddEntry() is called in the original (non-working) code. It needs to be at the beginning-of-stream in order for AddEntry() to successfully read the content in the stream as intended. AddEntry does not seek back in the stream before reading it. The caller must prepare the stream and set the cursor as needed. – Cheeso Feb 15 '10 at 20:09
  • Like @cheeso say. Set the Position of the memstream to 0 before writing it out. – Mikael Svenson Feb 15 '10 at 20:32
  • @Cheeso, @Mikael: AHHHHH!!! Thanks for the heads up - I missed that, yes. You are both correct!!!! But...wouldn't that render the dt.WriteXml(stream) useless then if the stream was seek'd back to the beginning? ie. it would have been overwritten...? – t0mm13b Feb 15 '10 at 21:21
  • dt.WriteXml() writes into the stream. After you call it, the position is at end-of-stream. Reading from such a stream will get you nothing. If you then seek back, to position 0, and try to read the stream, you will get all the content you have written via WriteXml. Even so, this is the wrong way to store a DataSet into a ZIP file. There's a much easier way, without the intermediate stream. http://stackoverflow.com/questions/2266204/create-zip-file-from-stream-and-download-it/2267750#2267750 – Cheeso Feb 15 '10 at 22:33
1

Have you tried to flush the stream before zipping?

dt.WriteXml(stream);
stream.Flush();
ZipFile zipFile = new ZipFile();
jmservera
  • 6,454
  • 2
  • 32
  • 45
1

Ok. It doesn't seem like we are getting very far here so you need to start debugging this a bit more.

Update you're code to do the following:

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

See if you have a valid XML file out. If you do then move on do the same with your ZipFile. Save it to a local file. See if it's there, has your xml file and your xml file has content in it.

If that works, try sending back just the memory stream as the response, see if that works.

You should then be able to track the problem down further.

Ian
  • 33,605
  • 26
  • 118
  • 198
  • You are correct; all that is needed is a Seek() on the MemoryStream, between writing and reading. – Cheeso Feb 15 '10 at 17:39
0

Add a ContentType header:

Response.ContentType = "application/zip";

this will allow the browsers to detect what you are sending.

Mikael Svenson
  • 39,181
  • 7
  • 73
  • 79
0

Double check the stream you are returning back too. In your example below

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

You are saving the zipFile to your response stream using the Save method, but then you are also calling Response.Write() with a zipstream variable. What is zipstream? Check that it isn't an empty stream too.

Ian
  • 33,605
  • 26
  • 118
  • 198
0

This code will help you in downloading a file from 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);
    }
}

Namespaces needed:

using System.IO.Compression;
using System.IO.Compression.ZipArchive;
Ghost4Man
  • 1,040
  • 1
  • 12
  • 19
Skull
  • 1,204
  • 16
  • 28
0

Creating a zip file from stream and downloading it. Below is the 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());
Tunaki
  • 132,869
  • 46
  • 340
  • 423
Sheo Dayal Singh
  • 1,591
  • 19
  • 11