Search code examples
c#asp.net-mvcwebclientobjectdisposedexceptionionic-zip

Cannot access Response.OutputStream in an ASP.NET MVC application because it's closed


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?


Solution

  • 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.