IE11 saves file from Web API without extension - c#

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?

Related

uploading and accessing images into asp .net api folder using angular

I have angular project as my client side and .Net 6 web api project as my backend. I am still new to both technologies. I am creating a website and there is a functionality that I am trying to add and haven't been successful so far. I want to upload images into a .Net web api project images folder using angular. I also want to later access those images from angular project. I want to store the path of the image files in the database. I have tried to check for the code on the internet without success. Your assistance will be appreciated.
First, submit your's files from frontend with FormData
postWithFile(url: string, obj: any, files: File[]) {
let cloneHeader: any = {};
let options: any = {
headers: new HttpHeaders(cloneHeader),
observe: 'response',
responseType: 'json'
};
let formData: FormData = new FormData();
if (typeof obj == 'object') { // obj is external submit data
formData.append('data', JSON.stringify(obj));
} else {
formData.append('data', obj);
}
if (files && files.length > 0) {
files.forEach((ds, index) => {
formData.append('file_' + index, ds, ds.name);
});
}
return this._http
.post(this.host + url, formData, options)
.pipe(map((res: any) => {
return res.body;
}));
}
And backend handle request with HttpContext.Current.Request.Files, save images to server and store path of images in database
[HttpPost]
public ResponseMessage<bool?> UploadImages()
{
var response = new ResponseMessage<bool?>();
try
{
if (!Request.Content.IsMimeMultipartContent())
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
ExternalDataModel model = MessageConvert.DeserializeObject<ExternalDataModel>(HttpContext.Current.Request["data"]); // obj in frontend
//
List<string> listImages = new List<string>();
DateTime now = DateTime.Now;
string buildPath = $"{string.Format("{0:00}", now.Year)}\\{string.Format("{0:00}", now.Month)}\\{string.Format("{0:00}", now.Day)}"; // change by your's folder path
foreach (string file in HttpContext.Current.Request.Files)
{
var fileContent = HttpContext.Current.Request.Files[file];
int fileLength = fileContent.ContentLength;
if (fileContent != null && fileLength > 0)
{
var stream = fileContent.InputStream;
byte[] imgByteArray;
using (MemoryStream memoryStream = new MemoryStream())
{
stream.CopyTo(memoryStream);
imgByteArray = memoryStream.ToArray();
}
string fileName = $"format_file_name_if_need_{fileContent.FileName}";
string RelativeFolder = $"{buildPath}";
string AbsoluteFolder = Path.Combine("FOLDER_IN_SERVER_FULL_PATH", RelativeFolder);
if (!Directory.Exists(AbsoluteFolder))
{
Directory.CreateDirectory(AbsoluteFolder);
}
string pathSave = Path.Combine(RelativeFolder, fileName);
FileHelper.SaveFileFromBinaryArray(pathSave, imgByteArray);
listImages.Add(pathSave);
}
}
// model.listImages = listImages; // assign to model to save to DB
//
// var data = _bus.uploadImage(model);
// if (data)
// {
// response.Data = true;
// response.MessageCode = MessageCodes.UpdateSuccessfully;
// }
}
catch (Exception ex)
{
response.MessageCode = ex.Message;
}
return response;
}

MVC Web Api + ajax to create and download Zip File

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;
}

Calling WEB API to download excel file

below is C# WEB API Code to generate Excel :
public class FileExportController : ApiController
{
[HttpGet]
public HttpResponseMessage Get()
{
var callerContext = CallerContext.DefaultCallerContext;
ReportingInput userInput = new ReportingInput();
userInput.ClientOneCode = "AVON";
string handle = Guid.NewGuid().ToString();
var #event = new GetJobReprotDataBlEvent(callerContext, userInput);
WebApiApplication.ApplicationInitializerObj.EventBus.Publish(#event);
XLWorkbook wb = new FileExportEngine().ExportExcel(#event.ReportData); //this is returning XLWorkbook
string fileName = "JobReport_" + DateTime.Now.ToString("yyyy -MM-dd HH':'mm':'ss") + ".xlsx";
using (MemoryStream memoryStream = new MemoryStream())
{
wb.SaveAs(memoryStream);
var result = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new ByteArrayContent(memoryStream.ToArray())
};
result.Content.Headers.ContentDisposition =
new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment")
{
FileName = fileName
};
result.Content.Headers.ContentType =
new MediaTypeHeaderValue("application/octet-stream");
return result;
}
}
When I call this API from browser, I am able to generate excel file.
http://localhost/ETLScheduler/api/FileExport -- this is working when hit direct in browser
Now I want to use consume this API in angular 5 application.I have a button.On click button I call the component method downloadFile() to download the file.
Below is the code :
downloadReport() {
this._service.downloadJobReport('AVON');
}
where downloadJobReport() is in my service file as below :
downloadJobReport(clientCode: string) {
return this._http.get(APIs.downloadJobReport);
}
When I am running the application and click on Download button, I am getting nothing, I mean file is not downloading. Can anyone have idea,how should I update my angular code to consume the API.
Thanks in advance.
As you mentioned above in comments you are using below angular code to download file:
downloadFile(data: Blob) {
const contentType = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
const blob = new Blob([data], { type: contentType });
const url = window.URL.createObjectURL(blob);
window.open(url);
}
As I also have tried this code, it is working in chrome browser but not working in IE and edge.
You may update your method somthing like below:
var downloadFile=function (file_name, content) {
var csvData = new Blob([content], { type: 'text/csv' });
if (window.navigator && window.navigator.msSaveOrOpenBlob) { // for IE
window.navigator.msSaveOrOpenBlob(csvData, file_name);
} else { // for Non-IE (chrome, firefox etc.)
var a = document.createElement("a");
document.body.appendChild(a);
a.style = "display: none";
var csvUrl = URL.createObjectURL(csvData);
a.href = csvUrl;
a.download = file_name;
a.click();
URL.revokeObjectURL(a.href)
a.remove();
}
};
you can refer below link for more information:
Open links made by createObjectURL in IE11
Problem is, that Angular expects JSON as a result. You need to configure your GET request so, that it expects something different.
public downloadJobReport(clientCode: string)): Observable<Blob> {
return this._http.get(APIs.downloadJobReport, { responseType: 'blob' });
}
Just a tiny question, you pass an argument clientCode to the downloadJobReport, but never use it. Maybe wise to leave that out?

open file from ajax responce in mvc c#

I want to open file from Ajax response. here is the code. Here Ajax call response contain PDF file.
I want to open file in new tab of browser.here i am using mvc framework.
function ViewPDF(key){
$.ajax({
url: '#Url.Action("OpenDocument", "DocumentApproveUser")',
type: "POST",
data: { "key": key},
async: true,
cache: false,
success: function (data, status, xhr) {
alert(data);
window.open(data);
if (xhr.getResponseHeader("Forcefullylogin") == "true") {
var url = "/Login/Login";
window.location.href = url;
}
else {
}
},
error: function (error) {
$("#divLoading").hide();
if (error.getResponseHeader("Forcefullylogin") == true") {
var url = '#Url.Action("Login", "Login")';
window.location.href = url;
}
else {
alert('Something went wrong in system.Please try again later!or contact to system administrator.');
}
}
});
}
Server Code :
see below is code of my controller. this code return pdf file as ajax response.
I want to open that response in my browser.
[HttpPost]
public ActionResult OpenDocument(string key)
{
try
{
int Id = 0;
try
{
byte[] data = Convert.FromBase64String(key);
string decodedString = System.Text.Encoding.UTF8.GetString(data);
if (!(String.IsNullOrEmpty(decodedString)))
Id = Convert.ToInt32(decodedString);
}
catch (Exception ex)
{
ViewBag.ErrorName = "An error occured while opening document.";
base.ErrorLogger.Error("***OpenDocument***", ex);
return null;
}
DocumentApproveViewModel vm = new DocumentApproveViewModel();
vm.DocumentsApprovalModel = DocumentApproveViewModel.GetDocTransactionModelList(_repo.GetAll());
DocumentApprovalModel lst;
lst = (from x in vm.DocumentsApprovalModel where x.Id.Equals(Id) select x).FirstOrDefault();
base.Logger.InfoFormat("User : '{0}' going to access pdf document at {1} ", SessionFactory.CurrentUser.Name, System.DateTime.Now);
/////////////////////////////////////////////////////////////////////
ICollection<PasswordManagementViewModel> passwordList = null;
PasswordManagementViewModel password = null;
passwordList = PasswordManagementViewModel.GetSystemEncryptionKeyList(_encryption.GetAll());
password = passwordList.OrderByDescending(x => x.CreatedDateTime).FirstOrDefault();
string decryptPassword = Base64EncodeDecode.Decrypt(password.EncryptionKey, true);
/////////////////////////////////////////////////////////////////////
// Inhariting Logic from PDFSharpUtil Class.
byte[] PdfFileByte = _docSecurity.OpenPdfFile(lst.File, decryptPassword, SessionFactory.CurrentUser.EncryptionKey, SessionFactory.CurrentUser.Name, lst.DocumentTransactionName, false, SessionFactory.PdfViewCount);
/// Added logic for adding data into Document History ///
DocumentHistory objDocumentHistory = new DocumentHistory();
objDocumentHistory.SentTo = null;
objDocumentHistory.Timestamp = DateTime.UtcNow;
objDocumentHistory.ActionPerformedBy = SessionFactory.CurrentUser.Id;
objDocumentHistory.Action = EDocumentAction.View;
objDocumentHistory.DocumentId = Id;
_docHistoryRepo.Add(objDocumentHistory);
//Increment view count not to ask password from second attempt to open PDF file
SessionFactory.PdfViewCount++;
return File(PdfFileByte, "application/pdf");
}
catch (Exception ex)
{
ViewBag.ErrorName = "An error occured while opening Document";
base.ErrorLogger.Error("***OpenDocument :: DocumentView***", ex);
}
return null;
}
Do not try to use ajax to download file. Just open the url in a new browser tab. Based on your browser settings, it will either open in the tab or ask whether you want to save it.
You can set the new url to window.location.href
function ViewPDF(key)
{
var url= '#Url.Action("OpenDocument", "DocumentApproveUser")?key='+key,
window.location.href = url;
}
Based on the browser setting, the above 2 approaches will either ask user whether he wishes to download or open the file or simply download/open the file. If you prefer to show the file content directly in the browser, you may send a filestream to the browser.
Here is a quick example, which reads the pdf from a disk in the Contents/Downloads directory in app root and return the file stream.
public ActionResult View(int id)
{
var pathToTheFile=Server.MapPath("~/Content/Downloads/sampleFile.pdf");
var fileStream = new FileStream(pathToTheFile,
FileMode.Open,
FileAccess.Read
);
return new FileStreamResult(fileStream, "application/pdf");
}

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