Question

I am creating a list of ExcelPackages (xlsx documents) using EPPlus, and adding them to a ZipOutputStream as ZipEntries. I think the Excel documents should be valid, as I can open them just fine when I write one of them to the Response object without zipping.
The zip folder is created as expected, and the file(s) are there and doesnt seem to be empty, but when I try to open them I get the following error in Excel:

Excel cannot open the file {Name}.xlsx because the file format or file extension is not valid. Verify that file has not been corrupted and that the file extension matches the format of the file

List<ExcelPackage> documents = new List<ExcelPackage>();
List<string> fileNames = new List<string>();

//Code for fetching documents and filenames here (removed for the sake of readability)

Response.Clear();
Context.Response.BufferOutput = false; 
Response.ContentType = "application/zip";
Response.AppendHeader("content-disposition", "attachment; filename=\"random-foldername.zip\"");
Response.CacheControl = "Private";
Response.Cache.SetExpires(DateTime.Now.AddMinutes(3)); 

ZipOutputStream zipOutputStream = new ZipOutputStream(Response.OutputStream);
zipOutputStream.SetLevel(3); //0-9, 9 being the highest level of compression
byte[] buffer = null;

for (int i = 0; i < documents.Count; i++)
{
    MemoryStream ms = new MemoryStream();

    documents[i].SaveAs(ms);

    ZipEntry entry = new ZipEntry(ZipEntry.CleanName(fileNames[i]));

    zipOutputStream.PutNextEntry(entry);
    buffer = new byte[ms.Length];

    ms.Read(buffer, 0, buffer.Length);
    entry.Size = ms.Length;

    ms.Close();

    zipOutputStream.Write(buffer, 0, buffer.Length);
    zipOutputStream.CloseEntry();

}
zipOutputStream.Finish();
zipOutputStream.Close();

Response.End();

As for the list of filenames, im just generating a name based on some arbitrary stuff and adding a ".xlsx"-extension to the end of it.

Im not sure where im going wrong here, any suggestions?

Was it helpful?

Solution

You have to rewind memory stream before you can read something (after write operation file pointer is that its end):

ms.Seek(0, SeekOrigin.Begin)
ms.Read(buffer, 0, buffer.Length);

That said a MemoryStream is nothing more than a byte array so you don't even need to allocate and read a new one then this code:

buffer = new byte[ms.Length];

ms.Read(buffer, 0, buffer.Length);
entry.Size = ms.Length;

ms.Close();

zipOutputStream.Write(buffer, 0, buffer.Length);

Can be simply replaced with:

entry.Size = ms.Length;
zipOutputStream.Write(ms.GetBuffer(), 0, ms.Length);
ms.Close();

Final note: if you don't want to use internal MemoryStream buffer (for any reason) and you want a trimmed copy of it (as you're doing manually) then simply use ToArray() method like this:

var buffer = ms.ToArray();
ms.Close();
entry.Size = buffer.Length;
zipOutputStream.Write(buffer, 0, buffer.Length);
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top