0

I have ASP.NET Web API project where a user can download some stuff from a database. My Download controller fetches data from the database instance. Every single result has a blob field which is some kind of data (1). I want add each result to a ZIP file (2). After all I send the HTTP response adding my stream content.

List<Result> results = m_Repository.GetResultsForResultId(given_id_by_request);

// 1
foreach (Result result in results)
{
    string fileName = String.Format("{0}-{1}.bin", id >> 16, result.Id);
    zipFile.AddEntry(fileName, result.Value);
}

// 2
PushStreamContent pushStreamContent = new PushStreamContent((stream, content, context) =>
{
    zipFile.Save(stream);
    stream.Close();
}

response = new HttpResponseMessage(HttpStatusCode.OK) { Content = pushStreamContent };

It works nice! But on big download requests this exhausts my memory. I need to find a way to put a stream into a zip archive bufferless. Can someone please help me?!

marrrschine
  • 622
  • 2
  • 10
  • 21
  • Looks like there is another question [Creating Zip file from stream and downloading it](http://stackoverflow.com/a/2267750/2127492) that covers this kind of problem. Best answer should help you with your problem. – jrbeverly May 03 '16 at 06:47

1 Answers1

1

As far as I can see from the code you posted, you are not disposing the streams you create after usage. This can add to a great amount of memory being reserved by your app which might cause your problems.

I am using the ZipArchive to put multiple files into a zip file in my web application. The code Looks somewhat like that:

using (var compressedFileStream = new MemoryStream())
        {
            using (var zipArchive = new ZipArchive(compressedFileStream, ZipArchiveMode.Update, false))
            {
                foreach (Result result in results)
                {
                    string fileName = String.Format("{0}-{1}.bin", id >> 16, result.Id);
                    var zipEntry = zipArchive.CreateEntry(fileName);

                    using (var originalFileStream = new MemoryStream(result.Value))
                    {
                        using (var zipEntryStream = zipEntry.Open())
                        {
                            originalFileStream.CopyTo(zipEntryStream);
                        }
                    }
                }
            }

            return File(compressedFileStream.ToArray(), "application/zip", string.Format("Download_{0:ddMMyyy_hhmm}.zip", DateTime.Now));
}

I am using that code snippet inside an MVC Controller method so you have to adapt the return part for your situation.

The above code works fine in my application for up to 300 entries or 50MB volume (those are the limits set by the requirements for my app).

Hope that helps you.

EDIT: Forgot the closing bracket of the first using block. the return Statement has to be inside this using-block, else the stream will be disposed.

MarcoLaser
  • 266
  • 1
  • 5
  • Thanks a lot for the snippet. I'll try! The return value has to be a HttpResponse with HttpContent as content type. Maybe I'll get it running with: response = new HttpResponseMessage(HttpStatusCode.OK) { Content = new StreamContent(compressedFileStream) }; – marrrschine May 03 '16 at 07:44
  • That should work. Perhaps it is a good idea to set the ContentType of the response to "application/octet-stream" as well – MarcoLaser May 03 '16 at 07:55
  • I added response = new HttpResponseMessage(HttpStatusCode.OK) { Content = new StreamContent(compressedFileStream) }; response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); response.Content.Headers.Add("x-filename", string.Format("{0}.zip", request.Filename)); return response; unfortunatelly without success. – marrrschine May 03 '16 at 08:12
  • What is the problem? Did you set the compressedFileStream to Position 0 before creating the StreamContent? Without that the stream pointer points to the end of the stream. compressedFileStream.Seek(0, SeekOrigin.Begin) should to the trick. – MarcoLaser May 03 '16 at 08:19
  • or the ContentDisposition could cause a problem. perhaps it needs to be set to something like this: result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") { FileName = "test.exe" }; – MarcoLaser May 03 '16 at 08:30
  • What is the exact position to set compressedFileStream.Seek(0, SeekOrigin.Begin)? Do I have to do this for originalFileStream as well? My zip file is empty. – marrrschine May 03 '16 at 08:57
  • you should do this right before you create the HttpResponseMessage. Not inside the loop. – MarcoLaser May 03 '16 at 08:59
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/110887/discussion-between-marrrschine-and-marcolaser). – marrrschine May 03 '16 at 09:18