Обробляти завантаження файлів з публікації Ajax


392

У мене є програма javascript, яка надсилає запити ajax POST на певну URL-адресу. Відповідь може бути рядком JSON або може бути файлом (як додаток). Я можу легко виявити тип вмісту та вміст-диспозицію у своєму виклику ajax, але як тільки я виявляю, що відповідь містить файл, як я запропоную клієнту завантажити його? Тут я прочитав ряд подібних тем, але жодна з них не дає відповіді, яку я шукаю.

Будь ласка, будь ласка, не публікуйте відповідей, які напрошують, що я не повинен використовувати ajax для цього або що я повинен перенаправляти браузер, оскільки нічого з цього не є можливим. Використання простої форми HTML також не є можливим. Що мені потрібно - це показати діалогу для завантаження клієнту. Чи можна це зробити і як?


Для тих , хто читає цю статтю, прочитав цей пост: stackoverflow.com/questions/20830309 / ...
Собхана

Я усунув ваше рішення з питання. Ви можете розмістити його як пост відповіді нижче, але він не належить до повідомлення.
Martijn Pieters

Відповіді:


111

Створіть форму, скористайтеся методом POST, надішліть форму - немає потреби в iframe. Коли сторінка сервера відповідає на запит, напишіть заголовок відповіді для типу mime файлу, і він представить діалогове вікно завантаження - я це робив неодноразово.

Ви хочете застосувати / завантажувати вміст типу вмісту - просто шукайте, як забезпечити завантаження будь-якою мовою.


35
Як зазначено в запитанні: "Використання простої форми HTML також не є варіантом".
Павло Продик

13
Ні, оскільки використання звичайного POST переходитиме до браузера до URL POST. Я не хочу переходити від сторінки. Я хочу виконати запит у фоновому режимі, обробити відповідь і представити його клієнту.
Павло Продик

6
Якщо сервер надсилає назад заголовки, як і інші відповіді, він відкриється у новому вікні - я це робив раніше. Він відправиться лише в тому випадку, якщо ваш серверний скрипт поверне HTML-код

1
@PavlePredic Ви нарешті з'ясували, як керувати обома сценаріями відповідей, тобто текстовою відповіддю JSON або завантаженням відповіді на файл?
Веб-користувач

9
Відповідь не зрозуміла, і запропоноване рішення не працює.
stack247

530

Не здавайтеся так швидко, адже це можна зробити (в сучасних браузерах) за допомогою частин 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));

Ось стара версія за допомогою jQuery.ajax. Він може обробляти двійкові дані, коли відповідь перетворюється на рядок якоїсь діаграми.

$.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
        }
    }
});

1
Дуже дякую ! Мені довелося додати "Зміст-диспозиція" до "Доступ-контроль-викрити-заголовки" та "доступ-контроль-дозволити-заголовки" у своєму HTTP-відповіді, щоб це працювало.
JulienD

1
Він не працює, коли файл перевищує 500 Мб, можливо, ми повинні використати інший api?
hirra

Що з видаленням елемента з DOM у частині очищення (а не лише у URL)? document.body.removeChild(a);
Scoregraphic

@hirra використовуйте ResponseType "blob" замість "arraybuffer" і перепишіть функцію зворотного виклику при завантаженні таким чином, що var blob - це this.response (var blob = this.response;) такvar 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 }); } */
Кріс Тобба

1
Це ідеальне рішення. Всього одна невелика зміна. В Typescript мені потрібно було window.location.href = downloadUrlзамістьwindow.location = downloadUrl
michal.jakubeczy

39

Я зіткнувся з тим же питанням і успішно вирішив його. Мій приклад використання такий.

" Опублікуйте дані JSON на сервер і отримайте файл excel. Цей файл excel створюється сервером і повертається як відповідь клієнтові. Завантажте цю відповідь як файл із власною назвою у браузері "

$("#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));
});

Наведений вище фрагмент просто робить наступне

  • Опублікування масиву як JSON на сервері за допомогою XMLHttpRequest.
  • Після отримання вмісту у вигляді краплі (бінарного) ми створюємо завантажувану URL-адресу та приєднуємо її до невидимого посилання "a" та натискаємо його.

Тут нам потрібно ретельно встановити кілька речей на стороні сервера. Я встановив кілька заголовків у Python Django HttpResponse. Ви повинні встановити їх відповідно, якщо ви використовуєте інші мови програмування.

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

Оскільки я завантажую сюди xls (excel), я налаштував contentType вище, ніж один. Потрібно встановити його відповідно до типу файлу. Ви можете використовувати цю техніку для завантаження будь-яких файлів.


33

Якою мовою на стороні сервера ви користуєтесь? У своєму додатку я можу легко завантажити файл із дзвінка AJAX, встановивши правильні заголовки у відповіді PHP:

Налаштування заголовків на стороні сервера

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;

Це насправді 'перенаправить' браузер на цю сторінку завантаження, але як сказав @ahren alread у своєму коментарі, він не відійде від поточної сторінки.

Вся справа в налаштуванні правильних заголовків, тому я впевнений, що ви знайдете підходяще рішення для мови серверного сервера, яку ви використовуєте, якщо це не PHP.

Поводження зі стороною клієнта відповіді

Припускаючи, що ви вже знаєте, як здійснити дзвінок AJAX, на стороні клієнта ви виконуєте запит AJAX на сервер. Потім сервер генерує посилання, звідки можна завантажити цей файл, наприклад URL-адресу "вперед", на яку ви хочете вказати. Наприклад, сервер відповідає:

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

Обробляючи відповідь, ви вводите iframeв своє тіло і встановлюєте iframeSRC в URL, який ви щойно отримали таким чином (використовуючи jQuery для зручності цього прикладу):

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

Якщо ви встановили правильні заголовки, як показано вище, iframe примушує діалогове вікно завантаження, не переходячи браузер від поточної сторінки.

Примітка

Додаткове доповнення стосовно вашого запитання; Я думаю, що найкраще завжди повертати JSON під час запиту матеріалів за технологією AJAX. Отримавши відповідь JSON, ви можете вирішити на стороні клієнта, що з цим робити. Можливо, наприклад, пізніше ви бажаєте, щоб користувач натискав посилання на завантаження до URL-адреси, а не примушував завантажувати безпосередньо, у вашій поточній настройці вам доведеться оновити як клієнт, так і сервер.


24

Для тих, хто шукає рішення з кутової точки зору, це працювало для мене:

$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();
});

Це допомогло, але мені потрібно зберегти оригінальне ім’я файлу. Я бачу ім'я файлу у заголовках відповідей у ​​розділі "Вміст-диспозиція", але не можу знайти це значення в об'єкті відповіді в коді. Встановлення link.download = ""результатів у випадковому імені файлу настанови та link.download = nullрезультаті у файлі під назвою "null".
Марі

@Marie, ви можете записати ім'я файлу під час завантаження, використовуючи властивість INPUTелемента HTMLInputElement.files. Докладніше дивіться документи MDN на вводі файлу .
Тім Хеттлер

Розмір Blob обмежений: stackoverflow.com/questions/28307789 / ...
user0800

22

Ось як я отримав цей робочий https://stackoverflow.com/a/27563953/2845977

$.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();
  }
});

Оновлена ​​відповідь за допомогою download.js

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


Дякую за це, я просто сьогодні цим скористався. Фантастичний
Райан Вілсон

Привіт, мені потрібно мати jQuery 3.0>, щоб це працювало?
gbade_

Я також отримую порожній pdf з обома прикладами, які ви подали. Я намагаюся змусити його завантажити файл PDF.
gbade_

@gbade_ Ні, вам не потрібна конкретна версія jQuery. Має працювати нормально з усіма версіями. Ви перевіряли, чи PDF-файл, який ви завантажуєте, має правильні заголовки CORS? Будь-які помилки на консолі можуть допомогти налагоджувати
Mayur Padshala

Це працювало для мене за допомогою download.js:success: function (response, status, request) { download(response, "filename.txt", "application/text"); }
Sga

12

Я бачу, що ви вже знайшли рішення, однак я просто хотів додати деяку інформацію, яка може допомогти комусь, хто намагається досягти того ж з великими POST-запитами.

У мене був такий самий випуск пару тижнів тому, дійсно неможливо досягти «чистого» завантаження через AJAX, група Filament створила плагін jQuery, який працює точно так, як ви вже дізналися, називається jQuery File Завантажте, проте, є недолік цієї методики.

Якщо ви надсилаєте великі запити через AJAX (скажімо, файли + 1 Мб), це негативно вплине на чуйність. У повільних підключеннях до Інтернету , вам доведеться чекати багато , поки клопотання не надсилаються , а також чекати файл для завантаження. Це не як миттєвий "click" => "popup" => "start download". Це більше схоже на "click" => "дочекатися надсилання даних" => "зачекати відповіді" => "почати завантаження", завдяки чому файл видається вдвічі більшим за його розмір, тому що вам доведеться чекати надсилання запиту через AJAX і поверніть його як завантажуваний файл.

Якщо ви працюєте з невеликими розмірами файлів <1 Мб, ви цього не помітите. Але, як я виявив у власній програмі, для великих розмірів файлів це майже нестерпно.

Моя програма дозволяє користувачам експортувати зображення, що динамічно генеруються, ці зображення надсилаються через POST-запити у форматі base64 на сервер (це єдиний можливий спосіб), потім обробляються та надсилаються користувачам у вигляді .png, .jpg файлів, base64 рядки для зображень + 1 Мб величезні, це змушує користувачів чекати більше, ніж потрібно, щоб файл розпочав завантаження. У повільних підключеннях до Інтернету це може бути дуже прикро.

Моє рішення для цього полягало в тимчасовому записуванні файлу на сервер, як тільки він буде готовий, динамічно генерувати посилання на файл у вигляді кнопки, яка змінюється між станами "Будь ласка, зачекайте ..." і "Завантажити" і одночасно Надрукуйте зображення base64 у спливаючому вікні попереднього перегляду, щоб користувачі могли «клацнути правою кнопкою миші» та зберегти її. Це робить весь час очікування більш приємним для користувачів, а також прискорює роботу.

Оновлення 30 вересня 2014 року:

Минуло місяці з моменту публікації цього запису, нарешті, я знайшов кращий підхід до прискорення роботи під час роботи з великими base64 рядками. Тепер я зберігаю рядки base64 в базі даних (використовуючи поля longtext або longblog), потім передаю її ідентифікатор запису через файл завантаження файлу jQuery, нарешті, на файл сценарію завантаження, я запитую базу даних за допомогою цього ідентифікатора, щоб витягнути рядок base64 і передати її через функція завантаження.

Приклад сценарію:

<?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;
?>

Я знаю, що це далеко не те, що запитувала ОП, проте я вважав, що було б добре доповнити свою відповідь своїми висновками. Коли я шукав рішення своєї проблеми, я прочитав багато тем "Завантажити з даних AJAX POST", які не дали мені відповіді, яку я шукав, сподіваюся, ця інформація допомагає комусь, хто прагне досягти чогось подібного.


jQuery File DownloadТільки перенаправити мене до заслання. Я називаю це так: jQuery.download("api/ide/download-this-file.php", {filePath: path2Down}, "POST");.
Каспер

5

Хочу зазначити деякі труднощі, які виникають при використанні методики у прийнятій відповіді, тобто при використанні форми форми:

  1. Ви не можете встановити заголовки на запит. Якщо ваша схема аутентифікації включає заголовки, Json-Web-Token, переданий у заголовку Авторизації, вам доведеться знайти інший спосіб надіслати його, наприклад, як параметр запиту.

  2. Ви не можете сказати, коли запит закінчено. Ну, ви можете використовувати файл cookie, який встановлюється у відповідь, як це робиться jquery.fileDownload , але це FAR від ідеального. Він не працює для одночасних запитів, і він порушиться, якщо відповідь ніколи не надійде.

  3. Якщо сервер відповість помилкою, користувач буде перенаправлений на сторінку помилок.

  4. Ви можете використовувати лише типи вмісту, підтримувані формою . Це означає, що ви не можете використовувати JSON.

Я в кінцевому підсумку використовував метод збереження файлу на S3 та надсилав попередньо підписану URL-адресу, щоб отримати файл.


5

Для тих, хто шукає більш сучасний підхід, можна скористатися fetch API. Наступний приклад показує, як завантажити файл електронних таблиць. Це легко зробити за допомогою наступного коду.

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();
})

Я вважаю, що цей підхід зрозуміти набагато простіше, ніж інші XMLHttpRequestрішення. Крім того, він має подібний синтаксис до jQueryпідходу, без необхідності додавати додаткові бібліотеки.

Звичайно, я б радив перевірити, для якого браузера ви розробляєте, оскільки цей новий підхід не працюватиме на IE. Повний список сумісності браузера ви можете знайти на наступному [посиланні] [1].

Важливо : У цьому прикладі я надсилаю запит JSON серверу, який слухає дані url. Це urlпотрібно встановити, на моєму прикладі я припускаю, що ви знаєте цю частину. Також врахуйте заголовки, необхідні для вашого запиту на роботу. Оскільки я надсилаю JSON, я повинен додати Content-Typeзаголовок і встановити його application/json; charset=utf-8, щоб сервер знав тип запиту, який він отримає.


1
Дивовижно! Щоб відкрити це на новій вкладці замість спливаючого вікна завантаження: використовуйте `` `вікно const = відкрити (downloadUrl," _blank "); if (window! == null) window.focus (); `` `
Енді

Чи є спосіб для мене зробити це для кількох наборів даних? Наприклад, поверніться {fileOne: data, fileTwo: data, fileThree: data} з виклику api та генеруйте три завантажені файли одночасно? Дякую!
il0v3d0g

Гммм, я цього не пробував. Але ви завжди можете стискати зображення у поштовому файлі та завантажувати їх. Я перевірю, чи можливо.
Ален Крус

4

Ось моє рішення з використанням тимчасової прихованої форми.

//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();

Зауважте, що я масово використовую JQuery, але ви можете зробити те ж саме з рідним JS.


3

Як заявили інші, ви можете створити та надіслати форму для завантаження через POST-запит. Однак вам не доведеться робити це вручну.

Одна дійсно проста бібліотека для саме цього - jquery.redirect . Він надає API, аналогічний стандартному jQuery.postметоду:

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

3

Це трирічне запитання, але у мене була така ж проблема і сьогодні. Я переглянув ваше відредаговане рішення, але думаю, що воно може принести в жертву продуктивність, оскільки воно має зробити подвійний запит. Тож якщо комусь потрібне інше рішення, яке не передбачає дзвонити в службу двічі, то це я так:

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

Ця форма просто використовується для виклику служби та уникає використання window.location (). Після цього вам просто потрібно змусити подати форму з jquery, щоб зателефонувати в службу та отримати файл. Це досить просто, але таким чином ви можете зробити завантаження за допомогою POST . Я тепер, що це може бути простіше, якщо послуга, яку ви телефонуєте, GET , але це не мій випадок.


1
Це не посада в аякс, оскільки в питанні використовується ajax
Нідхін Девід

це лише рішення вищезгаданої проблеми, однак не для дзвінків Ajax.
Номі Алі

1

Я використовував цей FileSaver.js . У моєму випадку з файлами csv я зробив це (у кофескрипті):

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

Я думаю, що для найбільш складного випадку дані повинні бути оброблені належним чином. Файл FileSaver.js реалізує той самий підхід у відповіді Джонатана Аменда .


1
..але ви можете завантажувати файли зазвичай в iOS?
Алекс Маршалл

1

см: http://www.henryalgus.com/reading-binary-files-using-jquery-ajax/ він буде повертати блоб в якості відповіді, який потім може бути введений в filesaver


2
Хоча це теоретично може відповісти на питання, бажано було б сюди включити істотні частини відповіді та надати посилання для довідки.
Bhargav Rao

1

Щоб Jonathan Amends відповів на роботу в Edge, я вніс такі зміни:

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

до цього

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

Я б швидше розмістив це як коментар, але мені не вистачає репутації для цього


0

Ось моє рішення, зібране з різних джерел: Реалізація на сервері:

    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;
    }

Реалізація на стороні клієнта (використовуючи 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();
    }
});   

0

є ще одне рішення для завантаження веб-сторінки в ajax. Але я маю на увазі сторінку, яку спочатку треба обробити, а потім завантажити.

Спочатку потрібно відокремити обробку сторінки від завантаження результатів.

1) У виклику ajax проводяться лише розрахунки сторінки.

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

       функція (дані, статус) 
       {
            if (статус == "успіх") 
            {
                / * 2) У відповідь завантажується сторінка, яка використовує попередні обчислення. Наприклад, це може бути сторінка, яка друкує результати таблиці, обчислені під час виклику ajax. * /
                window.location.href = DownloadPage.php + "? ID =" + 29;
            }               
       }
);

// Наприклад: у CalculusPage.php

    if (! порожній ($ _ POST ["calculusFunction"])) 
    {
        $ ID = $ _POST ["ID"];

        $ query = "ВСТАВИТЬ INTO ExamplePage (data1, data2) VALUES ('". $ _ POST ["data1"]. "", "". $ _ POST ["data2"]. "") WHERE id = ". $ ID;
        ...
    }

// Наприклад: у програмі DownloadPage.php

    $ ID = $ _GET ["ID"];

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

    $ filename = "Export_Data.xls";
    заголовок ("Content-Type: application / vnd.ms-excel");
    заголовок ("Content-Disposition: inline; filename = $ filename");

    ...

Я сподіваюся, що це рішення може бути корисним для багатьох, як і для мене.


0

Якщо відповідь - це буфер масиву , спробуйте це в рамках події успіху в 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;
      }
  • де event.data - відповідь, отримана у функції успіху xhr події.

0

Нижче моє рішення для завантаження декількох файлів залежно від списку, який складається з деяких ідентифікаторів та пошуку в базі даних, файли будуть визначені та готові до завантаження - якщо такі існують. Я закликаю дії C # MVC для кожного файлу за допомогою Ajax.

І так, як казали інші, це можна зробити в jQuery Ajax. Я зробив це з успіхом Ajax, і я завжди надсилаю відповідь 200.

Отже, це ключ:

  success: function (data, textStatus, xhr) {

І це мій код:

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);
            }
        }

Тоді дзвони:

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);
        }

Поки ви повернете відповідь 200, успіх в Ajax може працювати з ним, ви можете перевірити, чи файл існує насправді чи ні, оскільки рядок нижче в цьому випадку буде помилковим, і ви можете повідомити про це користувача:

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

0

Мені потрібно було подібне рішення до рішення @ alain-cruz, але в nuxt / vue з декількома завантаженнями. Я знаю, що браузери блокують кілька завантажень файлів, і у мене також є API, який повертає набір даних, відформатованих CSV. Спочатку я збирався використовувати JSZip, але мені потрібна підтримка IE, ось ось моє рішення. Якщо хтось може допомогти мені покращити це, це було б чудово, але це працює для мене поки що.

API повертає:

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

page.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();
   },
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.