atashbahar.com

thoughts, ideas and ...

Stream contents of a folder to output as a zip file

You have all seen "Download All" feature of popular email providers like Yahoo! And Gmail that allows you to download multiple attachments as a single zip file.

Recently we needed something like that in one of our projects. We wanted to download contents of a folder as a zip file. It was very important to stream the compressed result to the client as we were compressing them on the server. You may say that you could have zipped the folder on server so users could download it directly, but that was not an option in our case because contents of the folder were changing always.

When comes to compression for .NET, SharpZipLib is one of the best options you got. At start I thought solving this problem is not going to be very easy but I was in luck because SharpZipLib has a special class name FastZip that was exactly what I was looking for. FastZip class has a CreateZip method that has few overloads. One of the overloads gets an output stream and a path to a folder on disk; what it does is to send the compressed contents of the folder to output stream. This was exactly what I was looking for so I created static DownloadFolder method in my Utility class that accepts two parameters; first one is the path to the folder that we want to zip and second parameter is of type HttpResponse. We use HttpResponse OutputStream to send the zipped content to user. Here is the code for this method:

using System;
using System.Web;

public static class Utility
{
    public static void DownloadFolder(string path, HttpResponse res)
    {
        // cleat the response so we don't send any unwanted data to user
        res.Clear();
        // set the content type ZIP file
        res.ContentType = "application/zip";
        // setting Content-Disposition to attachement causes the save dialog of the 
        // browser to be shown. We also can set the name of the zip file in this header
        res.AppendHeader("Content-Disposition", "attachment; filename=Download.zip;");
        // it is very important to set BufferOutput to false. Default behavior is to buffer
        // the output before sending it to client and this is not the behavior we want
        res.BufferOutput = false;
        // flush the response so user will get the save dialog as soon as possible
        res.Flush();

        // FastZip is the easies way to zip all the file and folders in the
        // path and stream it through the Response OutputStream
        ICSharpCode.SharpZipLib.Zip.FastZip fz = new ICSharpCode.SharpZipLib.Zip.FastZip();
        fz.CreateZip(res.OutputStream, path, true, null, null);
    }
}

Using this method is very easy. You have two options one is to create an Http Handler; Users download the zipped content by access its address (for example by clicking a link that points to it).

using System;
using System.Web;

public class DownloadAll : IHttpHandler {
       
    public void ProcessRequest (HttpContext context) 
    {
        Utility.DownloadFolder(context.Server.MapPath("~/App_Data/Storage"), context.Response);
    }
 
    public bool IsReusable {
        get {
            return true;
        }
    }
}

Second method is to use it inside your ASPX page, for example when you click a button on your page. Here's how it is done:

<%@ Page Language="C#" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">

    protected void Page_Load(object sender, EventArgs e)
    { }

    protected void btnDownload_Click(object sender, EventArgs e)
    {
        Utility.DownloadFolder(Server.MapPath("~/App_Data/Storage"), Response);
    }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Download All</title>    
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:Button ID="btnDownload" runat="server" Text="Download All" onclick="btnDownload_Click" />
        <br />
        <a href="DownloadAll.ashx">Download All</a>
    </div>
    </form>
</body>
</html>

Well, that's it for now! As you can see it was very easy to do something that looked a little hard at start all thanks to SharpZipLib library.