【问题标题】:Creating Zip file from stream and downloading it从流中创建 Zip 文件并下载
【发布时间】:2011-01-16 23:54:08
【问题描述】:

我有一个 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();

zip 文件中的 xml 文件为空。

【问题讨论】:

  • 那么,哪一点不起作用呢? :)
  • zip文件中的xml文件为空。

标签: c# asp.net zip stream dotnetzip


【解决方案1】:

2 件事。首先,如果您保留现有的代码设计,则需要在将 MemoryStream 写入条目之前对其执行 Seek()。

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 examples 中所描述的那样,代替调用 Dispose()。 using() 子句在遇到故障时更可靠。

现在您可能想知道,为什么在调用 AddEntry() 之前需要在 MemoryStream 中查找?原因是,AddEntry() 旨在支持那些传递位置很重要的流的调用者。在这种情况下,调用者需要从流中读取条目数据,使用流的当前位置。 AddEntry() 支持这一点。因此,在调用 AddEntry() 之前设置流中的位置。

但是,更好的选择是修改您的代码以使用overload of AddEntry() that accepts a 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() 时被调用。仅执行一次写入(+压缩)。

【讨论】:

  • @Cheeso : zipFile.AddEntry 没有这个重载!
  • @MeysamJavadi - 在 DotNetZip 库的 v1.9 中确实如此。我承诺。检查 .chm 文件。
【解决方案2】:

您为什么不关闭 MemoryStream,我会将其包装在 using 子句中,zipFile 也可以这样说?还有dt我猜是一个DataTable...进行错误检查以查看是否有行,请参见下面的代码...

dt.TableName = "声明"; if (dt.Rows != null && dt.Rows.Count >= 1){ 使用 (MemoryStream 流 = new MemoryStream()){ dt.WriteXml(流); // 谢谢奶酪/米凯尔 stream.Seek(0, SeekOrigin.Begin); // 使用 (ZipFile zipFile = new ZipFile()){ zipFile.AddEntry("Report.xml", "", 流); Response.ClearContent(); Response.ClearHeaders(); Response.AppendHeader("content-disposition", "attachment; filename=Report.zip"); //zipFile.Save(Response.OutputStream); zipFile.Save(流); // 注释掉这个 /* Response.Write(zipstream); //

编辑: 再看一遍,意识到使用了来自 Codeplex 的 Ionic.Ziplib,我稍微更改了代码,而不是 zipFile.Save(Response.OutputStream); 我使用 zipFile.Save(stream); 使用 stream MemoryStream 类的实例并使用 Response.Write(stream); 将其写出来。

Edit#2:感谢 Cheeso + Mikael 指出明显的缺陷 - 我错过了一英里,直到我意识到流在最后才理解他们的评论......

【讨论】:

  • 有用的提示,但关键缺少的一点是,在调用 AddEntry() 之前,原始代码需要 Seek() 到 MemoryStream 的开头。
  • 我这么说是因为这是真的。当在原始(非工作)代码中调用 AddEntry() 时,MemoryStream 上的光标位于 EOS。它需要位于流的开头,以便 AddEntry() 按预期成功读取流中的内容。 AddEntry 在读取之前不会在流中回溯。调用者必须准备流并根据需要设置光标。
  • 就像@cheeso 说的那样。在写出之前将 memstream 的 Position 设置为 0。
  • @Cheeso,@Mikael:啊啊啊啊!!!感谢您的提醒-我错过了,是的。你俩都对!!!!但是......如果流被寻找回到开头,那不会使 dt.WriteXml(stream) 无用吗? IE。它会被覆盖...?
  • dt.WriteXml() 写入流。调用它后,该位置位于流的末尾。从这样的流中读取将一无所获。如果您随后返回位置 0 并尝试读取流,您将获得通过 WriteXml 写入的所有内容。即便如此,这是将 DataSet 存储到 ZIP 文件中的错误方法。有一个更简单的方法,没有中间流。 stackoverflow.com/questions/2266204/…
【解决方案3】:

您是否尝试在压缩之前刷新流?

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

【讨论】:

    【解决方案4】:

    好的。看起来我们在这里并没有走得太远,所以你需要开始更多地调试它。

    更新您的代码以执行以下操作:

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

    看看你是否有一个有效的 XML 文件。如果你这样做了,那么继续对你的 ZipFile 做同样的事情。将其保存到本地文件。看看它是否在那里,有你的 xml 文件并且你的 xml 文件中有内容。

    如果可行,请尝试仅将内存流作为响应发回,看看是否可行。

    然后您应该能够进一步跟踪问题。

    【讨论】:

    • 你是对的;所需要的只是 MemoryStream 上的 Seek(),介于写入和读取之间。
    【解决方案5】:

    添加 ContentType 标头:

    Response.ContentType = "application/zip";
    

    这将允许浏览器检测您发送的内容。

    【讨论】:

      【解决方案6】:

      仔细检查您要返回的流。在你下面的例子中

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

      您正在使用 Save 方法将 zipFile 保存到您的响应流中,但随后您也使用 zipstream 变量调用 Response.Write()。什么是压缩流?检查它是否也不是空流。

      【讨论】:

      • 对不起'Response.Write(zipstream);'这行必须是评论。
      【解决方案7】:

      从流中创建一个 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());
      

      【讨论】:

        【解决方案8】:

        此代码将帮助您从流中下载文件。

        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;
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-04-18
          • 1970-01-01
          • 2018-08-17
          • 2014-07-01
          相关资源
          最近更新 更多