C# ASP.NET MVC:从字节[]返回文件结果是个好主意吗?

C# ASP.NET MVC:从字节[]返回文件结果是个好主意吗?,c#,asp.net-mvc,C#,Asp.net Mvc,我的一个控制器中有一个操作,它创建了一个可下载的zip文件,该文件应该提供给用户。目前,我的代码如下所示: using(var memoryStream = new MemoryStream()) { // ... use SharpZipLib to write zip file content to the above MemoryStream ... return File(memoryStream.ToArray(), "application/zip", "file.z

我的一个控制器中有一个操作,它创建了一个可下载的zip文件,该文件应该提供给用户。目前,我的代码如下所示:

using(var memoryStream = new MemoryStream()) {
    // ... use SharpZipLib to write zip file content to the above MemoryStream ...
    return File(memoryStream.ToArray(), "application/zip", "file.zip");
}
public ActionResult Index()
{
    return new SharpZipLibResult(
        "result.zip", 
        @"c:\work\report1.pdf",
        @"c:\work\report2.pdf",
        @"c:\work\report3.pdf"
    );
}
我想知道将memoryStream转换为
字节[]
是否是一个好主意,我想这比使用流占用更多内存?
File()
有一个重载,它接受一个
Stream
对象,我传入了memoryStream变量,但随后只显示了一个空白页


理想情况下,我不必使用
FileStream
并将文件写入磁盘

使用MemoryStream作为流,而不是获取数组,可以一次复制所有内容。读取流还涉及复制,但这是在较小的块中进行的

在读取内存流之前,将其位置设置为起始位置:

memoryStream.Position = 0;

如果您真的关心内存,那么这里有一个直接写入响应流的解决方案。首先定义自定义操作结果:

public class SharpZipLibResult : FileResult
{
    private readonly string _fileDownloadName;
    private readonly string[] _filesToZip;
    private const int ChunkSize = 1024;

    public SharpZipLibResult(string fileDownloadName, params string[] filesToZip)
        : base("application/octet-stream")
    {
        _fileDownloadName = fileDownloadName;
        _filesToZip = filesToZip;
    }

    protected override void WriteFile(HttpResponseBase response)
    {
        var cd = new ContentDisposition();
        cd.FileName = _fileDownloadName;
        response.AddHeader("Content-Disposition", cd.ToString());
        response.BufferOutput = false;
        using (var zipStream = new ZipOutputStream(response.OutputStream))
        {
            foreach (var file in _filesToZip)
            {
                var entry = new ZipEntry(Path.GetFileName(file));
                zipStream.PutNextEntry(entry);
                using (var reader = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
                {
                    byte[] buffer = new byte[ChunkSize];
                    int bytesRead;
                    while ((bytesRead = reader.Read(buffer, 0, buffer.Length)) > 0)
                    {
                        byte[] actual = new byte[bytesRead];
                        Buffer.BlockCopy(buffer, 0, actual, 0, bytesRead);
                        zipStream.Write(actual, 0, actual.Length);
                    }
                }
            }
        }
    }
}
使用这种技术,您可以提供一些非常大的zip文件,而不必关心内存或清理服务器硬盘上的一些临时zip文件

最后,您的控制器操作可以如下所示:

using(var memoryStream = new MemoryStream()) {
    // ... use SharpZipLib to write zip file content to the above MemoryStream ...
    return File(memoryStream.ToArray(), "application/zip", "file.zip");
}
public ActionResult Index()
{
    return new SharpZipLibResult(
        "result.zip", 
        @"c:\work\report1.pdf",
        @"c:\work\report2.pdf",
        @"c:\work\report3.pdf"
    );
}
使用此方法可以最小化内存占用,因为zip直接写入响应流,响应流将由底层网络套接字表示


当然,根据文件的存储位置,
SharpZipLibResult
可以进行调整。在这里,我假设文件存储在文件系统上。

您重置了流的位置吗?也许您应该在制作zip之前估计大小,如果文件较大,则使用磁盘上的文件来保留内存。对于一个大文件来说,网络带宽将是瓶颈,而不是磁盘操作。@Guffa:为了仍然使用MemoryStream,“正常”大小是多少?该应用程序运行在一台4 GB的服务器上,我不希望有高负载和大量并行下载。使用内存流对我来说比较容易,因为我不需要清理旧的zip文件…你可以将限制设置为大约100MB。您不希望它太大,这样很容易通过发出大量请求而使服务器过载。如果将文件用作临时存储,则可以在将数据发送到客户端后立即将其删除,无需将其保留在服务器上一段时间。谢谢,100 MB似乎是一个不错的选择!在将较大的临时文件发送到客户端后,如何删除该文件?我的意思是,在控制器操作中返回我的文件结果后,我对流没有更多的控制了?或者在请求完成时是否有某种事件通知我?这仍然需要大量内存,因为整个zip将由SharpZipLib存储在内存流中。@Darin:当然可以。这就是为什么最好把它作为一个流来读取所有数据的另一个副本。如果响应流都是在内存中缓冲的,那么直接写响应流没有什么意义……当然,但我认为OP正在寻找一种解决方案,通过避免在内存中缓冲整个zip来最小化内存消耗。在我的示例中,整个zip从未加载到内存中。它是动态创建的,并以1KB的块直接写入响应流。