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

慎于行,敏于思!GGGGGG
原文地址:https://www.cnblogs.com/GarsonZhang/p/13037359.html