MVC Web Api + ajax to create and download Zip File - c#

I am working on a application that has a button, when that button is clicked it calls a web api GET method. This method takes files and creates a zip folder using System.IO.Compression. This part works great. I see the folder it creates and I am able to open / extract that folder with its files. The problem i have is when the file gets returned to the browser and the browser downloads the file I get the following error: " The Compressed (zipped) folder '...pathToTheDownloadedFile.zip' is invalid. I don't understand what I am doing wrong. All other non-zipped files download and open fine.
Here is my web api GET method:
[HttpGet]
[Route("api/OrderManager/ExtractAllDocuments/{orderNum}/{mappingId}/")]
public HttpResponseMessage ExtractAllDocuments(int orderNum, int mappingId)
{
try
{
var docPath = #"" + HttpContext.Current.Server.MapPath("MyPath");
var files = Directory.GetFiles(docPath);
string zipName = #"supportingdocuments_order" + orderNum + ".zip";
FileStream zipToOpen = new FileStream(docPath + zipName, FileMode.Create);
using (var zipArchive = new ZipArchive(zipToOpen, ZipArchiveMode.Create))
{
foreach (var fPath in files)
{
if (!fPath.Contains(".zip"))
{
zipArchive.CreateEntryFromFile(fPath, Path.GetFileName(fPath), CompressionLevel.Fastest);
}
}
}
zipToOpen.Close();
//At this point the directory is created and saved as it should be
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.OK);
var fullZipPath = docPath + zipName;
byte[] bytes = File.ReadAllBytes(fullZipPath);
response.Content = new ByteArrayContent(bytes);
response.Content.Headers.ContentLength = bytes.LongLength;
response.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment");
response.Content.Headers.ContentDisposition.FileName = zipName;
response.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(MimeMapping.GetMimeMapping(zipName));
return response;
}
catch (Exception e)
{
var b = e.Message;
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.NotFound);
response.ReasonPhrase = string.Format("Failed To Extract Files");
throw new HttpResponseException(response);
}
}
Here is my $.ajax call:
$.ajax({
url: 'myApiUrl',
method: 'GET'
}).done(function (data, status, xmlHeaderRequest) {
var downloadLink = document.createElement('a');
var blob = new Blob([data],
{
type: xmlHeaderRequest.getResponseHeader('Content-Type')
});
var url = window.URL || window.webkitURL;
var downloadUrl = url.createObjectURL(blob);
var fileName = '';
// get the file name from the content disposition
var disposition = xmlHeaderRequest.getResponseHeader('Content-Disposition');
if (disposition && disposition.indexOf('attachment') !== -1) {
var filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
var matches = filenameRegex.exec(disposition);
if (matches != null && matches[1]) {
fileName = matches[1].replace(/['"]/g, '');
}
}
// Blob download logic taken from: https://stackoverflow.com/questions/16086162/handle-file-download-from-ajax-post
if (typeof window.navigator.msSaveBlob !== 'undefined') {
// IE workaround for "HTML7007" and "Access Denied" error.
window.navigator.msSaveBlob(blob, fileName);
} else {
if (fileName) {
if (typeof downloadLink.download === 'undefined') {
window.location = downloadUrl;
} else {
downloadLink.href = downloadUrl;
downloadLink.download = fileName;
document.body.appendChild(downloadLink);
downloadLink.click();
}
} else {
window.location = downloadUrl;
}
setTimeout(function () {
url.revokeObjectURL(downloadUrl);
},
100);
}
}).fail(function (data) {
$.notify({
// options
message:
'<i class="fas fa-exclamation-circle"></i> Could not download all documents.'
},
{
// settings
type: 'danger',
placement: {
from: "top",
align: "left"
},
delay: 2500,
z_index: 10031
});
});
I'm at a total and complete loss on this one. Thank you in advance for any help you can provide as it is greatly appreciated.

So after searching I have found a solution that works. $.ajax doesn't like binary data and thinks everything is UTF-8 text encoding apparently. So I used an XMLHttpRequest (xhr). For those that need it below is as copy of the c# and the javascript solution.
C# WebApi Controller:
public HttpResponseMessage ExtractAllDocuments(int orderNum, int mappingId)
{
try
{
var docPath = #"" + HttpContext.Current.Server.MapPath("myPath");
var files = Directory.GetFiles(docPath);
string zipName = #"Supporting-Documents-Order-" + orderNum + ".zip";
FileStream zipToOpen = new FileStream(docPath + zipName, FileMode.Create);
using (var zipArchive = new ZipArchive(zipToOpen, ZipArchiveMode.Create))
{
foreach (var fPath in files)
{
if (!fPath.Contains(".zip"))
{
zipArchive.CreateEntryFromFile(fPath, Path.GetFileName(fPath), CompressionLevel.Fastest);
}
}
}
zipToOpen.Close();
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
response.Content = new StreamContent(new FileStream(docPath + zipName, FileMode.Open, FileAccess.Read));
response.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment");
response.Content.Headers.ContentDisposition.FileName = zipName;
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/zip");
return response;
}
catch (Exception e)
{
var b = e.Message;
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.NotFound);
response.ReasonPhrase = string.Format("Failed To Extract Files");
throw new HttpResponseException(response);
}
}
Front End JavaScript:
function extractAllDocuments() {
let xhr = new XMLHttpRequest();
xhr.addEventListener('readystatechange', function (e) {
if (xhr.readyState == 2 && xhr.status == 200) {
// Download is being started
}
else if (xhr.readyState == 3) {
// Download is under progress
}
else if (xhr.readyState == 4) {
//operation done
// Create a new Blob object using the response data of the onload object
var blob = new Blob([this.response], { type: 'application/zip' });
//Create a link element, hide it, direct it towards the blob, and then 'click' it programatically
let a = document.createElement("a");
a.style = "display: none";
document.body.appendChild(a);
//Create a DOMString representing the blob and point the link element towards it
let url = window.URL.createObjectURL(blob);
a.href = url;
// get the file name from the content disposition
var fileName = '';
var disposition = xhr.getResponseHeader('Content-Disposition');
if (disposition && disposition.indexOf('attachment') !== -1) {
var filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
var matches = filenameRegex.exec(disposition);
if (matches != null && matches[1]) {
fileName = matches[1].replace(/['"]/g, '');
}
}
a.download = fileName;
//programatically click the link to trigger the download
a.click();
//Remove From Server
$.ajax({
url: 'myUrl',
method: 'DELETE'
}).done(function (data) {
}).fail(function (data) {
});
setTimeout(function () {
window.URL.revokeObjectURL(url);
}, 60 * 1000);
} else if (xhr.status == 400){
$.notify({
// options
message:
'<i class="fas fa-exclamation-circle"></i> Could not download all documents.'
},
{
// settings
type: 'danger',
placement: {
from: "top",
align: "left"
},
delay: 2500,
z_index: 10031
});
}
});
//set the request type to post and the destination url to '/convert'
xhr.open('GET', 'MyUrl');
//set the reponse type to blob since that's what we're expecting back
xhr.responseType = 'blob';
xhr.send();
return false;
}

Related

Error 400 Bad Request : File upload in angular with ASP.NET backend

I am trying to upload a file and add it to my database (SQlite) in my angular frontend. My backend is in aspn.net core.
This is what I have tried :
Backend endpoint :
[HttpPost("upload-main-photo/{id}")]
[Consumes("multipart/form-data")]
public async Task<ActionResult<User>> UploadMain(string id,[FromForm] IFormFile file)
{
var user = _userManager.Users.Include(x => x.Photos).SingleOrDefault(x => x.Id == id);
string wwwRootPath = _hostEnvironment.WebRootPath;
if (file != null)
{
MemoryStream memoryStream = new MemoryStream();
file.OpenReadStream().CopyTo(memoryStream);
string photoUrl = Convert.ToBase64String(memoryStream.ToArray());
string filename = Guid.NewGuid().ToString();
var uploads = Path.Combine(wwwRootPath, #"images\users");
var extension = Path.GetExtension(file.FileName);
Uri domain = new Uri(Request.GetDisplayUrl());
using (var fileStreams = new FileStream(Path.Combine(uploads, filename + extension), FileMode.Create))
{
file.CopyTo(fileStreams);
}
photoUrl = domain.Scheme + "://" + domain.Host + (domain.IsDefaultPort ? "" : ":" + domain.Port) + "/images/users/" + filename + extension;
var photo = new Photo
{
Id = Guid.NewGuid().ToString(),
Url = photoUrl,
UserId = user.Id,
IsMain = true,
};
await _context.Photos.AddAsync(photo);
await _context.SaveChangesAsync();
user.Photos.Add(photo);
}
return user;
}
This is my service :
addMainPhoto(id: string, image: File) {
const config = new HttpHeaders().set('Content-Type', 'multipart/form-data')
.set('Accept', '*/*')
this.http.post<User>(this.baseUrl + "photos/upload-main-photo/" + id, image, { headers: config }).subscribe({
next: () => {
console.log("success");
},
error: (err) => {
console.log("this is the error : "+err.message);
}
});
}
and finally this is my component logic :
mainPhotoUpload() {
this.photoService.addMainPhoto(this.currentUser.id, this.photoForm.get('image')?.value);
}
onImagePick(event: Event) {
const file = (event.target as HTMLInputElement).files![0];
this.photoForm.get('image')?.patchValue(file);
this.photoForm.get('image')?.updateValueAndValidity();
const reader = new FileReader();
reader.onload = () => {
if (this.photoForm.get('image')?.valid) {
this.imagePreview = reader.result as string;
} else {
this.imagePreview = null;
}
}
reader.readAsDataURL(file);
}
I get this error :
Error
I have also configured the cors policy accordingly so the problem doesn't come from it.

Problem with C# Web API export excel file that can not open the file because file format or file extension is not valid

I'm trying to generate excel file from my database and download the file from my website but when I open the excel file the error "can not open the file because file format or file extension is not valid." has occurred.
So, This is my code for doing this.
Here is my script:
$(document).ready(function () {
$("#btnExportExcel").click(function (e) {
e.preventDefault();
const deferred = $.Deferred();
var paymentType = $("#transactionTypeDropdown").val();
var paymentStatus = $("#paymentStatusDropdown").val();
var merchantName = $("#merchantNameDropdown").val();
var dateTime = $("#reservation").val().split(" ");
var refNo = $("#refNoInput").val();
var terminalId = $("#terminalID").val();
$.ajax({
type: "POST",
crossOrigin: true,
url: "/api/Transaction/ExportExcel",
headers: {
Accept: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
},
data: {
"Date_1": dateTime[0],
"Date_2": dateTime[2],
"PaymentType": paymentType,
"Status": paymentStatus,
"Outtrade": refNo,
"Merchant": merchantName,
"TerminalID": terminalId
},
success: function (result) {
if (dateTime[0] == dateTime[2]) {
filename = "Transaction_Detail_" + dateTime[0].replace(/\//g, '-') + ".xlsx";
}
else {
filename = "Transaction_Detail_" + dateTime[0].replace(/\//g, '-') + " - "
+ dateTime[2].replace(/\//g, '-') + ".xlsx";
}
var uri = 'data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,' + result;
var link = document.createElement("a");
link.href = uri;
link.style = "visibility:hidden";
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
},
error: function (jqXHR, exception) {
getErrorMessage(jqXHR, exception);
}
});
return deferred.promise();
});
});
Here is my C# API:
public class TransactionController : ApiController
{
[AllowAnonymous]
[HttpPost]
[Route("api/Transaction/ExportExcel")]
public HttpResponseMessage ExportExcel(TransactionSearchReq req)
{
TransactionMgr transaction = new TransactionMgr();
List<TransactionRecordRes> transactionList = new List<TransactionRecordRes>();
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.OK);
MediaTypeHeaderValue mediaType = MediaTypeHeaderValue.Parse("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
byte[] fileContents;
string fileName;
try
{
// Get transaction records
transactionList = transaction.GetSearchTransactionRecord(req);
if (transactionList.Count() > 0)
{
ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
using (var package = new ExcelPackage())
{
var worksheet = package.Workbook.Worksheets.Add("Sheet1");
// I put data to worksheet here.
fileContents = package.GetAsByteArray();
}
if (fileContents == null || fileContents.Length == 0)
{
response = Request.CreateResponse(HttpStatusCode.InternalServerError);
response.Content = new StringContent("Export file error.", Encoding.Unicode);
return response;
}
else
{
if (string.Compare(req.Date_1, req.Date_2) == 0)
{
fileName = string.Format("Transaction_Detail_{0}.xlsx", req.Date_1.Replace('/', '-'));
}
else
{
fileName = string.Format("Transaction_Detail_{0} - {1}.xlsx", req.Date_1.Replace('/', '-'), req.Date_2.Replace('/', '-'));
}
MemoryStream memoryStream = new MemoryStream(fileContents);
response.Content = new StreamContent(memoryStream);
response.Content.Headers.ContentType = mediaType;
response.Content.Headers.ContentDisposition =
new ContentDispositionHeaderValue("attachment") { FileName = fileName };
return response;
}
}
else
{
response = Request.CreateResponse(HttpStatusCode.InternalServerError);
response.Content = new StringContent("Export file error.", Encoding.Unicode);
return response;
}
}
catch (Exception ex)
{
response = Request.CreateResponse(HttpStatusCode.InternalServerError);
response.Content = new StringContent("Export file error.", Encoding.Unicode);
return response;
}
}
}
Where I'm going wrong ?

Send file from angular to create new and download from .net

I've been trying to download .xml file, but sadly unsuccesfully.
From angular side I am sending *.xml file. In .NET side I take it to process and create a new *.xml file. And I need to download that new file, however I can't really find out how to workaround it.
this is my file-component.ts:
OnSubmit(value, File) {
const params1: FormData = new FormData();
params1.append('File', this.fileToUpload, this.fileToUpload.name);
params1.append('ProcessingMode', value.processingMode);
params1.append('StartDate', value.startDate.formatted);
const params = {
'File': this.fileToUpload,
'ProcessingMode': value.processingMode,
'StartDate': value.startDate.formatted
};
this.mapsConfigurationService.postFile(value, this.fileToUpload, value.startDate.formatted)
.subscribe((res: any) => {
this.downloadFile(res, 'xml'); debugger;
this.xmlProcessing = false;
},
(err) => {
if (err.status === 401) {
// this.router.navigate(['unauthorized']);
} else {
this.xmlProcessing = false;
}
});
downloadFile(data, type) {
const fileName = 'test';
var contentType;
if (type === 'xml') {
contentType = 'text/xml';
}
var blob = new Blob([data._body], { type: contentType });
const dwldLink = document.createElement('a');
const url = URL.createObjectURL(blob);
const isSafariBrowser = navigator.userAgent.indexOf('Safari') !== -1 && navigator.userAgent.indexOf('Chrome') === -1;
if (isSafariBrowser) {
dwldLink.setAttribute('target', '_blank');
}
const fullFileName = fileName + '.' + type;
dwldLink.setAttribute('href', url);
dwldLink.setAttribute('download', fullFileName);
dwldLink.style.visibility = 'hidden';
document.body.appendChild(dwldLink);
dwldLink.click();
document.body.removeChild(dwldLink);}
this is service.ts
postFile(value: any, fileToUpload: File, startDate) {
const formData: FormData = new FormData();
formData.append('File', fileToUpload, fileToUpload.name);
formData.append('ProcessingMode', value.processingMode);
formData.append('StartDate', '2015-05-23');
return this.http
.post(this.Url, formData);
}
and this is backend side:
[HttpPost, DisableRequestSizeLimit]
public ActionResult UploadFile()
{
try
{
var xml = Request.Form.Files["File"].ToString();
var httpRequest = HttpContext.Request.Form;
var postedFile = httpRequest.Files["File"];
string outputFile = Request.Form["info"].ToString();
var startDate = Request.Form["StartDate"];
var file = httpRequest.Files[0];
string fullPath = "";
string folderName = "Upload";
string antFile = #"C:\ant.bat";
string build = #"C:\build.xml";
string rootPath = #"C:\Users";
string newPath = Path.Combine(rootPath, folderName);
if (!Directory.Exists(newPath))
{
Directory.CreateDirectory(newPath);
}
if (file.Length > 0)
{
string fileName = ContentDispositionHeaderValue.Parse(file.ContentDisposition).FileName.Trim('"');
fullPath = Path.Combine(newPath, fileName);
using (var stream = new FileStream(fullPath, FileMode.Create))
{
file.CopyTo(stream);
}
}
return PhysicalFile(#"C:\Book1.xml", "application/xml", "Book1.xml");
}
catch (System.Exception ex)
{
return StatusCode(500);
}
}
I get error 500 and I do understand that the problem is with RequestHeaders but I can't figure out the way to solve it and in which side
for downloading for downloading any file... I am use this code in backend
and make and request the code from angular by normal http request
var myFile :: your file
if (System.IO.File.Exists (myFile.Path)) {// to know if the file is Exist or not
//Process File Here ...
} else {
return Json ("NotFound");
}
string contentType = "application/xml";
HttpContext.Response.ContentType = contentType;
var result = new FileContentResult (System.IO.File.ReadAllBytes (myFile.Path), contentType) {
FileDownloadName = $"{myFile.Title }" // + myFile.Extension
};
// System.IO.File.Delete (myFile.Path); //if you want to delete the file after download
return result;

IE11 saves file from Web API without extension

I have Angular SPA which downloads a file from ASP.NET Web API 2 using the following approach:
Angular Code
$scope.downloadFile = function(httpPath) {
// Use an arraybuffer
$http.get(httpPath, { responseType: 'arraybuffer' })
.success( function(data, status, headers) {
var octetStreamMime = 'application/octet-stream';
var success = false;
// Get the headers
headers = headers();
// Get the filename from the x-filename header or default to "download.bin"
var filename = headers['x-filename'] || 'download.bin';
// Determine the content type from the header or default to "application/octet-stream"
var contentType = headers['content-type'] || octetStreamMime;
try
{
// Try using msSaveBlob if supported
console.log("Trying saveBlob method ...");
var blob = new Blob([data], { type: contentType });
if(navigator.msSaveBlob)
navigator.msSaveBlob(blob, filename);
else {
// Try using other saveBlob implementations, if available
var saveBlob = navigator.webkitSaveBlob || navigator.mozSaveBlob || navigator.saveBlob;
if(saveBlob === undefined) throw "Not supported";
saveBlob(blob, filename);
}
console.log("saveBlob succeeded");
success = true;
}
catch(ex)
{
console.log("saveBlob method failed with the following exception:");
console.log(ex);
}
if(!success)
{
// Get the blob url creator
var urlCreator = window.URL || window.webkitURL || window.mozURL || window.msURL;
if(urlCreator)
{
// Try to use a download link
var link = document.createElement('a');
if('download' in link)
{
// Try to simulate a click
try
{
// Prepare a blob URL
console.log("Trying download link method with simulated click ...");
var blob = new Blob([data], { type: contentType });
var url = urlCreator.createObjectURL(blob);
link.setAttribute('href', url);
// Set the download attribute (Supported in Chrome 14+ / Firefox 20+)
link.setAttribute("download", filename);
// Simulate clicking the download link
var event = document.createEvent('MouseEvents');
event.initMouseEvent('click', true, true, window, 1, 0, 0, 0, 0, false, false, false, false, 0, null);
link.dispatchEvent(event);
console.log("Download link method with simulated click succeeded");
success = true;
}
catch(ex) {
console.log("Download link method with simulated click failed with the following exception:");
console.log(ex);
}
}
if(!success)
{
// Fallback to window.location method
try
{
// Prepare a blob URL
// Use application/octet-stream when using window.location to force download
console.log("Trying download link method with window.location ...");
var blob = new Blob([data], { type: octetStreamMime });
var url = urlCreator.createObjectURL(blob);
window.location = url;
console.log("Download link method with window.location succeeded");
success = true;
} catch(ex) {
console.log("Download link method with window.location failed with the following exception:");
console.log(ex);
}
}
}
}
if(!success)
{
//Fallback to window.open method
console.log("No methods worked for saving the arraybuffer, using last resort window.open");
window.open(httpPath, '_blank', '');
}
})
.error(function(data, status) {
console.log("Request failed with status: " + status);
// Optionally write the error out to scope
$scope.errorDetails = "Request failed with status: " + status;
});
};
httpPath is a url of my Web API Controller. The controller has the following code:
Web API Code
[HttpGet]
[Authorize]
public async Task<HttpResponseMessage> DownloadItems(string path)
{
try
{
Stream stream = await documentsRepo.getStream(path);
HttpResponseMessage result = Request.CreateResponse(HttpStatusCode.OK);
result.Content = new StreamContent(stream);
result.Content.Headers.ContentDisposition =
new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment")
{
FileName = "document.zip",
};
result.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/octet-stream");
result.Content.Headers.ContentLength = stream.Length;
return result;
}
catch (Exception e)
{
return Request.CreateResponse(HttpStatusCode.InternalServerError, e);
}
}
The above setup works perfectly well in all browsers (a .zip file is downloaded), except IE11.
For unknown reason, IE11 downloads the file without .zip extension
Any idea what could be wrong with my API Controller?

xmlHttpRequest is not working using chrome

I have the following code for uploading file:
var formData = new FormData();
var file = document.getElementById("file").files[0];
formData.append("file", file);
var xhr = new XMLHttpRequest();
xhr.open("POST", "UploadFileServer.axd", false);
xhr.setRequestHeader("Content-Type", "multipart/form-data");
xhr.setRequestHeader("Pragma", "no-cache");
xhr.setRequestHeader("Cache-Control", "no-cache, must-revalidate");
xhr.addEventListener('readystatechange', function(e) {
if (this.readyState === 4) {
if (this.status == 200 && this.response != null) {
var clientResponse = JSON.parse(this.response);
if (clientResponse.Success) {
//alert 1
}
else if (!clientResponse.Success) {
//alert 2
}
else {
//SOME ERROR!
}
}
}
});
xhr.send(formData);
Using IE10 everything works fine.
Using Chrome, I get no files on server side:
UploadFileServer.axd:
void IHttpHandler.ProcessRequest(HttpContext ctx)
{
OutputDebugString("Enter process req");
HttpFileCollection uploadFile = ctx.Request.Files;
if (uploadFile.Count > 0)
{
//do something
ctx.Response.ContentType = "application/json; charset=utf-8";
ctx.Response.Write(uploadFileResponse);
}
}
UploadFile.Count = 0
any ideas why is it empty?
In some browsers the uploaded file doesn't arrive at the server in the Request.Files collection when using xmlHttpRequest, but in the body of the request.
I would suggest something like:
if (Request.Files.AllKeys.Any())
{
var key = Request.Files.AllKeys.First();
fullSizeImage = new WebImage(Request.Files[key].InputStream);
}
else
{
fullSizeImage = new WebImage(Request.InputStream);
}
Or whatever you want to do with the image :)

Categories