Menangani unduhan file dari posting ajax

393

Saya memiliki aplikasi javascript yang mengirimkan permintaan POST ajax ke URL tertentu. Respons mungkin berupa string JSON atau mungkin file (sebagai lampiran). Saya dapat dengan mudah mendeteksi Tipe-Konten dan Disposisi-Konten dalam panggilan ajax saya, tetapi begitu saya mendeteksi bahwa responsnya berisi file, bagaimana cara saya menawarkan klien untuk mengunduhnya? Saya sudah membaca sejumlah utas serupa di sini, tetapi tidak ada yang memberikan jawaban yang saya cari.

Tolong, tolong, tolong jangan posting jawaban yang menyarankan bahwa saya tidak boleh menggunakan ajax untuk ini atau bahwa saya harus mengarahkan ulang browser, karena semua ini bukan opsi. Menggunakan formulir HTML biasa juga bukan pilihan. Yang saya butuhkan adalah menampilkan dialog unduhan kepada klien. Bisakah ini dilakukan dan bagaimana caranya?

Pavle Predic
sumber
Bagi mereka yang membaca artikel ini, baca posting ini: stackoverflow.com/questions/20830309/…
sobhan
Saya telah menghapus solusi Anda dari pertanyaan. Anda dipersilakan mempostingnya sebagai posting jawaban di bawah ini, tetapi itu tidak termasuk dalam pos pertanyaan.
Martijn Pieters

Jawaban:

111

Buat formulir, gunakan metode POST, kirim formulir - tidak perlu untuk iframe. Ketika halaman server menanggapi permintaan, tulis tajuk respons untuk jenis mime file, dan itu akan menyajikan dialog unduhan - Saya telah melakukan ini beberapa kali.

Anda ingin jenis aplikasi / unduhan konten - cukup cari cara menyediakan unduhan untuk bahasa apa pun yang Anda gunakan.


sumber
35
Seperti yang dinyatakan dalam pertanyaan: "Menggunakan formulir HTML biasa juga bukan pilihan."
Pavle Predic
13
Tidak, karena menggunakan POST biasa akan menavigasi browser ke URL POST. Saya tidak ingin bernavigasi dari halaman. Saya ingin melakukan permintaan di latar belakang, memproses respons dan menyampaikannya kepada klien.
Pavle Predic
6
Jika server mengirim kembali tajuk seperti jawaban yang lain, ia terbuka di jendela baru - saya pernah melakukannya sebelumnya. Ini hanya akan menavigasi jika skrip sisi server Anda mengembalikan kode HTML
1
@PavlePredic akhirnya Anda menemukan cara mengelola kedua skenario respons, yaitu respons teks JSON atau mengunduh respons file?
Pengguna Web
9
Jawabannya tidak jelas dan solusi yang diajukan tidak bekerja.
stack247
531

Jangan menyerah begitu cepat, karena ini dapat dilakukan (di browser modern) menggunakan bagian dari FileAPI:

var xhr = new XMLHttpRequest();
xhr.open('POST', url, 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;
        if (typeof File === 'function') {
            try {
                blob = new File([this.response], filename, { type: type });
            } catch (e) { /* Edge */ }
        }
        if (typeof blob === 'undefined') {
            blob = 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.href = downloadUrl;
                } else {
                    a.href = downloadUrl;
                    a.download = filename;
                    document.body.appendChild(a);
                    a.click();
                }
            } else {
                window.location.href = downloadUrl;
            }

            setTimeout(function () { URL.revokeObjectURL(downloadUrl); }, 100); // cleanup
        }
    }
};
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.send($.param(params));

Ini adalah versi lama menggunakan jQuery.ajax. Mungkin memotong-motong data biner ketika respons dikonversi ke string beberapa charset.

$.ajax({
    type: "POST",
    url: url,
    data: params,
    success: function(response, status, xhr) {
        // check for a filename
        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 = new Blob([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.href = downloadUrl;
                } else {
                    a.href = downloadUrl;
                    a.download = filename;
                    document.body.appendChild(a);
                    a.click();
                }
            } else {
                window.location.href = downloadUrl;
            }

            setTimeout(function () { URL.revokeObjectURL(downloadUrl); }, 100); // cleanup
        }
    }
});
Jonathan Amend
sumber
1
Terima kasih banyak ! Saya harus menambahkan 'Konten-disposisi' ke 'Access-Control-Expose-Headers' dan 'Access-Control-Allow-Headers' di respons HTTP saya agar bisa berfungsi.
JulienD
1
Itu tidak berfungsi ketika file lebih besar dari 500 mb, mungkin kita harus menggunakan api lain?
hirra
Bagaimana dengan menghapus elemen dari DOM di bagian pembersihan (dan bukan hanya URL)? document.body.removeChild(a);
Scoregraphic
@ Shirra menggunakan responseceType "blob" alih-alih "arraybuffer" dan menulis ulang fungsi onback callback seperti itu, var blob adalah this.response (var blob = this.response;) sovar blob =this.responce; /** if (typeof File === 'function') { try { blob = new File([this.response], filename, { type: type }); } catch (e) { /* Edge */ } } if (typeof blob === 'undefined') { blob = new Blob([this.response], { type: type }); } */
Chris Tobba
1
Ini solusi sempurna. Hanya satu perubahan kecil. Dalam naskah saya butuhkan window.location.href = downloadUrlbukannyawindow.location = downloadUrl
michal.jakubeczy
39

Saya menghadapi masalah yang sama dan berhasil menyelesaikannya. Kasing saya adalah ini.

" Kirim data JSON ke server dan terima file excel. File excel itu dibuat oleh server dan dikembalikan sebagai respons kepada klien. Unduh respons itu sebagai file dengan nama khusus di 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));
});

Cuplikan di atas hanya melakukan yang berikut

  • Posting array sebagai JSON ke server menggunakan XMLHttpRequest.
  • Setelah mengambil konten sebagai gumpalan (biner), kami membuat URL yang dapat diunduh dan melampirkannya ke tautan "a" yang tidak terlihat kemudian mengkliknya.

Di sini kita perlu hati-hati mengatur beberapa hal di sisi server. Saya menetapkan beberapa header di Python Django HttpResponse. Anda perlu mengaturnya jika Anda menggunakan bahasa pemrograman lain.

# In python django code
response = HttpResponse(file_content, content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")

Karena saya mengunduh xls (excel) di sini, saya menyesuaikan contentType dengan yang di atas. Anda perlu mengaturnya sesuai dengan jenis file Anda. Anda dapat menggunakan teknik ini untuk mengunduh semua jenis file.

Naren Yellavula
sumber
33

Bahasa sisi server apa yang Anda gunakan? Di aplikasi saya, saya dapat dengan mudah mengunduh file dari panggilan AJAX dengan mengatur tajuk yang benar dalam respons PHP:

Pengaturan tajuk sisi server

header("HTTP/1.1 200 OK");
header("Pragma: public");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");

// The optional second 'replace' parameter indicates whether the header
// should replace a previous similar header, or add a second header of
// the same type. By default it will replace, but if you pass in FALSE
// as the second argument you can force multiple headers of the same type.
header("Cache-Control: private", false);

header("Content-type: " . $mimeType);

// $strFileName is, of course, the filename of the file being downloaded. 
// This won't have to be the same name as the actual file.
header("Content-Disposition: attachment; filename=\"{$strFileName}\""); 

header("Content-Transfer-Encoding: binary");
header("Content-Length: " . mb_strlen($strFile));

// $strFile is a binary representation of the file that is being downloaded.
echo $strFile;

Ini sebenarnya akan 'mengarahkan' browser ke halaman unduhan ini, tetapi seperti yang dikatakan @ahren dalam komentarnya, browser tidak akan bernavigasi dari halaman saat ini.

Ini semua tentang pengaturan header yang benar jadi saya yakin Anda akan menemukan solusi yang cocok untuk bahasa sisi server yang Anda gunakan jika itu bukan PHP.

Menangani sisi respons klien

Dengan asumsi Anda sudah tahu cara melakukan panggilan AJAX, di sisi klien Anda menjalankan permintaan AJAX ke server. Server kemudian membuat tautan dari tempat file ini dapat diunduh, mis. URL 'maju' tempat Anda ingin mengarahkan. Misalnya, server merespons dengan:

{
    status: 1, // ok
    // unique one-time download token, not required of course
    message: 'http://yourwebsite.com/getdownload/ska08912dsa'
}

Saat memproses respons, Anda menyuntikkan sebuah iframedi tubuh Anda dan mengatur iframeSRC ke URL yang baru saja Anda terima seperti ini (menggunakan jQuery untuk kemudahan contoh ini):

$("body").append("<iframe src='" + data.message +
  "' style='display: none;' ></iframe>");

Jika Anda telah mengatur tajuk yang benar seperti yang ditunjukkan di atas, iframe akan memaksa dialog unduhan tanpa menavigasi browser dari halaman saat ini.

Catatan

Tambahan tambahan sehubungan dengan pertanyaan Anda; Saya pikir yang terbaik adalah selalu mengembalikan JSON ketika meminta hal-hal dengan teknologi AJAX. Setelah Anda menerima respons JSON, Anda kemudian dapat memutuskan sisi klien apa yang harus dilakukan dengannya. Mungkin, misalnya, nanti Anda ingin pengguna mengeklik tautan unduhan ke URL alih-alih memaksakan unduhan secara langsung, dalam pengaturan Anda saat ini, Anda harus memperbarui sisi klien dan sisi server untuk melakukannya.

Robin van Baalen
sumber
24

Bagi mereka yang mencari solusi dari sudut pandang Angular, ini bekerja untuk saya:

$http.post(
  'url',
  {},
  {responseType: 'arraybuffer'}
).then(function (response) {
  var headers = response.headers();
  var blob = new Blob([response.data],{type:headers['content-type']});
  var link = document.createElement('a');
  link.href = window.URL.createObjectURL(blob);
  link.download = "Filename";
  link.click();
});
Tim Hettler
sumber
Ini membantu, tapi saya harus mempertahankan nama file aslinya. Saya melihat nama file di header respons di bawah "Content-Disposition", tetapi saya tidak dapat menemukan nilai itu di objek respons dalam kode. Menyetel link.download = ""hasil dalam nama file panduan acak, dan link.download = nullmenghasilkan file bernama "null".
Marie
@Marie, Anda dapat merekam nama file pada saat mengunggah dengan menggunakan properti INPUTelemen HTMLInputElement.files. Lihat Dokumen MDN pada input file untuk detail lebih lanjut.
Tim Hettler
Ukuran
gumpalan
22

Inilah cara saya menjalankan https://stackoverflow.com/a/27563953/2845977 ini

$.ajax({
  url: '<URL_TO_FILE>',
  success: function(data) {
    var blob=new Blob([data]);
    var link=document.createElement('a');
    link.href=window.URL.createObjectURL(blob);
    link.download="<FILENAME_TO_SAVE_WITH_EXTENSION>";
    link.click();
  }
});

Jawaban yang diperbarui menggunakan download.js

$.ajax({
  url: '<URL_TO_FILE>',
  success: download.bind(true, "<FILENAME_TO_SAVE_WITH_EXTENSION>", "<FILE_MIME_TYPE>")
});

Mayur Padshala
sumber
Terima kasih untuk ini, saya hanya menggunakan ini hari ini. Fantastis
Ryan Wilson
Hai, apakah saya perlu memiliki jQuery 3.0> agar ini berfungsi?
gbade_
Saya juga mendapatkan pdf kosong dengan kedua contoh yang Anda berikan. Saya mencoba mendapatkannya untuk mengunduh file pdf.
gbade_
@ gbade_ Tidak, Anda tidak memerlukan versi jQuery tertentu. Harus bekerja dengan baik dengan semua versi. Apakah Anda memeriksa apakah PDF yang Anda unduh memiliki tajuk CORS yang benar? Kesalahan apa pun pada konsol mungkin membantu untuk melakukan debug
Mayur Padshala
Ini berhasil bagi saya menggunakan download.js:success: function (response, status, request) { download(response, "filename.txt", "application/text"); }
Sga
12

Saya melihat Anda sudah menemukan solusi, namun saya hanya ingin menambahkan beberapa informasi yang dapat membantu seseorang mencoba mencapai hal yang sama dengan permintaan POST besar.

Saya memiliki masalah yang sama beberapa minggu yang lalu, memang tidak mungkin untuk mencapai unduhan "bersih" melalui AJAX, Grup Filament membuat plugin jQuery yang bekerja persis bagaimana Anda sudah tahu, itu disebut jQuery File Unduh namun ada kelemahan dari teknik ini.

Jika Anda mengirim permintaan besar melalui AJAX (katakan file + 1MB) itu akan berdampak negatif terhadap respons. Dalam koneksi internet yang lambat Anda harus menunggu banyak sampai permintaan dikirim dan juga menunggu file untuk diunduh. Itu tidak seperti instan "klik" => "popup" => "mulai unduhan". Ini lebih seperti "klik" => "tunggu sampai data dikirim" => "tunggu respons" => "mulai unduhan" yang membuatnya tampak file dua kali lipat ukurannya karena Anda harus menunggu permintaan untuk dikirim melalui AJAX dan dapatkan kembali sebagai file yang dapat diunduh.

Jika Anda bekerja dengan ukuran file kecil <1MB Anda tidak akan melihat ini. Tapi ketika saya temukan di aplikasi saya sendiri, untuk ukuran file yang lebih besar hampir tak tertahankan.

Aplikasi saya memungkinkan pengguna untuk mengekspor gambar yang dihasilkan secara dinamis, gambar ini dikirim melalui permintaan POST dalam format base64 ke server (itu adalah satu-satunya cara yang mungkin), kemudian diproses dan dikirim kembali ke pengguna dalam bentuk file .png, .jpg, base64 string untuk gambar + 1MB sangat besar, ini memaksa pengguna untuk menunggu lebih dari yang diperlukan agar file mulai mengunduh. Dalam koneksi internet yang lambat itu bisa sangat menjengkelkan.

Solusi saya untuk ini adalah menulis sementara file ke server, setelah siap, secara dinamis menghasilkan tautan ke file dalam bentuk tombol yang berubah antara status "Harap tunggu ..." dan "Unduh" dan pada saat yang sama waktu, cetak gambar base64 di jendela sembulan pratinjau sehingga pengguna dapat "klik kanan" dan menyimpannya. Ini membuat semua waktu tunggu lebih tertahankan bagi pengguna, dan juga mempercepat.

Pembaruan 30 Sep 2014:

Berbulan-bulan telah berlalu sejak saya memposting ini, akhirnya saya telah menemukan pendekatan yang lebih baik untuk mempercepat ketika bekerja dengan string base64 besar. Saya sekarang menyimpan string base64 ke dalam basis data (menggunakan bidang longtext atau longblog), kemudian saya meneruskan ID rekamannya melalui jQuery File Download, akhirnya pada file skrip unduhan saya meminta database menggunakan ID ini untuk menarik string base64 dan meneruskannya melalui fungsi pengunduhan.

Unduh Contoh Skrip:

<?php
// Record ID
$downloadID = (int)$_POST['id'];
// Query Data (this example uses CodeIgniter)
$data       = $CI->MyQueries->GetDownload( $downloadID );
// base64 tags are replaced by [removed], so we strip them out
$base64     = base64_decode( preg_replace('#\[removed\]#', '', $data[0]->image) );
// This example is for base64 images
$imgsize    = getimagesize( $base64 );
// Set content headers
header('Content-Disposition: attachment; filename="my-file.png"');
header('Content-type: '.$imgsize['mime']);
// Force download
echo $base64;
?>

Saya tahu ini jauh melampaui apa yang diminta OP, namun saya merasa akan baik untuk memperbarui jawaban saya dengan temuan saya. Ketika saya mencari solusi untuk masalah saya, saya membaca banyak utas "Unduhan dari data POST AJAX" yang tidak memberi saya jawaban yang saya cari, saya harap informasi ini membantu seseorang yang ingin mencapai sesuatu seperti ini.

José SAYAGO
sumber
Satu- jQuery File Downloadsatunya mengarahkan saya ke url. Saya menyebutnya seperti ini: jQuery.download("api/ide/download-this-file.php", {filePath: path2Down}, "POST");.
Casper
5

Saya ingin menunjukkan beberapa kesulitan yang muncul saat menggunakan teknik dalam jawaban yang diterima, yaitu menggunakan formulir posting:

  1. Anda tidak dapat menetapkan tajuk atas permintaan. Jika skema otentikasi Anda melibatkan tajuk, Json-Web-Token yang diteruskan dalam tajuk Otorisasi, Anda harus menemukan cara lain untuk mengirimkannya, misalnya sebagai parameter kueri.

  2. Anda tidak dapat benar-benar tahu kapan permintaan telah selesai. Yah, Anda bisa menggunakan cookie yang diatur pada respons, seperti yang dilakukan oleh jquery.fileDownload , tapi itu JAUH dari sempurna. Itu tidak akan berfungsi untuk permintaan bersamaan dan itu akan rusak jika respons tidak pernah datang.

  3. Jika server merespons dengan kesalahan, pengguna akan diarahkan ke halaman kesalahan.

  4. Anda hanya dapat menggunakan tipe konten yang didukung oleh formulir . Yang berarti Anda tidak dapat menggunakan JSON.

Saya akhirnya menggunakan metode menyimpan file di S3 dan mengirim URL yang sudah ditandatangani untuk mendapatkan file.

tepez
sumber
5

Bagi mereka yang mencari pendekatan yang lebih modern, Anda dapat menggunakan fetch API. Contoh berikut menunjukkan cara mengunduh file spreadsheet. Ini mudah dilakukan dengan kode berikut.

fetch(url, {
    body: JSON.stringify(data),
    method: 'POST',
    headers: {
        'Content-Type': 'application/json; charset=utf-8'
    },
})
.then(response => response.blob())
.then(response => {
    const blob = new Blob([response], {type: 'application/application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'});
    const downloadUrl = URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = downloadUrl;
    a.download = "file.xlsx";
    document.body.appendChild(a);
    a.click();
})

Saya percaya pendekatan ini lebih mudah dipahami daripada XMLHttpRequestsolusi lain . Juga, ia memiliki sintaksis yang mirip dengan jQuerypendekatan, tanpa perlu menambahkan pustaka tambahan.

Tentu saja, saya akan menyarankan memeriksa browser yang Anda kembangkan, karena pendekatan baru ini tidak akan berfungsi pada IE. Anda dapat menemukan daftar kompatibilitas browser lengkap pada [tautan] [1] berikut.

Penting : Dalam contoh ini saya mengirim permintaan JSON ke server mendengarkan yang diberikan url. Ini urlharus ditetapkan, pada contoh saya, saya berasumsi Anda tahu bagian ini. Juga, pertimbangkan header yang diperlukan untuk permintaan Anda untuk bekerja. Karena saya mengirim JSON, saya harus menambahkan Content-Typeheader dan mengaturnya application/json; charset=utf-8, untuk memberi tahu server jenis permintaan yang akan diterimanya.

Alain Cruz
sumber
1
Luar biasa! Untuk membuka ini di tab baru dan bukan sembulan unduhan: gunakan `` `const window = buka (unduhUl," _blank "); if (window! == null) window.focus (); `` `
andy
Apakah ada cara bagi saya untuk melakukan ini untuk beberapa set data? Contoh, dapatkan kembali {fileOne: data, fileTwo: data, fileThree: data} dari panggilan api dan menghasilkan tiga file yang diunduh sekaligus? Terima kasih!
il0v3d0g
Hmmm, saya belum mencobanya. Tetapi Anda selalu dapat mengompres gambar dalam file zip dan mengunduhnya. Saya akan memeriksa apakah mungkin.
Alain Cruz
4

Ini solusi saya menggunakan formulir tersembunyi sementara.

//Create an hidden form
var form = $('<form>', {'method': 'POST', 'action': this.href}).hide();

//Add params
var params = { ...your params... };
$.each(params, function (k, v) {
    form.append($('<input>', {'type': 'hidden', 'name': k, 'value': v}));
});

//Make it part of the document and submit
$('body').append(form);
form.submit();

//Clean up
form.remove();

Perhatikan bahwa saya menggunakan JQuery secara besar-besaran tetapi Anda dapat melakukan hal yang sama dengan JS asli.

Ludovic Martin
sumber
3

Seperti yang telah dinyatakan orang lain, Anda dapat membuat dan mengirimkan formulir untuk diunduh melalui permintaan POST. Namun, Anda tidak harus melakukan ini secara manual.

Salah satu pustaka yang sangat sederhana untuk melakukan hal ini adalah jquery.redirect . Ini menyediakan API yang mirip dengan jQuery.postmetode standar :

$.redirect(url, [values, [method, [target]]])
KurtPreston
sumber
3

Ini pertanyaan 3 tahun tapi saya punya masalah yang sama hari ini. Saya melihat solusi Anda yang telah diedit tetapi saya pikir itu dapat mengorbankan kinerja karena harus membuat permintaan ganda. Jadi jika ada yang membutuhkan solusi lain yang tidak menyiratkan untuk memanggil layanan dua kali maka inilah cara saya melakukannya:

<form id="export-csv-form" method="POST" action="/the/path/to/file">
    <input type="hidden" name="anyValueToPassTheServer" value="">
</form>

Formulir ini hanya digunakan untuk memanggil layanan dan menghindari untuk menggunakan window.location (). Setelah itu Anda cukup membuat formulir kirim dari jquery untuk memanggil layanan dan mendapatkan file. Ini cukup sederhana tetapi dengan cara ini Anda dapat mengunduh menggunakan POST . Saya sekarang ini bisa lebih mudah jika layanan yang Anda panggil adalah GET , tapi itu bukan kasus saya.

Jairo Miranda
sumber
1
Ini bukan posting ajax karena pertanyaannya menggunakan ajax
Nidhin David
itu hanya solusi untuk masalah di atas, namun tidak untuk panggilan ajax.
Nomi Ali
1

Saya menggunakan FileSaver.js ini . Dalam kasus saya dengan file csv, saya melakukan ini (dalam coffescript):

  $.ajax
    url: "url-to-server"
    data: "data-to-send"
    success: (csvData)->
      blob = new Blob([csvData], { type: 'text/csv' })
      saveAs(blob, "filename.csv")

Saya pikir untuk kasus yang paling rumit, data harus diproses dengan benar. Di bawah tenda FileSaver.js menerapkan pendekatan yang sama dari jawaban Jonathan Amend .

Armando
sumber
1
..tapi bisakah Anda mengunduh file biasanya di iOS?
Alex Marshall
1

lihat: http://www.henryalgus.com/reading-binary-files-using-jquery-ajax/ itu akan mengembalikan gumpalan sebagai respons, yang kemudian dapat dimasukkan ke fileaver

Samantha Adrichem
sumber
2
Sementara ini secara teoritis dapat menjawab pertanyaan, akan lebih baik untuk memasukkan bagian-bagian penting dari jawaban di sini, dan menyediakan tautan untuk referensi.
Bhargav Rao
1

Untuk mendapatkan jawaban Jonathan Amends untuk bekerja di Edge, saya membuat perubahan berikut:

var blob = typeof File === 'function'
    ? new File([this.response], filename, { type: type })
    : new Blob([this.response], { type: type });

untuk ini

var f = typeof File+"";
var blob = f === 'function' && Modernizr.fileapi
    ? new File([this.response], filename, { type: type })
    : new Blob([this.response], { type: type });

Saya lebih suka memposting ini sebagai komentar tetapi saya tidak memiliki reputasi yang cukup untuk itu

fstrandner
sumber
0

Inilah solusi saya, dikumpulkan dari berbagai sumber: Implementasi sisi server:

    String contentType = MediaType.APPLICATION_OCTET_STREAM_VALUE;
    // Set headers
    response.setHeader("content-disposition", "attachment; filename =" + fileName);
    response.setContentType(contentType);
    // Copy file to output stream
    ServletOutputStream servletOutputStream = response.getOutputStream();
    try (InputStream inputStream = new FileInputStream(file)) {
        IOUtils.copy(inputStream, servletOutputStream);
    } finally {
        servletOutputStream.flush();
        Utils.closeQuitely(servletOutputStream);
        fileToDownload = null;
    }

Implementasi sisi klien (menggunakan jquery):

$.ajax({
type: 'POST',
contentType: 'application/json',
    url: <download file url>,
    data: JSON.stringify(postObject),
    error: function(XMLHttpRequest, textStatus, errorThrown) {
        alert(errorThrown);
    },
    success: function(message, textStatus, response) {
       var header = response.getResponseHeader('Content-Disposition');
       var fileName = header.split("=")[1];
       var blob = new Blob([message]);
       var link = document.createElement('a');
       link.href = window.URL.createObjectURL(blob);
       link.download = fileName;
       link.click();
    }
});   
Dvs Prajapati
sumber
0

ada solusi lain untuk mengunduh halaman web di ajax. Tetapi saya merujuk ke halaman yang pertama-tama harus diproses dan kemudian diunduh.

Pertama, Anda perlu memisahkan pemrosesan halaman dari hasil unduhan.

1) Hanya penghitungan halaman yang dibuat dalam panggilan ajax.

$ .post ("CalculusPage.php", {calculusFunction: true, ID: 29, data1: "a", data2: "b"},

       fungsi (data, status) 
       {
            if (status == "berhasil") 
            {
                / * 2) Pada jawaban halaman yang menggunakan perhitungan sebelumnya diunduh. Misalnya, ini bisa berupa halaman yang mencetak hasil dari tabel yang dihitung dalam panggilan ajax. * /
                window.location.href = UnduhPage.php + "? ID =" + 29;
            }               
       }
);

// Sebagai contoh: di CalculusPage.php

    if (! empty ($ _ POST ["calculusFunction"])) 
    {
        $ ID = $ _POST ["ID"];

        $ query = "INSERT INTO ExamplePage (data1, data2) VALUES ('". $ _ POST ["data1"]. "', '". $ _ POST ["data2"]. "') WHERE id =". $ ID;
        ...
    }

// Sebagai contoh: di DownloadPage.php

    $ ID = $ _GET ["ID"];

    $ sede = "SELECT * DARI ExamplePage WHERE id =". $ ID;
    ...

    $ filename = "Export_Data.xls";
    header ("Tipe-Konten: application / vnd.ms-excel");
    header ("Content-Disposition: inline; filename = $ filename");

    ...

Saya berharap solusi ini dapat bermanfaat bagi banyak orang, seperti juga bagi saya.

netluke
sumber
0

Jika respons adalah Array Buffer , coba ini dalam acara onsuccess di Ajax:

 if (event.data instanceof ArrayBuffer) {
          var binary = '';
          var bytes = new Uint8Array(event.data);
          for (var i = 0; i < bytes.byteLength; i++) {
              binary += String.fromCharCode(bytes[i])
          }
          $("#some_id").append("<li><img src=\"data:image/png;base64," + window.btoa(binary) + "\"/></span></li>");
          return;
      }
  • di mana event.data adalah respons yang diterima dalam fungsi keberhasilan acara xhr.
Abhishek Sinha
sumber
0

Di bawah ini adalah solusi saya untuk mengunduh banyak file tergantung pada beberapa daftar yang terdiri dari beberapa id dan mencari dalam database, file akan ditentukan dan siap untuk diunduh - jika ada. Saya memanggil tindakan C # MVC untuk setiap file menggunakan Ajax.

Dan Ya, seperti yang dikatakan orang lain, dimungkinkan untuk melakukannya di jQuery Ajax. Saya melakukannya dengan sukses Ajax dan saya selalu mengirim tanggapan 200.

Jadi, inilah kuncinya:

  success: function (data, textStatus, xhr) {

Dan ini kode saya:

var i = 0;
var max = 0;
function DownloadMultipleFiles() {
            if ($(".dataTables_scrollBody>tr.selected").length > 0) {
                var list = [];
                showPreloader();
                $(".dataTables_scrollBody>tr.selected").each(function (e) {
                    var element = $(this);
                    var orderid = element.data("orderid");
                    var iscustom = element.data("iscustom");
                    var orderlineid = element.data("orderlineid");
                    var folderPath = "";
                    var fileName = "";

                    list.push({ orderId: orderid, isCustomOrderLine: iscustom, orderLineId: orderlineid, folderPath: folderPath, fileName: fileName });
                });
                i = 0;
                max = list.length;
                DownloadFile(list);
            }
        }

Kemudian menelepon:

function DownloadFile(list) {
        $.ajax({
            url: '@Url.Action("OpenFile","OrderLines")',
            type: "post",
            data: list[i],
            xhrFields: {
                responseType: 'blob'
            },
            beforeSend: function (xhr) {
                xhr.setRequestHeader("RequestVerificationToken",
                    $('input:hidden[name="__RequestVerificationToken"]').val());

            },
            success: function (data, textStatus, xhr) {
                // check for a filename
                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 a = document.createElement('a');
                    var url = window.URL.createObjectURL(data);
                    a.href = url;
                    a.download = filename;
                    document.body.append(a);
                    a.click();
                    a.remove();
                    window.URL.revokeObjectURL(url);
                }
                else {
                    getErrorToastMessage("Production file for order line " + list[i].orderLineId + " does not exist");
                }
                i = i + 1;
                if (i < max) {
                    DownloadFile(list);
                }
            },
            error: function (XMLHttpRequest, textStatus, errorThrown) {

            },
            complete: function () {
                if(i===max)
                hidePreloader();
            }
        });
    }

C # MVC:

 [HttpPost]
 [ValidateAntiForgeryToken]
public IActionResult OpenFile(OrderLineSimpleModel model)
        {
            byte[] file = null;

            try
            {
                if (model != null)
                {
                    //code for getting file from api - part is missing here as not important for this example
                    file = apiHandler.Get<byte[]>(downloadApiUrl, token);

                    var contentDispositionHeader = new System.Net.Mime.ContentDisposition
                    {
                        Inline = true,
                        FileName = fileName
                    };
                    //    Response.Headers.Add("Content-Disposition", contentDispositionHeader.ToString() + "; attachment");
                    Response.Headers.Add("Content-Type", "application/pdf");
                    Response.Headers.Add("Content-Disposition", "attachment; filename=" + fileName);
                    Response.Headers.Add("Content-Transfer-Encoding", "binary");
                    Response.Headers.Add("Content-Length", file.Length.ToString());

                }
            }
            catch (Exception ex)
            {
                this.logger.LogError(ex, "Error getting pdf", null);
                return Ok();
            }

            return File(file, System.Net.Mime.MediaTypeNames.Application.Pdf);
        }

Selama Anda mengembalikan respons 200, kesuksesan di Ajax dapat bekerja dengannya, Anda dapat memeriksa apakah file benar-benar ada atau tidak karena baris di bawah dalam kasus ini akan salah dan Anda dapat memberi tahu pengguna tentang itu:

 if (disposition && disposition.indexOf('attachment') !== -1) {
geli
sumber
0

Saya membutuhkan solusi yang mirip dengan @ alain-cruz, tetapi dalam nuxt / vue dengan beberapa unduhan. Saya tahu browser memblokir banyak unduhan file, dan saya juga memiliki API yang mengembalikan satu set data berformat csv. Saya akan menggunakan JSZip pada awalnya, tetapi saya membutuhkan dukungan IE, jadi inilah solusi saya. Kalau ada yang bisa membantu saya meningkatkan ini akan menjadi besar, tetapi sejauh ini itu bekerja untuk saya.

Pengembalian API:

data : {
  body: {
    fileOne: ""col1", "col2", "datarow1.1", "datarow1.2"...so on",
    fileTwo: ""col1", "col2"..."
  }
}

halaman.vue:

<template>
  <b-link @click.prevent="handleFileExport">Export<b-link>
</template>

export default = {
   data() {
     return {
       fileNames: ['fileOne', 'fileTwo'],
     }
   },
  computed: {
    ...mapState({
       fileOne: (state) => state.exportFile.fileOne,
       fileTwo: (state) => state.exportFile.fileTwo,
    }),
  },
  method: {
    handleExport() {
      //exportFileAction in store/exportFile needs to return promise
      this.$store.dispatch('exportFile/exportFileAction', paramsToSend)
        .then(async (response) => {
           const downloadPrep = this.fileNames.map(async (fileName) => {
           // using lodash to get computed data by the file name
           const currentData = await _.get(this, `${fileName}`);
           const currentFileName = fileName;
           return { currentData, currentFileName };
         });
         const response = await Promise.all(downloadPrep);
         return response;
       })
       .then(async (data) => {
         data.forEach(({ currentData, currentFileName }) => {
           this.forceFileDownload(currentData, currentFileName);
         });
       })
       .catch(console.error);
    },
    forceFileDownload(data, fileName) {
     const url = window.URL
         .createObjectURL(new Blob([data], { type: 'text/csv;charset=utf-8;' }));
     const link = document.createElement('a');
     link.href = url;
     link.setAttribute('download', `${fileName}.csv`);
     document.body.appendChild(link);
     link.click();
   },
}
il0v3d0g
sumber