Upload file to virtual directory using Ajax call - c#

I am building a CMS as MVC 4 project and one of the features is to upload your photo. The user chooses a photo from his hard drive, which triggers an ajax request to the UploadFile method on the controller. This should copy the photo to a virtual folder on the server. The problem is I don't really understand where the browser stores the file and sends it to the server, and what I am supposed to do on the controller.
This is my code so far -
The view:
<input id="cng_pic_btn" type="file" name="file" accept="image/*" /></td>
JavaScript making the call to the server:
$('#cng_pic_btn').live('change', function () {
custom_url = "/SideBar/UploadFile";
return_msg = "file uploaded";
var file_path = $('#cng_pic_btn').attr("value");
alert(file_path);
sendInfo = {
upload_from: file_path
}
CreataAjaxRequest(custom_url, sendInfo, return_msg);
})
The conroller method:
[HttpPost]
public void UploadFile(string upload_from)
{
string path = #"D:\Temp\";
HttpPostedFileBase photo = Request.Files[upload_from];
photo.SaveAs(path + photo.FileName);
}
send ajax request:
function CreataAjaxRequest(custom_url, sendInfo, return_msg) {
$.ajax({ type: "POST", url: custom_url, data: sendInfo })
.success(function (html) {
$(".query-result").replaceWith(html);
})
}

You haven't shown your CreataAjaxRequest method but if you want to upload files using AJAX there are a couple of options:
your client browser supports the HTML 5 File API in which case you could use the XmlHttpRequest2 object
your client browser doesn't support the File API (Such as Internet Explorer) in which case you could use a file upload plugin such as Uploadify or Fine Uploader which use techniques like hidden iframes or Flash movies for those kind of legacy browsers.
Here's an example of how you could upload a file using the HTML 5 File API:
function CreataAjaxRequest(custom_url, sendInfo, return_msg) {
var xhr = new XMLHttpRequest();
var fd = new FormData();
xhr.open('POST', custom_url, true);
var file = document.getElementById('cng_pic_btn').files[0];;
fd.append('myFile', file);
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
$('.query-result').replaceWith(xhr.responseText);
}
};
xhr.send(fd);
}
and then on your server:
[HttpPost]
public ActionResult UploadFile(HttpPostedFileBase myFile)
{
var path = string path = #"D:\Temp\";
myFile.SaveAs(Path.Combine(path, myFile.FileName));
return PartialView();
}
Also notice that your controller action needs to return a PartialView if you want to use the $('.query-result').replaceWith(xhr.responseText); method in your AJAX callback, otherwise what are you replacing with?

Related

ASP .net MVC zip file is not downloading [duplicate]

I have a Struts2 action in the server side for file downloading.
<action name="download" class="com.xxx.DownAction">
<result name="success" type="stream">
<param name="contentType">text/plain</param>
<param name="inputName">imageStream</param>
<param name="contentDisposition">attachment;filename={fileName}</param>
<param name="bufferSize">1024</param>
</result>
</action>
However when I call the action using the jQuery:
$.post(
"/download.action",{
para1:value1,
para2:value2
....
},function(data){
console.info(data);
}
);
in Firebug I see the data is retrieved with the Binary stream. I wonder how to open the file downloading window with which the user can save the file locally?
2019 modern browsers update
This is the approach I'd now recommend with a few caveats:
A relatively modern browser is required
If the file is expected to be very large you should likely do something similar to the original approach (iframe and cookie) because some of the below operations could likely consume system memory at least as large as the file being downloaded and/or other interesting CPU side effects.
fetch('https://jsonplaceholder.typicode.com/todos/1')
.then(resp => resp.blob())
.then(blob => {
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.style.display = 'none';
a.href = url;
// the filename you want
a.download = 'todo-1.json';
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
alert('your file has downloaded!'); // or you know, something with better UX...
})
.catch(() => alert('oh no!'));
2012 Original jQuery/iframe/Cookie based approach
Bluish is completely right about this, you can't do it through Ajax because JavaScript cannot save files directly to a user's computer (out of security concerns). Unfortunately pointing the main window's URL at your file download means you have little control over what the user experience is when a file download occurs.
I created jQuery File Download which allows for an "Ajax like" experience with file downloads complete with OnSuccess and OnFailure callbacks to provide for a better user experience. Take a look at my blog post on the common problem that the plugin solves and some ways to use it and also a demo of jQuery File Download in action. Here is the source
Here is a simple use case demo using the plugin source with promises. The demo page includes many other, 'better UX' examples as well.
$.fileDownload('some/file.pdf')
.done(function () { alert('File download a success!'); })
.fail(function () { alert('File download failed!'); });
Depending on what browsers you need to support you may be able to use https://github.com/eligrey/FileSaver.js/ which allows more explicit control than the IFRAME method jQuery File Download uses.
Noone posted this #Pekka's solution... so I'll post it. It can help someone.
You don't need to do this through Ajax. Just use
window.location="download.action?para1=value1...."
You can with HTML5
NB: The file data returned MUST be base64 encoded because you cannot JSON encode binary data
In my AJAX response I have a data structure that looks like this:
{
result: 'OK',
download: {
mimetype: string(mimetype in the form 'major/minor'),
filename: string(the name of the file to download),
data: base64(the binary data as base64 to download)
}
}
That means that I can do the following to save a file via AJAX
var a = document.createElement('a');
if (window.URL && window.Blob && ('download' in a) && window.atob) {
// Do it the HTML5 compliant way
var blob = base64ToBlob(result.download.data, result.download.mimetype);
var url = window.URL.createObjectURL(blob);
a.href = url;
a.download = result.download.filename;
a.click();
window.URL.revokeObjectURL(url);
}
The function base64ToBlob was taken from here and must be used in compliance with this function
function base64ToBlob(base64, mimetype, slicesize) {
if (!window.atob || !window.Uint8Array) {
// The current browser doesn't have the atob function. Cannot continue
return null;
}
mimetype = mimetype || '';
slicesize = slicesize || 512;
var bytechars = atob(base64);
var bytearrays = [];
for (var offset = 0; offset < bytechars.length; offset += slicesize) {
var slice = bytechars.slice(offset, offset + slicesize);
var bytenums = new Array(slice.length);
for (var i = 0; i < slice.length; i++) {
bytenums[i] = slice.charCodeAt(i);
}
var bytearray = new Uint8Array(bytenums);
bytearrays[bytearrays.length] = bytearray;
}
return new Blob(bytearrays, {type: mimetype});
};
This is good if your server is dumping filedata to be saved. However, I've not quite worked out how one would implement a HTML4 fallback
The simple way to make the browser downloads a file is to make the request like that:
function downloadFile(urlToSend) {
var req = new XMLHttpRequest();
req.open("GET", urlToSend, true);
req.responseType = "blob";
req.onload = function (event) {
var blob = req.response;
var fileName = req.getResponseHeader("fileName") //if you have the fileName header available
var link=document.createElement('a');
link.href=window.URL.createObjectURL(blob);
link.download=fileName;
link.click();
};
req.send();
}
This opens the browser download pop up.
1. Framework agnostic: Servlet downloading file as attachment
<!-- with JS -->
<a href="javascript:window.location='downloadServlet?param1=value1'">
download
</a>
<!-- without JS -->
<a href="downloadServlet?param1=value1" >download</a>
2. Struts2 Framework: Action downloading file as attachment
<!-- with JS -->
<a href="javascript:window.location='downloadAction.action?param1=value1'">
download
</a>
<!-- without JS -->
<a href="downloadAction.action?param1=value1" >download</a>
It would be better to use <s:a> tag pointing with OGNL to an URL created with <s:url> tag:
<!-- without JS, with Struts tags: THE RIGHT WAY -->
<s:url action="downloadAction.action" var="url">
<s:param name="param1">value1</s:param>
</s:ulr>
<s:a href="%{url}" >download</s:a>
In the above cases, you need to write the Content-Disposition header to the response, specifying that the file needs to be downloaded (attachment) and not opened by the browser (inline). You need to specify the Content Type too, and you may want to add the file name and length (to help the browser drawing a realistic progressbar).
For example, when downloading a ZIP:
response.setContentType("application/zip");
response.addHeader("Content-Disposition",
"attachment; filename=\"name of my file.zip\"");
response.setHeader("Content-Length", myFile.length()); // or myByte[].length...
With Struts2 (unless you are using the Action as a Servlet, an hack for direct streaming, for example), you don't need to directly write anything to the response; simply using the Stream result type and configuring it in struts.xml will work: EXAMPLE
<result name="success" type="stream">
<param name="contentType">application/zip</param>
<param name="contentDisposition">attachment;filename="${fileName}"</param>
<param name="contentLength">${fileLength}</param>
</result>
3. Framework agnostic (/ Struts2 framework): Servlet(/Action) opening file inside the browser
If you want to open the file inside the browser, instead of downloading it, the Content-disposition must be set to inline, but the target can't be the current window location; you must target a new window created by javascript, an <iframe> in the page, or a new window created on-the-fly with the "discussed" target="_blank":
<!-- From a parent page into an IFrame without javascript -->
<a href="downloadServlet?param1=value1" target="iFrameName">
download
</a>
<!-- In a new window without javascript -->
<a href="downloadServlet?param1=value1" target="_blank">
download
</a>
<!-- In a new window with javascript -->
<a href="javascript:window.open('downloadServlet?param1=value1');" >
download
</a>
I have created little function as workaround solution (inspired by #JohnCulviner plugin):
// creates iframe and form in it with hidden field,
// then submit form with provided data
// url - form url
// data - data to form field
// input_name - form hidden input name
function ajax_download(url, data, input_name) {
var $iframe,
iframe_doc,
iframe_html;
if (($iframe = $('#download_iframe')).length === 0) {
$iframe = $("<iframe id='download_iframe'" +
" style='display: none' src='about:blank'></iframe>"
).appendTo("body");
}
iframe_doc = $iframe[0].contentWindow || $iframe[0].contentDocument;
if (iframe_doc.document) {
iframe_doc = iframe_doc.document;
}
iframe_html = "<html><head></head><body><form method='POST' action='" +
url +"'>" +
"<input type=hidden name='" + input_name + "' value='" +
JSON.stringify(data) +"'/></form>" +
"</body></html>";
iframe_doc.open();
iframe_doc.write(iframe_html);
$(iframe_doc).find('form').submit();
}
Demo with click event:
$('#someid').on('click', function() {
ajax_download('/download.action', {'para1': 1, 'para2': 2}, 'dataname');
});
I faced the same issue and successfully solved it. My use-case is this.
"Post JSON data to the server and receive an excel file.
That excel file is created by the server and returned as a response to the client. Download that response as a file with custom name in browser"
$("#my-button").on("click", function(){
// Data to post
data = {
ids: [1, 2, 3, 4, 5]
};
// Use XMLHttpRequest instead of Jquery $ajax
xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
var a;
if (xhttp.readyState === 4 && xhttp.status === 200) {
// Trick for making downloadable link
a = document.createElement('a');
a.href = window.URL.createObjectURL(xhttp.response);
// Give filename you wish to download
a.download = "test-file.xls";
a.style.display = 'none';
document.body.appendChild(a);
a.click();
}
};
// Post data to URL which handles post request
xhttp.open("POST", excelDownloadUrl);
xhttp.setRequestHeader("Content-Type", "application/json");
// You should set responseType as blob for binary responses
xhttp.responseType = 'blob';
xhttp.send(JSON.stringify(data));
});
The above snippet is just doing following
Posting an array as JSON to the server using XMLHttpRequest.
After fetching content as a blob(binary), we are creating a downloadable URL and attaching it to invisible "a" link then clicking it. I did a POST request here. Instead, you can go for a simple GET too. We cannot download the file through Ajax, must use XMLHttpRequest.
Here we need to carefully set few things on the server side. I set few headers in Python Django HttpResponse. You need to set them accordingly if you use other programming languages.
# In python django code
response = HttpResponse(file_content, content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
Since I download xls(excel) here, I adjusted contentType to above one. You need to set it according to your file type. You can use this technique to download any kind of files.
Ok, based on ndpu's code heres an improved (I think) version of ajax_download;-
function ajax_download(url, data) {
var $iframe,
iframe_doc,
iframe_html;
if (($iframe = $('#download_iframe')).length === 0) {
$iframe = $("<iframe id='download_iframe'" +
" style='display: none' src='about:blank'></iframe>"
).appendTo("body");
}
iframe_doc = $iframe[0].contentWindow || $iframe[0].contentDocument;
if (iframe_doc.document) {
iframe_doc = iframe_doc.document;
}
iframe_html = "<html><head></head><body><form method='POST' action='" +
url +"'>"
Object.keys(data).forEach(function(key){
iframe_html += "<input type='hidden' name='"+key+"' value='"+data[key]+"'>";
});
iframe_html +="</form></body></html>";
iframe_doc.open();
iframe_doc.write(iframe_html);
$(iframe_doc).find('form').submit();
}
Use this like this;-
$('#someid').on('click', function() {
ajax_download('/download.action', {'para1': 1, 'para2': 2});
});
The params are sent as proper post params as if coming from an input rather than as a json encoded string as per the previous example.
CAVEAT: Be wary about the potential for variable injection on those forms. There might be a safer way to encode those variables. Alternatively contemplate escaping them.
My approach is completly based on jQuery. The problem for me was that it has to be a POST-HTTP call. And I wanted it to be done by jQuery alone.
The solution:
$.ajax({
type: "POST",
url: "/some/webpage",
headers: {'X-CSRF-TOKEN': csrfToken},
data: additionalDataToSend,
dataType: "text",
success: function(result) {
let blob = new Blob([result], { type: "application/octetstream" });
let a = document.createElement('a');
a.href = window.URL.createObjectURL(blob);
a.download = "test.xml";;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(a.href);
...
},
error: errorDialog
});
Explanation:
What I and many others do is to create a link on the webpage, indicating that the target should be downloaded and putting the result of the http-request as the target. After that I append the link to the document than simply clicking the link and removing the link afterwards. You don't need an iframe anymore.
The magic lies in the lines
let blob = new Blob([result], { type: "application/octetstream" });
a.href = window.URL.createObjectURL(blob);
The interesting point is that this solution is only working with a "blob". As you can see in other answers, some are simply using a blob but not explaining why and how to create it.
As you can read e.g. in the Mozilla developer documentation you need a file, media ressource or blob for the function "createObjectURL()" to work. The problem is that your http-response might not be any of those.
Therefore the first thing you must do is to convert your response to a blob. This is what the first line does. Then you can use the "createObjectURL" with your newly created blob.
If you than click the link your browser will open a file-save dialog and you can save your data. Obviously it s possible that you cannot define a fixed filename for your file to download. Then you must make your response more complex like in the answer from Luke.
And don't forget to free up the memory especially when you are working with large files. For more examples and information you can look at the details of the JS blob object
Here is what I did, pure javascript and html. Did not test it but this should work in all browsers.
Javascript Function
var iframe = document.createElement('iframe');
iframe.id = "IFRAMEID";
iframe.style.display = 'none';
document.body.appendChild(iframe);
iframe.src = 'SERVERURL'+'?' + $.param($scope.filtro);
iframe.addEventListener("load", function () {
console.log("FILE LOAD DONE.. Download should start now");
});
Using just components that is supported in all browsers no additional
libraries.
Here is my server side JAVA Spring controller code.
#RequestMapping(value = "/rootto/my/xlsx", method = RequestMethod.GET)
public void downloadExcelFile(#RequestParam(value = "param1", required = false) String param1,
HttpServletRequest request, HttpServletResponse response)
throws ParseException {
Workbook wb = service.getWorkbook(param1);
if (wb != null) {
try {
String fileName = "myfile_" + sdf.format(new Date());
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setHeader("Content-disposition", "attachment; filename=\"" + fileName + ".xlsx\"");
wb.write(response.getOutputStream());
response.getOutputStream().close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
How to DOWNLOAD a file after receiving it by AJAX
It’s convenient when the file is created for a long time and you need to show PRELOADER
Example when submitting a web form:
<script>
$(function () {
$('form').submit(function () {
$('#loader').show();
$.ajax({
url: $(this).attr('action'),
data: $(this).serialize(),
dataType: 'binary',
xhrFields: {
'responseType': 'blob'
},
success: function(data, status, xhr) {
$('#loader').hide();
// if(data.type.indexOf('text/html') != -1){//If instead of a file you get an error page
// var reader = new FileReader();
// reader.readAsText(data);
// reader.onload = function() {alert(reader.result);};
// return;
// }
var link = document.createElement('a'),
filename = 'file.xlsx';
// if(xhr.getResponseHeader('Content-Disposition')){//filename
// filename = xhr.getResponseHeader('Content-Disposition');
// filename=filename.match(/filename="(.*?)"/)[1];
// filename=decodeURIComponent(escape(filename));
// }
link.href = URL.createObjectURL(data);
link.download = filename;
link.click();
}
});
return false;
});
});
</script>
Optional functional is commented out to simplify the example.
No need to create temporary files on the server.
On jQuery v2.2.4 OK. There will be an error on the old version:
Uncaught DOMException: Failed to read the 'responseText' property from 'XMLHttpRequest': The value is only accessible if the object's 'responseType' is '' or 'text' (was 'blob').
function downloadURI(uri, name)
{
var link = document.createElement("a");
link.download = name;
link.href = uri;
link.click();
}
I try to download a CSV file and then do something after download has finished. So I need to implement an appropriate callback function.
Using window.location="..." is not a good idea because I cannot operate the program after finishing download. Something like this, change header so it is not a good idea.
fetch is a good alternative however it cannot support IE 11. And window.URL.createObjectURL cannot support IE 11.You can refer this.
This is my code, it is similar to the code of Shahrukh Alam. But you should take care that window.URL.createObjectURL maybe create memory leaks. You can refer this. When response has arrived, data will be stored into memory of browser. So before you click a link, the file has been downloaded. It means that you can do anything after download.
$.ajax({
url: 'your download url',
type: 'GET',
}).done(function (data, textStatus, request) {
// csv => Blob
var blob = new Blob([data]);
// the file name from server.
var fileName = request.getResponseHeader('fileName');
if (window.navigator && window.navigator.msSaveOrOpenBlob) { // for IE
window.navigator.msSaveOrOpenBlob(blob, fileName);
} else { // for others
var url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.style.display = 'none';
a.href = url;
a.download = fileName;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
//Do something after download
...
}
}).then(after_download)
}
Adding some more things to above answer for downloading a file
Below is some java spring code which generates byte Array
#RequestMapping(value = "/downloadReport", method = { RequestMethod.POST })
public ResponseEntity<byte[]> downloadReport(
#RequestBody final SomeObejct obj, HttpServletResponse response) throws Exception {
OutputStream out = new ByteArrayOutputStream();
// write something to output stream
HttpHeaders respHeaders = new HttpHeaders();
respHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
respHeaders.add("X-File-Name", name);
ByteArrayOutputStream bos = (ByteArrayOutputStream) out;
return new ResponseEntity<byte[]>(bos.toByteArray(), respHeaders, HttpStatus.CREATED);
}
Now in javascript code using FileSaver.js ,can download a file with below code
var json=angular.toJson("somejsobject");
var url=apiEndPoint+'some url';
var xhr = new XMLHttpRequest();
//headers('X-File-Name')
xhr.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 201) {
var res = this.response;
var fileName=this.getResponseHeader('X-File-Name');
var data = new Blob([res]);
saveAs(data, fileName); //this from FileSaver.js
}
}
xhr.open('POST', url);
xhr.setRequestHeader('Authorization','Bearer ' + token);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.responseType = 'arraybuffer';
xhr.send(json);
The above will download file
In Rails, I do it this way:
function download_file(file_id) {
let url = '/files/' + file_id + '/download_file';
$.ajax({
type: 'GET',
url: url,
processData: false,
success: function (data) {
window.location = url;
},
error: function (xhr) {
console.log(' Error: >>>> ' + JSON.stringify(xhr));
}
});
}
The trick is the window.location part. The controller's method looks like:
# GET /files/{:id}/download_file/
def download_file
send_file(#file.file,
:disposition => 'attachment',
:url_based_filename => false)
end
Use window.open https://developer.mozilla.org/en-US/docs/Web/API/Window/open
For example, you can put this line of code in a click handler:
window.open('/file.txt', '_blank');
It will open a new tab (because of the '_blank' window-name) and that tab will open the URL.
Your server-side code should also have something like this:
res.set('Content-Disposition', 'attachment; filename=file.txt');
And that way, the browser should prompt the user to save the file to disk, instead of just showing them the file. It will also automatically close the tab that it just opened.
The HTML Code :
<button type="button" id="GetFile">Get File!</button>
The jQuery Code :
$('#GetFile').on('click', function () {
$.ajax({
url: 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/172905/test.pdf',
method: 'GET',
xhrFields: {
responseType: 'blob'
},
success: function (data) {
var a = document.createElement('a');
var url = window.URL.createObjectURL(data);
a.href = url;
a.download = 'myfile.pdf';
document.body.append(a);
a.click();
a.remove();
window.URL.revokeObjectURL(url);
}
});
});
Ok so here is the working code when Using MVC and you are getting your file from a controller
lets say you have your byte array declare and populate, the only thing you need to do is to use the File function (using System.Web.Mvc)
byte[] bytes = .... insert your bytes in the array
return File(bytes, System.Net.Mime.MediaTypeNames.Application.Octet, "nameoffile.exe");
and then, in the same controller, add thoses 2 functions
protected override void OnResultExecuting(ResultExecutingContext context)
{
CheckAndHandleFileResult(context);
base.OnResultExecuting(context);
}
private const string FILE_DOWNLOAD_COOKIE_NAME = "fileDownload";
/// <summary>
/// If the current response is a FileResult (an MVC base class for files) then write a
/// cookie to inform jquery.fileDownload that a successful file download has occured
/// </summary>
/// <param name="context"></param>
private void CheckAndHandleFileResult(ResultExecutingContext context)
{
if (context.Result is FileResult)
//jquery.fileDownload uses this cookie to determine that a file download has completed successfully
Response.SetCookie(new HttpCookie(FILE_DOWNLOAD_COOKIE_NAME, "true") { Path = "/" });
else
//ensure that the cookie is removed in case someone did a file download without using jquery.fileDownload
if (Request.Cookies[FILE_DOWNLOAD_COOKIE_NAME] != null)
Response.Cookies[FILE_DOWNLOAD_COOKIE_NAME].Expires = DateTime.Now.AddYears(-1);
}
and then you will be able to call your controller to download and get the "success" or "failure" callback
$.fileDownload(mvcUrl('name of the controller'), {
httpMethod: 'POST',
successCallback: function (url) {
//insert success code
},
failCallback: function (html, url) {
//insert fail code
}
});
I found a fix that while it's not actually using ajax it does allow you to use a javascript call to request the download and then get a callback when the download actually starts. I found this helpful if the link runs a server side script that takes a little bit to compose the file before sending it. so you can alert them that it's processing, and then when it does finally send the file remove that processing notification. which is why I wanted to try to load the file via ajax to begin with so that I could have an event happen when the file is requested and another when it actually starts downloading.
the js on the front page
function expdone()
{
document.getElementById('exportdiv').style.display='none';
}
function expgo()
{
document.getElementById('exportdiv').style.display='block';
document.getElementById('exportif').src='test2.php?arguments=data';
}
the iframe
<div id="exportdiv" style="display:none;">
<img src="loader.gif"><br><h1>Generating Report</h1>
<iframe id="exportif" src="" style="width: 1px;height: 1px; border:0px;"></iframe>
</div>
then the other file:
<!DOCTYPE html>
<html>
<head>
<script>
function expdone()
{
window.parent.expdone();
}
</script>
</head>
<body>
<iframe id="exportif" src="<?php echo "http://10.192.37.211/npdtracker/exportthismonth.php?arguments=".$_GET["arguments"]; ?>"></iframe>
<script>document.getElementById('exportif').onload= expdone;</script>
</body></html>
I think there's a way to read get data using js so then no php would be needed. but I don't know it off hand and the server I'm using supports php so this works for me. thought I'd share it in case it helps anyone.
If the server is writing the file back in the response (including cookies if
you use them to determine whether the file download started), Simply create a form with the values and submit it:
function ajaxPostDownload(url, data) {
var $form;
if (($form = $('#download_form')).length === 0) {
$form = $("<form id='download_form'" + " style='display: none; width: 1px; height: 1px; position: absolute; top: -10000px' method='POST' action='" + url + "'></form>");
$form.appendTo("body");
}
//Clear the form fields
$form.html("");
//Create new form fields
Object.keys(data).forEach(function (key) {
$form.append("<input type='hidden' name='" + key + "' value='" + data[key] + "'>");
});
//Submit the form post
$form.submit();
}
Usage:
ajaxPostDownload('/fileController/ExportFile', {
DownloadToken: 'newDownloadToken',
Name: $txtName.val(),
Type: $txtType.val()
});
Controller Method:
[HttpPost]
public FileResult ExportFile(string DownloadToken, string Name, string Type)
{
//Set DownloadToken Cookie.
Response.SetCookie(new HttpCookie("downloadToken", DownloadToken)
{
Expires = DateTime.UtcNow.AddDays(1),
Secure = false
});
using (var output = new MemoryStream())
{
//get File
return File(output.ToArray(), "application/vnd.ms-excel", "NewFile.xls");
}
}
I have tried Ajax and HttpRequest ways to get my result download file but I've failed, finally I've solved my problem using these steps:
implemented a simple hidden form in my html code:
<form method="post" id="post_form" style="display:none" action="amin.php" >
<input type="hidden" name="action" value="export_xlsx" />
<input type="hidden" name="post_form_data" value="" />
</form>
input with 'action' name is for calling function in my php code,
input with 'post_form_data' name for sending long data of a table which were not possible to send with GET. this data was encoded to json, and put json in input:
var list = new Array();
$('#table_name tr').each(function() {
var row = new Array();
$(this).find('td').each(function() {
row.push($(this).text());
});
list.push(row);
});
list = JSON.stringify(list);
$("input[name=post_form_data]").val(list);
now, the form is ready with my desire values in inputs, just need to trigger the submit.
document.getElementById('post_form').submit();
and done!
while my result is a file (xlsx file for me) the page wouldn't be redirected and instantly the file starts to download in last page, so no need to useiframe or window.open etc.
if you are trying to do something like this, this should be an easy trick 😉.
If you want to use jQuery File Download , please note this for IE.
You need to reset the response or it will not download
//The IE will only work if you reset response
getServletResponse().reset();
//The jquery.fileDownload needs a cookie be set
getServletResponse().setHeader("Set-Cookie", "fileDownload=true; path=/");
//Do the reset of your action create InputStream and return
Your action can implement ServletResponseAware to access getServletResponse()
It is certain that you can not do it through Ajax call.
However, there is a workaround.
Steps :
If you are using form.submit() for downloading the file, what you can do is :
Create an ajax call from client to server and store the file stream inside the session.
Upon "success" being returned from server, call your form.submit() to just stream the file stream stored in the session.
This is helpful in case when you want to decide whether or not file needs to be downloaded after making form.submit(), eg: there can be a case where on form.submit(), an exception occurs on the server side and instead of crashing, you might need to show a custom message on the client side, in such case this implementation might help.
there is another solution to download a web page in ajax. But I am referring to a page that must first be processed and then downloaded.
First you need to separate the page processing from the results download.
1) Only the page calculations are made in the ajax call.
$.post("CalculusPage.php", { calculusFunction: true, ID: 29, data1: "a", data2: "b" },
function(data, status)
{
if (status == "success")
{
/* 2) In the answer the page that uses the previous calculations is downloaded. For example, this can be a page that prints the results of a table calculated in the ajax call. */
window.location.href = DownloadPage.php+"?ID="+29;
}
}
);
// For example: in the CalculusPage.php
if ( !empty($_POST["calculusFunction"]) )
{
$ID = $_POST["ID"];
$query = "INSERT INTO ExamplePage (data1, data2) VALUES ('".$_POST["data1"]."', '".$_POST["data2"]."') WHERE id = ".$ID;
...
}
// For example: in the DownloadPage.php
$ID = $_GET["ID"];
$sede = "SELECT * FROM ExamplePage WHERE id = ".$ID;
...
$filename="Export_Data.xls";
header("Content-Type: application/vnd.ms-excel");
header("Content-Disposition: inline; filename=$filename");
...
I hope this solution can be useful for many, as it was for me.
That's it works so fine in any browser (I'm using asp.net core)
function onDownload() {
const api = '#Url.Action("myaction", "mycontroller")';
var form = new FormData(document.getElementById('form1'));
fetch(api, { body: form, method: "POST"})
.then(resp => resp.blob())
.then(blob => {
const url = window.URL.createObjectURL(blob);
$('#linkdownload').attr('download', 'Attachement.zip');
$('#linkdownload').attr("href", url);
$('#linkdownload')
.fadeIn(3000,
function() { });
})
.catch(() => alert('An error occurred'));
}
<button type="button" onclick="onDownload()" class="btn btn-primary btn-sm">Click to Process Files</button>
<a role="button" href="#" style="display: none" class="btn btn-sm btn-secondary" id="linkdownload">Click to download Attachments</a>
<form asp-controller="mycontroller" asp-action="myaction" id="form1"></form>
function onDownload() {
const api = '#Url.Action("myaction", "mycontroller")';
//form1 is your id form, and to get data content of form
var form = new FormData(document.getElementById('form1'));
fetch(api, { body: form, method: "POST"})
.then(resp => resp.blob())
.then(blob => {
const url = window.URL.createObjectURL(blob);
$('#linkdownload').attr('download', 'Attachments.zip');
$('#linkdownload').attr("href", url);
$('#linkdownload')
.fadeIn(3000,
function() {
});
})
.catch(() => alert('An error occurred'));
}
I struggled with this issue for a long time. Finally an elegant external library suggested here helped me out.

Unable to download excel file using ajax call [duplicate]

I have a Struts2 action in the server side for file downloading.
<action name="download" class="com.xxx.DownAction">
<result name="success" type="stream">
<param name="contentType">text/plain</param>
<param name="inputName">imageStream</param>
<param name="contentDisposition">attachment;filename={fileName}</param>
<param name="bufferSize">1024</param>
</result>
</action>
However when I call the action using the jQuery:
$.post(
"/download.action",{
para1:value1,
para2:value2
....
},function(data){
console.info(data);
}
);
in Firebug I see the data is retrieved with the Binary stream. I wonder how to open the file downloading window with which the user can save the file locally?
2019 modern browsers update
This is the approach I'd now recommend with a few caveats:
A relatively modern browser is required
If the file is expected to be very large you should likely do something similar to the original approach (iframe and cookie) because some of the below operations could likely consume system memory at least as large as the file being downloaded and/or other interesting CPU side effects.
fetch('https://jsonplaceholder.typicode.com/todos/1')
.then(resp => resp.blob())
.then(blob => {
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.style.display = 'none';
a.href = url;
// the filename you want
a.download = 'todo-1.json';
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
alert('your file has downloaded!'); // or you know, something with better UX...
})
.catch(() => alert('oh no!'));
2012 Original jQuery/iframe/Cookie based approach
Bluish is completely right about this, you can't do it through Ajax because JavaScript cannot save files directly to a user's computer (out of security concerns). Unfortunately pointing the main window's URL at your file download means you have little control over what the user experience is when a file download occurs.
I created jQuery File Download which allows for an "Ajax like" experience with file downloads complete with OnSuccess and OnFailure callbacks to provide for a better user experience. Take a look at my blog post on the common problem that the plugin solves and some ways to use it and also a demo of jQuery File Download in action. Here is the source
Here is a simple use case demo using the plugin source with promises. The demo page includes many other, 'better UX' examples as well.
$.fileDownload('some/file.pdf')
.done(function () { alert('File download a success!'); })
.fail(function () { alert('File download failed!'); });
Depending on what browsers you need to support you may be able to use https://github.com/eligrey/FileSaver.js/ which allows more explicit control than the IFRAME method jQuery File Download uses.
Noone posted this #Pekka's solution... so I'll post it. It can help someone.
You don't need to do this through Ajax. Just use
window.location="download.action?para1=value1...."
You can with HTML5
NB: The file data returned MUST be base64 encoded because you cannot JSON encode binary data
In my AJAX response I have a data structure that looks like this:
{
result: 'OK',
download: {
mimetype: string(mimetype in the form 'major/minor'),
filename: string(the name of the file to download),
data: base64(the binary data as base64 to download)
}
}
That means that I can do the following to save a file via AJAX
var a = document.createElement('a');
if (window.URL && window.Blob && ('download' in a) && window.atob) {
// Do it the HTML5 compliant way
var blob = base64ToBlob(result.download.data, result.download.mimetype);
var url = window.URL.createObjectURL(blob);
a.href = url;
a.download = result.download.filename;
a.click();
window.URL.revokeObjectURL(url);
}
The function base64ToBlob was taken from here and must be used in compliance with this function
function base64ToBlob(base64, mimetype, slicesize) {
if (!window.atob || !window.Uint8Array) {
// The current browser doesn't have the atob function. Cannot continue
return null;
}
mimetype = mimetype || '';
slicesize = slicesize || 512;
var bytechars = atob(base64);
var bytearrays = [];
for (var offset = 0; offset < bytechars.length; offset += slicesize) {
var slice = bytechars.slice(offset, offset + slicesize);
var bytenums = new Array(slice.length);
for (var i = 0; i < slice.length; i++) {
bytenums[i] = slice.charCodeAt(i);
}
var bytearray = new Uint8Array(bytenums);
bytearrays[bytearrays.length] = bytearray;
}
return new Blob(bytearrays, {type: mimetype});
};
This is good if your server is dumping filedata to be saved. However, I've not quite worked out how one would implement a HTML4 fallback
The simple way to make the browser downloads a file is to make the request like that:
function downloadFile(urlToSend) {
var req = new XMLHttpRequest();
req.open("GET", urlToSend, true);
req.responseType = "blob";
req.onload = function (event) {
var blob = req.response;
var fileName = req.getResponseHeader("fileName") //if you have the fileName header available
var link=document.createElement('a');
link.href=window.URL.createObjectURL(blob);
link.download=fileName;
link.click();
};
req.send();
}
This opens the browser download pop up.
1. Framework agnostic: Servlet downloading file as attachment
<!-- with JS -->
<a href="javascript:window.location='downloadServlet?param1=value1'">
download
</a>
<!-- without JS -->
<a href="downloadServlet?param1=value1" >download</a>
2. Struts2 Framework: Action downloading file as attachment
<!-- with JS -->
<a href="javascript:window.location='downloadAction.action?param1=value1'">
download
</a>
<!-- without JS -->
<a href="downloadAction.action?param1=value1" >download</a>
It would be better to use <s:a> tag pointing with OGNL to an URL created with <s:url> tag:
<!-- without JS, with Struts tags: THE RIGHT WAY -->
<s:url action="downloadAction.action" var="url">
<s:param name="param1">value1</s:param>
</s:ulr>
<s:a href="%{url}" >download</s:a>
In the above cases, you need to write the Content-Disposition header to the response, specifying that the file needs to be downloaded (attachment) and not opened by the browser (inline). You need to specify the Content Type too, and you may want to add the file name and length (to help the browser drawing a realistic progressbar).
For example, when downloading a ZIP:
response.setContentType("application/zip");
response.addHeader("Content-Disposition",
"attachment; filename=\"name of my file.zip\"");
response.setHeader("Content-Length", myFile.length()); // or myByte[].length...
With Struts2 (unless you are using the Action as a Servlet, an hack for direct streaming, for example), you don't need to directly write anything to the response; simply using the Stream result type and configuring it in struts.xml will work: EXAMPLE
<result name="success" type="stream">
<param name="contentType">application/zip</param>
<param name="contentDisposition">attachment;filename="${fileName}"</param>
<param name="contentLength">${fileLength}</param>
</result>
3. Framework agnostic (/ Struts2 framework): Servlet(/Action) opening file inside the browser
If you want to open the file inside the browser, instead of downloading it, the Content-disposition must be set to inline, but the target can't be the current window location; you must target a new window created by javascript, an <iframe> in the page, or a new window created on-the-fly with the "discussed" target="_blank":
<!-- From a parent page into an IFrame without javascript -->
<a href="downloadServlet?param1=value1" target="iFrameName">
download
</a>
<!-- In a new window without javascript -->
<a href="downloadServlet?param1=value1" target="_blank">
download
</a>
<!-- In a new window with javascript -->
<a href="javascript:window.open('downloadServlet?param1=value1');" >
download
</a>
I have created little function as workaround solution (inspired by #JohnCulviner plugin):
// creates iframe and form in it with hidden field,
// then submit form with provided data
// url - form url
// data - data to form field
// input_name - form hidden input name
function ajax_download(url, data, input_name) {
var $iframe,
iframe_doc,
iframe_html;
if (($iframe = $('#download_iframe')).length === 0) {
$iframe = $("<iframe id='download_iframe'" +
" style='display: none' src='about:blank'></iframe>"
).appendTo("body");
}
iframe_doc = $iframe[0].contentWindow || $iframe[0].contentDocument;
if (iframe_doc.document) {
iframe_doc = iframe_doc.document;
}
iframe_html = "<html><head></head><body><form method='POST' action='" +
url +"'>" +
"<input type=hidden name='" + input_name + "' value='" +
JSON.stringify(data) +"'/></form>" +
"</body></html>";
iframe_doc.open();
iframe_doc.write(iframe_html);
$(iframe_doc).find('form').submit();
}
Demo with click event:
$('#someid').on('click', function() {
ajax_download('/download.action', {'para1': 1, 'para2': 2}, 'dataname');
});
I faced the same issue and successfully solved it. My use-case is this.
"Post JSON data to the server and receive an excel file.
That excel file is created by the server and returned as a response to the client. Download that response as a file with custom name in browser"
$("#my-button").on("click", function(){
// Data to post
data = {
ids: [1, 2, 3, 4, 5]
};
// Use XMLHttpRequest instead of Jquery $ajax
xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
var a;
if (xhttp.readyState === 4 && xhttp.status === 200) {
// Trick for making downloadable link
a = document.createElement('a');
a.href = window.URL.createObjectURL(xhttp.response);
// Give filename you wish to download
a.download = "test-file.xls";
a.style.display = 'none';
document.body.appendChild(a);
a.click();
}
};
// Post data to URL which handles post request
xhttp.open("POST", excelDownloadUrl);
xhttp.setRequestHeader("Content-Type", "application/json");
// You should set responseType as blob for binary responses
xhttp.responseType = 'blob';
xhttp.send(JSON.stringify(data));
});
The above snippet is just doing following
Posting an array as JSON to the server using XMLHttpRequest.
After fetching content as a blob(binary), we are creating a downloadable URL and attaching it to invisible "a" link then clicking it. I did a POST request here. Instead, you can go for a simple GET too. We cannot download the file through Ajax, must use XMLHttpRequest.
Here we need to carefully set few things on the server side. I set few headers in Python Django HttpResponse. You need to set them accordingly if you use other programming languages.
# In python django code
response = HttpResponse(file_content, content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
Since I download xls(excel) here, I adjusted contentType to above one. You need to set it according to your file type. You can use this technique to download any kind of files.
Ok, based on ndpu's code heres an improved (I think) version of ajax_download;-
function ajax_download(url, data) {
var $iframe,
iframe_doc,
iframe_html;
if (($iframe = $('#download_iframe')).length === 0) {
$iframe = $("<iframe id='download_iframe'" +
" style='display: none' src='about:blank'></iframe>"
).appendTo("body");
}
iframe_doc = $iframe[0].contentWindow || $iframe[0].contentDocument;
if (iframe_doc.document) {
iframe_doc = iframe_doc.document;
}
iframe_html = "<html><head></head><body><form method='POST' action='" +
url +"'>"
Object.keys(data).forEach(function(key){
iframe_html += "<input type='hidden' name='"+key+"' value='"+data[key]+"'>";
});
iframe_html +="</form></body></html>";
iframe_doc.open();
iframe_doc.write(iframe_html);
$(iframe_doc).find('form').submit();
}
Use this like this;-
$('#someid').on('click', function() {
ajax_download('/download.action', {'para1': 1, 'para2': 2});
});
The params are sent as proper post params as if coming from an input rather than as a json encoded string as per the previous example.
CAVEAT: Be wary about the potential for variable injection on those forms. There might be a safer way to encode those variables. Alternatively contemplate escaping them.
My approach is completly based on jQuery. The problem for me was that it has to be a POST-HTTP call. And I wanted it to be done by jQuery alone.
The solution:
$.ajax({
type: "POST",
url: "/some/webpage",
headers: {'X-CSRF-TOKEN': csrfToken},
data: additionalDataToSend,
dataType: "text",
success: function(result) {
let blob = new Blob([result], { type: "application/octetstream" });
let a = document.createElement('a');
a.href = window.URL.createObjectURL(blob);
a.download = "test.xml";;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(a.href);
...
},
error: errorDialog
});
Explanation:
What I and many others do is to create a link on the webpage, indicating that the target should be downloaded and putting the result of the http-request as the target. After that I append the link to the document than simply clicking the link and removing the link afterwards. You don't need an iframe anymore.
The magic lies in the lines
let blob = new Blob([result], { type: "application/octetstream" });
a.href = window.URL.createObjectURL(blob);
The interesting point is that this solution is only working with a "blob". As you can see in other answers, some are simply using a blob but not explaining why and how to create it.
As you can read e.g. in the Mozilla developer documentation you need a file, media ressource or blob for the function "createObjectURL()" to work. The problem is that your http-response might not be any of those.
Therefore the first thing you must do is to convert your response to a blob. This is what the first line does. Then you can use the "createObjectURL" with your newly created blob.
If you than click the link your browser will open a file-save dialog and you can save your data. Obviously it s possible that you cannot define a fixed filename for your file to download. Then you must make your response more complex like in the answer from Luke.
And don't forget to free up the memory especially when you are working with large files. For more examples and information you can look at the details of the JS blob object
Here is what I did, pure javascript and html. Did not test it but this should work in all browsers.
Javascript Function
var iframe = document.createElement('iframe');
iframe.id = "IFRAMEID";
iframe.style.display = 'none';
document.body.appendChild(iframe);
iframe.src = 'SERVERURL'+'?' + $.param($scope.filtro);
iframe.addEventListener("load", function () {
console.log("FILE LOAD DONE.. Download should start now");
});
Using just components that is supported in all browsers no additional
libraries.
Here is my server side JAVA Spring controller code.
#RequestMapping(value = "/rootto/my/xlsx", method = RequestMethod.GET)
public void downloadExcelFile(#RequestParam(value = "param1", required = false) String param1,
HttpServletRequest request, HttpServletResponse response)
throws ParseException {
Workbook wb = service.getWorkbook(param1);
if (wb != null) {
try {
String fileName = "myfile_" + sdf.format(new Date());
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setHeader("Content-disposition", "attachment; filename=\"" + fileName + ".xlsx\"");
wb.write(response.getOutputStream());
response.getOutputStream().close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
How to DOWNLOAD a file after receiving it by AJAX
It’s convenient when the file is created for a long time and you need to show PRELOADER
Example when submitting a web form:
<script>
$(function () {
$('form').submit(function () {
$('#loader').show();
$.ajax({
url: $(this).attr('action'),
data: $(this).serialize(),
dataType: 'binary',
xhrFields: {
'responseType': 'blob'
},
success: function(data, status, xhr) {
$('#loader').hide();
// if(data.type.indexOf('text/html') != -1){//If instead of a file you get an error page
// var reader = new FileReader();
// reader.readAsText(data);
// reader.onload = function() {alert(reader.result);};
// return;
// }
var link = document.createElement('a'),
filename = 'file.xlsx';
// if(xhr.getResponseHeader('Content-Disposition')){//filename
// filename = xhr.getResponseHeader('Content-Disposition');
// filename=filename.match(/filename="(.*?)"/)[1];
// filename=decodeURIComponent(escape(filename));
// }
link.href = URL.createObjectURL(data);
link.download = filename;
link.click();
}
});
return false;
});
});
</script>
Optional functional is commented out to simplify the example.
No need to create temporary files on the server.
On jQuery v2.2.4 OK. There will be an error on the old version:
Uncaught DOMException: Failed to read the 'responseText' property from 'XMLHttpRequest': The value is only accessible if the object's 'responseType' is '' or 'text' (was 'blob').
function downloadURI(uri, name)
{
var link = document.createElement("a");
link.download = name;
link.href = uri;
link.click();
}
I try to download a CSV file and then do something after download has finished. So I need to implement an appropriate callback function.
Using window.location="..." is not a good idea because I cannot operate the program after finishing download. Something like this, change header so it is not a good idea.
fetch is a good alternative however it cannot support IE 11. And window.URL.createObjectURL cannot support IE 11.You can refer this.
This is my code, it is similar to the code of Shahrukh Alam. But you should take care that window.URL.createObjectURL maybe create memory leaks. You can refer this. When response has arrived, data will be stored into memory of browser. So before you click a link, the file has been downloaded. It means that you can do anything after download.
$.ajax({
url: 'your download url',
type: 'GET',
}).done(function (data, textStatus, request) {
// csv => Blob
var blob = new Blob([data]);
// the file name from server.
var fileName = request.getResponseHeader('fileName');
if (window.navigator && window.navigator.msSaveOrOpenBlob) { // for IE
window.navigator.msSaveOrOpenBlob(blob, fileName);
} else { // for others
var url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.style.display = 'none';
a.href = url;
a.download = fileName;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
//Do something after download
...
}
}).then(after_download)
}
Adding some more things to above answer for downloading a file
Below is some java spring code which generates byte Array
#RequestMapping(value = "/downloadReport", method = { RequestMethod.POST })
public ResponseEntity<byte[]> downloadReport(
#RequestBody final SomeObejct obj, HttpServletResponse response) throws Exception {
OutputStream out = new ByteArrayOutputStream();
// write something to output stream
HttpHeaders respHeaders = new HttpHeaders();
respHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
respHeaders.add("X-File-Name", name);
ByteArrayOutputStream bos = (ByteArrayOutputStream) out;
return new ResponseEntity<byte[]>(bos.toByteArray(), respHeaders, HttpStatus.CREATED);
}
Now in javascript code using FileSaver.js ,can download a file with below code
var json=angular.toJson("somejsobject");
var url=apiEndPoint+'some url';
var xhr = new XMLHttpRequest();
//headers('X-File-Name')
xhr.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 201) {
var res = this.response;
var fileName=this.getResponseHeader('X-File-Name');
var data = new Blob([res]);
saveAs(data, fileName); //this from FileSaver.js
}
}
xhr.open('POST', url);
xhr.setRequestHeader('Authorization','Bearer ' + token);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.responseType = 'arraybuffer';
xhr.send(json);
The above will download file
In Rails, I do it this way:
function download_file(file_id) {
let url = '/files/' + file_id + '/download_file';
$.ajax({
type: 'GET',
url: url,
processData: false,
success: function (data) {
window.location = url;
},
error: function (xhr) {
console.log(' Error: >>>> ' + JSON.stringify(xhr));
}
});
}
The trick is the window.location part. The controller's method looks like:
# GET /files/{:id}/download_file/
def download_file
send_file(#file.file,
:disposition => 'attachment',
:url_based_filename => false)
end
Use window.open https://developer.mozilla.org/en-US/docs/Web/API/Window/open
For example, you can put this line of code in a click handler:
window.open('/file.txt', '_blank');
It will open a new tab (because of the '_blank' window-name) and that tab will open the URL.
Your server-side code should also have something like this:
res.set('Content-Disposition', 'attachment; filename=file.txt');
And that way, the browser should prompt the user to save the file to disk, instead of just showing them the file. It will also automatically close the tab that it just opened.
The HTML Code :
<button type="button" id="GetFile">Get File!</button>
The jQuery Code :
$('#GetFile').on('click', function () {
$.ajax({
url: 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/172905/test.pdf',
method: 'GET',
xhrFields: {
responseType: 'blob'
},
success: function (data) {
var a = document.createElement('a');
var url = window.URL.createObjectURL(data);
a.href = url;
a.download = 'myfile.pdf';
document.body.append(a);
a.click();
a.remove();
window.URL.revokeObjectURL(url);
}
});
});
Ok so here is the working code when Using MVC and you are getting your file from a controller
lets say you have your byte array declare and populate, the only thing you need to do is to use the File function (using System.Web.Mvc)
byte[] bytes = .... insert your bytes in the array
return File(bytes, System.Net.Mime.MediaTypeNames.Application.Octet, "nameoffile.exe");
and then, in the same controller, add thoses 2 functions
protected override void OnResultExecuting(ResultExecutingContext context)
{
CheckAndHandleFileResult(context);
base.OnResultExecuting(context);
}
private const string FILE_DOWNLOAD_COOKIE_NAME = "fileDownload";
/// <summary>
/// If the current response is a FileResult (an MVC base class for files) then write a
/// cookie to inform jquery.fileDownload that a successful file download has occured
/// </summary>
/// <param name="context"></param>
private void CheckAndHandleFileResult(ResultExecutingContext context)
{
if (context.Result is FileResult)
//jquery.fileDownload uses this cookie to determine that a file download has completed successfully
Response.SetCookie(new HttpCookie(FILE_DOWNLOAD_COOKIE_NAME, "true") { Path = "/" });
else
//ensure that the cookie is removed in case someone did a file download without using jquery.fileDownload
if (Request.Cookies[FILE_DOWNLOAD_COOKIE_NAME] != null)
Response.Cookies[FILE_DOWNLOAD_COOKIE_NAME].Expires = DateTime.Now.AddYears(-1);
}
and then you will be able to call your controller to download and get the "success" or "failure" callback
$.fileDownload(mvcUrl('name of the controller'), {
httpMethod: 'POST',
successCallback: function (url) {
//insert success code
},
failCallback: function (html, url) {
//insert fail code
}
});
I found a fix that while it's not actually using ajax it does allow you to use a javascript call to request the download and then get a callback when the download actually starts. I found this helpful if the link runs a server side script that takes a little bit to compose the file before sending it. so you can alert them that it's processing, and then when it does finally send the file remove that processing notification. which is why I wanted to try to load the file via ajax to begin with so that I could have an event happen when the file is requested and another when it actually starts downloading.
the js on the front page
function expdone()
{
document.getElementById('exportdiv').style.display='none';
}
function expgo()
{
document.getElementById('exportdiv').style.display='block';
document.getElementById('exportif').src='test2.php?arguments=data';
}
the iframe
<div id="exportdiv" style="display:none;">
<img src="loader.gif"><br><h1>Generating Report</h1>
<iframe id="exportif" src="" style="width: 1px;height: 1px; border:0px;"></iframe>
</div>
then the other file:
<!DOCTYPE html>
<html>
<head>
<script>
function expdone()
{
window.parent.expdone();
}
</script>
</head>
<body>
<iframe id="exportif" src="<?php echo "http://10.192.37.211/npdtracker/exportthismonth.php?arguments=".$_GET["arguments"]; ?>"></iframe>
<script>document.getElementById('exportif').onload= expdone;</script>
</body></html>
I think there's a way to read get data using js so then no php would be needed. but I don't know it off hand and the server I'm using supports php so this works for me. thought I'd share it in case it helps anyone.
If the server is writing the file back in the response (including cookies if
you use them to determine whether the file download started), Simply create a form with the values and submit it:
function ajaxPostDownload(url, data) {
var $form;
if (($form = $('#download_form')).length === 0) {
$form = $("<form id='download_form'" + " style='display: none; width: 1px; height: 1px; position: absolute; top: -10000px' method='POST' action='" + url + "'></form>");
$form.appendTo("body");
}
//Clear the form fields
$form.html("");
//Create new form fields
Object.keys(data).forEach(function (key) {
$form.append("<input type='hidden' name='" + key + "' value='" + data[key] + "'>");
});
//Submit the form post
$form.submit();
}
Usage:
ajaxPostDownload('/fileController/ExportFile', {
DownloadToken: 'newDownloadToken',
Name: $txtName.val(),
Type: $txtType.val()
});
Controller Method:
[HttpPost]
public FileResult ExportFile(string DownloadToken, string Name, string Type)
{
//Set DownloadToken Cookie.
Response.SetCookie(new HttpCookie("downloadToken", DownloadToken)
{
Expires = DateTime.UtcNow.AddDays(1),
Secure = false
});
using (var output = new MemoryStream())
{
//get File
return File(output.ToArray(), "application/vnd.ms-excel", "NewFile.xls");
}
}
I have tried Ajax and HttpRequest ways to get my result download file but I've failed, finally I've solved my problem using these steps:
implemented a simple hidden form in my html code:
<form method="post" id="post_form" style="display:none" action="amin.php" >
<input type="hidden" name="action" value="export_xlsx" />
<input type="hidden" name="post_form_data" value="" />
</form>
input with 'action' name is for calling function in my php code,
input with 'post_form_data' name for sending long data of a table which were not possible to send with GET. this data was encoded to json, and put json in input:
var list = new Array();
$('#table_name tr').each(function() {
var row = new Array();
$(this).find('td').each(function() {
row.push($(this).text());
});
list.push(row);
});
list = JSON.stringify(list);
$("input[name=post_form_data]").val(list);
now, the form is ready with my desire values in inputs, just need to trigger the submit.
document.getElementById('post_form').submit();
and done!
while my result is a file (xlsx file for me) the page wouldn't be redirected and instantly the file starts to download in last page, so no need to useiframe or window.open etc.
if you are trying to do something like this, this should be an easy trick 😉.
If you want to use jQuery File Download , please note this for IE.
You need to reset the response or it will not download
//The IE will only work if you reset response
getServletResponse().reset();
//The jquery.fileDownload needs a cookie be set
getServletResponse().setHeader("Set-Cookie", "fileDownload=true; path=/");
//Do the reset of your action create InputStream and return
Your action can implement ServletResponseAware to access getServletResponse()
It is certain that you can not do it through Ajax call.
However, there is a workaround.
Steps :
If you are using form.submit() for downloading the file, what you can do is :
Create an ajax call from client to server and store the file stream inside the session.
Upon "success" being returned from server, call your form.submit() to just stream the file stream stored in the session.
This is helpful in case when you want to decide whether or not file needs to be downloaded after making form.submit(), eg: there can be a case where on form.submit(), an exception occurs on the server side and instead of crashing, you might need to show a custom message on the client side, in such case this implementation might help.
there is another solution to download a web page in ajax. But I am referring to a page that must first be processed and then downloaded.
First you need to separate the page processing from the results download.
1) Only the page calculations are made in the ajax call.
$.post("CalculusPage.php", { calculusFunction: true, ID: 29, data1: "a", data2: "b" },
function(data, status)
{
if (status == "success")
{
/* 2) In the answer the page that uses the previous calculations is downloaded. For example, this can be a page that prints the results of a table calculated in the ajax call. */
window.location.href = DownloadPage.php+"?ID="+29;
}
}
);
// For example: in the CalculusPage.php
if ( !empty($_POST["calculusFunction"]) )
{
$ID = $_POST["ID"];
$query = "INSERT INTO ExamplePage (data1, data2) VALUES ('".$_POST["data1"]."', '".$_POST["data2"]."') WHERE id = ".$ID;
...
}
// For example: in the DownloadPage.php
$ID = $_GET["ID"];
$sede = "SELECT * FROM ExamplePage WHERE id = ".$ID;
...
$filename="Export_Data.xls";
header("Content-Type: application/vnd.ms-excel");
header("Content-Disposition: inline; filename=$filename");
...
I hope this solution can be useful for many, as it was for me.
That's it works so fine in any browser (I'm using asp.net core)
function onDownload() {
const api = '#Url.Action("myaction", "mycontroller")';
var form = new FormData(document.getElementById('form1'));
fetch(api, { body: form, method: "POST"})
.then(resp => resp.blob())
.then(blob => {
const url = window.URL.createObjectURL(blob);
$('#linkdownload').attr('download', 'Attachement.zip');
$('#linkdownload').attr("href", url);
$('#linkdownload')
.fadeIn(3000,
function() { });
})
.catch(() => alert('An error occurred'));
}
<button type="button" onclick="onDownload()" class="btn btn-primary btn-sm">Click to Process Files</button>
<a role="button" href="#" style="display: none" class="btn btn-sm btn-secondary" id="linkdownload">Click to download Attachments</a>
<form asp-controller="mycontroller" asp-action="myaction" id="form1"></form>
function onDownload() {
const api = '#Url.Action("myaction", "mycontroller")';
//form1 is your id form, and to get data content of form
var form = new FormData(document.getElementById('form1'));
fetch(api, { body: form, method: "POST"})
.then(resp => resp.blob())
.then(blob => {
const url = window.URL.createObjectURL(blob);
$('#linkdownload').attr('download', 'Attachments.zip');
$('#linkdownload').attr("href", url);
$('#linkdownload')
.fadeIn(3000,
function() {
});
})
.catch(() => alert('An error occurred'));
}
I struggled with this issue for a long time. Finally an elegant external library suggested here helped me out.

Why is this file downloading code silently failing? [duplicate]

I have a large(ish) form in MVC.
I need to be able to generate an excel file containing data from a subset of that form.
The tricky bit is that this shouldn't affect the rest of the form and so I want to do it via AJAX. I've come across a few questions on SO that seem to be related, but I can't quite work out what the answers mean.
This one seems the closest to what I'm after: asp-net-mvc-downloading-excel - but I'm not sure I understand the response, and it is a couple years old now. I also came across another article (can't find it anymore) about using an iframe to handle the file download, but I'm not sure how to get this working with MVC.
My excel file returns fine if I'm doing a full post back but I can't get it working with AJAX in mvc.
You can't directly return a file for download via an AJAX call so, an alternative approach is to to use an AJAX call to post the related data to your server. You can then use server side code to create the Excel File (I would recommend using EPPlus or NPOI for this although it sounds as if you have this part working).
UPDATE September 2016
My original answer (below) was over 3 years old, so I thought I would update as I no longer create files on the server when downloading files via AJAX however, I have left the original answer as it may be of some use still depending on your specific requirements.
A common scenario in my MVC applications is reporting via a web page that has some user configured report parameters (Date Ranges, Filters etc.). When the user has specified the parameters they post them to the server, the report is generated (say for example an Excel file as output) and then I store the resulting file as a byte array in the TempData bucket with a unique reference. This reference is passed back as a Json Result to my AJAX function that subsequently redirects to separate controller action to extract the data from TempData and download to the end users browser.
To give this more detail, assuming you have a MVC View that has a form bound to a Model class, lets call the Model ReportVM.
First, a controller action is required to receive the posted model, an example would be:
public ActionResult PostReportPartial(ReportVM model){
// Validate the Model is correct and contains valid data
// Generate your report output based on the model parameters
// This can be an Excel, PDF, Word file - whatever you need.
// As an example lets assume we've generated an EPPlus ExcelPackage
ExcelPackage workbook = new ExcelPackage();
// Do something to populate your workbook
// Generate a new unique identifier against which the file can be stored
string handle = Guid.NewGuid().ToString();
using(MemoryStream memoryStream = new MemoryStream()){
workbook.SaveAs(memoryStream);
memoryStream.Position = 0;
TempData[handle] = memoryStream.ToArray();
}
// Note we are returning a filename as well as the handle
return new JsonResult() {
Data = new { FileGuid = handle, FileName = "TestReportOutput.xlsx" }
};
}
The AJAX call that posts my MVC form to the above controller and receives the response looks like this:
$ajax({
cache: false,
url: '/Report/PostReportPartial',
data: _form.serialize(),
success: function (data){
var response = JSON.parse(data);
window.location = '/Report/Download?fileGuid=' + response.FileGuid
+ '&filename=' + response.FileName;
}
})
The controller action to handle the downloading of the file:
[HttpGet]
public virtual ActionResult Download(string fileGuid, string fileName)
{
if(TempData[fileGuid] != null){
byte[] data = TempData[fileGuid] as byte[];
return File(data, "application/vnd.ms-excel", fileName);
}
else{
// Problem - Log the error, generate a blank file,
// redirect to another controller action - whatever fits with your application
return new EmptyResult();
}
}
One other change that could easily be accommodated if required is to pass the MIME Type of the file as a third parameter so that the one Controller action could correctly serve a variety of output file formats.
This removes any need for any physical files to created and stored on the server, so no housekeeping routines required and once again this is seamless to the end user.
Note, the advantage of using TempData rather than Session is that once TempData is read the data is cleared so it will be more efficient in terms of memory usage if you have a high volume of file requests. See TempData Best Practice.
ORIGINAL Answer
You can't directly return a file for download via an AJAX call so, an alternative approach is to to use an AJAX call to post the related data to your server. You can then use server side code to create the Excel File (I would recommend using EPPlus or NPOI for this although it sounds as if you have this part working).
Once the file has been created on the server pass back the path to the file (or just the filename) as the return value to your AJAX call and then set the JavaScript window.location to this URL which will prompt the browser to download the file.
From the end users perspective, the file download operation is seamless as they never leave the page on which the request originates.
Below is a simple contrived example of an ajax call to achieve this:
$.ajax({
type: 'POST',
url: '/Reports/ExportMyData',
data: '{ "dataprop1": "test", "dataprop2" : "test2" }',
contentType: 'application/json; charset=utf-8',
dataType: 'json',
success: function (returnValue) {
window.location = '/Reports/Download?file=' + returnValue;
}
});
url parameter is the Controller/Action method where your code will create the Excel file.
data parameter contains the json data that would be extracted from the form.
returnValue would be the file name of your newly created Excel file.
The window.location command redirects to the Controller/Action method that actually returns your file for download.
A sample controller method for the Download action would be:
[HttpGet]
public virtual ActionResult Download(string file)
{
string fullPath = Path.Combine(Server.MapPath("~/MyFiles"), file);
return File(fullPath, "application/vnd.ms-excel", file);
}
My 2 cents - you don't need to store the excel as a physical file on the server - instead, store it in the (Session) Cache. Use a uniquely generated name for your Cache variable (that stores that excel file) - this will be the return of your (initial) ajax call. This way you don't have to deal with file access issues, managing (deleting) the files when not needed, etc. and, having the file in the Cache, is faster to retrieve it.
I was recently able to accomplish this in MVC (although there was no need to use AJAX) without creating a physical file and thought I'd share my code:
Super simple JavaScript function (datatables.net button click triggers this):
function getWinnersExcel(drawingId) {
window.location = "/drawing/drawingwinnersexcel?drawingid=" + drawingId;
}
C# Controller code:
public FileResult DrawingWinnersExcel(int drawingId)
{
MemoryStream stream = new MemoryStream(); // cleaned up automatically by MVC
List<DrawingWinner> winnerList = DrawingDataAccess.GetWinners(drawingId); // simple entity framework-based data retrieval
ExportHelper.GetWinnersAsExcelMemoryStream(stream, winnerList, drawingId);
string suggestedFilename = string.Format("Drawing_{0}_Winners.xlsx", drawingId);
return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml", suggestedFilename);
}
In the ExportHelper class I do use a 3rd party tool (GemBox.Spreadsheet) to generate the Excel file and it has a Save to Stream option. That being said, there are a number of ways to create Excel files that can easily be written to a memory stream.
public static class ExportHelper
{
internal static void GetWinnersAsExcelMemoryStream(MemoryStream stream, List<DrawingWinner> winnerList, int drawingId)
{
ExcelFile ef = new ExcelFile();
// lots of excel worksheet building/formatting code here ...
ef.SaveXlsx(stream);
stream.Position = 0; // reset for future read
}
}
In IE, Chrome, and Firefox, the browser prompts to download the file and no actual navigation occurs.
First Create the controller action that will create the Excel File
[HttpPost]
public JsonResult ExportExcel()
{
DataTable dt = DataService.GetData();
var fileName = "Excel_" + DateTime.Now.ToString("yyyyMMddHHmm") + ".xls";
//save the file to server temp folder
string fullPath = Path.Combine(Server.MapPath("~/temp"), fileName);
using (var exportData = new MemoryStream())
{
//I don't show the detail how to create the Excel, this is not the point of this article,
//I just use the NPOI for Excel handler
Utility.WriteDataTableToExcel(dt, ".xls", exportData);
FileStream file = new FileStream(fullPath, FileMode.Create, FileAccess.Write);
exportData.WriteTo(file);
file.Close();
}
var errorMessage = "you can return the errors in here!";
//return the Excel file name
return Json(new { fileName = fileName, errorMessage = "" });
}
then create the Download action
[HttpGet]
[DeleteFileAttribute] //Action Filter, it will auto delete the file after download,
//I will explain it later
public ActionResult Download(string file)
{
//get the temp folder and file path in server
string fullPath = Path.Combine(Server.MapPath("~/temp"), file);
//return the file for download, this is an Excel
//so I set the file content type to "application/vnd.ms-excel"
return File(fullPath, "application/vnd.ms-excel", file);
}
if you want to delete the file after downloaded create this
public class DeleteFileAttribute : ActionFilterAttribute
{
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
filterContext.HttpContext.Response.Flush();
//convert the current filter context to file and get the file path
string filePath = (filterContext.Result as FilePathResult).FileName;
//delete the file after download
System.IO.File.Delete(filePath);
}
}
and finally ajax call from you MVC Razor view
//I use blockUI for loading...
$.blockUI({ message: '<h3>Please wait a moment...</h3>' });
$.ajax({
type: "POST",
url: '#Url.Action("ExportExcel","YourController")', //call your controller and action
contentType: "application/json; charset=utf-8",
dataType: "json",
}).done(function (data) {
//console.log(data.result);
$.unblockUI();
//get the file name for download
if (data.fileName != "") {
//use window.location.href for redirect to download action for download the file
window.location.href = "#Url.RouteUrl(new
{ Controller = "YourController", Action = "Download"})/?file=" + data.fileName;
}
});
I used the solution posted by CSL but I would recommend you dont store the file data in Session during the whole session. By using TempData the file data is automatically removed after the next request (which is the GET request for the file). You could also manage removal of the file data in Session in download action.
Session could consume much memory/space depending on SessionState storage and how many files are exported during the session and if you have many users.
I've updated the serer side code from CSL to use TempData instead.
public ActionResult PostReportPartial(ReportVM model){
// Validate the Model is correct and contains valid data
// Generate your report output based on the model parameters
// This can be an Excel, PDF, Word file - whatever you need.
// As an example lets assume we've generated an EPPlus ExcelPackage
ExcelPackage workbook = new ExcelPackage();
// Do something to populate your workbook
// Generate a new unique identifier against which the file can be stored
string handle = Guid.NewGuid().ToString()
using(MemoryStream memoryStream = new MemoryStream()){
workbook.SaveAs(memoryStream);
memoryStream.Position = 0;
TempData[handle] = memoryStream.ToArray();
}
// Note we are returning a filename as well as the handle
return new JsonResult() {
Data = new { FileGuid = handle, FileName = "TestReportOutput.xlsx" }
};
}
[HttpGet]
public virtual ActionResult Download(string fileGuid, string fileName)
{
if(TempData[fileGuid] != null){
byte[] data = TempData[fileGuid] as byte[];
return File(data, "application/vnd.ms-excel", fileName);
}
else{
// Problem - Log the error, generate a blank file,
// redirect to another controller action - whatever fits with your application
return new EmptyResult();
}
}
using ClosedXML.Excel;
public ActionResult Downloadexcel()
{
var Emplist = JsonConvert.SerializeObject(dbcontext.Employees.ToList());
DataTable dt11 = (DataTable)JsonConvert.DeserializeObject(Emplist, (typeof(DataTable)));
dt11.TableName = "Emptbl";
FileContentResult robj;
using (XLWorkbook wb = new XLWorkbook())
{
wb.Worksheets.Add(dt11);
using (MemoryStream stream = new MemoryStream())
{
wb.SaveAs(stream);
var bytesdata = File(stream.ToArray(), "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "myFileName.xlsx");
robj = bytesdata;
}
}
return Json(robj, JsonRequestBehavior.AllowGet);
}
$.ajax({
type: "GET",
url: "/Home/Downloadexcel/",
contentType: "application/json; charset=utf-8",
data: null,
success: function (Rdata) {
debugger;
var bytes = new Uint8Array(Rdata.FileContents);
var blob = new Blob([bytes], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" });
var link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = "myFileName.xlsx";
link.click();
},
error: function (err) {
}
});
The accepted answer didn't quite work for me as I got a 502 Bad Gateway result from the ajax call even though everything seemed to be returning fine from the controller.
Perhaps I was hitting a limit with TempData - not sure, but I found that if I used IMemoryCache instead of TempData, it worked fine, so here is my adapted version of the code in the accepted answer:
public ActionResult PostReportPartial(ReportVM model){
// Validate the Model is correct and contains valid data
// Generate your report output based on the model parameters
// This can be an Excel, PDF, Word file - whatever you need.
// As an example lets assume we've generated an EPPlus ExcelPackage
ExcelPackage workbook = new ExcelPackage();
// Do something to populate your workbook
// Generate a new unique identifier against which the file can be stored
string handle = Guid.NewGuid().ToString();
using(MemoryStream memoryStream = new MemoryStream()){
workbook.SaveAs(memoryStream);
memoryStream.Position = 0;
//TempData[handle] = memoryStream.ToArray();
//This is an equivalent to tempdata, but requires manual cleanup
_cache.Set(handle, memoryStream.ToArray(),
new MemoryCacheEntryOptions().SetSlidingExpiration(TimeSpan.FromMinutes(10)));
//(I'd recommend you revise the expiration specifics to suit your application)
}
// Note we are returning a filename as well as the handle
return new JsonResult() {
Data = new { FileGuid = handle, FileName = "TestReportOutput.xlsx" }
};
}
AJAX call remains as with the accepted answer (I made no changes):
$ajax({
cache: false,
url: '/Report/PostReportPartial',
data: _form.serialize(),
success: function (data){
var response = JSON.parse(data);
window.location = '/Report/Download?fileGuid=' + response.FileGuid
+ '&filename=' + response.FileName;
}
})
The controller action to handle the downloading of the file:
[HttpGet]
public virtual ActionResult Download(string fileGuid, string fileName)
{
if (_cache.Get<byte[]>(fileGuid) != null)
{
byte[] data = _cache.Get<byte[]>(fileGuid);
_cache.Remove(fileGuid); //cleanup here as we don't need it in cache anymore
return File(data, "application/vnd.ms-excel", fileName);
}
else
{
// Something has gone wrong...
return View("Error"); // or whatever/wherever you want to return the user
}
}
...
Now there is some extra code for setting up MemoryCache...
In order to use "_cache" I injected in the constructor for the controller like so:
using Microsoft.Extensions.Caching.Memory;
namespace MySolution.Project.Controllers
{
public class MyController : Controller
{
private readonly IMemoryCache _cache;
public LogController(IMemoryCache cache)
{
_cache = cache;
}
//rest of controller code here
}
}
And make sure you have the following in ConfigureServices in Startup.cs:
services.AddDistributedMemoryCache();
$.ajax({
global: false,
url: SitePath + "/User/ExportTeamMembersInExcel",
"data": { 'UserName': UserName, 'RoleId': RoleId, UserIds: AppraseeId },
"type": "POST",
"dataType": "JSON",
"success": function (result) {
var bytes = new Uint8Array(result.FileContents);
var blob = new Blob([bytes], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" });
var link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = "myFileName.xlsx";
link.click();
},
"error": function () {
alert("error");
}
})
[HttpPost]
public JsonResult ExportTeamMembersInExcel(string UserName, long? RoleId, string[] UserIds)
{
MemoryStream stream = new MemoryStream();
FileContentResult robj;
DataTable data = objuserservice.ExportTeamToExcel(UserName, RoleId, UserIds);
using (XLWorkbook wb = new XLWorkbook())
{
wb.Worksheets.Add(data, "TeamMembers");
using (stream)
{
wb.SaveAs(stream);
}
}
robj = File(stream.ToArray(), System.Net.Mime.MediaTypeNames.Application.Octet, "TeamMembers.xlsx");
return Json(robj, JsonRequestBehavior.AllowGet);
}
I may sound quite naive, and may attract quite a criticism, but here's how I did it,
(It doesn't involve ajax for export, but it doesn't do a full postback either )
Thanks for this post and this answer.
Create a simple controller
public class HomeController : Controller
{
/* A demo action
public ActionResult Index()
{
return View(model);
}
*/
[HttpPost]
public FileResult ExportData()
{
/* An example filter
var filter = TempData["filterKeys"] as MyFilter;
TempData.Keep(); */
var someList = db.GetDataFromDb(/*filter*/) // filter as an example
/*May be here's the trick, I'm setting my filter in TempData["filterKeys"]
in an action,(GetFilteredPartial() illustrated below) when 'searching' for the data,
so do not really need ajax here..to pass my filters.. */
//Some utility to convert list to Datatable
var dt = Utility.ConvertToDataTable(someList);
// I am using EPPlus nuget package
using (ExcelPackage pck = new ExcelPackage())
{
ExcelWorksheet ws = pck.Workbook.Worksheets.Add("Sheet1");
ws.Cells["A1"].LoadFromDataTable(dt, true);
using (var memoryStream = new MemoryStream())
{
pck.SaveAs(memoryStream);
return File(memoryStream.ToArray(),
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"ExportFileName.xlsx");
}
}
}
//This is just a supporting example to illustrate setting up filters ..
/* [HttpPost]
public PartialViewResult GetFilteredPartial(MyFilter filter)
{
TempData["filterKeys"] = filter;
var filteredData = db.GetConcernedData(filter);
var model = new MainViewModel();
model.PartialViewModel = filteredData;
return PartialView("_SomePartialView", model);
} */
}
And here are the Views..
/*Commenting out the View code, in order to focus on the imp. code
#model Models.MainViewModel
#{Layout...}
Some code for, say, a partial View
<div id="tblSampleBody">
#Html.Partial("_SomePartialView", Model.PartialViewModel)
</div>
*/
//The actual part.. Just **posting** this bit of data from the complete View...
//Here, you are not posting the full Form..or the complete View
#using (Html.BeginForm("ExportData", "Home", FormMethod.Post))
{
<input type="submit" value="Export Data" />
}
//...
//</div>
/*And you may require to pass search/filter values.. as said in the accepted answer..
That can be done while 'searching' the data.. and not while
we need an export..for instance:-
<script>
var filterData = {
SkipCount: someValue,
TakeCount: 20,
UserName: $("#UserName").val(),
DepartmentId: $("#DepartmentId").val(),
}
function GetFilteredData() {
$("#loader").show();
filterData.SkipCount = 0;
$.ajax({
url: '#Url.Action("GetFilteredPartial","Home")',
type: 'POST',
dataType: "html",
data: filterData,
success: function (dataHTML) {
if ((dataHTML === null) || (dataHTML == "")) {
$("#tblSampleBody").html('<tr><td>No Data Returned</td></tr>');
$("#loader").hide();
} else {
$("#tblSampleBody").html(dataHTML);
$("#loader").hide();
}
}
});
}
</script>*/
The whole point of the trick seems that, we are posting a form (a part of the Razor View ) upon which we are calling an Action method, which returns: a FileResult, and this FileResult returns the Excel File..
And for posting the filter values, as said, ( and if you require to), I am making a post request to another action, as has been attempted to describe..
This thread helped me create my own solution that I will share here. I was using a GET ajax request at first without issues but it got to a point where the request URL length was exceeded so I had to swith to a POST.
The javascript uses JQuery file download plugin and consists of 2 succeeding calls. One POST (To send params) and one GET to retreive the file.
function download(result) {
$.fileDownload(uri + "?guid=" + result,
{
successCallback: onSuccess.bind(this),
failCallback: onFail.bind(this)
});
}
var uri = BASE_EXPORT_METADATA_URL;
var data = createExportationData.call(this);
$.ajax({
url: uri,
type: 'POST',
contentType: 'application/json',
data: JSON.stringify(data),
success: download.bind(this),
fail: onFail.bind(this)
});
Server side
[HttpPost]
public string MassExportDocuments(MassExportDocumentsInput input)
{
// Save query for file download use
var guid = Guid.NewGuid();
HttpContext.Current.Cache.Insert(guid.ToString(), input, null, DateTime.Now.AddMinutes(5), Cache.NoSlidingExpiration);
return guid.ToString();
}
[HttpGet]
public async Task<HttpResponseMessage> MassExportDocuments([FromUri] Guid guid)
{
//Get params from cache, generate and return
var model = (MassExportDocumentsInput)HttpContext.Current.Cache[guid.ToString()];
..... // Document generation
// to determine when file is downloaded
HttpContext.Current
.Response
.SetCookie(new HttpCookie("fileDownload", "true") { Path = "/" });
return FileResult(memoryStream, "documents.zip", "application/zip");
}
CSL's answer was implemented in a project I'm working on but the problem I incurred was scaling out on Azure broke our file downloads. Instead, I was able to do this with one AJAX call:
SERVER
[HttpPost]
public FileResult DownloadInvoice(int id1, int id2)
{
//necessary to get the filename in the success of the ajax callback
HttpContext.Response.Headers.Add("Access-Control-Expose-Headers", "Content-Disposition");
byte[] fileBytes = _service.GetInvoice(id1, id2);
string fileName = "Invoice.xlsx";
return File(fileBytes, System.Net.Mime.MediaTypeNames.Application.Octet, fileName);
}
CLIENT
(modified version of Handle file download from ajax post)
$("#downloadInvoice").on("click", function() {
$("#loaderInvoice").removeClass("d-none");
var xhr = new XMLHttpRequest();
var params = [];
xhr.open('POST', "#Html.Raw(Url.Action("DownloadInvoice", "Controller", new { id1 = Model.Id1, id2 = Model.Id2 }))", true);
xhr.responseType = 'arraybuffer';
xhr.onload = function () {
if (this.status === 200) {
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, '');
}
var type = xhr.getResponseHeader('Content-Type');
var blob = typeof File === 'function'
? new File([this.response], filename, { type: type })
: new Blob([this.response], { type: type });
if (typeof window.navigator.msSaveBlob !== 'undefined') {
// IE workaround for "HTML7007: One or more blob URLs were revoked by closing the blob for which they were created. These URLs will no longer resolve as the data backing the URL has been freed."
window.navigator.msSaveBlob(blob, filename);
} else {
var URL = window.URL || window.webkitURL;
var downloadUrl = URL.createObjectURL(blob);
if (filename) {
// use HTML5 a[download] attribute to specify filename
var a = document.createElement("a");
// safari doesn't support this yet
if (typeof a.download === 'undefined') {
window.location = downloadUrl;
} else {
a.href = downloadUrl;
a.download = filename;
document.body.appendChild(a);
a.click();
}
} else {
window.location = downloadUrl;
}
setTimeout(function() {
URL.revokeObjectURL(downloadUrl);
$("#loaderInvoice").addClass("d-none");
}, 100); // cleanup
}
}
};
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.send($.param(params));
});
This works for me. Make sure you return a File from your controller action with contentType as "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" and file name as e.g. "List.xlsx" which should be the same as in the AJAX success call. I have used ClosedXML NuGet package to generate the excel file.
$.ajax({
url: "Home/Export",
type: 'GET',
contentType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
xhrFields: { responseType: 'blob' },
success: function (data) {
var a = document.createElement('a');
var url = window.URL.createObjectURL(data);
a.href = url;
a.download = 'List.xlsx';
a.click();
window.URL.revokeObjectURL(url);
}
});
I am using Asp.Net WebForm and just I wanna to download a file from server side. There is a lot article but I cannot find just basic answer.
Now, I tried a basic way and got it.
That's my problem.
I have to create a lot of input button dynamically on runtime. And I want to add each button to download button with giving an unique fileNumber.
I create each button like this:
fragment += "<div><input type=\"button\" value=\"Create Excel\" onclick=\"CreateExcelFile(" + fileNumber + ");\" /></div>";
Each button call this ajax method.
$.ajax({
type: 'POST',
url: 'index.aspx/CreateExcelFile',
data: jsonData,
contentType: 'application/json; charset=utf-8',
dataType: 'json',
success: function (returnValue) {
window.location = '/Reports/Downloads/' + returnValue.d;
}
});
Then I wrote a basic simple method.
[WebMethod]
public static string CreateExcelFile2(string fileNumber)
{
string filePath = string.Format(#"Form_{0}.xlsx", fileNumber);
return filePath;
}
I am generating this Form_1, Form_2, Form_3.... And I am going to delete this old files with another program. But if there is a way to just sending byte array to download file like using Response. I wanna to use it.
I hope this will be usefull for anyone.
On Submit form
public ActionResult ExportXls()
{
var filePath="";
CommonHelper.WriteXls(filePath, "Text.xls");
}
public static void WriteXls(string filePath, string targetFileName)
{
if (!String.IsNullOrEmpty(filePath))
{
HttpResponse response = HttpContext.Current.Response;
response.Clear();
response.Charset = "utf-8";
response.ContentType = "text/xls";
response.AddHeader("content-disposition", string.Format("attachment; filename={0}", targetFileName));
response.BinaryWrite(File.ReadAllBytes(filePath));
response.End();
}
}

How to upload files to a Web API server and send parameters along to the action?

Good day!
I am working on a ASP.NET Web API 2 project. At a certain point there is a need to upload some files. The files need to be linked to a certain FileModel (our own class). So, the client needs to send IEnumerable as parameter and the files as content. Because it's a RESTful API, both have to be sent in the same request.
The best we could come up with is the follow Controller action:
public async Task<HttpResponseMessage> Add([FromUri] IEnumerable<FileModel> fileModels)
{
// REQUEST INTEGRITY TESTING
var streamProvider = new CustomMultipartFormDataStreamProvider(fileSavePath, fileModels);
// Read the MIME multipart content using the stream provider we just created.
var work = await Request.Content.ReadAsMultipartAsync(streamProvider).ContinueWith(async t =>
{
// SOME WORK DONE AFTER SAVING THE FILES TO THE HARD DRIVE
}
}
The problem is following: the files are being uploaded with a 'multipart/form-data' Content-Type header. We need to know the contents of the FileModels before manipulating the files on the server side. If we use the MultipartFormDataStreamProvider, we can only access the non file parameters after the files have already been saved to the hard drive.
The only workaround for this we could find is to send the IEnumerable< FileModel > parameter in the URL. But given that the URL has a limited max length, this is not a reliable method.
The question is: Is there a way to submit both the IEnumerable< FileModel > fileModels parameter and the files in the body of the request and get access to the fileModels parameter before accessing the files? We also want to be able to use HttpContext.Current.Request.Files.Count;
Our current jQuery for file upload looks like this (for early testing purposes, it only supports one file upload):
$('#Upload').click(function(e) {
e.preventDefault();
var headers = new Array();
headers["SessionId"] = sessionId;
var files = $('#fileInput').get(0).files;
var formData = new FormData();
formData.append("files", files[0]);
var fileModel = $('#fileSubmission').serialize();
$.ajax({
url: "api/Submissions/Add/?" + fileModel,
headers: headers,
type: 'POST',
data: formData,
cache: false,
contentType: false,
processData: false,
dataType: 'json'
});
});
Thank you very much!
I am sorry for the late answer, but we solved the problem (I forgot that I didn't upload the answer here). Basically what we did is that we called the ReadAsMultiPartAsync method on a temporary location and then we extracted the other parameters from the request. Afterwards, we validated the input and moved the files from the temporary to the permanent location.
If you want to see the code, this is what worked for our particular example and I believe it's pretty straight-forward to adapt to any work case scenario:
On the client side, we have the following form (yes, this implementation is for demo purposes and only supports sending one file ... also, the input type="file" field is indeed outside of the form; the fileId text input is completed manually in our case, just for testing purposes)
<input type="file" name="data" id="fileInput" multiple="multiple" />
<form id="fileSubmission">
<input type="text" width="10" onchange="getFileDetails()" autocomplete="off" placeholder="FileId" name="files[0].Id" id="fileId" />
<input type="hidden" name="files[0].FileName" id="FileName"/>
<input type="hidden" name="files[0].Extension" id="Extension"/>
<input type="hidden" name="files[0].EntityId" id="EntityId"/>
<br /><br />
<input type="submit" id="Upload" value="Upload" />
</form>
where getFileDetails() populates the other input fields. Also, the form is being sent to the server by using the following jQuery/Javascript:
$('#Upload').click(function(e) {
e.preventDefault();
var courseId = $('#courseId').val();
var fileId = $('#fileId').val();
if (!courseId || !fileId) {
return;
}
var headers = new Array();
headers["SessionId"] = sessionId;
headers["contentType"] = "application/json; charset=UTF-8";
var formData = new FormData();
var opmlFile = $('#fileInput').get(0).files;
// this is like the model we're expecting on the server
var files = [];
files.push({ 'Id': $('#fileId').val(), 'OriginalFileName': opmlFile[0].name, 'FileName': $('#FileName').val(), 'Extension': $('#Extension').val(), 'EntityId': $('#EntityId').val() });
formData.append("fileModels", JSON.stringify(files));
formData.append("File_0", opmlFile[0]);
$.ajax({
url: "api/Courses/" + courseId + "/Submissions/Add/",
headers: headers,
type: 'POST',
data: formData,
cache: false,
contentType: false,
processData: false,
dataType: 'json'
});
});
On the server side, we have the following :
// POST: api/Courses/{courseId}/Submissions/Add
[HttpPost]
[ValidateModelState]
[ValidateMimeMultipartContent]
[PermissionsAuthorize(CoursePermissions.CanCreateSubmissions)]
public async Task<HttpResponseMessage> Add(int courseId)
{
// the same as in the jQuery part
const string paramName = "fileModels";
// Put the files in a temporary location
// this way we call ReadAsMultiPartAsync and we get access to the other data submitted
var tempPath = HttpContext.Current.Server.MapPath("~/App_Data/Temp/" + Guid.NewGuid());
Directory.CreateDirectory(tempPath);
var streamProvider = new MultipartFormDataStreamProvider(tempPath);
var readResult = await Request.Content.ReadAsMultipartAsync(streamProvider);
if (readResult.FormData[paramName] == null)
{
// We don't have the FileModels ... delete the TempFiles and return BadRequest
Directory.Delete(tempPath, true);
return Request.CreateResponse(HttpStatusCode.BadRequest);
}
// The files have been successfully saved in a TempLocation and the FileModels are not null
// Validate that everything else is fine with this command
var fileModels = JsonConvert.DeserializeObject<IEnumerable<FileModelExtension>>(readResult.FormData[paramName]).ToList();
// AT THIS POINT, ON THE SERVER, WE HAVE ALL THE FILE MODELS
// AND ALL THE FILES ARE SAVED IN A TEMPORARY LOCATION
// NEXT STEPS ARE VALIDATION OF THE INPUT AND THEN
// MOVING THE FILE FROM THE TEMP TO THE PERMANENT LOCATION
// YOU CAN ACCESS THE INFO ABOUT THE FILES LIKE THIS:
foreach (var tempFile in readResult.FileData)
{
var originalFileName = tempFile.Headers.ContentDisposition.FileName.Replace("\"", string.Empty);
var localTempPath = tempFile.LocalFileName;
}
}
I hope this will help anyone out there trying to submit files and other parameters at once to the server by using Post requests! :)
NOTE: Some of the attributes used on the server are custom. PermissionAuthorize, ValidateModelState and ValidateMimeMultiPartContent are custom Filters we have used. The implementation of the latter two has been inspired by http://benfoster.io/blog/automatic-modelstate-validation-in-aspnet-mvc
The multipartcontent attribute just does a check on actionContext.Request.Content.IsMimeMultipartContent(), like this:
public class ValidateMimeMultipartContent : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (!actionContext.Request.Content.IsMimeMultipartContent())
{
actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.UnsupportedMediaType, Messages.UnsupportedMediaType);
}
}
}

How to implement file download with AJAX and MVC

I would like to provide a file download operation by using the jQuery AJAX call with some params under MVC
Example
(javascript)
function DoDownload(startDate) {
$.ajax({
url:"controller/GetFile/",
data: {startDate:startDate}
...
});
}
C# Controller Code
public void GetFile(string startDate) {
var results = doQueryWith(startDate);
// Create file based on results
....
// How do I tell the server to make this a file download??
}
I typically would just make my file download a link such as:
<a h r e f="mycontroller/getfile/1"/>Download</a>
but in the case above the date will be dynamic.
If I don't use ajax, what would be a preferred way to pass in the params to the MVC controller using javascript?
Example:
window.location = "mycontroller/GetFile/" + $("#fromDate").val();
assuming the date is 12-25-2012
Would this produce
mycontroller/GetFile/12/25/2012
would MVC treat this as three params?
What I ended up doing is calling my controller from my javascript like:
var url = "/mycontroller/GetFile?startDate=" + $("#mydate").val() + etc...
window.location = url;
mycontroller.cs
public void GetFile(DateTime startDate)
{
}
My original concern was with the date parameters. I didnt want to have to parse it.
Using the ActionLink helper, you can pass multiple params to your controller:
HtmlHelper.ActionLink(
string linkText,
string actionName,
string controllerName,
object routeValues,
object htmlAttributes
)
So in your case:
#Html.ActionLink("Download file", "GetFile", "MyController", new { startDate = "##" }, new { id="mydownloadlink" })
Using jQuery you can change the value of the startDate in the link with the content of your date picker or textbox.
$("#mydownloadlink").attr("href").replace("##", $("#yourdatetexbox").val);
Then, in your controller, just use one of the other answers here, about FileResult.
Hope this help you...
You can use the File method of controller class to return a file back to the browser.
The below sample returns a pdf file.
public ActionResult GetFile(int id)
{
var fileInfo=repositary.GetFileDedetails(id);
var byteArrayOFFile=fileInfo.FileContentAsByteArray();
return File(byteArrayOFFile,"application/pdf","yourFriendlyName.pdf");
}
Assuming repositary.GetFileDedetails method returns the details of the file from the id.
You may also return the file from a physical location(a path) or a stream. Check all the overloads of the File method and use appropriate one.
This has nothing to do with ajax. this is normal GET request over a browser.
Your controller action method should return a FileResult instead of void. And there is no need to do this via AJAX - in fact, you don't want to do this with AJAX. You'll need the browser involved so it knows to provide a download dialog for the user.
See these links:
Handling an ASP.NET MVC FileResult returned in an (jQuery) Ajax call
File download in Asp.Net MVC 2
I hope this helps.
This works for me. Make sure you return a File from your controller action with contentType as "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" and file name as e.g. "List.xlsx" which should be the same as in the AJAX success call. I have used ClosedXML NuGet package to generate the excel file.
public IActionResult GetFile(DateTime startDate)
{
var results = doQueryWith(startDate);
DataTable dt = new DataTable("Grid");
//populate dt here.
//if you result is a data table you can assign it to dt
dt = results;
string contentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
string fileName = "List.xlsx";
using (var workbook = new XLWorkbook())
{
workbook.Worksheets.Add(dt);
using (var stream = new MemoryStream())
{
workbook.SaveAs(stream);
workbook.SaveAs(stream);
var content = stream.ToArray();
return File(content, contentType, fileName);
}
}
}
//.cshtml (JQuery AJAX call to the controller action)
$.ajax({
url:"ControllerName/GetFile/",
data: {startDate:startDate}
contentType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
xhrFields: { responseType: 'blob' },
success: function (data) {
var a = document.createElement('a');
var url = window.URL.createObjectURL(data);
a.href = url;
a.download = 'List.xlsx';
a.click();
window.URL.revokeObjectURL(url);
}
});

Categories

Resources