14

I am trying to use the following code: I get a corrupted zip file. Why? The file names seem OK. Perhaps they are not relative names, and that's the problem?

      private void trySharpZipLib(ArrayList filesToInclude)
    {
        // Response header
        Response.Clear();
        Response.ClearHeaders();
        Response.Cache.SetCacheability(HttpCacheability.NoCache);
        Response.StatusCode = 200; // http://community.icsharpcode.net/forums/p/6946/20138.aspx
        long zipSize = calculateZipSize(filesToInclude);
        string contentValue = 
            string.Format("attachment; filename=moshe.zip;"
                          ); // + " size={0}", zipSize);
        Response.ContentType = "application/octet-stream"; //"application/zip"; 
        Response.AddHeader("Content-Disposition", contentValue);
        Response.Flush();

        using (ZipOutputStream zipOutputStream = new ZipOutputStream(Response.OutputStream) ) 
        {
            zipOutputStream.SetLevel(0);

            foreach (string f in filesToInclude)
            {
                string filename = Path.Combine(Server.MapPath("."), f);
                using (FileStream fs = File.OpenRead(filename))
                {
                    ZipEntry entry =
                        new ZipEntry(ZipEntry.CleanName(filename))
                            {
                                DateTime = File.GetCreationTime(filename),
                                CompressionMethod = CompressionMethod.Stored,
                                Size = fs.Length
                            };
                    zipOutputStream.PutNextEntry(entry);

                    byte[] buffer = new byte[fs.Length];
                    // write to zipoutStream via buffer. 
                    // The zipoutStream is directly connected to Response.Output (in the constructor)
                    ICSharpCode.SharpZipLib.Core.StreamUtils.Copy(fs, zipOutputStream, buffer); 
                    Response.Flush(); // for immediate response to user
                } // .. using file stream
            }// .. each file
        }
        Response.Flush();
        Response.End();
    }
4

6 回答 6

16

男孩,这是很多代码!使用DotNetZip您的工作会更简单。假设一个 HTTP 1.1 客户端,这是有效的:

Response.Clear();
Response.BufferOutput = false;
string archiveName= String.Format("archive-{0}.zip", DateTime.Now.ToString("yyyy-MMM-dd-HHmmss"));
Response.ContentType = "application/zip";
// see http://support.microsoft.com/kb/260519
Response.AddHeader("content-disposition", "attachment; filename=" + archiveName);  
using (ZipFile zip = new ZipFile())
{
    // filesToInclude is a IEnumerable<String> (String[] or List<String> etc)
    zip.AddFiles(filesToInclude, "files");
    zip.Save(Response.OutputStream);
}
// Response.End(); // will throw an exception internally.
// Response.Close(); // Results in 'Failed - Network error' in Chrome.
Response.Flush(); // See https://stackoverflow.com/a/736462/481207
// ...more code here...

如果要对 zip 进行密码加密,请在 AddFiles() 之前插入以下行:

    zip.Password = tbPassword.Text; // optional
    zip.Encryption = EncryptionAlgorithm.WinZipAes256; // optional

如果您想要一个自解压存档,请将 zip.Save() 替换为 zip.SaveSelfExtractor()。


附录; 有些人对我说 DotNetZip “不好”,因为它会在将整个 ZIP 流式传输出来之前在内存中创建整个 ZIP。事实并非如此。当您调用AddFiles时,该库会创建一个条目列表 - 表示要压缩的事物状态的对象。在调用 Save 之前不会进行压缩或加密。如果您为 Save() 调用指定流,则所有压缩字节都会直接流式传输到客户端。

在 SharpZipLib 模型中,可以创建一个条目,然后将其流式传输,然后创建另一个条目,然后将其流式传输,等等。使用 DotNetZip,您的应用程序首先会创建完整的条目列表,然后将它们全部输出。两种方法都不一定比另一种“更快”,尽管对于长文件列表,比如 30,000 个,使用 SharpZipLib 的时间到第一个字节会更快。另一方面,我不建议动态创建包含 30,000 个条目的 zip 文件。


编辑

从 DotNetZip v1.9 开始,DotNetZip 也支持 ZipOutputStream。不过,我仍然认为按照我在这里展示的方式做事更简单。


有些人的情况是,他们的 zip 内容对于所有用户来说“基本相同”,但每个用户都有几个不同的文件。DotNetZip 在这方面也很擅长。您可以从文件系统文件中读取 zip 存档,更新一些条目(添加一些,删除一些等),然后保存到 Response.OutputStream。在这种情况下,DotNetZip 不会重新压缩或重新加密您未更改的任何条目。快多了。

当然,DotNetZip 适用于任何 .NET 应用程序,而不仅仅是 ASP.NET。因此,您可以保存到任何流。

如果您想了解更多信息,请查看该站点或在dotnetzip 论坛上发帖。

于 2009-07-03T21:13:12.657 回答
2

不太清楚如何在 ASP.NET 中执行此操作(之前没有尝试过),但一般来说,如果 HTTP 客户端支持 HTTP v1.1(如其请求的版本所示),服务器可以发送一个 'Transfer -Encoding' 指定“分块”的响应标头,然后在可用时使用多个数据块发送响应数据。这允许在您提前不知道最终数据大小的情况下实时传输数据(因此无法设置“Content-Length”响应标头)。查看RFC 2616第 3.6 节了解更多详细信息。

于 2009-07-02T21:49:40.650 回答
1

对于那些会错过 SharpZipLib 的 ZipOutputStream 的人,这里有一个简单的代码,它可以使用“常规 .NET 流方式”的 DotNetZip。

但是,请注意,与 SharpZipLib 等真正的即时流解决方案相比,它效率低下,因为它在实际调用 DotNetZip.Save() 函数之前使用内部 MemoryStream。但不幸的是,SharpZibLib 还没有运动 EAS 加密(当然从来没有)。让我们希望 Cheeso 很快就会在 dotNetZip 中添加这个功能吗?;-)

/// <summary>
/// DotNetZip does not support streaming out-of-the-box up to version v1.8.
/// This wrapper class helps doing so but unfortunately it has to use
/// a temporary memory buffer internally which is quite inefficient
/// (for instance, compared with the ZipOutputStream of SharpZibLib which has other drawbacks besides).
/// </summary>
public class DotNetZipOutputStream : Stream
{
    public ZipFile ZipFile { get; private set; }

    private MemoryStream memStream = new MemoryStream();
    private String nextEntry = null;
    private Stream outputStream = null;
    private bool closed = false;

    public DotNetZipOutputStream(Stream baseOutputStream)
    {
        ZipFile = new ZipFile();
        outputStream = baseOutputStream;
    }

    public void PutNextEntry(String fileName)
    {
        memStream = new MemoryStream();
        nextEntry = fileName;
    }

    public override bool CanRead { get { return false; } }
    public override bool CanSeek { get { return false; } }
    public override bool CanWrite { get { return true; } }
    public override long Length { get { return memStream.Length; } }
    public override long Position
    {
        get { return memStream.Position; }
        set { memStream.Position = value; }
    }

    public override void Close()
    {
        if (closed) return;

        memStream.Position = 0;
        ZipFile.AddEntry(nextEntry, Path.GetDirectoryName(nextEntry), memStream);
        ZipFile.Save(outputStream);
        memStream.Close();
        closed = true;
    }

    public override void Flush()
    {
        memStream.Flush();
    }

    public override int Read(byte[] buffer, int offset, int count)
    {
        throw new NotSupportedException("Read");
    }

    public override long Seek(long offset, SeekOrigin origin)
    {
        throw new NotSupportedException("Seek");
    }

    public override void SetLength(long value)
    {
        memStream.SetLength(value);
    }

    public override void Write(byte[] buffer, int offset, int count)
    {
        memStream.Write(buffer, offset, count);
    }
}
于 2009-10-02T13:53:04.267 回答
0

Try adding the following header.

Response.AddHeader("Content-Length", zipSize);

I know that was causing me issues before.

Edit:

These other 2 may help as well:

Response.AddHeader("Content-Description", "File Transfer");
Response.AddHeader("Content-Transfer-Encoding", "binary");
于 2009-07-02T14:48:49.903 回答
0

您是否尝试过在刷新响应之前刷新 ZipOutputStream?您可以将 zip 保存在客户端上并在 zip 实用程序中进行测试吗?

于 2009-07-02T15:08:23.983 回答
0

死灵术。
以下是使用闭包正确完成的方法,从 DotNetZip v1.9+ 开始,Cheeso 在评论中推荐:

public static void Run()
{
    using (Ionic.Zip.ZipFile zip = new Ionic.Zip.ZipFile())
    {

        for (int i = 1; i < 11; ++i)
        {
            zip.AddEntry("LeaseContractForm_" + i.ToString() + ".xlsx", delegate(string filename, System.IO.Stream output)
            {
                // ByteArray from ExecuteReport - only ONE ByteArray at a time, because i might be > 100, and ba.size might be > 20 MB
                byte[] ba = Portal_Reports.LeaseContractFormPostProcessing.ProcessWorkbook();
                output.Write(ba, 0, ba.Length);
            });
        } // Next i 

        using (System.IO.Stream someStream = new System.IO.FileStream(@"D:\test.zip", System.IO.FileMode.Create, System.IO.FileAccess.Write, System.IO.FileShare.None))
        {
            zip.Save(someStream);
        }
    } // End Using zip 

} // End Sub Run 

还有 VB.NET 变体,以防万一有人需要它(请注意,这只是一个测试;实际上,循环中的每个步骤都会使用不同的 in_contract_uid 和 in_premise_uid 调用它):

Imports System.Web
Imports System.Web.Services


Public Class test
    Implements System.Web.IHttpHandler


    Sub ProcessRequest(ByVal context As HttpContext) Implements IHttpHandler.ProcessRequest
        Dim in_contract_uid As String = context.Request.Params("in_contract_uid")
        Dim in_premise_uid As String = context.Request.Params("in_premise_uid")

        If String.IsNullOrWhiteSpace(in_contract_uid) Then
            in_contract_uid = "D57A62D7-0FEB-4FAF-BB09-84106E3E15E9"
        End If

        If String.IsNullOrWhiteSpace(in_premise_uid) Then
            in_premise_uid = "165ECACA-04E6-4DF4-B7A9-5906F16653E0"
        End If

        Dim in_multiple As String = context.Request.Params("in_multiple")
        Dim bMultiple As Boolean = False

        Boolean.TryParse(in_multiple, bMultiple)


        If bMultiple Then
            Using zipFile As New Ionic.Zip.ZipFile

                For i As Integer = 1 To 10 Step 1
                    ' Dim ba As Byte() = Portal_Reports.LeaseContractFormReport.GetLeaseContract(in_contract_uid, in_premise_uid) '
                    ' zipFile.AddEntry("LeaseContractForm_" + i.ToString() + ".xlsx", ba) '

                    zipFile.AddEntry("LeaseContractForm_" + i.ToString() + ".xlsx", Sub(filename As String, output As System.IO.Stream)
                                                                                        Dim ba As Byte() = Portal_Reports.LeaseContractFormReport _
                                                                                        .GetLeaseContract(in_contract_uid, in_premise_uid)
                                                                                        output.Write(ba, 0, ba.Length)
                                                                                    End Sub)
                Next i

                context.Response.ClearContent()
                context.Response.ClearHeaders()
                context.Response.ContentType = "application/zip"
                context.Response.AppendHeader("content-disposition", "attachment; filename=LeaseContractForm.zip")
                zipFile.Save(context.Response.OutputStream)
                context.Response.Flush()
            End Using ' zipFile '
        Else
            Dim ba As Byte() = Portal_Reports.LeaseContractFormReport.GetLeaseContract(in_contract_uid, in_premise_uid)
            Portal.ASP.NET.DownloadFile("LeaseContractForm.xlsx", "attachment", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", ba)
        End If

    End Sub


    ReadOnly Property IsReusable() As Boolean Implements IHttpHandler.IsReusable
        Get
            Return False
        End Get
    End Property


End Class
于 2016-06-28T15:05:34.610 回答