Handle Datei Download von Ajax Post

393

Ich habe eine Javascript-App, die Ajax-POST-Anfragen an eine bestimmte URL sendet. Die Antwort kann eine JSON-Zeichenfolge oder eine Datei (als Anhang) sein. Ich kann den Inhaltstyp und die Inhaltsdisposition in meinem Ajax-Aufruf leicht erkennen. Wenn ich jedoch feststelle, dass die Antwort eine Datei enthält, wie biete ich dem Client an, sie herunterzuladen? Ich habe hier eine Reihe ähnlicher Themen gelesen, aber keine liefert die Antwort, nach der ich suche.

Bitte, bitte, bitte poste keine Antworten, die darauf hindeuten, dass ich Ajax dafür nicht verwenden oder den Browser umleiten sollte, da nichts davon eine Option ist. Die Verwendung eines einfachen HTML-Formulars ist ebenfalls keine Option. Ich muss dem Client einen Download-Dialog anzeigen. Kann das gemacht werden und wie?

Pavle Predic
quelle
Für diejenigen, die diesen Artikel lesen, lesen Sie diesen Beitrag: stackoverflow.com/questions/20830309/…
sobhan
Ich habe Ihre Lösung aus der Frage entfernt. Sie können es gerne als Antwortbeitrag unten veröffentlichen, aber es gehört nicht in den Fragenbeitrag.
Martijn Pieters

Antworten:

111

Erstellen Sie ein Formular, verwenden Sie die POST-Methode, senden Sie das Formular ab - es ist kein Iframe erforderlich. Wenn die Serverseite auf die Anfrage antwortet, schreiben Sie einen Antwortheader für den MIME-Typ der Datei. Daraufhin wird ein Download-Dialog angezeigt. Ich habe dies einige Male getan.

Sie möchten eine inhaltliche Anwendung / einen inhaltlichen Download - suchen Sie einfach, wie Sie einen Download für die von Ihnen verwendete Sprache bereitstellen können.


quelle
35
Wie in der Frage angegeben: "Die Verwendung eines einfachen HTML-Formulars ist ebenfalls keine Option."
Pavle Predic
13
Nein, da die Verwendung eines normalen POST den Browser zur POST-URL navigieren würde. Ich möchte nicht von der Seite weg navigieren. Ich möchte die Anfrage im Hintergrund ausführen, die Antwort verarbeiten und dem Client präsentieren.
Pavle Predic
6
Wenn der Server Header zurücksendet, wie es die andere Antwort getan hat, wird er in einem neuen Fenster geöffnet - ich habe es bereits getan. Es würde nur weg navigieren, wenn Ihr serverseitiges Skript HTML-Code
1
@PavlePredic Haben Sie am Ende herausgefunden, wie beide Antwortszenarien verwaltet werden können, dh JSON-Textantwort oder Antwort auf Download-Dateien?
Web User
9
Die Antwort ist nicht klar und die vorgeschlagene Lösung funktioniert nicht.
stack247
531

Geben Sie nicht so schnell auf, da dies (in modernen Browsern) mit Teilen der FileAPI möglich ist:

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

Hier ist die alte Version mit jQuery.ajax. Es kann Binärdaten beschädigen, wenn die Antwort in eine Zeichenfolge mit einem Zeichensatz konvertiert wird.

$.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
quelle
1
Vielen Dank ! Ich musste in meiner HTTP-Antwort 'Content-Disposition' zu 'Access-Control-Expose-Headers' und 'Access-Control-Allow-Headers' hinzufügen, damit es funktioniert.
JulienD
1
Es funktioniert nicht, wenn die Datei größer als 500 MB ist. Vielleicht sollten wir eine andere API verwenden?
Hirra
Was ist mit dem Entfernen des a-Elements aus dem DOM im Bereinigungsteil (und nicht nur der URL)? document.body.removeChild(a);
Scoregraphic
@hirra benutze responceType "blob" anstelle von "arraybuffer" und schreibe die Onload-Rückruffunktion so um, dass var blob this.response ist (var blob = this.response;) alsovar 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
Dies ist die perfekte Lösung. Nur eine kleine Änderung. In Typescript brauchte ich window.location.href = downloadUrlstattwindow.location = downloadUrl
michal.jakubeczy
39

Ich stand vor dem gleichen Problem und löste es erfolgreich. Mein Anwendungsfall ist dies.

" JSON-Daten an den Server senden und eine Excel-Datei empfangen. Diese Excel-Datei wird vom Server erstellt und als Antwort an den Client zurückgegeben. Laden Sie diese Antwort als Datei mit benutzerdefiniertem Namen im Browser herunter. "

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

Das obige Snippet macht nur folgendes

  • Posten eines Arrays als JSON auf dem Server mit XMLHttpRequest.
  • Nachdem wir den Inhalt als Blob (binär) abgerufen haben, erstellen wir eine herunterladbare URL, hängen sie an einen unsichtbaren "a" -Link an und klicken darauf.

Hier müssen wir einige Dinge auf der Serverseite sorgfältig einstellen. Ich habe einige Header in Python Django HttpResponse gesetzt. Sie müssen sie entsprechend einstellen, wenn Sie andere Programmiersprachen verwenden.

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

Da ich hier xls (excel) herunterlade, habe ich contentType auf über eins eingestellt. Sie müssen es entsprechend Ihrem Dateityp einstellen. Mit dieser Technik können Sie beliebige Dateien herunterladen.

Naren Yellavula
quelle
33

Welche serverseitige Sprache verwenden Sie? In meiner App kann ich einfach eine Datei von einem AJAX-Aufruf herunterladen, indem ich die richtigen Header in der Antwort von PHP setze:

Header serverseitig einstellen

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;

Dadurch wird der Browser zwar auf diese Download-Seite umgeleitet, aber wie @ahren bereits in seinem Kommentar sagte, wird er nicht von der aktuellen Seite weg navigieren.

Es geht darum, die richtigen Header festzulegen, damit Sie sicher eine geeignete Lösung für die von Ihnen verwendete serverseitige Sprache finden, wenn es sich nicht um PHP handelt.

Umgang mit der Antwortclientseite

Angenommen, Sie wissen bereits, wie ein AJAX-Aufruf getätigt wird, führen Sie auf der Clientseite eine AJAX-Anforderung an den Server aus. Der Server generiert dann einen Link, von dem diese Datei heruntergeladen werden kann, z. B. die Weiterleitungs-URL, auf die Sie verweisen möchten. Der Server antwortet beispielsweise mit:

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

Bei der Verarbeitung der Antwort fügen Sie eine iframein Ihren Körper ein und setzen den iframeSRC des S auf die URL, die Sie gerade so erhalten haben (zur Vereinfachung dieses Beispiels verwenden Sie jQuery):

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

Wenn Sie die richtigen Header wie oben gezeigt festgelegt haben, erzwingt der Iframe einen Download-Dialog, ohne den Browser von der aktuellen Seite weg zu navigieren.

Hinweis

Zusätzlicher Zusatz in Bezug auf Ihre Frage; Ich denke, es ist am besten, JSON immer zurückzugeben, wenn Sie Dinge mit AJAX-Technologie anfordern. Nachdem Sie die JSON-Antwort erhalten haben, können Sie clientseitig entscheiden, was damit geschehen soll. Vielleicht möchten Sie beispielsweise später, dass der Benutzer auf einen Download-Link zur URL klickt, anstatt den Download direkt zu erzwingen. In Ihrem aktuellen Setup müssten Sie dazu sowohl die Client- als auch die Serverseite aktualisieren.

Robin van Baalen
quelle
24

Für diejenigen, die eine Lösung aus einer Winkelperspektive suchen, hat dies für mich funktioniert:

$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
quelle
Dies hat geholfen, aber ich muss den ursprünglichen Dateinamen beibehalten. Ich sehe den Dateinamen in den Antwortheadern unter "Inhaltsdisposition", aber ich kann diesen Wert im Antwortobjekt im Code nicht finden. Das Festlegen link.download = ""führt zu einem zufälligen Guid-Dateinamen und link.download = nullzu einer Datei mit dem Namen "null".
Marie
@Marie, Sie können den Dateinamen zum Zeitpunkt des Uploads mithilfe der Eigenschaft des INPUTElements aufzeichnen HTMLInputElement.files. Weitere Informationen finden Sie in den MDN-Dokumenten zur Dateieingabe .
Tim Hettler
Die Blob-Größe ist begrenzt: stackoverflow.com/questions/28307789/…
user0800
22

Hier ist, wie ich das zum Laufen gebracht habe: 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();
  }
});

Aktualisierte Antwort mit download.js

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

Mayur Padshala
quelle
Danke dafür, ich habe das heute gerade benutzt. Fantastisch
Ryan Wilson
Hallo, brauche ich jQuery 3.0>, damit dies funktioniert?
gbade_
Ich bekomme auch ein leeres PDF mit beiden Beispielen, die Sie gegeben haben. Ich versuche, eine PDF-Datei herunterzuladen.
gbade_
@gbade_ Nein, Sie benötigen keine bestimmte jQuery-Version. Sollte mit allen Versionen gut funktionieren. Haben Sie überprüft, ob das heruntergeladene PDF die richtigen CORS-Header enthält? Alle Fehler auf der Konsole könnten beim Debuggen helfen
Mayur Padshala
Dies funktionierte bei mir mit download.js:success: function (response, status, request) { download(response, "filename.txt", "application/text"); }
Sga
12

Ich sehe, dass Sie bereits eine Lösung gefunden haben, aber ich wollte nur einige Informationen hinzufügen, die jemandem helfen können, mit großen POST-Anfragen dasselbe zu erreichen.

Ich hatte das gleiche Problem vor ein paar Wochen, tatsächlich ist es nicht möglich, einen "sauberen" Download über AJAX zu erreichen. Die Filament Group hat ein jQuery-Plugin erstellt, das genau so funktioniert, wie Sie es bereits herausgefunden haben. Es heißt jQuery File Download hat jedoch einen Nachteil dieser Technik.

Wenn Sie große Anfragen über AJAX senden (z. B. Dateien + 1 MB), wirkt sich dies negativ auf die Reaktionsfähigkeit aus. Bei langsamen Internetverbindungen müssen Sie viel warten, bis die Anforderung gesendet wird, und auch warten, bis die Datei heruntergeladen wurde. Es ist nicht wie ein sofortiger "Klick" => "Popup" => "Download-Start". Es ist eher wie "Klicken" => "Warten, bis Daten gesendet werden" => "Warten auf Antwort" => "Download-Start", wodurch die Datei doppelt so groß erscheint, da Sie auf das Senden der Anforderung warten müssen über AJAX und erhalten Sie es als herunterladbare Datei zurück.

Wenn Sie mit kleinen Dateigrößen <1 MB arbeiten, werden Sie dies nicht bemerken. Aber wie ich in meiner eigenen App festgestellt habe, ist es für größere Dateien fast unerträglich.

Mit meiner App können Benutzer dynamisch generierte Bilder exportieren. Diese Bilder werden über POST-Anforderungen im Base64-Format an den Server gesendet (dies ist der einzig mögliche Weg), dann verarbeitet und in Form von PNG-, JPG-Dateien und Base64 an Benutzer zurückgesendet Zeichenfolgen für Bilder + 1 MB sind sehr groß. Dadurch müssen Benutzer mehr als nötig warten, bis die Datei heruntergeladen wird. Bei langsamen Internetverbindungen kann es sehr ärgerlich sein.

Meine Lösung hierfür bestand darin, die Datei vorübergehend auf den Server zu schreiben, sobald sie fertig ist, dynamisch einen Link zur Datei in Form einer Schaltfläche zu generieren, die zwischen den Zuständen "Bitte warten ..." und "Herunterladen" wechselt Drucken Sie das base64-Bild in einem Vorschau-Popup-Fenster aus, damit Benutzer mit der rechten Maustaste darauf klicken und es speichern können. Dies macht die Wartezeit für Benutzer erträglicher und beschleunigt die Arbeit.

Update 30. September 2014:

Monate sind vergangen, seit ich dies gepostet habe. Endlich habe ich einen besseren Ansatz gefunden, um die Arbeit mit großen Base64-Strings zu beschleunigen. Ich speichere jetzt Base64-Zeichenfolgen in der Datenbank (unter Verwendung von Longtext- oder Longblog-Feldern), übergebe dann ihre Datensatz-ID über den Download der jQuery-Datei. Schließlich frage ich in der Download-Skriptdatei die Datenbank mit dieser ID ab, um die Base64-Zeichenfolge abzurufen und durchzuleiten die Download-Funktion.

Beispiel für ein Download-Skript:

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

Ich weiß, dass dies weit über das hinausgeht, was das OP verlangt hat, aber ich hielt es für gut, meine Antwort mit meinen Ergebnissen zu aktualisieren. Als ich nach Lösungen für mein Problem suchte, las ich viele "Download from AJAX POST data" -Threads, die mir nicht die gesuchte Antwort gaben. Ich hoffe, diese Informationen helfen jemandem, der so etwas erreichen möchte.

José SAYAGO
quelle
Der jQuery File Downloadeinzige leitet mich zur URL weiter. Ich nenne es so : jQuery.download("api/ide/download-this-file.php", {filePath: path2Down}, "POST");.
Casper
5

Ich möchte auf einige Schwierigkeiten hinweisen, die bei der Verwendung der Technik in der akzeptierten Antwort auftreten, dh bei der Verwendung eines Formularposts:

  1. Sie können keine Header für die Anforderung festlegen. Wenn Ihr Authentifizierungsschema Header enthält, ein Json-Web-Token, das im Authorization-Header übergeben wird, müssen Sie eine andere Möglichkeit zum Senden finden, z. B. als Abfrageparameter.

  2. Sie können nicht wirklich sagen, wann die Anfrage abgeschlossen ist. Nun, Sie können ein Cookie verwenden, das bei der Antwort gesetzt wird, wie es von jquery.fileDownload ausgeführt wird , aber es ist bei weitem nicht perfekt. Es funktioniert nicht für gleichzeitige Anforderungen und wird unterbrochen, wenn eine Antwort nie eintrifft.

  3. Wenn der Server mit einem Fehler antwortet, wird der Benutzer zur Fehlerseite weitergeleitet.

  4. Sie können nur die von einem Formular unterstützten Inhaltstypen verwenden . Das heißt, Sie können JSON nicht verwenden.

Am Ende habe ich die Methode zum Speichern der Datei in S3 und zum Senden einer vorsignierten URL verwendet, um die Datei abzurufen.

Tepez
quelle
5

Für diejenigen, die einen moderneren Ansatz suchen, können Sie die verwenden fetch API. Das folgende Beispiel zeigt, wie Sie eine Tabellenkalkulationsdatei herunterladen. Mit dem folgenden Code ist dies problemlos möglich.

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

Ich glaube, dieser Ansatz ist viel einfacher zu verstehen als andere XMLHttpRequestLösungen. Außerdem hat es eine ähnliche Syntax wie der jQueryAnsatz, ohne dass zusätzliche Bibliotheken hinzugefügt werden müssen.

Natürlich würde ich empfehlen, zu überprüfen, in welchem ​​Browser Sie entwickeln, da dieser neue Ansatz im IE nicht funktioniert. Die vollständige Browserkompatibilitätsliste finden Sie unter folgendem [Link] [1].

Wichtig : In diesem Beispiel sende ich eine JSON-Anfrage an einen Server, der die angegebene Funktion abhört url. Dieseurl muss festgelegt werden. In meinem Beispiel gehe ich davon aus, dass Sie diesen Teil kennen. Berücksichtigen Sie auch die Header, die für die Ausführung Ihrer Anforderung erforderlich sind. Da ich einen JSON sende, muss ich den Content-TypeHeader hinzufügen und auf setzen application/json; charset=utf-8, um dem Server den Typ der Anforderung mitzuteilen, die er erhalten wird.

Alain Cruz
quelle
1
Genial! So öffnen Sie dies in einem neuen Tab anstelle eines Download-Popups: Verwenden Sie `` `const window = open (downloadUrl," _blank "); if (window! == null) window.focus (); `` `
andy
Gibt es eine Möglichkeit für mich, dies für mehrere Datensätze zu tun? Beispiel: Holen Sie sich {fileOne: data, fileTwo: data, fileThree: data} vom API-Aufruf zurück und generieren Sie drei heruntergeladene Dateien gleichzeitig? Vielen Dank!
il0v3d0g
Hmmm, das habe ich nicht versucht. Sie können die Bilder jedoch jederzeit in einer Zip-Datei komprimieren und herunterladen. Ich werde prüfen, ob es möglich ist.
Alain Cruz
4

Hier ist meine Lösung mit einem temporären versteckten Formular.

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

Beachten Sie, dass ich JQuery massiv verwende, aber Sie können dasselbe mit nativem JS tun.

Ludovic Martin
quelle
3

Wie bereits erwähnt, können Sie ein Formular zum Herunterladen über eine POST-Anfrage erstellen und senden. Sie müssen dies jedoch nicht manuell tun.

Eine wirklich einfache Bibliothek, um genau dies zu tun, ist jquery.redirect . Es bietet eine API ähnlich der Standardmethode jQuery.post:

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

Dies ist eine 3 Jahre alte Frage, aber ich hatte heute das gleiche Problem. Ich habe nach Ihrer bearbeiteten Lösung gesucht, aber ich denke, dass sie die Leistung beeinträchtigen kann, da eine doppelte Anfrage gestellt werden muss. Wenn also jemand eine andere Lösung benötigt, bei der der Dienst nicht zweimal aufgerufen werden muss, habe ich dies folgendermaßen getan:

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

Dieses Formular wird nur verwendet, um den Dienst aufzurufen und die Verwendung von window.location () zu vermeiden. Danach müssen Sie nur noch ein Formular von jquery senden, um den Dienst aufzurufen und die Datei abzurufen. Es ist ziemlich einfach, aber auf diese Weise können Sie einen Download mit einem POST durchführen . Ich weiß jetzt, dass dies einfacher sein könnte, wenn der Dienst, den Sie anrufen, ein GET ist , aber das ist nicht mein Fall.

Jairo Miranda
quelle
1
Dies ist kein Ajax-Beitrag, da die Frage
Ajax
Es ist nur eine Lösung für das oben genannte Problem, jedoch nicht für Ajax-Anrufe.
Nomi Ali
1

Ich habe diese FileSaver.js verwendet . In meinem Fall mit CSV-Dateien habe ich dies getan (in Coffescript):

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

Ich denke, für den kompliziertesten Fall müssen die Daten ordnungsgemäß verarbeitet werden. Unter der Haube implementieren FileSaver.js den gleichen Ansatz wie die Antwort von Jonathan Amend .

Armando
quelle
1
..aber können Sie Dateien normalerweise in iOS herunterladen?
Alex Marshall
1

Um Jonathan Amends Antwort auf die Arbeit in Edge zu erhalten, habe ich die folgenden Änderungen vorgenommen:

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

dazu

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

Ich hätte dies lieber als Kommentar gepostet, aber dafür habe ich nicht genug Ruf

fstrandner
quelle
0

Hier ist meine Lösung aus verschiedenen Quellen: Serverseitige Implementierung:

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

Clientseitige Implementierung (mit 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
quelle
0

Es gibt eine andere Lösung zum Herunterladen einer Webseite in Ajax. Ich beziehe mich jedoch auf eine Seite, die zuerst verarbeitet und dann heruntergeladen werden muss.

Zuerst müssen Sie die Seitenverarbeitung vom Ergebnisdownload trennen.

1) Im Ajax-Aufruf werden nur die Seitenberechnungen durchgeführt.

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

       Funktion (Daten, Status) 
       {
            if (status == "success") 
            {
                / * 2) In der Antwort wird die Seite heruntergeladen, die die vorherigen Berechnungen verwendet. Dies kann beispielsweise eine Seite sein, auf der die Ergebnisse einer im Ajax-Aufruf berechneten Tabelle gedruckt werden. * /
                window.location.href = DownloadPage.php + "? ID =" + 29;
            }}               
       }}
);

// Zum Beispiel: in der CalculusPage.php

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

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

// Zum Beispiel: in der DownloadPage.php

    $ ID = $ _GET ["ID"];

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

    $ filename = "Export_Data.xls";
    Header ("Inhaltstyp: application / vnd.ms-excel");
    Header ("Content-Disposition: Inline; Dateiname = $ Dateiname");

    ...

Ich hoffe, diese Lösung kann für viele nützlich sein, so wie es für mich war.

Netluke
quelle
0

Wenn die Antwort ein Array-Puffer ist , versuchen Sie dies unter onsuccess-Ereignis in 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;
      }
  • Dabei ist event.data die Antwort, die in der Erfolgsfunktion des xhr-Ereignisses empfangen wurde.
Abhishek Sinha
quelle
0

Unten ist meine Lösung zum Herunterladen mehrerer Dateien in Abhängigkeit von einer Liste, die aus einigen IDs besteht, und zum Nachschlagen in der Datenbank. Die Dateien werden ermittelt und können heruntergeladen werden - sofern vorhanden. Ich rufe die C # MVC-Aktion für jede Datei mit Ajax auf.

Und ja, wie andere sagten, ist es möglich, dies in jQuery Ajax zu tun. Ich habe es mit Ajax-Erfolg gemacht und sende immer die Antwort 200.

Das ist also der Schlüssel:

  success: function (data, textStatus, xhr) {

Und das ist mein Code:

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

Dann rufen Sie an:

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

Solange Sie die Antwort 200 zurückgeben, kann der Erfolg in Ajax damit funktionieren. Sie können überprüfen, ob die Datei tatsächlich vorhanden ist oder nicht, da die folgende Zeile in diesem Fall falsch wäre, und Sie können den Benutzer darüber informieren:

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

Ich brauchte eine ähnliche Lösung wie @ alain-cruz, aber in nuxt / vue mit mehreren Downloads. Ich weiß, dass Browser mehrere Dateidownloads blockieren, und ich habe auch eine API, die eine Reihe von CSV-formatierten Daten zurückgibt. Ich wollte zuerst JSZip verwenden, brauchte aber IE-Unterstützung. Hier ist meine Lösung. Wenn mir jemand helfen kann, das zu verbessern, wäre das großartig, aber es funktioniert bisher für mich.

API gibt zurück:

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