2009-07-02 12 views
10

Sto cercando di utilizzare il seguente codice: ottengo un file zip danneggiato. Perché? I nomi dei file sembrano OK. Forse non sono nomi relativi, e questo è il problema?Come faccio a fare il zip al volo e lo streaming su Response.Output in tempo reale?

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

risposta

0

Prova ad aggiungere la seguente intestazione.

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

So che mi stava causando problemi prima.

Edit:

Questi altri 2 possono aiutare pure:

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

Hai provato il lavaggio del ZipOutputStream prima di lavare la risposta? Puoi salvare lo zip sul client e testarlo in una utility zip?

2

Non sono sicuro di come farlo in ASP.NET (non l'ho provato prima), ma in generale se il client HTTP supporta HTTP v1.1 (come indicato dalla versione della sua richiesta), il server può invia un'intestazione di risposta 'Transfer-Encoding' che specifica 'chunked', quindi invia i dati di risposta utilizzando più blocchi di dati man mano che diventano disponibili. Ciò consente lo streaming dei dati in tempo reale in cui non si conosce la dimensione dei dati finali prima del tempo (e quindi non è possibile impostare l'intestazione di risposta "Content-Length"). Dai un'occhiata alla sezione 3.6 RFC 2616 per maggiori dettagli.

+0

in ASP.NET, si esegue questa operazione impostando Response.BufferOutput = false. – Cheeso

14

Ragazzo, questo è un sacco di codice! Il tuo lavoro sarebbe più semplice con DotNetZip. Assumendo un client HTTP 1.1, questo funziona:

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

Se si desidera una password crittografare la zip, quindi prima che i AddFiles(), inserire queste righe:

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

Se si desidera un auto estrarre l'archivio, quindi sostituire zip.Save() con zip.SaveSelfExtractor().


Addendum; some people have commented to me che DotNetZip non è "buono" perché crea l'intero ZIP in memoria prima di eseguirne lo streaming. Questo non è il caso. Quando chiami AddFiles, la libreria crea un elenco di voci - oggetti che rappresentano lo stato delle cose da comprimere. Non viene eseguita alcuna compressione o crittografia fino alla chiamata a Salva. Se si specifica un flusso per la chiamata Save(), tutti i byte compressi vengono inviati in streaming direttamente al client.

Nel modello SharpZipLib, è possibile creare una voce, quindi eseguirne lo streaming, quindi creare un'altra voce, espanderla e così via. Con DotNetZip la tua app crea per prima cosa l'elenco completo delle voci, quindi le trasmette via tutto. Nessuno dei due approcci è necessariamente "più veloce" rispetto all'altro, sebbene per lunghi elenchi di file, ad esempio 30.000, il time-to-first-byte sarà più veloce con SharpZipLib. D'altra parte non consiglierei di creare dinamicamente file zip con 30.000 voci.


EDIT

A partire dal DotNetZip v1.9, DotNetZip supporta una ZipOutputStream pure.Continuo a pensare che sia più semplice fare le cose come ho mostrato qui, però.


Alcune persone hanno il caso in cui essi hanno un contenuto zip che è "in gran parte la stessa" per tutti gli utenti, ma ci sono un paio di file che sono diversi per ciascuno di essi. DotNetZip è anche bravo in questo. Puoi leggere in un archivio zip da un file system, aggiornare alcune voci (aggiungerne alcune, rimuoverne alcune, ecc.), Quindi salvare su Response.OutputStream. In questo caso, DotNetZip non ricomprime né ricodifica le voci che non sono state modificate. Più veloce.

Naturalmente DotNetZip è per qualsiasi app .NET, non solo ASP.NET. Quindi puoi salvare su qualsiasi stream.

Se si desiderano ulteriori informazioni, consultare lo site o il post su dotnetzip forums.

+0

Ah, collegando la tua libreria. :) Certo, sembra un po 'più semplice però. – Noldorin

+0

Provalo, ti piacerà! – Cheeso

+1

Stavamo usando ICSharpCode SharpZipLib e lo abbiamo appena sostituito con DotNetZip. L'interfaccia di DotNetZip è più semplice di SharpZipLib. Abbiamo finito con meno della metà della quantità di codice. Ora sono un grande fan. Se è necessaria una libreria Zip, non utilizzare SharpZip, utilizzare DotNetZip. –

1

Per coloro che non si accorgono dello ZipOutputStream di SharpZipLib, ecco un semplice codice che rende possibile l'uso di DotNetZip come "normale modo di flusso .NET".

Tuttavia, prendere atto che è inefficiente rispetto a una vera soluzione di streaming in tempo reale come SharpZipLib, poiché utilizza un MemoryStream interno prima di chiamare effettivamente la funzione DotNetZip.Save(). Ma sfortunatamente, SharpZibLib non ha ancora lo sport EAS Encryption (e certamente mai). Speriamo che Cheeso aggiunga questa funzionalità in dotNetZip in qualunque momento presto? ;-)

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

DotNetZip v1.9 consente di scrivere direttamente sul flusso di output. Utilizzare AddEntry (string, WriteDelegate). Esempio per incorporare l'XML da un DataSet in un file zip, con un metodo anonimo: zip.AddEntry (zipEntryName, (name, stream) => dataset1.WriteXml (stream)); – Cheeso

0

Negromante.
Ecco come è fatto correttamente, utilizzando le chiusure, a partire da DotNetZip v1.9 + come raccomandato dal Cheeso nei commenti:

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 

e la variante VB.NET, nel caso qualcuno ne ha bisogno (si ricorda che questa è solo un test, ma in realtà si sarebbe chiamato con diversi in_contract_uid e in_premise_uid per ogni fase del ciclo):

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 
Problemi correlati