FileStreamResult 下載或導出文件


FileStreamResult的一個常用構造函數:

public virtual FileStreamResult File(Stream fileStream, string contentType, string fileDownloadName);

下面通過循序漸進的方式 講述 項目中的實際應用。

1. using塊中的流

            image.png

    按照良好的編程習慣,將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后的代碼如下:

image.png

    運行成功,文件保存至本地。

image.png

2. 注意流的當前位置

有些時候我們所調用的組件恰好收集了流數據,要利用這個流。

下面的例子 通過 EPPlus組件創建Excel數據(須引用 EPPlus.dll 並 using OfficeOpenXml;)

image.png

    上述代碼將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() 。微軟對此有說明:

    image.png

 

    添加一行代碼 stream.Position = 0 以重置流的當前位置至開始。

image.png

    運行成功,保存至本地的文件如下:

    image.png

3. 壓縮流

上述2中的例子,文件很小,只簡單寫了兩個單元格。如果文件大,則需要壓縮,供客戶端下載壓縮包。

有些同學真的是先創建文件保存至磁盤某路徑A,然后壓縮那個文件至路徑B,最終將B返回。

還沒完,因為還得善后:清理掉文件A及壓縮包B。
我傾向於:不存磁盤,直接利用流。其實在上面的第2小節中的示例已經體現。

 

下面的示例須添加 using System.IO.Compression

image.png

 

用於創建壓縮包的流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() 就不會關閉(釋放)流了。

新的代碼如下:

 image.png

    運行成功,Happy
    dialog.png

    解壓看看:
    image.png

    重點字眼“末端”,說明 不是寫excel數據的問題。

    結合“ZipArchive.Dispose() 此方法完成寫入存檔並釋放” ,第一感覺是 沒有完成存檔,之前關注點都放在釋放上了。

    仔細閱讀微軟的說明:
image.png

    就是說,必須得調用ZipArchive.Dispose() 以完成存檔。但是,leaveOpen=true 才能保證流不關閉。

    image.png

    這次是真的運行成功了:

    image.png


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM