Создание Zip-файла из потока и загрузка его - PullRequest
45 голосов
/ 15 февраля 2010

У меня есть DataTable, который я хочу преобразовать в XML, а затем сжать, используя DotNetZip. наконец, пользователь может загрузить его через веб-страницу Asp.Net. Мой код ниже

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

XML-файл в zip-файле пуст.

Ответы [ 8 ]

69 голосов
/ 15 февраля 2010

2 вещи. Во-первых, если вы сохраняете дизайн кода, который вам нужен, вам нужно выполнить Seek () для MemoryStream перед записью его в запись.

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

Даже если вы сохраните этот дизайн, я бы предложил предложение using (), как я показал, и как описано во всех примерах DotNetZip вместо вызова Dispose (). Предложение using () более надежно в случае сбоев.

Теперь вы можете задаться вопросом, зачем искать в MemoryStream перед вызовом AddEntry ()? Причина в том, что AddEntry () предназначен для поддержки тех вызывающих абонентов, которые передают поток, где важна позиция. В этом случае вызывающей стороне необходимо, чтобы входные данные были прочитаны из потока, , используя текущую позицию потока . AddEntry () поддерживает это. Поэтому установите позицию в потоке перед вызовом AddEntry ().

Но лучший вариант - изменить код для использования перегрузки метода AddEntry (), который принимает WriteDelegate . Он был разработан специально для добавления наборов данных в zip-файлы. Ваш исходный код записывает набор данных в поток памяти, затем ищет поток и записывает содержимое потока в zip. Это быстрее и проще, если вы записываете данные один раз, что позволяет вам делать WriteDelegate. Код выглядит так:

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

Это записывает набор данных непосредственно в сжатый поток в zip-файле. Очень эффективный! Нет двойной буферизации. Анонимный делегат вызывается во время ZipFile.Save (). Выполняется только одна запись (+ сжатие).

6 голосов
/ 15 февраля 2010

Почему вы не закрыли MemoryStream, я бы заключил это в предложение using, то же самое можно сказать и о zipFile? Также dt Я предполагаю, что это DataTable ... вставлен в проверку ошибок, чтобы увидеть, есть ли строки, см. Код ниже ...

    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...

Редактировать: Еще раз посмотрев на это и поняв, что был использован Ionic.Ziplib из Codeplex, я немного изменил код, вместо zipFile.Save(Response.OutputStream); я использовал zipFile.Save(stream); используя stream экземпляр класса MemoryStream и запишите его, используя Response.Write(stream);.

Редактировать # 2: Спасибо Cheeso + Микаэль за указание на очевидный недостаток - I пропустил это за милю и не понял их комментарий, пока я не понял, что поток был в конце ...

1 голос
/ 15 февраля 2010

Хорошо. Не похоже, что мы продвинулись слишком далеко, поэтому вам нужно начать отлаживать это немного подробнее.

Обновите код, сделав следующее:

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

Проверьте, есть ли у вас действительный файл XML. Если вы это сделаете, то продолжайте делать то же самое с вашим ZipFile. Сохраните его в локальном файле. Посмотрите, есть ли там ваш xml-файл, а ваш xml-файл содержит содержимое.

Если это работает, попробуйте отправить обратно только поток памяти в качестве ответа, посмотрите, работает ли он.

Затем вы сможете отследить проблему дальше.

1 голос
/ 15 февраля 2010

Вы пытались очистить поток перед архивированием?

dt.WriteXml(stream);
stream.Flush();
ZipFile zipFile = new ZipFile();
0 голосов
/ 04 сентября 2016

Создание zip-файла из потока и загрузка его. Ниже приведен код.

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 голосов
/ 17 декабря 2015

Этот код поможет вам в загрузке файла из потока.

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

Необходимые пространства имен:

using System.IO.Compression;
using System.IO.Compression.ZipArchive;
0 голосов
/ 15 февраля 2010

Дважды проверьте поток, который вы возвращаете тоже. В вашем примере ниже

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

Вы сохраняете zipFile в свой поток ответов, используя метод Save, но затем вы также вызываете Response.Write () с переменной zipstream. Что такое Zipstream? Убедитесь, что это не пустой поток тоже.

0 голосов
/ 15 февраля 2010

Добавить заголовок ContentType:

Response.ContentType = "application/zip";

это позволит браузерам определить, что вы отправляете.

...