FileStreamResult的一個常用構造函數:
public virtual FileStreamResult File(Stream fileStream, string contentType, string fileDownloadName);
下面通過循序漸進的方式 講述 項目中的實際應用。
1. using塊中的流
按照良好的編程習慣,將stream放在using塊中,以確保它被釋放。結果:
fail : An unhandled exception has occurred while executing the request.
System.ObjectDisposedException: Cannot access a closed Stream.
原因在於 using塊一執行完stream就被關閉了,view無法讀取已關閉的流。
但,流總不能不關閉。難道,有誰替我們做了這些?
是的,FileStreamResult 自動做了這些,下面是反編譯的源碼:
protected override void WriteFile(HttpResponseBase response) { // grab chunks of data and write to the output stream Stream outputStream = response.OutputStream; using (FileStream) { byte[] buffer = new byte[_bufferSize]; while (true) { int bytesRead = FileStream.Read(buffer, 0, _bufferSize); if (bytesRead == 0) { // no more data break; } outputStream.Write(buffer, 0, bytesRead); } } }
刪掉using后的代碼如下:
運行成功,文件保存至本地。
2. 注意流的當前位置
有些時候我們所調用的組件恰好收集了流數據,要利用這個流。
下面的例子 通過 EPPlus組件創建Excel數據(須引用 EPPlus.dll 並 using OfficeOpenXml;)
上述代碼將ExcelPackage的數據流直接作為即將被下載的文件的數據流而輸出。看起來很完美,結果
fail : An unhandled exception was thrown by the application.
System.InvalidOperationException: Response Content-Length mismatch: too few bytes written <0 of 2546>
原因在於stream的當前位置在末尾。
常見的相同狀況的還有 Stream.CopyTo() 。微軟對此有說明:
添加一行代碼 stream.Position = 0 以重置流的當前位置至開始。
運行成功,保存至本地的文件如下:
3. 壓縮流
上述2中的例子,文件很小,只簡單寫了兩個單元格。如果文件大,則需要壓縮,供客戶端下載壓縮包。
有些同學真的是先創建文件保存至磁盤某路徑A,然后壓縮那個文件至路徑B,最終將B返回。
還沒完,因為還得善后:清理掉文件A及壓縮包B。
我傾向於:不存磁盤,直接利用流。其實在上面的第2小節中的示例已經體現。
下面的示例須添加 using System.IO.Compression
用於創建壓縮包的流stream,即是將來要返回的流。
向壓縮包中添加的entry並寫入數據, 用的entryStream,也是流。這個entry是一個Excel文件,恰好由EPPlus組件的流填充。
換一種說法:
ExcelPackage的流,構成 System.IO.Compression某個ZipArchiveEntry的流,一個或多個ZipArchiveEntry流 構成了ZipArchive流,而后 ZipArchive流構成FileStreamResult中的流 供返回。
全程都是流之間的流轉,沒有寫磁盤,用不着去清理什么過程文件。
美美的運行一下:oops, fail : Cannot access a closed Stream.
我並沒有將 MemoryStream stream = new MemoryStream() 放入using塊啊,是哪里關閉了它?
查幫助文檔,微軟說:ZipArchive.Dispose() 此方法完成寫入存檔並釋放由該ZipArchive對象使用的所有資源。
那就不把 ZipArchive archive = ... 放入using塊唄,這樣就不調用 ZipArchive.Dispose() 就不會關閉(釋放)流了。
新的代碼如下:
運行成功,Happy
解壓看看:
重點字眼“末端”,說明 不是寫excel數據的問題。
結合“ZipArchive.Dispose() 此方法完成寫入存檔並釋放” ,第一感覺是 沒有完成存檔,之前關注點都放在釋放上了。
仔細閱讀微軟的說明:
就是說,必須得調用ZipArchive.Dispose() 以完成存檔。但是,leaveOpen=true 才能保證流不關閉。
這次是真的運行成功了: