tags:

views:

172

answers:

2

I have an action in one of my controllers which creates a downloadable zip file which should be served to the user. Currently my code looks something like this:

using(var memoryStream = new MemoryStream()) {
    // ... use SharpZipLib to write zip file content to the above MemoryStream ...
    return File(memoryStream.ToArray(), "application/zip", "file.zip");
}

I am wondering if it is a good idea go convert memoryStream to a byte[], I guess this takes up more memory then using the stream? There is an overload to File() which takes a Stream object, and I passed in my memoryStream variable, but then just a blank page showed up.

Ideally I dont have to use a FileStream and write a file to disk.

+2  A: 

Using the MemoryStream as a stream instead of getting the array elliminates copying all of the content at once. Reading the stream also involves copying, but that is in smaller chunks.

Set the position of the memory stream to the beginning before reading from it:

memoryStream.Position = 0;
Guffa
This still could take a lot of memory as the whole zip will be stored in the memory stream by SharpZipLib.
Darin Dimitrov
@Darin: Yes of course. That's why it's better to read it as a stream to elliminate another copy of all the data.
Guffa
+2  A: 

If you really care about memory then here's a solution that will directly write to the response stream. First define your custom ActionResult:

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);
                    }
                }
            }
        }
    }
}

With this technique you could serve some really huge zip files without caring about memory or having to clean some temporary zip files on your server hard drives.

And finally your controller action could look like this:

public ActionResult Index()
{
    return new SharpZipLibResult(
        "result.zip", 
        @"c:\work\report1.pdf",
        @"c:\work\report2.pdf",
        @"c:\work\report3.pdf"
    );
}

Using this method memory footprint is minimized because the zip is written directly to the response stream which in terms will be represented by an underlying network socket.

Of course depending on where your files are stored the SharpZipLibResult could be tweaked. Here I assume the files are stored on the file system.

Darin Dimitrov
Consider response buffering also. There is little point in spending a lot of work writing directly to the response stream if it's all buffered in memory anyway...
Guffa
Sure, but I thought that the OP was looking for a solution to minimize memory consumption by avoid buffering the whole zip in memory. In my example the whole zip is never loaded into memory. It is being created dynamically and written directly to the response stream in chunks of 1KB.
Darin Dimitrov