12

I have created a text file in a folder and zipped that folder and saved @same location for test purpose. I wanted to download that zip file directly on user machine after it is created. I am using dotnetzip library and have done following:

Response.Clear();
Response.ContentType = "application/zip";
Response.AddHeader("content-disposition", "filename=" + "sample.zip");
using (ZipFile zip = new ZipFile())
{
    zip.AddDirectory(Server.MapPath("~/Directories/hello"));
    zip.Save(Server.MapPath("~/Directories/hello/sample.zip"));
}

Can someone please suggest how the zip file can be downloaded at user's end.?

ruffin
  • 16,507
  • 9
  • 88
  • 138
user2801336
  • 187
  • 1
  • 2
  • 10
  • possible duplicate of [ASP MVC Download Zip Files](http://stackoverflow.com/questions/15385958/asp-mvc-download-zip-files) – Madan Sapkota May 21 '15 at 04:14

5 Answers5

27

You may use the controller's File method to return a file, like:

public ActionResult Download()
{
    using (ZipFile zip = new ZipFile())
    {
        zip.AddDirectory(Server.MapPath("~/Directories/hello"));
        zip.Save(Server.MapPath("~/Directories/hello/sample.zip"));
        return File(Server.MapPath("~/Directories/hello/sample.zip"), 
                                   "application/zip", "sample.zip");
    }
}

If the zip file is not required otherwise to be stored, it is unnecessary to write it into a file on the server:

public ActionResult Download()
{
    using (ZipFile zip = new ZipFile())
    {
        zip.AddDirectory(Server.MapPath("~/Directories/hello"));

        MemoryStream output = new MemoryStream();
        zip.Save(output);
        return File(output.ToArray(), "application/zip", "sample.zip");
    }  
}
Milind Anantwar
  • 81,290
  • 25
  • 94
  • 125
mcserep
  • 3,231
  • 21
  • 36
  • Getting error as Duplicate headers received from server. I don't have comma in my file name but still it is not working. There might be some issue in Response.AddHeader(). Please reply @CMate – user2801336 Apr 28 '15 at 17:59
  • @user2801336: you should not add the headers manually with Response.AddHeader(), as it will be automatically added by the File method. I omitted the manual addition of the header on purpose. – mcserep Apr 29 '15 at 14:36
  • 3
    Can someone identify where this ZipFile class comes from? The only one I have is a static only file, in System.IO.Compression.FileSystem. – Chris Dec 08 '15 at 22:21
  • 1
    @Chris have you looked at the tags?? It comes from `DotNETzip`. – cramopy Jan 05 '16 at 23:10
  • 9
    If your zipfile comes back empty, try putting output.Position = 0 to reset the position of the stream, otherwise it will read from the end of the stream and you'll get empty zip. Credits: René Wolferink – Alexander Troshchenko Dec 15 '16 at 20:50
  • 1
    I had to add `.ToArray()` when outputting the file, otherwise I would get an empty zip. – Richard Vanbergen Dec 28 '18 at 17:26
  • How could you do this if i already have all of the filepaths from the db and storing them in a variable. do i need to mappath to server? –  Jun 30 '20 at 20:57
  • I did this, but I don't get the zip file, I get the binary text in screen – Chariot May 24 '22 at 09:05
6

First of all, consider a way without creating any files on the server's disk. Bad practise. I'd recommend creating a file and zipping it in memory instead. Hope, you'll find my example below useful.

/// <summary>
///     Zip a file stream
/// </summary>
/// <param name="originalFileStream"> MemoryStream with original file </param>
/// <param name="fileName"> Name of the file in the ZIP container </param>
/// <returns> Return byte array of zipped file </returns>
private byte[] GetZippedFiles(MemoryStream originalFileStream, string fileName)
{
    using (MemoryStream zipStream = new MemoryStream())
    {
        using (ZipArchive zip = new ZipArchive(zipStream, ZipArchiveMode.Create, true))
        {
            var zipEntry = zip.CreateEntry(fileName);
            using (var writer = new StreamWriter(zipEntry.Open()))
            {
                originalFileStream.WriteTo(writer.BaseStream);
            }
            return zipStream.ToArray();
        }
    }
}

/// <summary>
///     Download zipped file
/// </summary>
[HttpGet]
public FileContentResult Download()
{
    var zippedFile = GetZippedFiles(/* your stream of original file */, "hello");
    return File(zippedFile, // We could use just Stream, but the compiler gets a warning: "ObjectDisposedException: Cannot access a closed Stream" then.
                "application/zip",
                "sample.zip");
}

Notes to the code above:

  1. Passing a MemoryStream instance requires checks that it's open, valid and etc. I omitted them. I'd rather passed a byte array of the file content instead of a MemoryStream instance to make the code more robust, but it'd be too much for this example.
  2. It doesn't show how to create a required context (your file) in memory. I'd refer to MemoryStream class for instructions.
Alex Klaus
  • 8,168
  • 8
  • 71
  • 87
  • With the file in memory, can I pause the download in the browser? – Black Frog Apr 27 '15 at 23:51
  • You can, but resuming the download will trigger all actions in Download method (creating streams, zipping file, etc.). See more information here - http://superuser.com/a/641957. Of course, keeping a cached file for downloading would reduce overhead for the server, but you'll have to handle simultaneous access by multiple users to the same resource/page and likely, here you'll encounter problems. – Alex Klaus Apr 28 '15 at 01:49
  • Of course, it is not bad practise - it really depends on your situation. If you have a lot of volume, then you might want to reconsider the dependency on server memory. Similarly, if creating the zip is process heavy and this is not a one-time file. Sometimes storing to disc is the best option, sometimes storing to memory is the best option. Above "Bad practise" is just the preference of @AlexKlaus – JDandChips Nov 02 '19 at 13:20
2

just a fix to Klaus solution: (as I can not add comment I have to add another answer!)

The solution is great but for me it gave corrupted zip file and I realized that it is because of return is before finalizing zip object so it did not close zip and result in a corrupted zip.

so to fix we need to just move return line after using zip block so it works. the final result is :

/// <summary>
///     Zip a file stream
/// </summary>
/// <param name="originalFileStream"> MemoryStream with original file </param>
/// <param name="fileName"> Name of the file in the ZIP container </param>
/// <returns> Return byte array of zipped file </returns>
private byte[] GetZippedFiles(MemoryStream originalFileStream, string fileName)
{
    using (MemoryStream zipStream = new MemoryStream())
    {
        using (ZipArchive zip = new ZipArchive(zipStream, ZipArchiveMode.Create, true))
        {
            var zipEntry = zip.CreateEntry(fileName);
            using (var writer = new StreamWriter(zipEntry.Open()))
            {
                originalFileStream.WriteTo(writer.BaseStream);
            }
        }
        return zipStream.ToArray();
    }
}

/// <summary>
///     Download zipped file
/// </summary>
[HttpGet]
public FileContentResult Download()
{
    var zippedFile = GetZippedFiles(/* your stream of original file */, "hello");
    return File(zippedFile, // We could use just Stream, but the compiler gets a warning: "ObjectDisposedException: Cannot access a closed Stream" then.
                "application/zip",
                "sample.zip");
}
HamedH
  • 2,814
  • 1
  • 26
  • 37
0

Create a GET-only controller action that returns a FileResult, like this:

[HttpGet]
public FileResult Download()
{   
    // Create file on disk
    using (ZipFile zip = new ZipFile())
    {
        zip.AddDirectory(Server.MapPath("~/Directories/hello"));
        //zip.Save(Response.OutputStream);
        zip.Save(Server.MapPath("~/Directories/hello/sample.zip"));
    }

    // Read bytes from disk
    byte[] fileBytes = System.IO.File.ReadAllBytes(
        Server.MapPath("~/Directories/hello/sample.zip"));
    string fileName = "sample.zip";

    // Return bytes as stream for download
    return File(fileBytes, "application/zip", fileName);
}
Karl Anderson
  • 34,606
  • 12
  • 65
  • 80
0

For those just wanting to return an existing Zip file from the App_Data folder (just dump in your zip files there), in the Home controller create this action method:

    public FileResult DownLoad(string filename)
    {
        var content = XFile.GetFile(filename);
        return File(content, System.Net.Mime.MediaTypeNames.Application.Zip, filename);

    }

Get File is an extention method:

   public static byte[] GetFile(string name)
    {
        string path = AppDomain.CurrentDomain.GetData("DataDirectory").ToString();
        string filenanme = path + "/" + name;
        byte[] bytes = File.ReadAllBytes(filenanme);
        return bytes;
    }

Home controller Index view looks like this:

@model  List<FileInfo>

<table class="table">
    <tr>
        <th>
            @Html.DisplayName("File Name")
        </th>
        <th>
            @Html.DisplayName("Last Write Time")
        </th>
        <th>
            @Html.DisplayName("Length (mb)")
        </th>
        <th></th>
    </tr>

    @foreach (var item in Model)
    {
        <tr>
            <td>
                @Html.ActionLink("DownLoad","DownLoad",new {filename=item.Name})
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Name)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.LastWriteTime)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Length)
            </td>
        </tr>
    }
</table>

The main index file action method:

    public ActionResult Index()
    {
        var names = XFile.GetFileInformation();
        return View(names);
    }

Where GetFileInformation is an extension method:

    public static List<FileInfo> GetFileInformation()
    {
        string path = AppDomain.CurrentDomain.GetData("DataDirectory").ToString();
        var dirInfo = new DirectoryInfo(path);
        return dirInfo.EnumerateFiles().ToList();
    }
JWP
  • 6,672
  • 3
  • 50
  • 74