26

So I've got an MVC 3 application that has a couple places where a text file gets generated and returned in an action using:

return File(System.Text.Encoding.UTF8.GetBytes(someString),
                 "text/plain", "Filename.extension");

and this works fabulously. Now i've got a situation where I'm trying to return a pair of files in a similar fashion. On the view, i have an action link like "Click here to get those 2 files" and i'd like both files to be downloaded much like the single file is downloaded in the above code snippet.

How can I achieve this? Been searching around quite a bit and haven't even seen this question posed anywhere...

Giovanni B
  • 1,032
  • 1
  • 9
  • 12
  • 2
    http://www.motobit.com/tips/detpg_multiple-files-one-request/ – Robert Harvey Oct 03 '12 at 17:33
  • HTML is "One Request Per File", so I think you're going to need to do it client-side using Javascript. – Robert Harvey Oct 03 '12 at 17:34
  • So two separate requests? Like have a little javascript that fires off two different gets? – Giovanni B Oct 03 '12 at 17:37
  • Any hints on how to accomplish that? i've tried a couple different routes, and i can't get them to fire off synchronously, resulting in only the second file getting returned. – Giovanni B Oct 03 '12 at 18:02
  • http://stackoverflow.com/q/9047645 – Robert Harvey Oct 03 '12 at 18:04
  • Wow this is incredibly involved for what seems like should be a pretty simple problem. I guess it's more complicated than it seems... – Giovanni B Oct 03 '12 at 18:25
  • 2
    I personally like the Zip solution. Everyone has a way to unzip files nowadays. Presumably all of the files are related, which means that if you don't zip them, your client winds up with a bunch of random (but related) files in their Download folder. Better to package them up in a Zip and let the user decide what to do with them. – Robert Harvey Oct 03 '12 at 18:27

4 Answers4

42

Building on Yogendra Singh's idea and using DotNetZip:

var outputStream = new MemoryStream();

using (var zip = new ZipFile())
{
    zip.AddEntry("file1.txt", "content1");
    zip.AddEntry("file2.txt", "content2");
    zip.Save(outputStream);
}

outputStream.Position = 0;
return File(outputStream, "application/zip", "filename.zip");

Update 2019/04/10: As @Alex pointed out, zipping is supported natively since .NET Framework 4.5, from JitBit and others:

using (var memoryStream = new MemoryStream())
{
   using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true))
   {
      var file1 = archive.CreateEntry("file1.txt");
      using (var streamWriter = new StreamWriter(file1.Open()))
      {
         streamWriter.Write("content1");
      }

      var file2 = archive.CreateEntry("file2.txt");
      using (var streamWriter = new StreamWriter(file2.Open()))
      {
         streamWriter.Write("content2");
      }
   }

   return File(memoryStream.ToArray(), "application/zip", "Images.zip")
}
tocallaghan
  • 8,432
  • 1
  • 28
  • 23
  • 1
    Genius. One of those answers you feel dumb for not thinking of yourself, lol. – BVernon Feb 06 '18 at 07:45
  • 1
    Just wanted to point out that zip-operations are now part of .NET Framework https://www.jitbit.com/alexblog/278-returning-a-zip-file-from-aspnet-mvc-action---in-pure-net/ – Alex from Jitbit Apr 09 '19 at 20:37
  • 1
    The zipArchiveEntry (file1, file2) and streamWriter can be replaced with zipArchive.CreateEntryFromFile(pathFileName, fileName). Will require reference System.IO.Compression.FileSystem – Will Feb 03 '20 at 20:28
6

Sorry for bumping an old question but...

Another alternative would be to initiate multiple file downloads using JavaScript, and serve files in two different Action Methods on ASP.NET's side.

You're saying you have a link:

On the view, i have an action link like "Click here to get those 2 files"

So make this link like this:

<a href="#" onclick="downloadFile(url1); downloadFile(url2);">Click to get 2 files</a>
<script src="download.js"></script>

I'm using download.js script found here but you can find plenty of different other options, see this SO question: starting file download with JavaScript for example

Community
  • 1
  • 1
jazzcat
  • 4,351
  • 5
  • 36
  • 37
4

I would advice to create a zip file to include both the files using steps(ALGORITHM):

  1. Create a Zip file and add the desired files into the zip
  2. Return the zip file having all desired files from the action

Java Syntax (Just for understanding)

      FileOutputStream fos = new FileOutputStream("downloadFile.zip");
      ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(fos));
      zos.putNextEntry(new ZipEntry("Filename1.extension"+));
      //write data in FileName1.extension
      zos.write(contentBuffer1, 0, len);

      zos.putNextEntry(new ZipEntry("Filename2.extension"));
      //write data in FileName2.extension
      zos.write(contentBuffer2, 0, len);

      //write other files.....
      zos.close();

Once zip file is created, return the newly created zip file to download.

     return File("downloadFile.zip");

.DOT Net Equivalent using DotNetZip

     var os = new MemoryStream();

     using (var zip = new ZipFile())
     {
         //write the first file into the zip
         zip.AddEntry("file1.txt", "content1");

         //write the second file into the zip
         zip.AddEntry("file2.txt", "content2");

         //write other files.....
         zip.Save(os);
      }

      outputStream.Position = 0;
      return File(outputStream, "application/zip", "filename.zip");

Hope this helps!

Gilles
  • 5,269
  • 4
  • 34
  • 66
Yogendra Singh
  • 33,927
  • 6
  • 63
  • 73
  • 1
    +1 That sounds like a good solution. Off topic: You would *advise to create [...]. – user1477388 Oct 03 '12 at 17:41
  • I was trying to avoid creating a zip file. But i'm a little confused.. How would creating a zip file and then not using it in the return File() call help anything? In the solution you posted, you create a nice little zip file, and then just return the original (single) text file as a zip... – Giovanni B Oct 03 '12 at 17:49
  • I am suggesting to return the new zip file including both the original files. I am not suggesting to return one text file as zip file. – Yogendra Singh Oct 03 '12 at 17:55
  • So how would return File(System.Text.Encoding.UTF8.GetBytes(someString), "application/zip", "downloadFile.zip"); do that? – Giovanni B Oct 03 '12 at 17:55
  • Not sure if you're familiar with the signature of File() that I'm using, but what your code does is returns an (incorrectly labeled) zip file called downloadFile.zip with contents that are just the value of someString. So if someString was "This is a test" the code you posted just returns a text file with "This is a test" called downloadFile.zip. – Giovanni B Oct 03 '12 at 17:57
  • I got your point. In this case, I think you can first create two files and add them in the zip. Finally return the zip file. – Yogendra Singh Oct 03 '12 at 18:00
  • This is a fantastic idea but you seem to be using java classes. I followed the same basic idea but used [DotNetZip](http://dotnetzip.codeplex.com/) – tocallaghan Feb 21 '13 at 01:59
1

Look at this SO solution: MVC Streaming Zip File

The advantage of this solution is that it streams the file to the client.

I just implemented this solution a couple of days ago and it worked fantastic.

Community
  • 1
  • 1
Shai Cohen
  • 6,074
  • 4
  • 31
  • 54