it-swarm.com.de

JavaScript / jQuery zum Herunterladen der Datei über POST mit JSON-Daten

Ich habe eine JQuery-basierte Single-Page-Webapp. Es kommuniziert mit einem RESTful-Webdienst über AJAX Anrufe.

Ich versuche Folgendes zu erreichen:

  1. Senden Sie eine POST, die JSON-Daten enthält, an eine REST url.
  2. Wenn die Anforderung eine JSON-Antwort angibt, wird JSON zurückgegeben.
  3. Wenn die Anforderung eine PDF/XLS/etc-Antwort angibt, wird eine herunterladbare Binärdatei zurückgegeben.

Ich habe 1 & 2 jetzt arbeiten, und die Client-JQuery-App zeigt die zurückgegebenen Daten auf der Webseite durch Erstellen von DOM-Elementen basierend auf den JSON-Daten. Ich habe auch # 3 aus der Sicht des Webservices, was bedeutet, dass eine Binärdatei erstellt und zurückgegeben wird, wenn die richtigen JSON-Parameter angegeben werden. Ich bin mir jedoch nicht sicher, wie ich am besten mit Nummer 3 des Client-Javascript-Codes umgehen soll.

Ist es möglich, eine herunterladbare Datei von einem solchen Ajax-Aufruf zurückzugewinnen? Wie lasse ich den Browser die Datei herunterladen und speichern?

$.ajax({
    type: "POST",
    url: "/services/test",
    contentType: "application/json",
    data: JSON.stringify({category: 42, sort: 3, type: "pdf"}),
    dataType: "json",
    success: function(json, status){
        if (status != "success") {
            log("Error loading data");
            return;
        }
        log("Data loaded!");
    },
    error: function(result, status, err) {
        log("Error loading data");
        return;
    }
});

Der Server antwortet mit folgenden Headern:

Content-Disposition:attachment; filename=export-1282022272283.pdf
Content-Length:5120
Content-Type:application/pdf
Server:Jetty(6.1.11)

Eine andere Idee ist, das PDF und es auf dem Server zu speichern und JSON zurückzugeben, das eine URL zu der Datei enthält. Dann setzen Sie einen weiteren Aufruf im Ajax Success Handler ab, um etwas wie das Folgende zu tun:

success: function(json,status) {
    window.location.href = json.url;
}

Dies bedeutet jedoch, dass ich mehr als einen Anruf beim Server tätigen müsste und mein Server herunterladbare Dateien erstellen, diese irgendwo speichern und diesen Speicherbereich dann regelmäßig bereinigen müsste.

Es muss einen einfacheren Weg geben, dies zu erreichen. Ideen?


BEARBEITEN: Nachdem ich die Dokumente auf $ .ajax überprüft habe, sehe ich, dass der Antwortdatentyp nur xml, html, script, json, jsonp, text Sein kann; Betten Sie die Binärdatei in das Daten-URI-Schema ein, wie in der @ VinayC-Antwort vorgeschlagen (was ich nicht tun möchte).

Ich schätze, meine Optionen sind:

  1. Verwenden Sie nicht Ajax und senden Sie stattdessen einen Formularbeitrag und binden Sie meine JSON-Daten in die Formularwerte ein. Müsste wohl mit versteckten iframes und so was rumspielen.

  2. Verwenden Sie nicht Ajax und konvertieren Sie stattdessen meine JSON-Daten in eine Abfragezeichenfolge, um eine Standard-GET-Anforderung zu erstellen und window.location.href auf diese URL festzulegen. Möglicherweise muss event.preventDefault () in meinem Click-Handler verwendet werden, damit sich der Browser nicht von der Anwendungs-URL ändert.

  3. Verwenden Sie meine andere Idee oben, aber mit Vorschlägen aus der Antwort von @naikus erweitert. Senden AJAX Anfrage mit einem Parameter, der dem Webdienst mitteilt, dass dies über einen Ajax-Aufruf erfolgt. Wenn der Webdienst über einen Ajax-Aufruf aufgerufen wird, geben Sie einfach JSON mit einer URL an die generierte Adresse zurück resource: Wenn die Ressource direkt aufgerufen wird, geben Sie die eigentliche Binärdatei zurück.

Je mehr ich darüber nachdenke, desto mehr gefällt mir die letzte Option. Auf diese Weise kann ich Informationen über die Anforderung (Generierungszeit, Dateigröße, Fehlermeldungen usw.) abrufen und auf diese Informationen reagieren, bevor ich mit dem Download beginne. Der Nachteil ist die zusätzliche Dateiverwaltung auf dem Server.

Irgendwelche anderen Möglichkeiten, um dies zu erreichen? Welche Vor-/Nachteile dieser Methoden sollten mir bekannt sein?

235
Tauren

letronje funktioniert nur bei sehr einfachen Seiten. document.body.innerHTML += übernimmt den HTML-Text des Texts, hängt den iframe-HTML-Code an und setzt das innerHTML der Seite auf diesen String. Dadurch werden unter anderem alle Event-Bindungen auf Ihrer Seite gelöscht. Erstellen Sie ein Element und verwenden Sie stattdessen appendChild.

$.post('/create_binary_file.php', postData, function(retData) {
  var iframe = document.createElement("iframe");
  iframe.setAttribute("src", retData.url);
  iframe.setAttribute("style", "display: none");
  document.body.appendChild(iframe);
}); 

Oder mit jQuery

$.post('/create_binary_file.php', postData, function(retData) {
  $("body").append("<iframe src='" + retData.url+ "' style='display: none;' ></iframe>");
}); 

Was dies tatsächlich bewirkt: Führen Sie ein Posting in /create_binary_file.php mit den Daten in der Variablen postData durch; Wenn dieser Beitrag erfolgreich abgeschlossen wurde, fügen Sie dem Hauptteil der Seite einen neuen Iframe hinzu. Die Annahme ist, dass die Antwort von /create_binary_file.php den Wert 'url' enthält. Dies ist die URL, von der die generierte PDF/XLS/etc-Datei heruntergeladen werden kann. Wenn Sie der Seite, die auf diese URL verweist, einen Iframe hinzufügen, fordert der Browser den Benutzer zum Herunterladen der Datei auf, vorausgesetzt, der Webserver verfügt über die entsprechende Konfiguration des MIME-Typs.

167
SamStephens

Ich habe mit einer anderen Option herumgespielt, die Blobs verwendet. Ich habe es geschafft, Textdokumente herunterzuladen, und ich habe PDFs heruntergeladen (jedoch sind sie beschädigt).

Mit der Blob-API können Sie Folgendes tun:

$.post(/*...*/,function (result)
{
    var blob=new Blob([result]);
    var link=document.createElement('a');
    link.href=window.URL.createObjectURL(blob);
    link.download="myFileName.txt";
    link.click();

});

Dies ist IE 10+, Chrome 8+, FF 4+. Siehe https://developer.mozilla.org/en-US /docs/Web/API/URL.createObjectURL

Es wird nur die Datei in Chrome, Firefox und Opera heruntergeladen. Hierbei wird ein Download-Attribut für das Ankertag verwendet, um den Browser zum Download zu zwingen.

40
JoshBerke

Ich kenne diese Art von alt, aber ich denke, ich habe eine elegantere Lösung gefunden. Ich hatte genau das gleiche Problem. Das Problem mit den vorgeschlagenen Lösungen bestand darin, dass alle Dateien auf dem Server gespeichert werden mussten, aber ich wollte die Dateien nicht auf dem Server speichern, da dies zu anderen Problemen führte (Sicherheit: Auf die Datei konnte dann von zugegriffen werden nicht authentifizierte Benutzer, Bereinigung: Wie und wann werden die Dateien entfernt? Und wie Sie waren meine Daten komplexe, verschachtelte JSON-Objekte, die sich nur schwer in ein Formular einfügen ließen.

Ich habe zwei Serverfunktionen erstellt. Der erste validierte die Daten. Wenn ein Fehler aufgetreten ist, wird er zurückgegeben. Wenn es kein Fehler war, habe ich alle Parameter als base64-Zeichenfolge serialisiert/codiert zurückgegeben. Dann habe ich auf dem Client ein Formular, das nur eine versteckte Eingabe hat und an eine zweite Serverfunktion sendet. Ich setze die versteckte Eingabe auf den base64-String und übermittle das Format. Die zweite Serverfunktion dekodiert/deserialisiert die Parameter und generiert die Datei. Das Formular kann in einem neuen Fenster oder einem Iframe auf der Seite eingereicht werden, und die Datei wird geöffnet.

Es ist ein bisschen mehr Arbeit und vielleicht ein bisschen mehr Verarbeitung erforderlich, aber insgesamt habe ich mich mit dieser Lösung viel besser gefühlt.

Code ist in C #/MVC

    public JsonResult Validate(int reportId, string format, ReportParamModel[] parameters)
    {
        // TODO: do validation

        if (valid)
        {
            GenerateParams generateParams = new GenerateParams(reportId, format, parameters);

            string data = new EntityBase64Converter<GenerateParams>().ToBase64(generateParams);

            return Json(new { State = "Success", Data = data });
        }

        return Json(new { State = "Error", Data = "Error message" });
    }

    public ActionResult Generate(string data)
    {
        GenerateParams generateParams = new EntityBase64Converter<GenerateParams>().ToEntity(data);

        // TODO: Generate file

        return File(bytes, mimeType);
    }

auf dem Client

    function generate(reportId, format, parameters)
    {
        var data = {
            reportId: reportId,
            format: format,
            params: params
        };

        $.ajax(
        {
            url: "/Validate",
            type: 'POST',
            data: JSON.stringify(data),
            dataType: 'json',
            contentType: 'application/json; charset=utf-8',
            success: generateComplete
        });
    }

    function generateComplete(result)
    {
        if (result.State == "Success")
        {
            // this could/should already be set in the HTML
            formGenerate.action = "/Generate";
            formGenerate.target = iframeFile;

            hidData = result.Data;
            formGenerate.submit();
        }
        else
            // TODO: display error messages
    }
15
amersk

Es gibt eine einfachere Möglichkeit, ein Formular zu erstellen und es zu veröffentlichen. Dies birgt die Gefahr, dass die Seite zurückgesetzt wird, wenn der Rückgabemimetyp etwas ist, das ein Browser öffnen würde, aber für csv und so ist es perfekt

Beispiel erfordert Unterstrich und Abfrage

var postData = {
    filename:filename,
    filecontent:filecontent
};
var fakeFormHtmlFragment = "<form style='display: none;' method='POST' action='"+SAVEAS_PHP_MODE_URL+"'>";
_.each(postData, function(postValue, postKey){
    var escapedKey = postKey.replace("\\", "\\\\").replace("'", "\'");
    var escapedValue = postValue.replace("\\", "\\\\").replace("'", "\'");
    fakeFormHtmlFragment += "<input type='hidden' name='"+escapedKey+"' value='"+escapedValue+"'>";
});
fakeFormHtmlFragment += "</form>";
$fakeFormDom = $(fakeFormHtmlFragment);
$("body").append($fakeFormDom);
$fakeFormDom.submit();

Stellen Sie für Dinge wie HTML, Text und dergleichen sicher, dass der Mimetyp so etwas wie application/octet-stream ist

pHP-Code

<?php
/**
 * get HTTP POST variable which is a string ?foo=bar
 * @param string $param
 * @param bool $required
 * @return string
 */
function getHTTPPostString ($param, $required = false) {
    if(!isset($_POST[$param])) {
        if($required) {
            echo "required POST param '$param' missing";
            exit 1;
        } else {
            return "";
        }
    }
    return trim($_POST[$param]);
}

$filename = getHTTPPostString("filename", true);
$filecontent = getHTTPPostString("filecontent", true);

header("Content-type: application/octet-stream");
header("Content-Disposition: attachment; filename=\"$filename\"");
echo $filecontent;
9
aqm

Kurz gesagt, es gibt keinen einfacheren Weg. Sie müssen einen weiteren Server anfordern, um die PDF Datei anzuzeigen. Es gibt zwar nur wenige Alternativen, diese sind jedoch nicht perfekt und funktionieren nicht in allen Browsern:

  1. Schauen Sie sich Daten-URI-Schema an. Wenn die Binärdaten klein sind, können Sie möglicherweise Javascript verwenden, um ein Fenster zu öffnen, in dem Daten in URI übergeben werden.
  2. Die einzige Lösung für Windows/IE wäre, über die .NET-Steuerung oder FileSystemObject zu verfügen, um die Daten im lokalen Dateisystem zu speichern und von dort aus zu öffnen.
8
VinayC

Es ist schon eine Weile her, dass diese Frage gestellt wurde, aber ich hatte die gleiche Herausforderung und möchte meine Lösung teilen. Es verwendet Elemente aus den anderen Antworten, aber ich konnte es nicht vollständig finden. Es wird kein Formular oder iframe verwendet, es ist jedoch ein Post/Get-Anforderungspaar erforderlich. Anstatt die Datei zwischen den Anforderungen zu speichern, werden die Post-Daten gespeichert. Es scheint sowohl einfach als auch effektiv zu sein.

klient

var apples = new Array(); 
// construct data - replace with your own
$.ajax({
   type: "POST",
   url: '/Home/Download',
   data: JSON.stringify(apples),
   contentType: "application/json",
   dataType: "text",

   success: function (data) {
      var url = '/Home/Download?id=' + data;
      window.location = url;
   });
});

server

[HttpPost]
// called first
public ActionResult Download(Apple[] apples)
{
   string json = new JavaScriptSerializer().Serialize(apples);
   string id = Guid.NewGuid().ToString();
   string path = Server.MapPath(string.Format("~/temp/{0}.json", id));
   System.IO.File.WriteAllText(path, json);

   return Content(id);
}

// called next
public ActionResult Download(string id)
{
   string path = Server.MapPath(string.Format("~/temp/{0}.json", id));
   string json = System.IO.File.ReadAllText(path);
   System.IO.File.Delete(path);
   Apple[] apples = new JavaScriptSerializer().Deserialize<Apple[]>(json);

   // work with apples to build your file in memory
   byte[] file = createPdf(apples); 

   Response.AddHeader("Content-Disposition", "attachment; filename=juicy.pdf");
   return File(file, "application/pdf");
}
7
Frank Rem
$scope.downloadSearchAsCSV = function(httpOptions) {
  var httpOptions = _.extend({
    method: 'POST',
    url:    '',
    data:   null
  }, httpOptions);
  $http(httpOptions).then(function(response) {
    if( response.status >= 400 ) {
      alert(response.status + " - Server Error \nUnable to download CSV from POST\n" + JSON.stringify(httpOptions.data));
    } else {
      $scope.downloadResponseAsCSVFile(response)
    }
  })
};
/**
 * @source: https://github.com/asafdav/ng-csv/blob/master/src/ng-csv/directives/ng-csv.js
 * @param response
 */
$scope.downloadResponseAsCSVFile = function(response) {
  var charset = "utf-8";
  var filename = "search_results.csv";
  var blob = new Blob([response.data], {
    type: "text/csv;charset="+ charset + ";"
  });

  if (window.navigator.msSaveOrOpenBlob) {
    navigator.msSaveBlob(blob, filename); // @untested
  } else {
    var downloadContainer = angular.element('<div data-tap-disabled="true"><a></a></div>');
    var downloadLink      = angular.element(downloadContainer.children()[0]);
    downloadLink.attr('href', window.URL.createObjectURL(blob));
    downloadLink.attr('download', "search_results.csv");
    downloadLink.attr('target', '_blank');

    $document.find('body').append(downloadContainer);

    $timeout(function() {
      downloadLink[0].click();
      downloadLink.remove();
    }, null);
  }

  //// Gets blocked by Chrome popup-blocker
  //var csv_window = window.open("","","");
  //csv_window.document.write('<meta name="content-type" content="text/csv">');
  //csv_window.document.write('<meta name="content-disposition" content="attachment;  filename=data.csv">  ');
  //csv_window.document.write(response.data);
};
6
James McGuigan

Dies ist keine vollständige Antwort auf den ursprünglichen Beitrag, sondern eine schnelle und fehlerhafte Lösung zum Posten eines JSON-Objekts auf dem Server und zum dynamischen Generieren eines Downloads.

Clientseitige jQuery:

var download = function(resource, payload) {
     $("#downloadFormPoster").remove();
     $("<div id='downloadFormPoster' style='display: none;'><iframe name='downloadFormPosterIframe'></iframe></div>").appendTo('body');
     $("<form action='" + resource + "' target='downloadFormPosterIframe' method='post'>" +
      "<input type='hidden' name='jsonstring' value='" + JSON.stringify(payload) + "'/>" +
      "</form>")
      .appendTo("#downloadFormPoster")
      .submit();
}

..und dann den json-String auf der Serverseite dekodieren und Header zum Download setzen (PHP-Beispiel):

$request = json_decode($_POST['jsonstring']), true);
header('Content-Type: application/csv');
header('Content-Disposition: attachment; filename=export.csv');
header('Pragma: no-cache');
3
ralftar

Ich denke, der beste Ansatz ist die Verwendung einer Kombination. Ihr zweiter Ansatz scheint eine elegante Lösung zu sein, bei der Browser beteiligt sind.

Also je nachdem wie der Anruf getätigt wird. (unabhängig davon, ob es sich um einen Browser oder einen Webdienstaufruf handelt) Sie können eine Kombination aus beidem verwenden, indem Sie eine URL an den Browser senden und Rohdaten an einen beliebigen anderen Webdienst-Client senden.

2
naikus

Ich bin jetzt seit zwei Tagen wach und versuche herauszufinden, wie man eine Datei mit jquery und ajax call herunterlädt. Die ganze Unterstützung, die ich bekam, konnte meiner Situation nicht helfen, bis ich das versuche.

Client-Seite

function exportStaffCSV(t) {
   
    var postData = { checkOne: t };
    $.ajax({
        type: "POST",
        url: "/Admin/Staff/exportStaffAsCSV",
        data: postData,
        success: function (data) {
            SuccessMessage("file download will start in few second..");
            var url = '/Admin/Staff/DownloadCSV?data=' + data;
            window.location = url;
        },
       
        traditional: true,
        error: function (xhr, status, p3, p4) {
            var err = "Error " + " " + status + " " + p3 + " " + p4;
            if (xhr.responseText && xhr.responseText[0] == "{")
                err = JSON.parse(xhr.responseText).Message;
            ErrorMessage(err);
        }
    });

}

Serverseitig

 [HttpPost]
    public string exportStaffAsCSV(IEnumerable<string> checkOne)
    {
        StringWriter sw = new StringWriter();
        try
        {
            var data = _db.staffInfoes.Where(t => checkOne.Contains(t.staffID)).ToList();
            sw.WriteLine("\"First Name\",\"Last Name\",\"Other Name\",\"Phone Number\",\"Email Address\",\"Contact Address\",\"Date of Joining\"");
            foreach (var item in data)
            {
                sw.WriteLine(string.Format("\"{0}\",\"{1}\",\"{2}\",\"{3}\",\"{4}\",\"{5}\",\"{6}\"",
                    item.firstName,
                    item.lastName,
                    item.otherName,
                    item.phone,
                    item.email,
                    item.contact_Address,
                    item.doj
                    ));
            }
        }
        catch (Exception e)
        {

        }
        return sw.ToString();

    }

    //On ajax success request, it will be redirected to this method as a Get verb request with the returned date(string)
    public FileContentResult DownloadCSV(string data)
    {
        return File(new System.Text.UTF8Encoding().GetBytes(data), System.Net.Mime.MediaTypeNames.Application.Octet, filename);
        //this method will now return the file for download or open.
    }

Viel Glück.

1
Otis-iDev

Mit HTML5 können Sie einfach einen Anker erstellen und darauf klicken. Sie müssen es nicht als untergeordnetes Element zum Dokument hinzufügen.

const a = document.createElement('a');
a.download = '';
a.href = urlForPdfFile;
a.click();

Alles erledigt.

Wenn Sie einen speziellen Namen für den Download haben möchten, übergeben Sie ihn einfach im download -Attribut:

const a = document.createElement('a');
a.download = 'my-special-name.pdf';
a.href = urlForPdfFile;
a.click();
1
rewritten

Ein anderer Ansatz, anstatt die Datei auf dem Server zu speichern und abzurufen, ist die Verwendung von .NET 4.0+ ObjectCache mit einem kurzen Ablauf bis zur zweiten Aktion (zu diesem Zeitpunkt kann sie endgültig gesichert werden). Der Grund, warum ich JQuery Ajax für den Aufruf verwenden möchte, ist, dass es asynchron ist. Das Erstellen meiner dynamischen PDF - Datei dauert ziemlich lange, und in dieser Zeit wird ein Dialogfeld mit einem aktiven Drehfeld angezeigt (damit können auch andere Arbeiten ausgeführt werden) Der "Erfolg:" beim Erstellen eines Blobs funktioniert nicht zuverlässig. Er hängt vom Inhalt der PDF Datei ab. Er kann leicht durch Daten in der Antwort beschädigt werden, wenn er nicht vollständig in Textform vorliegt ist alles, was Ajax verarbeiten kann.

0
Wray Smallwood

Fand es irgendwo vor langer Zeit und es funktioniert perfekt!

let payload = {
  key: "val",
  key2: "val2"
};

let url = "path/to/api.php";
let form = $('<form>', {'method': 'POST', 'action': url}).hide();
$.each(payload, (k, v) => form.append($('<input>', {'type': 'hidden', 'name': k, 'value': v})) );
$('body').append(form);
form.submit();
form.remove();
0
Den Nikitin