Search code examples
c#angulardownloadzipxlsm

Downloading more than one .xlsm files as ZIP is corrupted


I could download the .xlsm file individually but when I tried to download them as ZIP. It says 'The file is corrupt and cannot be opened'. This is happening only for the .xlsm files but not while downloading .csv files as ZIP

Server-side API controller

public async Task<IActionResult> DownloadZIP([FromBody] List<SomeList> req)
{
     List<string> pathLists = new List<string>();
     // From the req getting the file paths
     pathLists.Add(filePath);
     
     try
     {
        // if pathLists null, return from there
        Stream streamZip = await Task.Run(() => someServer.ConvertToZIP(pathLists));
        return File(streamZip, "application/zip", "file_name.zip");
     }
     catch (Exception ex)
     {
        // Handle exception and return
     }
}

Server-side service API

public Stream ConvertToZIP (List<string> pathList)
{
    var memoryStream = new MemoryStream();
    using (ZipArchive zipArchive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true)) 
    {
        pathList.ForEach(filePath =>
        {
            string fileName = Path.GetFileName(filePath);
            var demoFile = zipArchive.CreateEntry(fileName);
            var stream = CreateStream(filePath, FileMode.Open);
            var reader = new StreamReader(stream);
            var entryStream = demoFile.Open();
            using (var streamWriter = new StreamWriter(entryStream))
            {
               streamWriter.Write(reader.ReadToEnd());
            }
        });
    }
    memoryStream.Position = 0;
    return memoryStream;
}

Client side

downloadZIPFile() 
{
    someService.DownloadZIP(this.createList()).subscribe(res => {
       if (res) {
           var binaryData = [];
           binaryData.push(res);
           var url= window.URL.createObjectURL(new Blob(binaryData, {type: "application/zip"}));
           var link = document.createElement('a');
           link.href = url;
           link.download = "filename.zip";
           link.click();a
       }
    });
}

Client service

DownloadZIP(req: someEvents[]) : observable<Blob> 
{
   let dowloadAPI = "someapi/some/some";
   const httpOptions = {
      responseType : 'blob' as 'json';
      headers: new HttpHeaders({
         // type as 'text/csv' also not worked
         'Accept': 'application/vnd.ms-excel.sheet.macroEnabled.12'  
      })
   };
   return this.http.post<Blob>(downloadAPI, req, httpOptions);
}

The file size is same both at the server and client side. What am I missing?


Solution

  • StreamReader and StreamWriter are designed to read/write text. If you use them to read and write non-text binary data (like .xlsm files) you run the risks of:

    • The StreamReader misinterpreting binary data as characters, or:
    • The StreamWriter messing up the output data

    Either situation, or the combination of both, causes the data that's fed to the compression algorithm to differ from that of the original file, in essence corrupting it.

    The reason CSV files work is because they are plain-text files, which is exactly what StreamReader/StreamWriter were designed for. Assuming that the encoding of the CSV files are ASCII or UTF-8, the compressed data will pretty much be 1:1 to the original file (by default, StreamReader and StreamWriter use UTF-8 encoding to interpret/write data).

    To solve the problem, remove the use of StreamReader and StreamWriter, and copy the data between the base streams directly:

    using (ZipArchive zipArchive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true)) 
    {
        pathList.ForEach(filePath =>
        {
            string fileName = Path.GetFileName(filePath);
            var demoFile = zipArchive.CreateEntry(fileName);
    
            // Don't forget to dispose the input stream
            using (var stream = CreateStream(filePath, FileMode.Open))
            {
                using (var entryStream = demoFile.Open())
                {
                     stream.CopyTo(entryStream);
                }
            }
        });
    }