Create ZIP File Then Send to Client

3.6k views Asked by At

I have a button in my web page that will export CSV files. There are 5 files in total. When the client clicks the button, the server will create the files, compress them into one ZIP file, then send the ZIP file to the client for download.

I have heard around the forums about SharpZipLab and DotNetZip, but I haven't explored any yet. I have also heard using System.IO.Compression. Which of these methods would you recommend?

I have this code to create the 5 CSV files:

StringBuilder sb = new StringBuilder();
        DataTable[] dtCSV =
        {
            file1BLO.SelectFile1ForCSV(),
            file2BLO.SelectFile2ForCSV(),
            file3BLO.SelectFile3ForCSV(),
            file4BLO.SelectFile4ForCSV(),
            file5BLO.SelectFile5ForCSV()
        };
        for (int i = 0; i <= 4; i++)
        {
            DataTable dt = dtCSV[i];
            foreach (DataRow dr in dt.Rows)
            {
                string[] fields = dr.ItemArray.Select(field => field.ToString()).ToArray();
                sb.AppendLine(string.Join("|", fields));
            }
            Response.ContentType = "application/text";
            Response.AddHeader("content-disposition", "attachment;filename=CAPRES-FILE" +
                (i + 1) + "-" + DateTime.Now.ToString("yyyyMMdd-HHmmss") + ".txt");
            Response.Output.Write(sb);
            Response.Flush();
            sb.Clear();
        }
        Response.End();

EDIT I'm using ASP.NET v4.0.

EDIT 2 Apparently I have System.IO.Compression, which is weird because I though it is only supported in v4.5. Coincidentally, I don't have System.IO.Packaging.

1

There are 1 answers

0
Jeano Ermitaño On BEST ANSWER

With the help of Sachu, we were able to accomplish this requirement. We used DotNetZip over SharpZipLib due to its licensing issues.

In facilitate our development of this functionality, I ought to create a program flow based on my requirements:

  1. Create text files
  2. Add the text files to a folder
  3. Compress this folder in Zip format
  4. Send to client using Response
  5. Delete files

Step 0 - Setup Project

Before we start the process, we must prepare the project. This include adding necessary folders and instantiate variables.

First we add a folder to which we will 'temporarily' add the text files. This folder will also be the one that will get compressed. I decided to create the folder in the root directory of the project with the name CSV.

Now we'll be using the DotNetZip library. You can download it here. Add the library to your project references. Then add the using, which is using Ionic.Zip;.

Then we instantiate the variables such as the zipFileName, textFileName, etc. The names speak for themselves.

The data that I'll be using for the text files will be from the DataTable[] array, which each DataTable corresponding to a specific SQL query.

DataTable[] dtCSV =
{
    file1BLO.SelectFile1ForCSV(),
    file2BLO.SelectFile2ForCSV(),
    file3BLO.SelectFile3ForCSV(),
    file4BLO.SelectFile4ForCSV(),
    file5BLO.SelectFile5ForCSV()
};
StringBuilder sb = new StringBuilder();
string textFileNameTemplate = Server.MapPath(@"~\CSV") + @"\file";
Response.Clear();
Response.BufferOutput = false;
Response.ContentType = "application/zip";
Response.AppendHeader("content-disposition", "attachment;filename=CAPRES-" + 
    DateTime.Now.ToString("yyyyMMdd-HHmmss") + ".zip");

Step 1 - Create Text Files

This is fairly easy. I used a StringBuilder to convert the results from the DataTables. Using this, I then used a StreamWriter to build the text files themselves.

for (int i = 0; i <= 4; i++)
{
    DataTable dt = dtCSV[i];
    foreach (DataRow dr in dt.Rows)
    {
        string[] fields = dr.ItemArray.Select(field => field.ToString()).ToArray();
        sb.AppendLine(string.Join("|", fields));
    }
    string textFileName = textFileNameTemplate + (i + 1) + ".txt";
    var textFile = new StreamWriter(textFileName);
    textFile.WriteLine(sb.ToString());
    textFile.Flush();
    textFile.Close();
}

Notice how I used the textFileNameTemplate variable. I append the iterator and a .txt file extension. Therefore, we will have files named file1.txt, file2.txt, file3.txt, etc.

Step 3 & 4 - Compress The Folder & Send To Client

Now we can proceed with the zipping. We modified the code in Step 2 to accommodate the library.

using (ZipFile zip = new ZipFile()) //encapsulate Step 2 code in this code block
{
    for (int i = 0; i <= 4; i++)
    {
        DataTable dt = dtCSV[i];
        foreach (DataRow dr in dt.Rows)
        {
            string[] fields = dr.ItemArray.Select(field => field.ToString()).ToArray();
            sb.AppendLine(string.Join("|", fields));
        }
        string textFileName = textFileNameTemplate + (i + 1) + ".txt";
        var textFile = new StreamWriter(textFileName);
        textFile.WriteLine(sb.ToString());
        textFile.Flush();
        textFile.Close();
        sb.Clear();
        zip.AddFile(textFileName, @"\"); //this is new
    }
    zip.Save(Response.OutputStream); //this is also new
}
Response.Flush();
Response.End(); 

zip.AddFile(textFileName, @"\"); adds the text file to an archive. The @"\" means that DotNetZip will not create subfolders that lead to the file, e.g. if my file is in this path: C:\User\Documents\...\file1.txt, the archive would have a similar structure of folders. With @"\", the archive will only contain the text file.

Also take note of sb.Clear(); and its position in the code. It's important that it is inside the for loop but after the textFile.WriteLine(sb.ToString()); line. This makes sure that strings written before are cleared before looping again. This avoid carrying over strings from File1 to File2, and File2 to File3, and so on.

zip.Save(Response.OutputStream); will directly output the Zip file to the Response and does not save the file in the server.

Step 5 - Delete Files

This step depends on your requirements. For me, we will delete the generated files. Using System.IO.File, we will delete the text files. After the using ZipFile zip = new ZipFile()) code block, we'll add the following lines:

for (int i = 1; i <= 5; i++)
{
    File.Delete(textFileNameTemplate + i + ".txt");
}

My code probably isn't the most optimized code. But it works. If anyone can suggest a better code that would be great. But for now, I'll be using this code. Many thanks! Especially to Sachu, a really helpful person.