I have seen several articles explaining how to create a ZIP file in memory, and also how to download a file with JavaScript. Nothing exactly covers what I'm trying to do.
In my ASP.NET Core application, a form posts to the controller and does several processing steps, culminating in creating one to many different physical files on the server (works fine), then creating a specifically named ZIP file containing these files (works fine). I then want to return an object containing several bits of information: number of things processed, overall status of completion, path to the archive to download.
I can pass my object back and retrieve all this information just fine.
Object that is returned:
public class UpdateResultsVM
{
public int ClaimUpdateReturn {get; set; }
public string ClaimUpdateMsg {get; set; }
public string ZipFile {get; set; } // full physical path to archive
public bool Completed {get; set; }
public string Message {get; set; }
}
And the client code:
$.ajax({
type: "POST",
url: "/Work/PerformProcess",
data: { IDs: IDs, LetterID: LetterID },
dataType: "json",
success: function (result) {
d.resolve(result);
if (result.ClaimUpdateReturn === 0) {
console.log("returned 0");
// error handling
}
else if (result.ClaimUpdateReturn === 1) {
//console.log("returned 1");
if (result.Completed === true && result.ZipFile !== null) {
//console.log("got something to download: " + result.ZipFile);
var zipfilepath = result.ZipFile;
var zipfilename = zipfilepath.split('\\').pop().split('/').pop();
console.log("path " + zipfilepath + " name " + zipfilename);
var a = document.createElement('a');
document.body.appendChild(a);
a.style= "display: none";
a.href = zipfilepath;
a.download = zipfilename;
a.click();
document.body.removeChild(a);
console.log("done with download");
}
}
else {
//console.log("else statement");
// do some other stuff
}
},
error: function (response) {
alert("There was an error in updating " " + response.Message);
d.resolve(response);
}
});
The controller function to create the zip file is such:
// modify to return in-memory binary data of zip instead?
public string CreateZipFile(string destinationDirectory, string tagName, List<string> Documents)
{
string FileName;
try
{
string filePath = Path.Combine(destinationDirectory, "TempOutput");
System.IO.Directory.CreateDirectory(filePath);
string zipName = filePath + "\\" + (String.IsNullOrEmpty(tagName) ? "Zip_" : tagName + "_") + DateTimeOffset.Now.ToString("yyyyMMddHHmmssffff") + ".zip";
using (ZipArchive zip = ZipFile.Open(zipName, ZipArchiveMode.Create))
foreach (string document in Documents)
{
zip.CreateEntryFromFile(document, Path.GetFileName(document));
// these need to remain as physical files, they
// are used elsewhere in the app.
}
FileName = zipName;
}
catch (Exception ex)
{
// stuff
}
return FileName;
}
But the physical location of the created zip file is not under web root, so it throws an error when I attempt to download it in this manner. I can't place it under web root.
Having a physical file created on the server is actually not optimal, since I will then have to do some cleanup to remove these files, all uniquely named. So I'm thinking to convert to doing this archive file in memory instead.
I am relatively clear how I can modify the controller to create a zip file in memory, although input is definitely welcome. I'm thinking that I might want to add to UpdateResultsVM the actual binary contents of the zip file and download that, retaining the passed string ZipFile to name it for download. Tried several things and I can't get this to work. I want to return all this information AND the generated file name for the download.
What type should I use to capture the in-memory zip file, and what do I need to do in my JavaScript to successfully download this zip file?
I resolved the problem. I added a new member to the UpdateResultsVM byte[] ZipFileData. In my controller, I'm filling in both the unique file name for download, and generating the zip file in memory and putting that byte[] data into ZipFileData.
In my javascript, I'm now doing the following and it's working great:
var a = document.createElement('a');
var binaryData = [];
binaryData.push(result.ZipFileData);
var url = window.URL.createObjectURL(b64toBlob(binaryData, 'application/zip'), { type: "application/zip" });
a.href = url;
var link = result.ZipFile; // the name of the file to download
a.download = link;
document.body.append(a);
a.click();
a.remove();
window.URL.revokeObjectURL(url);
I found the b64toBlob function on some related answer here on StackOverflow, now I can't find it, but the function is:
var b64toBlob = function (b64Data, contentType, sliceSize) {
contentType = contentType || '';
sliceSize = sliceSize || 512;
var byteCharacters = atob(b64Data);
var byteArrays = [];
for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) {
var slice = byteCharacters.slice(offset, offset + sliceSize);
var byteNumbers = new Array(slice.length);
for (var i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
var byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
}
var blob = new Blob(byteArrays, { type: contentType });
return blob;
};