1

I've looked at other questions on here but nobody answers my question as to why I can't use the already existing MemoryStream that exists to create the zip.

Here is a working API method to get a zipped folder containing files. The files already exist on the server and the method simply looks up there location and, all in memory, retrieves all the files and sends the user a HttpResponMessage object with the Zipped folder containing all the files. There is one line of code below that doesn't seem to make sense however. I have to parse the MemoryStream to a byte array then back to a MemoryStream in order for the Zip to be created and sent back correctly. This seems wrong, but I don't know how to correct it. (See below "//Works:" and "//Does Not work:")

public HttpResponseMessage GetEvalByCompany([FromUri]Guid id, [FromUri] int year)
{
        try
        {
            string companyName = _portalDB.Companies.FirstOrDefault(c => c.Id == id).FirmName;
            //Get all evaluation file paths for a given company for a given year
            var files = _evaluationDB.Evaluations.Where(j => j.CompanyId == id).Select(j => @"C:\Path\To\File" + j.RelativePath.Replace("~", @"").Replace(@"/", @"\")).Distinct().ToList<string>();

            using (var outStream = new MemoryStream())
            {
                using (var archive = new ZipArchive(outStream, ZipArchiveMode.Create, true))
                {
                    foreach (var record in files)
                    {
                        var fileInArchive = archive.CreateEntryFromFile(record, Path.GetFileName(record), CompressionLevel.Optimal);
                    }
                }

                // Create a response
                HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.OK);



                //Works:
                response.Content = new StreamContent(new MemoryStream(outStream.ToArray())); //Why does this need to happen?
                //Does Not work:
                //response.Content = new StreamContent(outStream); //Why doesn't this work?



                // Add appropriate headers to make browser recognize response as a download
                response.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment");
                response.Content.Headers.ContentDisposition.FileName = "Evaluations for Company - " + companyName + ".zip";
                response.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/zip");
                return response;
            }
        }
        catch (Exception ex)
        {
            return ErrorHandler.HandleException(ex, Request);
        }
}

Based on the answers I received I was able to fix the code so it doesn't have to parse the stream unnecessarily. Here's the updated code for future devs looking for the solution simply. Removing the using doesn't seem to cause any GC issues either. I found a link to explain that a bit better as well. (MemoryStream.Close() or MemoryStream.Dispose())

public HttpResponseMessage GetEvalByCompany([FromUri]Guid id, [FromUri] int year)
{
        try
        {
            string companyName = _portalDB.Companies.FirstOrDefault(c => c.Id == id).FirmName;
            //Get all evaluation file paths for a given company for a given year
            var files = _evaluationDB.Evaluations.Where(j => j.CompanyId == id).Select(j => @"C:\Path\To\File" + j.RelativePath.Replace("~", @"").Replace(@"/", @"\")).Distinct().ToList<string>();

            var outStream = new MemoryStream();
            using (var archive = new ZipArchive(outStream, ZipArchiveMode.Create, true))
            {
                foreach (var record in files)
                {
                    var fileInArchive = archive.CreateEntryFromFile(record, Path.GetFileName(record), CompressionLevel.Optimal);
                }
            }

            // Create a response
            HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.OK);

            //Updated code
            outStream.Seek(0, SeekOrigin.Begin);
            response.Content = new StreamContent(outStream); //works now because outStream isn't in a using block


            // Add appropriate headers to make browser recognize response as a download
            response.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment");
            response.Content.Headers.ContentDisposition.FileName = "Evaluations for Company - " + companyName + ".zip";
            response.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/zip");
            return response;

        }
        catch (Exception ex)
        {
            return ErrorHandler.HandleException(ex, Request);
        }
}
Grim Coder
  • 394
  • 2
  • 12
  • 1
    Shouldn't it be sufficient to seek the stream back to zero? – Kyle Huff May 11 '18 at 02:17
  • When I use .Seek(0, SeekOrigin.Begin) or .Position = 0, I get the same result as the "Does Not work" code. the broswer gets a "successful" 200 response with headers acknowledging a document in the content but status of failed and no zip downloaded. – Grim Coder May 11 '18 at 03:05

2 Answers2

1

The problem here is that your original MemoryStream is getting disposed before it is used. StreamContent is keeping a reference to an object that is disposed. You need to remove the using block for outStream and somehow arrange to dispose of it later.

Kyle Huff
  • 169
  • 7
  • Upvoting, your answer, because it does help explain the issue, but ipavlu's has the actual code to fix the problem. Both are helpful. One in fixing the problem and one in understanding. – Grim Coder May 11 '18 at 22:03
1

Remove

using (var outStream = new MemoryStream()),

leave instead only:

var outStream = new MemoryStream();

Then later :

outStream.Seek(0, SeekOrigin.Begin)
response.Content = new StreamContent(outStream);
ipavlu
  • 1,617
  • 14
  • 24
  • This worked. The using was the issue and based on this link, https://stackoverflow.com/questions/4274590/memorystream-close-or-memorystream-dispose, it seems I didn't really need it anyway because the disposal of the Stream seems to not have any "real world impacts". I thought anything wrapped in the using bracket could use the implemented stream, but in this case, at least when returning, there seems to be a caveat. – Grim Coder May 11 '18 at 22:01