Manejar la descarga de archivos desde la publicación ajax

393

Tengo una aplicación de JavaScript que envía solicitudes POST de ajax a una determinada URL. La respuesta puede ser una cadena JSON o un archivo (como un archivo adjunto). Puedo detectar fácilmente Content-Type y Content-Disposition en mi llamada ajax, pero una vez que detecto que la respuesta contiene un archivo, ¿cómo ofrezco al cliente que lo descargue? He leído varios hilos similares aquí, pero ninguno de ellos proporciona la respuesta que estoy buscando.

Por favor, por favor, no publique respuestas que sugieran que no debería usar ajax para esto o que debería redirigir el navegador, porque nada de esto es una opción. Usar un formulario HTML simple tampoco es una opción. Lo que sí necesito es mostrar un diálogo de descarga al cliente. ¿Se puede hacer esto y cómo?

Pavle Predic
fuente
Para aquellos que leen este artículo, lea esta publicación: stackoverflow.com/questions/20830309/…
sobhan
He eliminado tu solución de la pregunta. Puede publicarlo como respuesta a continuación, pero no pertenece a la publicación de preguntas.
Martijn Pieters

Respuestas:

111

Cree un formulario, use el método POST, envíe el formulario; no hay necesidad de un iframe. Cuando la página del servidor responde a la solicitud, escriba un encabezado de respuesta para el tipo mime del archivo, y presentará un cuadro de diálogo de descarga. Lo he hecho varias veces.

Desea el tipo de contenido de la aplicación / descarga: solo busque cómo proporcionar una descarga para el idioma que esté utilizando.


fuente
35
Como se indica en la pregunta: "Usar un formulario HTML simple tampoco es una opción".
Pavle Predic
13
No, porque usar un POST normal navegaría el navegador a la URL POST. No quiero navegar fuera de la página. Quiero realizar la solicitud en segundo plano, procesar la respuesta y presentarla al cliente.
Pavle Predic
66
Si el servidor devuelve encabezados como lo ha hecho la otra respuesta, se abre en una nueva ventana, lo he hecho antes. Sería solamente navegar de inmediato si el script del lado del servidor devolvió el código HTML
1
@PavlePredic ¿Terminaste descubriendo cómo administrar ambos escenarios de respuesta, es decir, respuesta de texto JSON o respuesta de descarga de archivo?
Usuario web
99
La respuesta no es clara y la solución propuesta no funciona.
stack247
531

No te rindas tan rápido, porque esto se puede hacer (en navegadores modernos) utilizando partes de 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));

Aquí está la versión anterior usando jQuery.ajax. Puede destrozar datos binarios cuando la respuesta se convierte en una cadena de algún conjunto de caracteres.

$.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 Enmienda
fuente
1
Muchas gracias ! Sin embargo, tuve que agregar 'Content-disposition' a 'Access-Control-Expose-Headers' y 'Access-Control-Allow-Headers' en mi respuesta HTTP para que funcione.
JulienD
1
No funciona cuando el archivo es mayor de 500 mb, ¿tal vez deberíamos usar otra API?
hirra
¿Qué pasa con la eliminación de un elemento del DOM en la parte de limpieza (y no solo la URL)? document.body.removeChild(a);
Scoregraphic
@hirra usa responceType "blob" en lugar de "arraybuffer" y reescribe la función de devolución de llamada de esa manera, ese var blob es this.response (var blob = this.response;) así quevar 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
Esta es la solución perfecta. Solo un pequeño cambio. En mecanografiado que necesitaba en window.location.href = downloadUrllugar dewindow.location = downloadUrl
michal.jakubeczy
39

Enfrenté el mismo problema y lo resolví con éxito. Mi caso de uso es este.

" Publique datos JSON en el servidor y reciba un archivo de Excel. Ese archivo de Excel es creado por el servidor y devuelto como respuesta al cliente. Descargue esa respuesta como un archivo con nombre personalizado en el navegador "

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

El fragmento anterior solo hace lo siguiente

  • Publicar una matriz como JSON en el servidor utilizando XMLHttpRequest.
  • Después de buscar contenido como un blob (binario), estamos creando una URL descargable y adjuntándola a un enlace invisible "a" y luego haciendo clic en ella.

Aquí necesitamos establecer cuidadosamente algunas cosas en el lado del servidor. Configuré algunos encabezados en Python Django HttpResponse. Debe configurarlos en consecuencia si usa otros lenguajes de programación.

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

Desde que descargo xls (excel) aquí, ajusté contentType al anterior. Debe configurarlo de acuerdo con su tipo de archivo. Puede usar esta técnica para descargar cualquier tipo de archivo.

Naren Yellavula
fuente
33

¿Qué idioma del lado del servidor estás usando? En mi aplicación, puedo descargar fácilmente un archivo de una llamada AJAX configurando los encabezados correctos en la respuesta de PHP:

Establecer encabezados del lado del servidor

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;

De hecho, esto 'redirigirá' el navegador a esta página de descarga, pero como dijo @ahren alread en su comentario, no se alejará de la página actual.

Se trata de configurar los encabezados correctos, así que estoy seguro de que encontrará una solución adecuada para el lenguaje del lado del servidor que está utilizando si no es PHP.

Manejo del lado del cliente de respuesta

Suponiendo que ya sabe cómo hacer una llamada AJAX, en el lado del cliente ejecuta una solicitud AJAX al servidor. Luego, el servidor genera un enlace desde donde se puede descargar este archivo, por ejemplo, la URL 'hacia adelante' a la que desea apuntar. Por ejemplo, el servidor responde con:

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

Cuando procesas la respuesta, inyectas una iframeen tu cuerpo y configuras el iframeSRC de la URL que acabas de recibir (usando jQuery para facilitar este ejemplo):

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

Si ha configurado los encabezados correctos como se muestra arriba, el iframe forzará un cuadro de diálogo de descarga sin desplazar el navegador fuera de la página actual.

Nota

Adición adicional en relación a su pregunta; Creo que es mejor devolver siempre JSON al solicitar cosas con tecnología AJAX. Después de recibir la respuesta JSON, puede decidir qué hacer con el lado del cliente. Tal vez, por ejemplo, más adelante desee que el usuario haga clic en un enlace de descarga a la URL en lugar de forzar la descarga directamente, en su configuración actual tendría que actualizar tanto el cliente como el servidor para hacerlo.

Robin van Baalen
fuente
24

Para aquellos que buscan una solución desde una perspectiva angular, esto funcionó para mí:

$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
fuente
Esto ayudó, pero necesito preservar el nombre de archivo original. Veo el nombre de archivo en los encabezados de respuesta en "Disposición de contenido", pero no puedo encontrar ese valor en el objeto de respuesta en el código. Establecer link.download = ""resultados en un nombre de archivo guid aleatorio y link.download = nullresultados en un archivo llamado "nulo".
Marie
@Marie, puede grabar el nombre del archivo en el momento de la carga utilizando la propiedad INPUTdel elemento HTMLInputElement.files. Consulte los documentos de MDN en la entrada del archivo para obtener más detalles.
Tim Hettler
El tamaño de blob es limitado: stackoverflow.com/questions/28307789/…
user0800
22

Así es como conseguí que esto funcionara 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();
  }
});

Respuesta actualizada usando download.js

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

Mayur Padshala
fuente
Gracias por esto, acabo de usar esto hoy. Fantástico
Ryan Wilson
Hola, ¿necesito tener jQuery 3.0> para que esto funcione?
gbade_
También recibo un pdf en blanco con los dos ejemplos que diste. Estoy intentando que descargue un archivo pdf.
gbade_
@ gbade_ No, no necesita ninguna versión específica de jQuery. Debería funcionar bien con todas las versiones. ¿Verificó si el PDF que está descargando tiene encabezados CORS correctos? Cualquier error en la consola podría ayudar a depurar
Mayur Padshala
Esto funcionó para mí usando download.js:success: function (response, status, request) { download(response, "filename.txt", "application/text"); }
Sga
12

Veo que ya ha encontrado una solución, sin embargo, solo quería agregar información que pueda ayudar a alguien que intenta lograr lo mismo con grandes solicitudes POST.

Tuve el mismo problema hace un par de semanas, de hecho, no es posible lograr una descarga "limpia" a través de AJAX, el Grupo Filament creó un complemento jQuery que funciona exactamente como ya lo descubrió, se llama jQuery File Descargar sin embargo, hay una desventaja en esta técnica.

Si envía solicitudes grandes a través de AJAX (digamos archivos + 1 MB), tendrá un impacto negativo en la capacidad de respuesta. En conexiones lentas a Internet, tendrá que esperar mucho hasta que se envíe la solicitud y también esperar a que se descargue el archivo. No es como un "clic" instantáneo => "emergente" => "inicio de descarga". Es más como "hacer clic" => "esperar hasta que se envíen los datos" => "esperar respuesta" => "inicio de descarga", lo que hace que parezca que el archivo duplica su tamaño porque tendrá que esperar a que se envíe la solicitud a través de AJAX y recuperarlo como un archivo descargable.

Si está trabajando con archivos de tamaño pequeño <1 MB, no lo notará. Pero como descubrí en mi propia aplicación, para archivos de mayor tamaño es casi insoportable.

Mi aplicación permite a los usuarios exportar imágenes generadas dinámicamente, estas imágenes se envían a través de solicitudes POST en formato base64 al servidor (es la única forma posible), luego se procesan y se envían a los usuarios en forma de archivos .png, .jpg, base64 Las cadenas de imágenes + 1 MB son enormes, esto obliga a los usuarios a esperar más de lo necesario para que el archivo comience a descargarse. En conexiones lentas a Internet puede ser realmente molesto.

Mi solución para esto fue escribir temporalmente el archivo en el servidor, una vez que esté listo, generar dinámicamente un enlace al archivo en forma de un botón que cambia entre los estados "Por favor, espere ..." y "Descargar" y al mismo tiempo vez, imprima la imagen base64 en una ventana emergente de vista previa para que los usuarios puedan hacer clic con el botón derecho y guardarla. Esto hace que todo el tiempo de espera sea más soportable para los usuarios, y también acelera las cosas.

Actualización 30 de septiembre 2014:

Han pasado meses desde que publiqué esto, finalmente encontré un mejor enfoque para acelerar las cosas cuando trabajo con grandes cadenas de base64. Ahora almaceno cadenas base64 en la base de datos (usando campos de texto largo o de registro largo), luego paso su ID de registro a través de jQuery File Download, finalmente en el archivo de script de descarga, consulto la base de datos usando esta ID para extraer la cadena base64 y pasarla La función de descarga.

Descargar Script Ejemplo:

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

Sé que esto va mucho más allá de lo que pidió el OP, sin embargo, sentí que sería bueno actualizar mi respuesta con mis hallazgos. Cuando estaba buscando soluciones a mi problema, leí muchos hilos de "Descarga de datos POST AJAX" que no me dieron la respuesta que estaba buscando, espero que esta información ayude a alguien que busca lograr algo como esto.

José SAYAGO
fuente
El jQuery File Downloadúnico me redirige a la url. Lo llamo así: jQuery.download("api/ide/download-this-file.php", {filePath: path2Down}, "POST");.
Casper
5

Quiero señalar algunas dificultades que surgen al usar la técnica en la respuesta aceptada, es decir, usar una publicación de formulario:

  1. No puede establecer encabezados en la solicitud. Si su esquema de autenticación involucra encabezados, un Json-Web-Token pasado en el encabezado de Autorización, tendrá que encontrar otra forma de enviarlo, por ejemplo, como un parámetro de consulta.

  2. Realmente no se puede saber cuándo ha finalizado la solicitud. Bueno, puede usar una cookie que se establece en la respuesta, como lo hizo jquery.fileDownload , pero está LEJOS de ser perfecta. No funcionará para solicitudes concurrentes y se romperá si nunca llega una respuesta.

  3. Si el servidor responde con un error, el usuario será redirigido a la página de error.

  4. Solo puede usar los tipos de contenido admitidos por un formulario . Lo que significa que no puedes usar JSON.

Terminé usando el método de guardar el archivo en S3 y enviando una URL previamente firmada para obtener el archivo.

tepez
fuente
5

Para aquellos que buscan un enfoque más moderno, pueden usar el fetch API. El siguiente ejemplo muestra cómo descargar un archivo de hoja de cálculo. Se hace fácilmente con el siguiente código.

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

Creo que este enfoque es mucho más fácil de entender que otras XMLHttpRequestsoluciones. Además, tiene una sintaxis similar al jQueryenfoque, sin la necesidad de agregar bibliotecas adicionales.

Por supuesto, recomendaría verificar en qué navegador está desarrollando, ya que este nuevo enfoque no funcionará en IE. Puede encontrar la lista completa de compatibilidad del navegador en el siguiente [enlace] [1].

Importante : en este ejemplo, estoy enviando una solicitud JSON a un servidor que escucha en el dado url. Esto urldebe establecerse, en mi ejemplo, supongo que conoce esta parte. Además, considere los encabezados necesarios para que su solicitud funcione. Como estoy enviando un JSON, debo agregar el Content-Typeencabezado y configurarlo application/json; charset=utf-8para que el servidor sepa el tipo de solicitud que recibirá.

Alain Cruz
fuente
1
¡Increíble! Para abrir esto en una nueva pestaña en lugar de una ventana emergente de descarga: use `` `const window = open (downloadUrl," _blank "); if (ventana! == nulo) window.focus (); ``
Andy
¿Hay alguna manera de hacer esto para múltiples conjuntos de datos? Ejemplo, recuperar {fileOne: data, fileTwo: data, fileThree: data} de la llamada a la API y generar tres archivos descargados a la vez. ¡Gracias!
il0v3d0g
Hmmm, no lo he intentado. Pero siempre puede comprimir las imágenes en un archivo zip y descargarlo. Comprobaré si es posible.
Alain Cruz
4

Aquí está mi solución usando un formulario oculto temporal.

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

Tenga en cuenta que uso masivamente JQuery, pero puede hacer lo mismo con JS nativo.

Ludovic Martin
fuente
3

Como han dicho otros, puede crear y enviar un formulario para descargar a través de una solicitud POST. Sin embargo, no tiene que hacer esto manualmente.

Una biblioteca realmente simple para hacer exactamente esto es jquery.redirect . Proporciona una API similar al jQuery.postmétodo estándar :

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

Esta es una pregunta de 3 años, pero tuve el mismo problema hoy. Busqué su solución editada, pero creo que puede sacrificar el rendimiento porque tiene que hacer una doble solicitud. Entonces, si alguien necesita otra solución que no implique llamar al servicio dos veces, esta es la forma en que lo hice:

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

Este formulario solo se usa para llamar al servicio y evitar el uso de window.location (). Después de eso, simplemente tiene que enviar un formulario desde jquery para llamar al servicio y obtener el archivo. Es bastante simple, pero de esta manera puedes hacer una descarga usando un POST . Ahora sé que esto podría ser más fácil si el servicio al que llama es un GET , pero ese no es mi caso.

Jairo Miranda
fuente
1
Esta no es una publicación de ajax ya que la pregunta está usando ajax
Nidhin David
es solo una solución al problema anterior, sin embargo, no para llamadas ajax.
Nomi Ali
1

Usé este FileSaver.js . En mi caso con archivos csv, hice esto (en coffescript):

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

Creo que para el caso más complicado, los datos deben procesarse correctamente. Bajo el capó, FileSaver.js implementa el mismo enfoque de la respuesta de Jonathan Enmienda .

Armando
fuente
1
... pero ¿puedes descargar archivos generalmente en iOS?
Alex Marshall
1

Para obtener la respuesta de Jonathan Amends para trabajar en Edge, hice los siguientes cambios:

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

a esto

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

Preferiría haber publicado esto como un comentario, pero no tengo suficiente reputación para eso

fstrandner
fuente
0

Aquí está mi solución, recopilada de diferentes fuentes: Implementación del lado del servidor:

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

Implementación del lado del cliente (usando 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
fuente
0

Hay otra solución para descargar una página web en ajax. Pero me estoy refiriendo a una página que primero debe procesarse y luego descargarse.

Primero debe separar el procesamiento de la página de la descarga de resultados.

1) Solo los cálculos de la página se realizan en la llamada ajax.

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

       función (datos, estado) 
       {
            if (estado == "éxito") 
            {
                / * 2) En la respuesta se descarga la página que usa los cálculos anteriores. Por ejemplo, esta puede ser una página que imprime los resultados de una tabla calculada en la llamada ajax. * /
                window.location.href = DownloadPage.php + "? ID =" + 29;
            }               
       }
);

// Por ejemplo: en CalculusPage.php

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

        $ query = "INSERTAR EN LA PÁGINA DE EJEMPLO (data1, data2) VALORES ('". $ _ POST ["data1"]. "', '". $ _ POST ["data2"]. "') DONDE id =". $ ID;
        ...
    }

// Por ejemplo: en el DownloadPage.php

    $ ID = $ _GET ["ID"];

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

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

    ...

Espero que esta solución pueda ser útil para muchos, como lo fue para mí.

netluke
fuente
0

Si la respuesta es un Array Buffer , intente esto bajo un evento de éxito en 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;
      }
  • donde event.data es la respuesta recibida en función de éxito del evento xhr.
Abhishek Sinha
fuente
0

A continuación se muestra mi solución para descargar varios archivos dependiendo de una lista que consiste en algunos identificadores y buscar en la base de datos, los archivos estarán determinados y listos para descargar, si es que existen. Estoy llamando a la acción C # MVC para cada archivo usando Ajax.

Y sí, como dijeron otros, es posible hacerlo en jQuery Ajax. Lo hice con éxito Ajax y siempre estoy enviando la respuesta 200.

Entonces, esta es la clave:

  success: function (data, textStatus, xhr) {

Y este es mi código:

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

Entonces llamando:

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

Siempre que devuelva la respuesta 200, el éxito en Ajax puede funcionar con ella, puede verificar si el archivo realmente existe o no, ya que la línea a continuación en este caso sería falsa y puede informar al usuario sobre eso:

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

Necesitaba una solución similar a la de @ alain-cruz, pero en nuxt / vue con múltiples descargas. Sé que los navegadores bloquean la descarga de múltiples archivos, y también tengo una API que devuelve un conjunto de datos con formato csv. Al principio iba a usar JSZip, pero necesitaba el soporte de IE, así que aquí está mi solución. Si alguien puede ayudarme a mejorar esto, sería genial, pero hasta ahora me está funcionando.

API devuelve:

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
fuente