XMLHttpRequest, jQuery, fetch を使ってファイルをダウンロードするにはどうすれば良いかということを調べましたので備忘録として書いておきます。
例えば、先の記事「MVC でファイルのダウンロード」で書いたようなアクションメソッドがあるとして、XMLHttpRequest, jQuery, fetch などを使ってその URL に非同期要求をかけたとします。
そうすると、同期要求した場合と全く同様に、応答ヘッダには Content-Type と Content-Disposition が適切に設定され、コンテンツ(ファイルのバイナリデータ)も正しく返ってきます。
しかしながら、上の画像のような通知バーは表示されませんし、ファイルは PC のディスクには保存されません。
では、ファイルを PC のディスクに保存するにはどうすればいいかと言うと、応答コンテンツを Blob(生データ)として取得し、それを保存するためのスクリプトを書くことになります。
jQuery ajax を使う場合、バージョン 3.0 以降であれば Blob を取得できますが、バージョン 2.x 以前では Blob を取得できないので、ネイティブの XMLHttpRequest を使うことになるそうです (fetch API が使えればそちらを使う方がお勧め)。
詳しくは stackoverflow の記事 Using jQuery's ajax method to retrieve images as a blob にある回答を読んでください。
取得した Blob をファイルとして PC に保存するには、IE, 旧 Edge の場合は msSaveBlob method を使います。
Chrome, Chromium ベースの新 Edge, Firefox, Opera の場合は URL.createObjectURL を使って Blob の URL を取得し、それを html の a 要素の href 属性に設定し、download 属性にファイル名を設定してスクリプトでクリックするようにします。
具体的には、ネイティブの XMLHttpRequest を使った例ですが、以下のコードの通りです。(下の方に jQuery Ajax と fetch API を利用したサンプルコードを追記しました)
function download() {
var url = "/Home/FileDownload";
var filename = "testfile";
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = 'blob';
xhr.onreadystatechange = function (e) {
if (this.readyState == 4 && this.status == 200) {
var blob = this.response;
//IE, 旧 Edge とその他で処理の切り分け
if (window.navigator.msSaveBlob) {
window.navigator.msSaveBlob(blob, filename + ".pdf");
} else {
var a = document.createElement("a");
// IE11, 旧 Edge は URL API をサポートしてない
var url = window.URL;
a.href = url.createObjectURL(new Blob([blob],
{ type: blob.type }));
document.body.appendChild(a);
a.style = "display: none";
a.download = filename + ".pdf";
a.click();
}
}
};
xhr.send();
}
2, 3 注意点を書いておきます。
IE11 は window.URL をサポートしてないのでファイルとして保存するには msSaveBlob メソッドを使う以外に手はなさそうです。
msSaveBlob の第 2 引数を設定しないと、ファイル名は IE の場合 1A31A31A-1F57-4D8D-8C70-150839D02536.pdf のように、Edge の場合は (1) となってしまいます。
MDN のドキュメントには、Content-Disposition ヘッダーで download 属性の指定と異なるファイル名が与えられた場合は、この属性より HTTP ヘッダーが優先するということが書いてありますが、実際試すと download 属性の指定通りとなります。"" を設定したりすると aae9adeb-1005-407f-a3a6-046fa79c4351.pdf のようになります。
【追記 2023/12/6】
jQuery Ajax を利用
上に紹介した stackoverflow の記事によると jQuery バージョン 3.0 以降であれば Blob を取得できるそうで、サンプルコードも載っていましたので試してみました。以下のコードでダウンロードできます。
const jqueryAjax = () => {
const url = "/Download/VirtualFileResult";
const filename = "testjquery";
$.ajax({
url: url,
cache: false,
xhrFields: { responseType: "blob" }
}).done(blob => {
const a = document.createElement("a");
const url = window.URL;
a.href = url.createObjectURL(new Blob([blob],
{ type: blob.type }));
document.body.appendChild(a);
a.style = "display: none";
a.download = filename + ".pdf";
a.click();
}).fail((jqXHR, textStatus, errorThrown) => {
// エラー処理(省略)
});
};
fetch API を利用
fetch 関数の戻り値の Response オブジェクトの blob メソッドで blob を取得できるそうなので試してみました。以下のコードがそれです。async / await と組み合わせることにより、上の 2 つの例より、可読性がかなり向上すると思います。
const fetchApi = async () => {
const url = "/Download/VirtualFileResult";
const filename = "testfetch";
const response = await fetch(url);
if (response.ok) {
const blob = await response.blob();
const a = document.createElement("a");
const url = window.URL;
a.href = url.createObjectURL(new Blob([blob],
{ type: blob.type }));
document.body.appendChild(a);
a.style = "display: none";
a.download = filename + ".pdf";
a.click();
} else {
// エラー処理(省略)
}
};
(メモ: サンプルは Visual Studio 2022 > AspNet7 > McvNet7App > DownloadController)