2013-05-16 37 views
32

我一直試圖讓「新」 ZipArchive包含在.NET 4.5(System.IO.Compression.ZipArchive)在ASP.NET網站的工作。但它似乎不喜歡寫入HttpContext.Response.OutputStream流。寫ZipArchive使用的HttpContext的OutputStream

我下面的代碼示例將拋出

System.NotSupportedException:指定的方法是不是隻要一個嘗試寫在流支持

流上的CanWrite屬性返回true。

如果我將OutputStream與指向本地目錄的文件流進行交換,它將起作用。是什麼賦予了?

ZipArchive archive = new ZipArchive(HttpContext.Response.OutputStream, ZipArchiveMode.Create, false); 

ZipArchiveEntry entry = archive.CreateEntry("filename"); 

using (StreamWriter writer = new StreamWriter(entry.Open())) 
{ 
    writer.WriteLine("Information about this package."); 
    writer.WriteLine("========================"); 
} 

堆棧跟蹤:

[NotSupportedException: Specified method is not supported.] 
System.Web.HttpResponseStream.get_Position() +29 
System.IO.Compression.ZipArchiveEntry.WriteLocalFileHeader(Boolean isEmptyFile) +389 
System.IO.Compression.DirectToArchiveWriterStream.Write(Byte[] buffer, Int32 offset, Int32 count) +94 
System.IO.Compression.WrappedStream.Write(Byte[] buffer, Int32 offset, Int32 count) +41 
+0

你在開發環境還是服務器上嘗試它 – Alok

+0

我目前在本地運行它,所以開發環境。我從IHttpHandler獲取HttpContext。 –

+0

好的,MVC或Web表單? – Alok

回答

4

如果你比較你的代碼適應與MSDN page提出的版本,你會看到,從未使用過的ZipArchiveMode.Create,使用什麼是ZipArchiveMode.Update。

儘管如此,的主要問題是,不支持讀取並尋求這是由ZipArchive更新模式需要的OutputStream:

當您設置的模式來更新,基本文件或流必須 支持閱讀,寫作和尋求。整個 存檔的內容保存在內存中,並且沒有數據被寫入底層 文件或流,直到存檔被丟棄。

Source: MSDN

你沒有得到與創建模式中的任何異常,因爲它只需要寫:

當您設置模式創建,底層的文件或流必須支持寫作,但不必支持尋求。檔案中的每個條目只能打開一次才能寫入。如果您創建單個條目,則只要數據可用,就會將數據寫入基礎流或文件。如果創建了多個條目(例如通過調用CreateFromDirectory方法),則在創建所有條目後將數據寫入基礎流或文件。

Source: MSDN

我相信你無法在OutputStream中直接創建一個zip文件,因爲它是一個網絡數據流,並尋求不支持:

流可以支持查找。尋找是指查詢和修改流內的當前位置。尋找能力取決於流的後備存儲類型。例如,網絡流沒有當前位置的統一概念,因此通常不支持查找。

另一種方法是寫入內存流,然後使用OutputStream.Write方法發送zip文件。

MemoryStream ZipInMemory = new MemoryStream(); 

    using (ZipArchive UpdateArchive = new ZipArchive(ZipInMemory, ZipArchiveMode.Update)) 
    { 
     ZipArchiveEntry Zipentry = UpdateArchive.CreateEntry("filename.txt"); 

     foreach (ZipArchiveEntry entry in UpdateArchive.Entries) 
     { 
      using (StreamWriter writer = new StreamWriter(entry.Open())) 
      { 
       writer.WriteLine("Information about this package."); 
       writer.WriteLine("========================"); 
      } 
     } 
    } 
    byte[] buffer = ZipInMemory.GetBuffer(); 
    Response.AppendHeader("content-disposition", "attachment; filename=Zip_" + DateTime.Now.ToString() + ".zip"); 
    Response.AppendHeader("content-length", buffer.Length.ToString()); 
    Response.ContentType = "application/x-compressed"; 
    Response.OutputStream.Write(buffer, 0, buffer.Length); 

編輯:與意見反饋和進一步的閱讀,你可以創造較大的zip文件,所以內存流可能會導致你的問題。

在這種情況下,我建議你使用Response.WriteFile創建Web服務器上的zip文件,然後輸出的文件。

+0

換句話說,直接寫入outputStream是不可能的?就像我之前說過的,memoryStream不是一個選項,因爲文件大小不一,可能會變得很大。 –

+0

我與response.filter想看看如果我能找到一個解決辦法,但根據MSDN不能: 流可以支持查找。尋找是指查詢和修改流內的當前位置。尋找能力取決於流的後備存儲類型。例如,網絡流沒有當前位置的統一概念,因此通常不支持查找。 –

+0

我很困惑,不要將您的報價意味着它應與'ZipArchiveMode.Create'工作? – svick

34

Calbertoferreira的回答有一些有用的信息,但得出的結論是錯誤的大多。要創建檔案,您不需要查找,但您需要能夠讀取Position

the documentation,閱讀Position應僅用於查找流的支持,但ZipArchive似乎從非查找流,我認爲這是a bug甚至需要這個。

因此,您只需要支持將ZIP文件直接寫入OutputStream即可將其包裝在支持獲取Position的自定義Stream中。喜歡的東西:

class PositionWrapperStream : Stream 
{ 
    private readonly Stream wrapped; 

    private int pos = 0; 

    public PositionWrapperStream(Stream wrapped) 
    { 
     this.wrapped = wrapped; 
    } 

    public override bool CanSeek { get { return false; } } 

    public override bool CanWrite { get { return true; } } 

    public override long Position 
    { 
     get { return pos; } 
     set { throw new NotSupportedException(); } 
    } 

    public override void Write(byte[] buffer, int offset, int count) 
    { 
     pos += count; 
     wrapped.Write(buffer, offset, count); 
    } 

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

    protected override void Dispose(bool disposing) 
    { 
     wrapped.Dispose(); 
     base.Dispose(disposing); 
    } 

    // all the other required methods can throw NotSupportedException 
} 

利用這一點,下面的代碼會寫一個ZIP壓縮文件到OutputStream

using (var outputStream = new PositionWrapperStream(Response.OutputStream)) 
using (var archive = new ZipArchive(outputStream, ZipArchiveMode.Create, false)) 
{ 
    var entry = archive.CreateEntry("filename"); 

    using (var writer = new StreamWriter(entry.Open())) 
    { 
     writer.WriteLine("Information about this package."); 
     writer.WriteLine("========================"); 
    } 
} 
+0

不錯,我也想知道是否有人剛剛起步,這是這個問題,並認爲會有其他問題上的立場。我沒有嘗試,因此感謝您的發現!我同意,ZipArchive代碼可以很容易地跟蹤它已經寫入了多少字節,而不需要像這樣的包裝器。 – AndyD

+0

確實不錯,我使用了第三方zip庫,因爲我當時找不到解決方案。但是當我有時間時,我會回去嘗試使用你的想法。我會回來,並標記它的答案,如果它的工作! :) –

+0

保存我的培根,因爲我們說@svick謝謝你一噸。 – TheNorthWes

0

svick的答案的一個簡化版本荏苒一個服務器端的文件和發送它通過爲OutputStream:

using (var outputStream = new PositionWrapperStream(Response.OutputStream)) 
using (var archive = new ZipArchive(outputStream, ZipArchiveMode.Create, false)) 
{ 
    var entry = archive.CreateEntryFromFile(fullPathOfFileOnDisk, fileNameAppearingInZipArchive); 
} 

(在此情況下,看起來很明顯,這不是我的!)

0

據推測,這是不是一個MVC應用程序,在那裏你可以很容易地只使用FileStreamResult類。

我用這與目前使用MemoryStream創建ZipArchive,所以我知道它的工作原理。

考慮到這一點,看看在FileStreamResult.WriteFile()方法:

protected override void WriteFile(HttpResponseBase response) 
{ 
    // grab chunks of data and write to the output stream 
    Stream outputStream = response.OutputStream; 
    using (FileStream) 
    { 
     byte[] buffer = newbyte[_bufferSize]; 
     while (true) 
     { 
      int bytesRead = FileStream.Read(buffer, 0, _bufferSize); 
      if (bytesRead == 0) 
      { 
       // no more data 
       break; 
      } 
      outputStream.Write(buffer, 0, bytesRead); 
     } 
    } 
} 

Entire FileStreamResult on CodePlex

這裏是我要生成並返回ZipArchive
你應該沒有任何問題與上述WriteFile方法,其中FileStream從下面的代碼變得resultStream的膽量更換FSR:

var resultStream = new MemoryStream(); 

using (var zipArchive = new ZipArchive(resultStream, ZipArchiveMode.Create, true)) 
{ 
    foreach (var doc in req) 
    { 
     var fileName = string.Format("Install.Rollback.{0}.v{1}.docx", doc.AppName, doc.Version); 
     var xmlData = doc.GetXDocument(); 
     var fileStream = WriteWord.BuildFile(templatePath, xmlData); 

     var docZipEntry = zipArchive.CreateEntry(fileName, CompressionLevel.Optimal); 
     using (var entryStream = docZipEntry.Open()) 
     { 
      fileStream.CopyTo(entryStream); 
     } 
    } 
} 
resultStream.Position = 0; 

// add the Response Header for downloading the file 
var cd = new ContentDisposition 
    { 
     FileName = string.Format(
      "{0}.{1}.{2}.{3}.Install.Rollback.Documents.zip", 
      DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, (long)DateTime.Now.TimeOfDay.TotalSeconds), 
     // always prompt the user for downloading, set to true if you want 
     // the browser to try to show the file inline 
     Inline = false, 
    }; 
Response.AppendHeader("Content-Disposition", cd.ToString()); 

// stuff the zip package into a FileStreamResult 
var fsr = new FileStreamResult(resultStream, MediaTypeNames.Application.Zip);  
return fsr; 

最後,如果你會寫大流(或在任何給定的時間更多數量的人),那麼你可能要考慮使用匿名管道將數據寫入到你把它寫在zip文件底層流後立即輸出流。因爲你將所有的文件內容保存在服務器的內存中。The end of this answer到一個類似的問題有一個很好的解釋如何做到這一點。

3

細化到2月2日2014年的svick的答案,我發現,它以實現更多一些流抽象類的方法和屬性,並且只要申報POS成員是必要的。之後,它就像一個魅力。我沒有廣泛測試這個類,但它的作用是返回HttpResponse中的ZipArchive。我假設我已經實現了Seek and Read,但他們可能需要進行一些調整。

class PositionWrapperStream : Stream 
{ 
    private readonly Stream wrapped; 

    private long pos = 0; 

    public PositionWrapperStream(Stream wrapped) 
    { 
     this.wrapped = wrapped; 
    } 

    public override bool CanSeek 
    { 
     get { return false; } 
    } 

    public override bool CanWrite 
    { 
     get { return true; } 
    } 

    public override long Position 
    { 
     get { return pos; } 
     set { throw new NotSupportedException(); } 
    } 

    public override bool CanRead 
    { 
     get { return wrapped.CanRead; } 
    } 

    public override long Length 
    { 
     get { return wrapped.Length; } 
    } 

    public override void Write(byte[] buffer, int offset, int count) 
    { 
     pos += count; 
     wrapped.Write(buffer, offset, count); 
    } 

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

    protected override void Dispose(bool disposing) 
    { 
     wrapped.Dispose(); 
     base.Dispose(disposing); 
    } 

    public override long Seek(long offset, SeekOrigin origin) 
    { 
     switch (origin) 
     { 
      case SeekOrigin.Begin: 
       pos = 0; 
       break; 
      case SeekOrigin.End: 
       pos = Length - 1; 
       break; 
     } 
     pos += offset; 
     return wrapped.Seek(offset, origin); 
    } 

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

    public override int Read(byte[] buffer, int offset, int count) 
    { 
     pos += offset; 
     int result = wrapped.Read(buffer, offset, count); 
     pos += count; 
     return result; 
    } 
}