解決AJAX Request沒有辦法收取檔案回應(檔案下載)的問題
今天在開發網頁的遇到一個詭異的需求,就是需要使用XMLHttpRequest(也就是大家熟悉的AJAX)來進行POST,送出後的動作就是後端會因應要求吐出一個檔案給你,以往要不是收取XML就是收取JSON資料,這還是第一次遇到這樣的需求,沒想到劈哩趴啦寫完後發現:疑?啊瀏覽器怎麼都沒有顯示檔案下載?
按下F12除錯才發現,檔案其實是有Response回來,該設定的Http Header、Content-Type一個都沒少,但是瀏覽器就是不顯示成檔案下載。經過Google後發現,原來XMLHttpRequest(AJAX)無法接受檔案型的回傳資料,除非要無視IE瀏覽器,選擇新世代支援XMLHttpRequest的responseType: blob設定之瀏覽器。
讓AJAX Request可以收到回傳檔案的正確解法
StackOverflow上有一篇Recieving a Zip file as response on AJAX request文章討論到這種問題的解法,大致上就是用a標籤的新屬性(download)來進行,或是使用window.open()另開一個視窗來觸發,也是不錯的解法。
缺點就是太煩太囉嗦了,由於我不一定要採用AJAX非同步的方式來處理,因此我個人選擇回歸傳統。
利用動態建立Form POST來解決
正所謂山不轉路轉,既然要用AJAX回傳一個檔案,這代表按下按鈕後其實你的畫面也不會變動到啥,就是瀏覽器彈出檔案下載而已。操起熟悉的jQuery,在背景動態的建立一個表單,然後把表單POST出去,所有的畫面動作其實都是一樣的,但我用更簡單明瞭的程式碼來解決問題,符合我心中喜好的Style。
$("#downloadButton").click(function () {
event.preventDefault();
var oForm = jQuery("<form>", {
"action": "/someBackend.aspx",
"method": "post"
}).append(jQuery("<input>", {
"name": "cParameter",
"value": "ABCD",
"type": "hidden"
})).append(jQuery("<input>", {
"name": "iParameter",
"value": 1234,
"type": "hidden"
})).appendTo("body");
oForm.submit();
});
寫一段你幾年後再回來依然能夠秒懂的程式碼,才是最好的程式碼。
拋棄IE束縛,使用新世代瀏覽器XMLHttpRequest API / responseType: blob來解決AJAX二進制下載問題
另外一種做法是變更XMLHttpRequest API的responseType,使之設定為blob,就可以進行二進制資料的回傳,這種做法的缺點在於你必須把IE瀏覽器升級到第10版,當然以時至今日(2020年)來說,這樣的抉擇並不困難。我把範例程式碼寫在下方,相信這段程式碼會隨著時間越來越久而越來越好用。(畢竟時間會推著瀏覽器前進)
$("#downloadButton").click(function () {
$.ajax(
"YourServicesURL",
{ xhrFields: { responseType: "blob" } })
.then(function (oData, cStatus, oXHR) {
let cFileName = "";
let cContentDisposition = oXHR.getResponseHeader("Content-Disposition");
let cNameIndexStart = "filename\*=utf-8''";
let iNameIndexStart = cContentDisposition.indexOf(cNameIndexStart);
if (iNameIndexStart !== -1) {
cFileName = decodeURIComponent(cContentDisposition.substring(iNameIndexStart + cNameIndexStart.length));
}
let cURL = window.URL.createObjectURL(oData);
if (cFileName) {
$("<a>")
.attr("href", cURL)
.attr("download", cFileName)
.get(0)
.click();
} else {
window.location.href = cURL;
}
});
});
註:Content-Disposition裡面的編碼機制請依據自己伺服器端的規範修正。