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);
}
}