Descargue el archivo Excel a través de AJAX MVC

Resuelto Valuk asked hace 11 años • 15 respuestas

Tengo un formulario grande (más o menos) en MVC.

Necesito poder generar un archivo de Excel que contenga datos de un subconjunto de ese formulario.

El truco es que esto no debería afectar el resto del formulario y por eso quiero hacerlo a través de AJAX. Me encontré con algunas preguntas sobre SO que parecen estar relacionadas, pero no puedo entender qué significan las respuestas.

Este parece el más cercano a lo que busco: asp-net-mvc-downloading-excel , pero no estoy seguro de entender la respuesta, y ya tiene un par de años. También encontré otro artículo (ya no puedo encontrarlo) sobre el uso de un iframe para manejar la descarga de archivos, pero no estoy seguro de cómo hacer que esto funcione con MVC.

Mi archivo de Excel regresa bien si estoy haciendo una publicación completa pero no puedo hacerlo funcionar con AJAX en mvc.

Valuk avatar May 21 '13 19:05 Valuk
Aceptado

No puede devolver directamente un archivo para descargar mediante una llamada AJAX, por lo que un método alternativo es utilizar una llamada AJAX para publicar los datos relacionados en su servidor. Luego puede usar el código del lado del servidor para crear el archivo Excel (recomendaría usar EPPlus o NPOI para esto, aunque parece que esta parte está funcionando).

ACTUALIZACIÓN Septiembre 2016

Mi respuesta original (a continuación) tenía más de 3 años, así que pensé en actualizar, ya que ya no creo archivos en el servidor cuando descargo archivos a través de AJAX. Sin embargo, dejé la respuesta original, ya que puede ser de alguna utilidad aún dependiendo de sus requisitos específicos.

Un escenario común en mis aplicaciones MVC es generar informes a través de una página web que tiene algunos parámetros de informe configurados por el usuario (intervalos de fechas, filtros, etc.). Cuando el usuario ha especificado los parámetros, los publica en el servidor, se genera el informe (digamos, por ejemplo, un archivo de Excel como salida) y luego almaceno el archivo resultante como una matriz de bytes en el TempDatadepósito con una referencia única. Esta referencia se devuelve como resultado Json a mi función AJAX que posteriormente redirige a una acción de controlador separada para extraer los datos TempDatay descargarlos en el navegador del usuario final.

Para brindar más detalles, suponiendo que tiene una vista MVC que tiene un formulario vinculado a una clase de modelo, llamemos al modelo ReportVM.

Primero, se requiere una acción del controlador para recibir el modelo publicado, un ejemplo sería:

public ActionResult PostReportPartial(ReportVM model){

   // Validate the Model is correct and contains valid data
   // Generate your report output based on the model parameters
   // This can be an Excel, PDF, Word file - whatever you need.

   // As an example lets assume we've generated an EPPlus ExcelPackage

   ExcelPackage workbook = new ExcelPackage();
   // Do something to populate your workbook

   // Generate a new unique identifier against which the file can be stored
   string handle = Guid.NewGuid().ToString();

   using(MemoryStream memoryStream = new MemoryStream()){
        workbook.SaveAs(memoryStream);
        memoryStream.Position = 0;
        TempData[handle] = memoryStream.ToArray();
   }      

   // Note we are returning a filename as well as the handle
   return new JsonResult() { 
         Data = new { FileGuid = handle, FileName = "TestReportOutput.xlsx" }
   };

}

La llamada AJAX que publica mi formulario MVC en el controlador anterior y recibe la respuesta se ve así:

$ajax({
    cache: false,
    url: '/Report/PostReportPartial',
    data: _form.serialize(), 
    success: function (data){
         var response = JSON.parse(data);
         window.location = '/Report/Download?fileGuid=' + response.FileGuid 
                           + '&filename=' + response.FileName;
    }
})

La acción del controlador para manejar la descarga del archivo:

[HttpGet]
public virtual ActionResult Download(string fileGuid, string fileName)
{   
   if(TempData[fileGuid] != null){
        byte[] data = TempData[fileGuid] as byte[];
        return File(data, "application/vnd.ms-excel", fileName);
   }   
   else{
        // Problem - Log the error, generate a blank file,
        //           redirect to another controller action - whatever fits with your application
        return new EmptyResult();
   }
}

Otro cambio que podría adaptarse fácilmente si fuera necesario es pasar el tipo MIME del archivo como un tercer parámetro para que una acción del Controlador pueda servir correctamente a una variedad de formatos de archivos de salida.

Esto elimina la necesidad de crear y almacenar archivos físicos en el servidor, por lo que no se requieren rutinas de limpieza y, una vez más, esto es perfecto para el usuario final.

Tenga en cuenta que la ventaja de usar TempDataen lugar de Sessiones que una vez TempDataleídos, los datos se borran, por lo que será más eficiente en términos de uso de memoria si tiene un gran volumen de solicitudes de archivos. Consulte las mejores prácticas de TempData .

Respuesta ORIGINAL

No puede devolver directamente un archivo para descargar mediante una llamada AJAX, por lo que un método alternativo es utilizar una llamada AJAX para publicar los datos relacionados en su servidor. Luego puede usar el código del lado del servidor para crear el archivo Excel (recomendaría usar EPPlus o NPOI para esto, aunque parece que esta parte está funcionando).

Una vez que se haya creado el archivo en el servidor, devuelva la ruta al archivo (o simplemente el nombre del archivo) como valor de retorno a su llamada AJAX y luego configure JavaScript window.locationen esta URL, lo que solicitará al navegador que descargue el archivo.

Desde la perspectiva de los usuarios finales, la operación de descarga de archivos es perfecta ya que nunca abandonan la página en la que se origina la solicitud.

A continuación se muestra un ejemplo simple ideado de una llamada ajax para lograr esto:

$.ajax({
    type: 'POST',
    url: '/Reports/ExportMyData', 
    data: '{ "dataprop1": "test", "dataprop2" : "test2" }',
    contentType: 'application/json; charset=utf-8',
    dataType: 'json',
    success: function (returnValue) {
        window.location = '/Reports/Download?file=' + returnValue;
    }
});
  • El parámetro URL es el método Controlador/Acción donde su código creará el archivo de Excel.
  • El parámetro de datos contiene los datos json que se extraerían del formulario.
  • returnValue sería el nombre del archivo de Excel recién creado.
  • El comando window.location redirige al método Controlador/Acción que realmente devuelve su archivo para descargar.

Un método de controlador de muestra para la acción Descargar sería:

[HttpGet]
public virtual ActionResult Download(string file)
{   
  string fullPath = Path.Combine(Server.MapPath("~/MyFiles"), file);
  return File(fullPath, "application/vnd.ms-excel", file);
}
connectedsoftware avatar May 21 '2013 12:05 connectedsoftware

Mis 2 centavos: no es necesario almacenar Excel como un archivo físico en el servidor; en su lugar, guárdelo en la caché (de sesión). Utilice un nombre generado de forma exclusiva para su variable de caché (que almacena ese archivo de Excel); este será el retorno de su llamada ajax (inicial). De esta manera no tiene que lidiar con problemas de acceso a archivos, administrar (eliminar) los archivos cuando no los necesita, etc. y, al tener el archivo en la caché, es más rápido recuperarlo.

Luchian avatar Mar 05 '2014 18:03 Luchian