2009-07-02 11 views
10

Ich versuche, den folgenden Code zu verwenden: Ich bekomme eine beschädigte ZIP-Datei. Warum? Die Dateinamen scheinen OK. Vielleicht sind sie keine relativen Namen, und das ist das Problem?Wie kann ich in Echtzeit zip-komprimieren und zu Response.Output streamen?

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

Antwort

0

Versuchen Sie, den folgenden Header hinzuzufügen.

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

Ich weiß, dass das mich Probleme vorher verursacht hat.

Edit:

Diese anderen 2 kann auch helfen:

Response.AddHeader("Content-Description", "File Transfer"); 
Response.AddHeader("Content-Transfer-Encoding", "binary"); 
0

Haben Sie versucht, die ZipOutputStream Spülung vor der Reaktion Spülung? Können Sie die Zip auf dem Client speichern und in einem Zip-Dienstprogramm testen?

2

Nicht ganz sicher, wie dies in ASP.NET zu tun (habe es nicht vorher versucht), aber im Allgemeinen, wenn das HTTP-Client HTTP v1.1 unterstützt (wie von der Version der Anfrage angegeben), kann der Server Senden Sie einen 'Transfer-Encoding'-Response-Header, der' Chunked 'angibt, und senden Sie die Antwortdaten mit mehreren Datenblöcken, sobald diese verfügbar sind. Dies ermöglicht das Echtzeit-Streaming von Daten, bei denen Sie die endgültige Datengröße nicht im Voraus kennen (und somit den Antwortkopf "Content-Length" nicht festlegen können). Sehen Sie sich RFC 2616 Abschnitt 3.6 für weitere Details an.

+0

in ASP.NET, tun Sie dies, indem Sie Response.BufferOutput = false. – Cheeso

14

Junge, das ist eine Menge Code! Ihr Job wäre einfacher mit DotNetZip. Unter der Annahme eines HTTP 1.1-Client, das funktioniert:

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

Wenn Sie möchten, ein Passwort verschlüsseln die Zip, dann vor den AddFiles(), legen Sie die folgenden Zeilen:

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

Wenn Sie eine Selbst wollen extrahieren Sie das Archiv und ersetzen Sie zip.Save() mit zip.SaveSelfExtractor().


Nachtrag; some people have commented to me, dass DotNetZip "nicht gut" ist, weil es das gesamte ZIP im Speicher erstellt, bevor es es ausströmt. Dies ist nicht der Fall. Wenn Sie AddFiles aufrufen, erstellt die Bibliothek eine Liste der Einträge - Objekte, die den Status der Dinge darstellen, die gezippt werden sollen. Bis zum Aufruf von Speichern wird keine Komprimierung oder Verschlüsselung durchgeführt. Wenn Sie einen Stream für den Aufruf Save() angeben, werden alle komprimierten Bytes direkt an den Client gestreamt.

Im SharpZipLib-Modell ist es möglich, einen Eintrag zu erstellen, ihn dann zu streamen, dann einen weiteren Eintrag zu erstellen, ihn zu streamen und so weiter. Mit DotNetZip erstellt Ihre App zuerst die komplette Liste der Einträge und streamt sie dann alle raus. Kein Ansatz ist notwendigerweise "schneller" als der andere, obwohl bei langen Listen von Dateien, sagen wir 30.000, das Time-to-First-Byte mit SharpZipLib schneller ist. Auf der anderen Seite würde ich nicht empfehlen, Zip-Dateien mit 30.000 Einträgen dynamisch zu erstellen.


EDIT

Ab DotNetZip v1.9, unterstützt DotNetZip A als auch ZipOutputStream.Ich denke immer noch, dass es einfacher ist, Dinge so zu machen, wie ich es hier gezeigt habe.


Einige Leute haben den Fall, dass sie zip Inhalt haben, „meist die gleichen“ für alle Benutzer ist, aber es gibt ein paar Dateien, die für jeden einzelnen unterschiedlich sind. DotNetZip ist auch darin gut. Sie können ein Zip-Archiv aus einer Dateisystemdatei einlesen, ein paar Einträge aktualisieren (ein paar hinzufügen, ein paar entfernen usw.) und dann in Response.OutputStream speichern. In diesem Fall führt DotNetZip keine Neukomprimierung oder Neuverschlüsselung der Einträge durch, die Sie nicht geändert haben. Viel schneller.

Natürlich ist DotNetZip für jede .NET App, nicht nur ASP.NET. So können Sie in jedem Stream speichern.

Wenn Sie weitere Informationen wünschen, überprüfen Sie die site oder Post auf der dotnetzip forums.

+0

Ah, verstopfte deine eigene Bibliothek. :) Zugegeben, es sieht etwas einfacher aus. – Noldorin

+0

Probieren Sie es aus, Sie werden es lieben! – Cheeso

+1

Wir verwendeten ICSharpCode SharpZipLib und haben es gerade durch DotNetZip ersetzt. Die DotNetZip-Schnittstelle ist viel einfacher als SharpZipLib. Wir hatten weniger als die Hälfte des Codes. Ich bin jetzt ein großer Fan. Wenn Sie eine Zip-Bibliothek benötigen, verwenden Sie nicht SharpZip, verwenden Sie DotNetZip. –

1

Für diejenigen, die den ZipOutputStream von SharpZipLib verpassen würden, hier ist ein einfacher Code, der es ermöglicht, DotNetZip den "normalen .NET-Stream-Weg" zu verwenden.

Beachten Sie jedoch, dass es im Vergleich zu einer realen On-the-Fly-Streaming-Lösung wie SharpZipLib ineffizient ist, da es einen internen MemoryStream verwendet, bevor die DotNetZip.Save() -Funktion aufgerufen wird. Leider hat SharpZibLib noch keine EAS-Verschlüsselung (und sicherlich niemals). Hoffen wir, dass Cheeso diese Funktionalität in dotNetZip in naher Zukunft hinzufügen wird? ;-)

/// <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); 
    } 
} 
+1

Mit DotNetZip v1.9 können Sie direkt in den Ausgabestrom schreiben. Verwenden Sie AddEntry (Zeichenfolge, WriteDelegate). Beispiel zum Einbetten der XML aus einem DataSet in eine Zip-Datei mit einer anonymen Methode: zip.AddEntry (zipEntryName, (name, stream) => dataset1.WriteXml (stream)); – Cheeso

0

Necromancing.
Hier ist, wie es richtig gemacht wird, Verschlüsse verwenden, mit DotNetZip v1.9 beginnend + wie Cheeso in den Kommentaren zu empfehlen:

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 

und die Variante VB.NET, braucht es für den Fall, jemand (bitte beachten Sie, dass diese ; nur ein Test ist in Wirklichkeit wäre es mit verschiedenen in_contract_uid und in_premise_uid für jeden Schritt in der Schleife) aufgerufen werden:

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