我有一个基于jQuery的单页web应用。 它通过AJAX调用RESTful Web服务进行通信。
我试图完成以下任务:
- 提交包含JSON数据的REST URL一个POST。
- 如果所述请求指定的JSON响应,然后JSON被返回。
- 如果请求指定PDF / XLS /等反应,然后可下载的二进制被返回。
我有1&2现在的工作,并在客户端的jQuery应用程序通过创建基于JSON数据的DOM元素显示在网页中返回的数据。 我也有#3从视网络服务点的工作,这意味着它会创建并如果给予正确的JSON参数返回一个二进制文件。 但我不能确定在客户端的JavaScript代码来处理#3的最佳途径。
是否有可能从这样一个Ajax调用得到一个可下载的文件回来? 如何让浏览器下载并保存文件?
$.ajax({
type: "POST",
url: "/services/test",
contentType: "application/json",
data: JSON.stringify({category: 42, sort: 3, type: "pdf"}),
dataType: "json",
success: function(json, status){
if (status != "success") {
log("Error loading data");
return;
}
log("Data loaded!");
},
error: function(result, status, err) {
log("Error loading data");
return;
}
});
服务器与下列头回应:
Content-Disposition:attachment; filename=export-1282022272283.pdf
Content-Length:5120
Content-Type:application/pdf
Server:Jetty(6.1.11)
另一个想法是生成PDF并将其存储在服务器上,并返回JSON包含一个URL文件。 然后,发出在阿贾克斯成功处理程序再次调用执行类似以下内容:
success: function(json,status) {
window.location.href = json.url;
}
但这样做,这意味着我需要让多个呼叫服务器,我的服务器将需要建立下载文件,存储它们的地方,然后定期清理储存区。
必须有完成这个简单的方法。 想法?
编辑:检讨$阿贾克斯的文档后,我看到的响应数据类型都只能是一个xml, html, script, json, jsonp, text
,所以我猜是没有办法使用直接下载文件Ajax请求,除非我嵌入二进制文件中使用数据URI方案作为@VinayC答案建议(这不是我想做的事)。
所以我想我的选择是:
不使用AJAX,而是提交表单后,并嵌入我的JSON数据到表单中值。 可能会需要惹隐藏的iframe也这样。
不使用AJAX,而是我的JSON数据转换成一个查询字符串,以建立一个标准的GET请求,并设置window.location.href这个URL。 可能需要使用event.preventDefault()在我的点击处理程序,以保持浏览器从应用程序URL变化。
使用我的其他想法上面,但是从@naikus答案建议增强。 提交AJAX请求一些参数,使网络服务知道这正在通过Ajax调用调用。 如果Web服务是从一个Ajax调用调用,只需用一个URL来生成的资源返回JSON。 如果资源被直接调用,然后返回实际二进制文件。
我越去想它,我越喜欢最后一个选项。 这样我可以得到信息反馈关于请求(时间来生成,文件大小,错误信息等),我可以开始下载前对信息采取行动。 缺点是额外增加服务器的文件管理。
任何其他的方法来做到这一点? 任何优点/缺点这些方法我应该知道的?
Answer 1:
letronje的解决方案仅适用于非常简单的页面。 document.body.innerHTML +=
需要身体的HTML文本,追加了iframe HTML,并设置页面的innerHTML该字符串。 这将消灭任何事件绑定你的页面有,除其他事项。 创建一个元素,并使用appendChild
来代替。
$.post('/create_binary_file.php', postData, function(retData) {
var iframe = document.createElement("iframe");
iframe.setAttribute("src", retData.url);
iframe.setAttribute("style", "display: none");
document.body.appendChild(iframe);
});
或使用jQuery
$.post('/create_binary_file.php', postData, function(retData) {
$("body").append("<iframe src='" + retData.url+ "' style='display: none;' ></iframe>");
});
这是什么实际执行:执行后,在变量POSTDATA的数据/create_binary_file.php; 如果该职位成功完成,添加一个新的iframe页面的正文。 假设是从/create_binary_file.php响应将包括值“网址”,这是生成的PDF / XLS / etc文件均可以从下载的URL。 添加一个iframe来引用该URL会导致浏览器推动用户下载文件,假设Web服务器有适当的MIME类型配置的页面。
Answer 2:
我一直在玩弄使用斑点另一种选择。 我已经成功地得到它下载的文本文件,我已经下载PDF格式的(不过,他们已损坏)。
使用blob API,你将能够做到以下几点:
$.post(/*...*/,function (result)
{
var blob=new Blob([result]);
var link=document.createElement('a');
link.href=window.URL.createObjectURL(blob);
link.download="myFileName.txt";
link.click();
});
这是IE 10,铬8 +,FF 4+。 见https://developer.mozilla.org/en-US/docs/Web/API/URL.createObjectURL
它只会下载的文件在Chrome,Firefox和Opera。 它使用锚标记上下载属性强制浏览器下载。
Answer 3:
我知道这样的老了,但我想我已经想出了一个更好的解决方案。 我有同样的问题。 这个问题我与建议是他们所需要的所有文件保存在服务器上的解决方案有,但我不想将文件保存在服务器上,因为它引入了其他问题(安全:文件然后可以通过访问非认证的用户,清理:如何以及何时你摆脱的文件)。 和你一样,我的数据是复杂的,嵌套的JSON对象,这将是很难用一种形式。
我所做的就是创建两个服务器的功能。 第一个经过验证的数据。 如果有错误,它会被退回。 如果它是不是一个错误,我返回的所有编码为Base64字符串参数序列化/的。 然后,在客户端上,我有一个只有一个隐藏的输入和帖子到第二服务器功能的形式。 我设置隐藏输入字符串的base64并提交格式。 第二服务器功能进行解码/解串的参数,并生成文件。 该表格可以提交到一个新的窗口或一个iframe页面上,文件将打开。
有参与多一点的工作,也许一点点处理,但总体来说,我觉得这个解决方案要好得多。
代码是C#/ MVC
public JsonResult Validate(int reportId, string format, ReportParamModel[] parameters)
{
// TODO: do validation
if (valid)
{
GenerateParams generateParams = new GenerateParams(reportId, format, parameters);
string data = new EntityBase64Converter<GenerateParams>().ToBase64(generateParams);
return Json(new { State = "Success", Data = data });
}
return Json(new { State = "Error", Data = "Error message" });
}
public ActionResult Generate(string data)
{
GenerateParams generateParams = new EntityBase64Converter<GenerateParams>().ToEntity(data);
// TODO: Generate file
return File(bytes, mimeType);
}
在客户端上
function generate(reportId, format, parameters)
{
var data = {
reportId: reportId,
format: format,
params: params
};
$.ajax(
{
url: "/Validate",
type: 'POST',
data: JSON.stringify(data),
dataType: 'json',
contentType: 'application/json; charset=utf-8',
success: generateComplete
});
}
function generateComplete(result)
{
if (result.State == "Success")
{
// this could/should already be set in the HTML
formGenerate.action = "/Generate";
formGenerate.target = iframeFile;
hidData = result.Data;
formGenerate.submit();
}
else
// TODO: display error messages
}
Answer 4:
有一种simplier方式,创建一个表单,并张贴,这个运行如果返回MIME类型的东西,浏览器会打开重置页面的风险,但对于CSV和这样它的完美
示例需要下划线和jQuery
var postData = {
filename:filename,
filecontent:filecontent
};
var fakeFormHtmlFragment = "<form style='display: none;' method='POST' action='"+SAVEAS_PHP_MODE_URL+"'>";
_.each(postData, function(postValue, postKey){
var escapedKey = postKey.replace("\\", "\\\\").replace("'", "\'");
var escapedValue = postValue.replace("\\", "\\\\").replace("'", "\'");
fakeFormHtmlFragment += "<input type='hidden' name='"+escapedKey+"' value='"+escapedValue+"'>";
});
fakeFormHtmlFragment += "</form>";
$fakeFormDom = $(fakeFormHtmlFragment);
$("body").append($fakeFormDom);
$fakeFormDom.submit();
对于像HTML,文本和这样的,确保MIME类型就像应用程序/八位字节流的一些事情
PHP代码
<?php
/**
* get HTTP POST variable which is a string ?foo=bar
* @param string $param
* @param bool $required
* @return string
*/
function getHTTPPostString ($param, $required = false) {
if(!isset($_POST[$param])) {
if($required) {
echo "required POST param '$param' missing";
exit 1;
} else {
return "";
}
}
return trim($_POST[$param]);
}
$filename = getHTTPPostString("filename", true);
$filecontent = getHTTPPostString("filecontent", true);
header("Content-type: application/octet-stream");
header("Content-Disposition: attachment; filename=\"$filename\"");
echo $filecontent;
Answer 5:
总之,有没有简单的方法。 你需要做的另一台服务器的请求,以显示PDF文件。 虽然铝,有几个选择,但他们并不完美,也不会在所有的浏览器:
- 看看数据URI方案 。 如果二进制数据是小的,那么你也许可以使用JavaScript的URI打开窗口传递数据。
- 在Windows / IE唯一的解决办法是有.NET控件或FileSystemObject的,以节省本地文件系统中的数据,并从那里打开它。
Answer 6:
这是一段时间,因为这个问题被问,但我有同样的挑战,并希望分享我的解决方案。 它使用的元素,从其他的答案,但我没能找到它的全部。 它不使用表单或iframe中,但它确实需要一个帖子/ get请求对。 如果不想保存请求之间的文件,这样可以节省后的数据。 这似乎是既简单又有效。
客户
var apples = new Array();
// construct data - replace with your own
$.ajax({
type: "POST",
url: '/Home/Download',
data: JSON.stringify(apples),
contentType: "application/json",
dataType: "text",
success: function (data) {
var url = '/Home/Download?id=' + data;
window.location = url;
});
});
服务器
[HttpPost]
// called first
public ActionResult Download(Apple[] apples)
{
string json = new JavaScriptSerializer().Serialize(apples);
string id = Guid.NewGuid().ToString();
string path = Server.MapPath(string.Format("~/temp/{0}.json", id));
System.IO.File.WriteAllText(path, json);
return Content(id);
}
// called next
public ActionResult Download(string id)
{
string path = Server.MapPath(string.Format("~/temp/{0}.json", id));
string json = System.IO.File.ReadAllText(path);
System.IO.File.Delete(path);
Apple[] apples = new JavaScriptSerializer().Deserialize<Apple[]>(json);
// work with apples to build your file in memory
byte[] file = createPdf(apples);
Response.AddHeader("Content-Disposition", "attachment; filename=juicy.pdf");
return File(file, "application/pdf");
}
Answer 7:
$scope.downloadSearchAsCSV = function(httpOptions) {
var httpOptions = _.extend({
method: 'POST',
url: '',
data: null
}, httpOptions);
$http(httpOptions).then(function(response) {
if( response.status >= 400 ) {
alert(response.status + " - Server Error \nUnable to download CSV from POST\n" + JSON.stringify(httpOptions.data));
} else {
$scope.downloadResponseAsCSVFile(response)
}
})
};
/**
* @source: https://github.com/asafdav/ng-csv/blob/master/src/ng-csv/directives/ng-csv.js
* @param response
*/
$scope.downloadResponseAsCSVFile = function(response) {
var charset = "utf-8";
var filename = "search_results.csv";
var blob = new Blob([response.data], {
type: "text/csv;charset="+ charset + ";"
});
if (window.navigator.msSaveOrOpenBlob) {
navigator.msSaveBlob(blob, filename); // @untested
} else {
var downloadContainer = angular.element('<div data-tap-disabled="true"><a></a></div>');
var downloadLink = angular.element(downloadContainer.children()[0]);
downloadLink.attr('href', window.URL.createObjectURL(blob));
downloadLink.attr('download', "search_results.csv");
downloadLink.attr('target', '_blank');
$document.find('body').append(downloadContainer);
$timeout(function() {
downloadLink[0].click();
downloadLink.remove();
}, null);
}
//// Gets blocked by Chrome popup-blocker
//var csv_window = window.open("","","");
//csv_window.document.write('<meta name="content-type" content="text/csv">');
//csv_window.document.write('<meta name="content-disposition" content="attachment; filename=data.csv"> ');
//csv_window.document.write(response.data);
};
Answer 8:
不完全的答案,原来的职位,但张贴的JSON对象到服务器,并动态地生成一个下载一个快速和肮脏的解决方案。
客户端的jQuery:
var download = function(resource, payload) {
$("#downloadFormPoster").remove();
$("<div id='downloadFormPoster' style='display: none;'><iframe name='downloadFormPosterIframe'></iframe></div>").appendTo('body');
$("<form action='" + resource + "' target='downloadFormPosterIframe' method='post'>" +
"<input type='hidden' name='jsonstring' value='" + JSON.stringify(payload) + "'/>" +
"</form>")
.appendTo("#downloadFormPoster")
.submit();
}
然后..和解码在服务器侧的JSON串和设置下载(例如PHP)报头:
$request = json_decode($_POST['jsonstring']), true);
header('Content-Type: application/csv');
header('Content-Disposition: attachment; filename=export.csv');
header('Pragma: no-cache');
Answer 9:
我认为最好的办法是使用一个组合,你的第二个方法似乎是一个优雅的解决方案,其中浏览器都参与其中。
所以,这取决于呼叫是怎么回事。 (无论其浏览器或Web服务调用),您可以使用两者的组合,以发送一个网址到浏览器和原始数据发送给其他Web服务客户端。
Answer 10:
我已经醒了两天,现在试图找出如何下载使用jQuery使用Ajax调用的文件。 所有支持我得到忍不住我的情况,直到我试试这个。
客户端
function exportStaffCSV(t) { var postData = { checkOne: t }; $.ajax({ type: "POST", url: "/Admin/Staff/exportStaffAsCSV", data: postData, success: function (data) { SuccessMessage("file download will start in few second.."); var url = '/Admin/Staff/DownloadCSV?data=' + data; window.location = url; }, traditional: true, error: function (xhr, status, p3, p4) { var err = "Error " + " " + status + " " + p3 + " " + p4; if (xhr.responseText && xhr.responseText[0] == "{") err = JSON.parse(xhr.responseText).Message; ErrorMessage(err); } }); }
服务器端
[HttpPost]
public string exportStaffAsCSV(IEnumerable<string> checkOne)
{
StringWriter sw = new StringWriter();
try
{
var data = _db.staffInfoes.Where(t => checkOne.Contains(t.staffID)).ToList();
sw.WriteLine("\"First Name\",\"Last Name\",\"Other Name\",\"Phone Number\",\"Email Address\",\"Contact Address\",\"Date of Joining\"");
foreach (var item in data)
{
sw.WriteLine(string.Format("\"{0}\",\"{1}\",\"{2}\",\"{3}\",\"{4}\",\"{5}\",\"{6}\"",
item.firstName,
item.lastName,
item.otherName,
item.phone,
item.email,
item.contact_Address,
item.doj
));
}
}
catch (Exception e)
{
}
return sw.ToString();
}
//On ajax success request, it will be redirected to this method as a Get verb request with the returned date(string)
public FileContentResult DownloadCSV(string data)
{
return File(new System.Text.UTF8Encoding().GetBytes(data), System.Net.Mime.MediaTypeNames.Application.Octet, filename);
//this method will now return the file for download or open.
}
祝好运。
Answer 11:
有了HTML5,你可以创建一个锚,并点击它。 没有必要将其添加到文档作为一个孩子。
const a = document.createElement('a');
a.download = '';
a.href = urlForPdfFile;
a.click();
全部完成。
如果你想为下载一个特殊的名字,只是通过它在download
属性:
const a = document.createElement('a');
a.download = 'my-special-name.pdf';
a.href = urlForPdfFile;
a.click();
Answer 12:
另一种方法中,而不是保存服务器上的文件和检索它,是使用.NET 4.0+ ObjectCache具有短届满直到第二动作(在该时间可明确转储)。 我想使用jQuery阿贾克斯做呼叫的原因,就在于它是异步的。 建立我的动态PDF文件需要相当长的时间,我在这段时间里显示一个繁忙的微调对话框(它也允许做其他工作)。 “成功:”使用在返回的数据的方法来创建BLOB不正常。 这取决于PDF文件的内容。 这是很容易通过数据的响应损坏,如果不是完全的文本这是所有的Ajax可以处理。
文章来源: JavaScript/jQuery to download file via POST with JSON data