2

I'm trying to steam file in my web application from another domain and I'll zip this files for a download. For this I use Ionic Zip. But I've got this error on the marked line in my code:

System.ObjectDisposedException: No access to a closed Stream.

Here is my code. I'm using C# in an ASP.NET MVC application.

using Ionic.Zip;
using System.IO;
using System.Net;

[HttpPost]
public async Task<ActionResult> Downloads(string lang, string product, IEnumerable<string> file, string action)
{
    string zipname = "manuals.zip";

    using (ZipFile zip = new ZipFile())
    {
        foreach (string f in file.Distinct())
        {
            using (WebClient client = new WebClient())
            {
                using (MemoryStream output = new MemoryStream())
                {
                    byte[] b = client.DownloadData(f);
                    await output.WriteAsync(b, 0, b.Length);
                    await output.FlushAsync();
                    output.Position = 0;
                    zip.AddEntry(f.Split('/').Last(), output);
                    output.Close();
                }
            }
        }

        Response.Clear();
        Response.ContentType = "application/zip, application/octet-stream";
        Response.AddHeader("content-disposition", $"attachment; filename={product.Replace('/', '-')}-{zipname}");
        zip.Save(Response.OutputStream); // ← error on this line
        Response.End();
    }
}

What's wrong with this code?

H. Pauwelyn
  • 13,575
  • 26
  • 81
  • 144
  • 1
    Could you test what happens if you don't dispose the memorystream and don't call close on it. It might be that reading the actual memory stream is deferred until `Save` is called. In your code all those streams are then already closed, disposed and maybe even GC'd. – rene Mar 27 '18 at 06:51
  • @rene: This works. But will **all the streams** be closed after the `Response.End()` or must I do that manualy? – H. Pauwelyn Mar 27 '18 at 07:01
  • 1
    The caller is normally responsible for closing/disposing of what it created. It might transfer that responsibility but it has to do so explicitly. None of that applies to what HttpResponse is capable of, so yes, you need to do that manually. – rene Mar 27 '18 at 08:19

1 Answers1

1

By a comment of @rene I've found an answer that works. He said:

Could you test what happens if you don't dispose the MemoryStream and don't call close on it. It might be that reading the actual memory stream is deferred until Save is called. In your code all those streams are then already closed, disposed and maybe even GC'd.

See here my code.

using Ionic.Zip;
using System.IO;
using System.Net;

[HttpPost]
public ActionResult Downloads(string lang, string product, IEnumerable<string> file, string action)
{
    string zipname = "manuals.zip";
    List<MemoryStream> streams = new List<MemoryStream>();

    using (ZipFile zip = new ZipFile())
    {
        foreach (string f in file.Distinct())
        {
            using (WebClient client = new WebClient())
            {
                MemoryStream output = new MemoryStream();
                byte[] b = client.DownloadData(f);

                output.Write(b, 0, b.Length);
                output.Flush();
                output.Position = 0;
                zip.AddEntry(f.Split('/').Last(), output);

                // output.Close(); // ← removed this line
                streams.Add(output);
            }
        }

        Response.Clear();
        Response.ContentType = "application/zip, application/octet-stream";
        Response.AddHeader("content-disposition", $"attachment; filename={product.Replace('/', '-')}-{zipname}");
        zip.Save(Response.OutputStream);

        foreach (MemoryStream stream in streams)
        {
            stream.Close();
            stream.Dispose();
        }

        Response.End();
    }
}

To be sure that all streams are closed I've added all the opened MemoryStreams to a list and before Response.End();, I go close and dispose them all.

H. Pauwelyn
  • 13,575
  • 26
  • 81
  • 144