How to create a zip file and return it to the client for download?
Problem:
I'm making a .NET Core 7 MVC project, I have an endpoint that I call with a fetch from the front that should get some files from the server zip them and return them.
So far I have this
[HttpGet] public HttpResponseMessage DownloadPDFs(string jobId) { var selectedFiles = _requestsManager.GetPDFsZipFromJobId(jobId);
using var memoryStream = new MemoryStream();
using var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true);
foreach (var pdfFile in selectedFiles) { if (pdfFile != string.Empty) { var entry = archive.CreateEntry(Path.GetFileName(pdfFile));
using var entryStream = entry.Open(); using var pdfFileStream = System.IO.File.OpenRead(pdfFile);
pdfFileStream.CopyTo(entryStream); } } //UPDATE 1 archive.Dispose(); // memoryStream.Seek(0, SeekOrigin.Begin);
var response = new HttpResponseMessage(HttpStatusCode.OK) { Content = new StreamContent(memoryStream) };
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") { FileName = $"{jobId}_PDFs.zip" };
return response; } |
This gets me a file with some data I see it's size not 0, but I can't open it, when I try with WinRAR I get
The archive is either in unknown format or damaged |
This is the javascript for sending the request
function handlePDFsBatch() { fetch("/Engine/DownloadPDFs") .then((response) => { if (response.status === 200) return response.blob(); else if (response.status === 404) throw new Error("File not found."); else throw new Error("Unexpected error."); }) .then((blob) => { const url = window.URL.createObjectURL(blob);
const a = document.createElement("a"); a.style.display = "none"; a.href = url; a.download = `${crntJobId}.zip`; document.body.appendChild(a);
a.click(); window.URL.revokeObjectURL(url); }) .catch((error) => { console.error("Error downloading file:", error); }); } |
UPDATE 1 The code for creating a zip file is working correctly and making a valid zip file, I tried it on a console app and saved it to the disk. So the problem is either in sending the file or receiving it.
UPDATE 2 Solved here is a working code.
public MemoryStream GetZipStream(List<string> files) { var memoryStream = new MemoryStream();
using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true)) {
foreach (var pdfFile in files) { if (pdfFile != string.Empty) { var entry = archive.CreateEntry(Path.GetFileName(pdfFile));
using var entryStream = entry.Open(); using var pdfFileStream = System.IO.File.OpenRead(pdfFile);
pdfFileStream.CopyTo(entryStream); } } }
return memoryStream; }
[HttpGet] public IActionResult DownloadPDFs(string jobId) { var selectedFiles = _ruleEngineRequestsManager.GetPDFsNamesFromJobId(jobId);
var memoryStream = _ruleEngineRequestsManager.GetZipStream(selectedFiles);
memoryStream.Seek(0, SeekOrigin.Begin);
return File(memoryStream, "application/zip", $"{jobId}_PDFs.zip"); } |
JavaScript:
fetch("/Engine/DownloadPDFs") .then((response) => { if (response.ok) return response.blob(); else throw new Error('Failed to fetch the file'); }) .then(blob => startDownload(blob)) .catch((error) => { console.error("Error downloading file:", error); });
function startDownload(blob) { const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'PDFs.zip'; document.body.appendChild(a); a.click(); window.URL.revokeObjectURL(url); } |
Solution:
MVC and Web API actions return IActionResult or IResult objects, not HttpResponseMessage. All the code after memoryStream.Seek should be replaced with return File(memoryStream,"application/zip","MyZip.zip"), eg:
Stream PackageFiles(IEnumerable<string> files) { var memoryStream = new MemoryStream();
using(var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true)) { foreach (var pdfFile in files) { if (pdfFile != string.Empty) { var entry = archive.CreateEntry(Path.GetFileName(pdfFile));
using var entryStream = entry.Open(); using var pdfFileStream = System.IO.File.OpenRead(pdfFile);
pdfFileStream.CopyTo(entryStream); } } } memoryStream.Seek(0, SeekOrigin.Begin); return memoryStream;
}
[HttpGet] public IActionResult DownloadPDFs(string jobId) { var selectedFiles = _requestsManager.GetPDFsZipFromJobId(jobId);
var archiveStream= PackageFiles(selectedFiles) return File(archiveStream,"application/zip",$"PDFs_for_{jobId}.zip") } |
The >File can return an >ETag and >Last-Modified header too.
If the PDFs are stored in a specific folder, the entire PackageFiles method can be replaced by >ZipArchive.CreateFromDirectory :
[HttpGet] public IActionResult DownloadPDFs(string jobId) { ... var tempPath=$"path_to_temp/PDFs_for_{jobId}.zip"; ZipArchive.CreateFromDirectory(jobFilePath, tempPath, CompressionLevel.SmallestSize, false);
var archiveStream= PackageFiles(selectedFiles) return File(tempPath,"application/zip",$"PDFs_for_{jobId}.zip") } |
The temporary file won't be deleted automatically. You'll have to clean up the temporary folder periodically, or use a custom FileResult object >like this one that deletes the temporary file at the end
Suggested blogs:
>What are common syntax errors and exceptions in Python
>What are the steps to fix error while downloading PDF file- Python
>How to do PHP Decryption from Node.js Encryption?
>How to do PHP gd Installation on Ubuntu?
>How to do Web Scraping with Python?
>How to do wild grouping of friends in Python?
>How to download an entire S3 bucket - Complete Guide