Download the zip file using DotNetZip in c# MVC5 - javascript

The scenario:
filter a jquery datatable according to our need.
use download button present besides page length dropdown(its a simple button not a submit button). to download the images for the filtered data.
get the record ids from the table, make its array and send it to the controller.
In controller, fetch the file paths from the database associated with the record ids sent from the
ajax req.
get the files, make a zip and send it back (in response) to the view (download).
as I mentioned the scenario I want the zip to get downloaded on the machine.
But the file is not getting downloaded.
--------------------------Edit [solution]:---------------------------
After Trying Many Solutions I finally got the solution.
So first, I used Controller Code as :
[HttpPost]
public ActionResult Ajax_DownloadImages(int[] records)
{
#region Variable Declaration
List<tbl_image_Details> obj_records = new List<tbl_image_Details>();
tbl_image_Details singleRecord = new tbl_image_Details();
var memorystream = new MemoryStream();
int temp = 0;
#endregion
using (Symphony_webServer_DBEntities db = new Symphony_webServer_DBEntities())
{
#region Get File paths from the database.
for (int i = 0; i < records.Count(); i++)
{
temp = records[i];
singleRecord = db.tbl_image_Details.Where(x => x.record_id == temp).FirstOrDefault<tbl_image_Details>();
obj_records.Add(singleRecord);
}
#endregion
#region Zipping and sending the data to download.
using (ZipFile obj_Zip = new ZipFile())
{
obj_Zip.AlternateEncodingUsage = ZipOption.AsNecessary;
obj_Zip.AddDirectoryByName("Images");
foreach (var file in obj_records)
{
obj_Zip.AddFile(file.image_path, "Images");
}
Response.ClearContent();
Response.ClearHeaders();
Response.AppendHeader("content-disposition", "attachment; filename=Myzip.zip");
obj_Zip.Save(memorystream);
}
memorystream.Position = 0;
return new FileStreamResult(memorystream, "application/octet-stream");
#endregion
}
}
The View:
Create a simple button and call the download function on its click event as follows.
Function That requests the zipped data from server is-
Note : I used XMLHttpRequest object to make a request call because,
jquery ajax call is not efficient to handle the blob response content.
funtion DownloadImages(){
// selecting the table
var Displayedtable = $("#recordTable").DataTable();
// fetching the rows of the table
var datatable_rows = Displayedtable.rows().data().toArray();
// creating an array to hold data.
var table_data = new Array();
// fetching data from each cell and putting it into the array.
$.each(datatable_rows, function (index, value) {
table_data.push(value['record_id']);
});
var records = JSON.stringify(table_data);
var ajax = new XMLHttpRequest();
ajax.open("Post", "/ReportsPage/Ajax_DownloadImages", true);
ajax.setRequestHeader("Content-Type", "application/json");
ajax.responseType = "blob";
ajax.onreadystatechange = function () {
if (this.readyState == 4) {
var blob = new Blob([this.response], { type: "application/octet-stream" });
console.log(this.response);
alert(this.response);
var fileName = "Myzip.zip";
saveAs(blob, fileName);
}
};
ajax.send(records);
}
This will definitely download the intended zip file.

Related

How to handle data from JsonResult in controller as a JS

I have an app that uploads and reads excel files.
the controller returns JSON
public ActionResult OnPost(IFormFile file)
{
List<FlightModel> flights = new List<FlightModel>();
System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance);
using (var stream = new MemoryStream())
{
file.CopyTo(stream);
stream.Position = 0;
using (var reader = ExcelReaderFactory.CreateReader(stream))
{
while (reader.Read()) //Each row of the file
{
flights.Add(new FlightModel
{ Side = reader.GetValue(0).ToString(), REG = reader.GetValue(1).ToString()
,
Actual_Date = reader.GetValue(2).ToString(),
Actual_Time = reader.GetValue(3).ToString()
});
}
}
}
return new JsonResult(flights);
}
And it will be displayed in the view as JSON objects. I want to do some operation on the data using JS, so how to return it as a js object. I already tried $get but because I have to upload the file first the method won't work unless I have the data on a dataset?

Can't open zip file created from System.IO.Compression namespace

I'm trying to zip varying amounts of files so that one zip folder can be served to the user instead of them having to click multiple anchor tags. I am using the System.IO.Compression namespace in asp.net core 3.1 to create the zip folder.
Here is the code I'm using to create the Zip folder.
public IActionResult DownloadPartFiles(string[] fileLocations, string[] fileNames)
{
List<InMemoryFile> files = new List<InMemoryFile>();
for (int i = 0; i < fileNames.Length; i++)
{
InMemoryFile inMemoryFile = GetInMemoryFile(fileLocations[i], fileNames[i]).Result;
files.Add(inMemoryFile);
}
byte[] archiveFile;
using (MemoryStream archiveStream = new MemoryStream())
{
using (ZipArchive archive = new ZipArchive(archiveStream, ZipArchiveMode.Create, true))
{
foreach (InMemoryFile file in files)
{
ZipArchiveEntry zipArchiveEntry = archive.CreateEntry(file.FileName, CompressionLevel.Fastest);
using (Stream zipStream = zipArchiveEntry.Open())
{
zipStream.Write(file.Content, 0, file.Content.Length);
zipStream.Close();
}
}
archiveStream.Position = 0;
}
archiveFile = archiveStream.ToArray();
}
return File(archiveFile, "application/octet-stream");
}
The files I am trying to zip are stored remotely so I grab them with this block of code. The InMemoryFile is a class to group the file name and file bytes together.
private async Task<InMemoryFile> GetInMemoryFile(string fileLocation, string fileName)
{
InMemoryFile file;
using (HttpClient client = new HttpClient())
using (HttpResponseMessage response = await client.GetAsync(fileLocation))
{
byte[] fileContent = await response.Content.ReadAsByteArrayAsync();
file = new InMemoryFile(fileName, fileContent);
}
return file;
}
The DownloadPartFiles method is called using Ajax. I grab the remote paths to the files and their respective names using javascript and pass them into the Ajax call.
function downloadAllFiles() {
let partTable = document.getElementById("partTable");
let linkElements = partTable.getElementsByTagName('a');
let urls = [];
for (let i = 0; i < linkElements.length; i++) {
urls.push(linkElements[i].href);
}
if (urls.length != 0) {
var fileNames = [];
for (let i = 0; i < linkElements.length; i++) {
fileNames.push(linkElements[i].innerText);
}
$.ajax({
type: "POST",
url: "/WebOrder/DownloadPartFiles/",
data: { 'fileLocations': urls, 'fileNames': fileNames },
success: function (response) {
var blob = new Blob([response], { type: "application/zip" });
var link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = "PartFiles.zip";
link.click();
window.URL.revokeObjectURL(blob);
},
failure: function (response) {
alert(response.responseText);
},
error: function (response) {
alert(response.responseText);
}
});
}
}
Now the issue I keep running into is that I can't open the zip folder within Windows 10. Every time I try to open the zip folder using Windows or 7-zip I get an error message that the folder can't be opened or the folder is invalid. I've tried looking at various similar issues on stackoverflow, ie Invalid zip file after creating it with System.IO.Compression, but still can't figure out why this is.
Could it be the encoding? I found that Ajax expects its responses to be encoded UTF-8 and when I view the zip file using notepad++ with UTF-8 I see that there are � characters indicating corruption.
Any thoughts on this would be helpful. Let me know if more information is needed.
If one of the corrupt zip files is needed I can provide that as well.
Edit:
I have since changed my method of receiving the byte array in javascript. I am using a XMLHttpRequest to receive the byte array.
var parameters = {};
parameters.FileLocations = urls;
parameters.FileNames = fileNames;
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("POST", "/WebOrder/DownloadPartFiles/", true);
xmlhttp.setRequestHeader("Content-Type", "application/json");
xmlhttp.responseType = "arraybuffer";
xmlhttp.onload = function (oEvent) {
var arrayBuffer = xmlhttp.response;
if (arrayBuffer) {
var byteArray = new Uint8Array(arrayBuffer);
var blob = new Blob([byteArray], { type: "application/zip" });
var link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = "PartFiles.zip";
link.click();
window.URL.revokeObjectURL(blob);
}
}
xmlhttp.send(JSON.stringify(parameters));
From what I read, Ajax is not the best for receiving byte arrays and binary data. With this method I was able to open one of the zip file with 7-zip, but not Windows, however, one of the files within the archive was showing as a size of 0KB and couldn't be opened. The other three files in the archive were fine. Other zip folders with different files could not be opened at all though.
After some time I found a post that was able to fix my issue, Create zip file from byte[]
From that post this is the revised method I'm using to create a zip folder with files in it.
public IActionResult DownloadPartFiles([FromBody] FileRequestParameters parameters)
{
List<InMemoryFile> files = new List<InMemoryFile>();
for (int i = 0; i < parameters.FileNames.Length; i++)
{
InMemoryFile inMemoryFile = GetInMemoryFile(parameters.FileLocations[i], parameters.FileNames[i]).Result;
files.Add(inMemoryFile);
}
byte[] archiveFile = null;
using (MemoryStream archiveStream = new MemoryStream())
{
using (ZipArchive archive = new ZipArchive(archiveStream, ZipArchiveMode.Create, true))
{
foreach (InMemoryFile file in files)
{
ZipArchiveEntry zipArchiveEntry = archive.CreateEntry(file.FileName, CompressionLevel.Optimal);
using (MemoryStream originalFileStream = new MemoryStream(file.Content))
using (Stream zipStream = zipArchiveEntry.Open())
{
originalFileStream.CopyTo(zipStream);
}
}
}
archiveFile = archiveStream.ToArray();
}
return File(archiveFile, "application/octet-stream");
}
I still don't know why the previous method was having issues so if anyone knows the answer to that in the future I'd love to know.

XHR Multipart File Upload Spring Boot Issue

I have an upload progress bar that works upon file input and when the bar reaches 100%, there is no error. But as I print the contents of the file in Spring Boot, I notice that the code within the for loop does not run. Here is the code. Please help and thank you!
JavaScript:
function upload(file) {
var formData = new FormData();
formData.append("newFile", file);
var xhr = new XMLHttpRequest();
xhr.open('POST', '/upload');
xhr.onload = function(e) {
console.log("xhr onload function");
};
var progressBar = document.querySelector('progress');
xhr.upload.onprogress = function(e) {
if (e.lengthComputable) {
progressBar.value = (e.loaded / e.total) * 100;
progressBar.textContext = progressBar.value;
}
};
xhr.send(formData);
}
Spring Boot:
#RequestMapping(value="/upload", method=RequestMethod.POST)
public String upload(#RequestParam("files") MultipartFile[] files) {
// This prints [Lorg.springframework.web.multipart.MultipartFile;#........].
System.out.println(files);
for (MultipartFile file : files) {
// This doesn't get printed.
System.out.println(file.getOriginalFilename());
}
return "redirect:/";
}
In JavaScript you are adding file to ‘newFile’ variable but on spring side you are extracting file from request param ‘files’.
Are you uploading multiple files? It doesn’t seems so. Try only with Multipart.
Because MultipartFile[] files were just initialized but does not contain any array of values.
Change the
public String upload(#RequestParam("files") MultipartFile[] files)
to
public String upload(#RequestPart(value = "files", required = true) MultipartFile[] files)

Error on Downloading From using Asp.net web api

I'm using the code below for downloading with the web API in ASP.NET.
When I'm trying to click the download button, it calls the API.
After executing the "DownloadFile"-function, the download dialog box isn't coming .
[HttpGet]
public HttpResponseMessage DownloadFile(string DownloadFilePath)
{
HttpResponseMessage result = null;
var localFilePath = HttpContext.Current.Server.MapPath(DownloadFilePath);
// check if parameter is valid
if (String.IsNullOrEmpty(DownloadFilePath))
{
result = Request.CreateResponse(HttpStatusCode.BadRequest);
}
// check if file exists on the server
else if (!File.Exists(localFilePath))
{
result = Request.CreateResponse(HttpStatusCode.Gone);
}
else
{// serve the file to the client
result = Request.CreateResponse(HttpStatusCode.OK);
result.Content = new StreamContent(new FileStream(localFilePath, FileMode.Open, FileAccess.Read));
result.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment");
result.Content.Headers.ContentDisposition.FileName = DownloadFilePath;
}
return result;
}
I didn't get any exception from the code above, but the dialog box for downloading the file isn't coming.
Here is the code, I am using and it works great. I hope it will give you an idea
....
var fileBytes = Helper.GetFileBytes(filePath);//convert file to bytes
var stream = new MemoryStream(fileBytes);
resp.Content = new StreamContent(stream);
resp.Content.Headers.ContentType = new MediaTypeHeaderValue("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
resp.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") { FileName = filerequest.FileName };
resp.Content.Headers.Add("Content-Encoding", "UTF-8");
return resp;
And, here is the code for GetFileBytes method,
public static byte[] GetFileBytes(string filePath)
{
var fileInfo = new FileInfo(filePath);
if (fileInfo.Exists)
{
return File.ReadAllBytes(fileInfo.FullName);
}
return null;
}

Downloading a file in MVC app using AngularJS and $http.post

Any help is most welcomed and really appreciated.
I have an MVC action which retries a file content from a web service. This action is invoked from a Angular service (located in services.js) using $http.post(action, model), and the action is returning a FileContentResult object, which contains the byte array and the content type.
public ActionResult DownloadResults(DownloadResultsModel downloadResultsModel)
{
downloadResult = ... // Retrieving the file from a web service
Response.ClearHeaders();
Response.AddHeader("Content-Disposition", string.Format("attachment; filename={0}", downloadResult.FileName));
Response.BufferOutput = false;
return new FileContentResult(downloadResult.Contents, downloadResult.ContentType);
}
The issue I'm having is about the browser not performing the default behavior of handing a file (for example, prompting to open it, saving it or cancel). The action is completed successfully with having the content of the file and the file name (injected to the FileContentResult object), but there s no response from the browser.
When I'm replacing the post with $window.location.href, and construct the URI myself, I'm hitting the action and after it completes the browser is handling the file as expected.
Does anyone can think of any idea how to complete the 'post' as expected?
Thanks,
Elad
I am using below code to download the file, given that the file does exist on the server and client is sending server the full path of the file...
as per you requirement change the code to specify path on server itself.
[HttpGet]
public HttpResponseMessage DownloadFile(string filename)
{
filename = filename.Replace("\\\\", "\\").Replace("'", "").Replace("\"", "");
if (!char.IsLetter(filename[0]))
{
filename = filename.Substring(2);
}
var fileinfo = new FileInfo(filename);
if (!fileinfo.Exists)
{
throw new FileNotFoundException(fileinfo.Name);
}
try
{
var excelData = File.ReadAllBytes(filename);
var result = new HttpResponseMessage(HttpStatusCode.OK);
var stream = new MemoryStream(excelData);
result.Content = new StreamContent(stream);
result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
{
FileName = fileinfo.Name
};
return result;
}
catch (Exception ex)
{
return Request.CreateResponse(HttpStatusCode.ExpectationFailed, ex);
}
}
and then on client side in angular:
var downloadFile = function (filename) {
var ifr = document.createElement('iframe');
ifr.style.display = 'none';
document.body.appendChild(ifr);
ifr.src = document.location.pathname + "api/GridApi/DownloadFile?filename='" + escape(filename) + "'";
ifr.onload = function () {
document.body.removeChild(ifr);
ifr = null;
};
};

Categories

Resources