c# - files - Descargar archivo de Excel a través de AJAX MVC
download files mvc c# (8)
Tengo una forma grande (ish) 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, así que quiero hacerlo a través de AJAX. He encontrado algunas preguntas sobre SO que parecen estar relacionadas, pero no puedo entender qué significan las respuestas.
Este parece ser el más cercano a lo que busco: asp-net-mvc-downloading-excel , pero no estoy seguro de entender la respuesta, y ahora tengo un par de años. También encontré otro artículo (no puedo encontrarlo más) sobre el uso de un iframe para manejar la descarga del archivo, pero no estoy seguro de cómo hacerlo funcionar con MVC.
Mi archivo de Excel vuelve bien si estoy haciendo una publicación completa pero no puedo hacerlo funcionar con AJAX en mvc.
En Enviar formulario
public ActionResult ExportXls()
{
var filePath="";
CommonHelper.WriteXls(filePath, "Text.xls");
}
public static void WriteXls(string filePath, string targetFileName)
{
if (!String.IsNullOrEmpty(filePath))
{
HttpResponse response = HttpContext.Current.Response;
response.Clear();
response.Charset = "utf-8";
response.ContentType = "text/xls";
response.AddHeader("content-disposition", string.Format("attachment; filename={0}", targetFileName));
response.BinaryWrite(File.ReadAllBytes(filePath));
response.End();
}
}
Este hilo me ayudó a crear mi propia solución que compartiré aquí. Estaba usando una solicitud GET ajax al principio sin problemas, pero llegó a un punto en el que se excedió la longitud de la URL de solicitud, así que tuve que pasar a un POST.
JavaScript utiliza el complemento de descarga de archivos JQuery y consta de 2 llamadas sucesivas. Un POST (Para enviar params) y un GET para recuperar el archivo.
function download(result) {
$.fileDownload(uri + "?guid=" + result,
{
successCallback: onSuccess.bind(this),
failCallback: onFail.bind(this)
});
}
var uri = BASE_EXPORT_METADATA_URL;
var data = createExportationData.call(this);
$.ajax({
url: uri,
type: ''POST'',
contentType: ''application/json'',
data: JSON.stringify(data),
success: download.bind(this),
fail: onFail.bind(this)
});
Lado del servidor
[HttpPost]
public string MassExportDocuments(MassExportDocumentsInput input)
{
// Save query for file download use
var guid = Guid.NewGuid();
HttpContext.Current.Cache.Insert(guid.ToString(), input, null, DateTime.Now.AddMinutes(5), Cache.NoSlidingExpiration);
return guid.ToString();
}
[HttpGet]
public async Task<HttpResponseMessage> MassExportDocuments([FromUri] Guid guid)
{
//Get params from cache, generate and return
var model = (MassExportDocumentsInput)HttpContext.Current.Cache[guid.ToString()];
..... // Document generation
// to determine when file is downloaded
HttpContext.Current
.Response
.SetCookie(new HttpCookie("fileDownload", "true") { Path = "/" });
return FileResult(memoryStream, "documents.zip", "application/zip");
}
Estoy usando Asp.Net WebForm y solo quiero descargar un archivo desde el servidor. Hay mucho artículo pero no puedo encontrar solo una respuesta básica. Ahora, lo intenté de una manera básica y lo obtuve.
Ese es mi problema.
Tengo que crear una gran cantidad de botón de entrada de forma dinámica en el tiempo de ejecución. Y quiero agregar cada botón al botón de descarga con un número de archivo único.
Creo cada botón de esta manera:
fragment += "<div><input type=/"button/" value=/"Create Excel/" onclick=/"CreateExcelFile(" + fileNumber + ");/" /></div>";
Cada botón llama a este método ajax.
$.ajax({
type: ''POST'',
url: ''index.aspx/CreateExcelFile'',
data: jsonData,
contentType: ''application/json; charset=utf-8'',
dataType: ''json'',
success: function (returnValue) {
window.location = ''/Reports/Downloads/'' + returnValue.d;
}
});
Luego escribí un método simple básico.
[WebMethod]
public static string CreateExcelFile2(string fileNumber)
{
string filePath = string.Format(@"Form_{0}.xlsx", fileNumber);
return filePath;
}
Estoy generando este Form_1, Form_2, Form_3 ... Y voy a eliminar estos viejos archivos con otro programa. Pero si hay una forma de enviar simplemente una matriz de bytes para descargar un archivo, como usar Response. Quiero usarlo
Espero que esto sea útil para cualquiera.
Mis 2 centavos, no es necesario que almacene el Excel como un archivo físico en el servidor, sino que lo almacena en el Caché (Session). Use un nombre generado exclusivamente para su variable de caché (que almacena ese archivo de Excel): esta será la devolución de su llamada (inicial) ajax. De esta forma, no tendrá que lidiar con problemas de acceso a archivos, administrar (eliminar) los archivos cuando no los necesite, etc., y tener el archivo en el caché es más rápido para recuperarlo.
No puede devolver directamente un archivo para su descarga a través de una llamada AJAX, por lo que un enfoque alternativo es utilizar una llamada AJAX para publicar los datos relacionados en su servidor. A continuación, puede usar el código del lado del servidor para crear el archivo de Excel (recomendaría usar EPPlus o NPOI para esto, aunque parece que esta parte funciona).
ACTUALIZACIÓN de septiembre de 2016
Mi respuesta original (abajo) tenía más de 3 años, así que pensé que me actualizaría porque ya no creo archivos en el servidor cuando descargo archivos a través de AJAX. De todas formas, he dejado la respuesta original, ya que puede ser de alguna utilidad dependiendo de tus requerimientos específicos
Un escenario común en mis aplicaciones de MVC es informar a través de una página web que tiene algunos parámetros de informe configurados por el usuario (Rangos 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 TempData
el archivo resultante como una matriz de bytes en el depósito TempData
con una referencia única. Esta referencia se transmite como un Resultado Json a mi función AJAX que posteriormente redirige a una acción de controlador separada para extraer los datos de TempData
y descargarlos al navegador de los usuarios finales.
Para dar más detalles, suponiendo que tiene una vista MVC que tiene un formulario vinculado a una clase de modelo, llamemos al Model ReportVM
.
Primero, se requiere una acción de 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 acomodarse fácilmente si se requiere es pasar el tipo MIME del archivo como un tercer parámetro para que la acción del Controlador pueda servir correctamente una variedad de formatos de archivo de salida.
Esto elimina la necesidad de que se creen y almacenen archivos físicos en el servidor, por lo que no se requieren rutinas de mantenimiento y una vez más esto es perfecto para el usuario final.
Tenga en cuenta que la ventaja de utilizar TempData
lugar de Session
es que una vez que se lee TempData
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. Vea las mejores prácticas de TempData .
ORIGINAL Respuesta
No puede devolver directamente un archivo para su descarga a través de una llamada AJAX, por lo que un enfoque alternativo es utilizar una llamada AJAX para publicar los datos relacionados en su servidor. A continuación, puede usar el código del lado del servidor para crear el archivo de Excel (recomendaría usar EPPlus o NPOI para esto, aunque parece que esta parte funciona).
Una vez que el archivo ha sido creado en el servidor, vuelva a enviar la ruta al archivo (o solo el nombre del archivo) como valor de retorno a su llamada AJAX y luego configure la window.location
de ventana de JavaScript en esta URL 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 simple ejemplo artificial 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 de Controlador / Acción donde su código creará el archivo de Excel.
- data parameter contiene los datos json que se extraerán del formulario.
- returnValue sería el nombre de archivo de su archivo Excel recién creado.
- El comando window.location redirige al controlador / método de acción que realmente devuelve el archivo para su descarga.
Un método de controlador de muestra para la acción de descarga 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);
}
Primero crea la acción del controlador que creará el archivo de Excel
[HttpPost]
public JsonResult ExportExcel()
{
DataTable dt = DataService.GetData();
var fileName = "Excel_" + DateTime.Now.ToString("yyyyMMddHHmm") + ".xls";
//save the file to server temp folder
string fullPath = Path.Combine(Server.MapPath("~/temp"), fileName);
using (var exportData = new MemoryStream())
{
//I don''t show the detail how to create the Excel, this is not the point of this article,
//I just use the NPOI for Excel handler
Utility.WriteDataTableToExcel(dt, ".xls", exportData);
FileStream file = new FileStream(fullPath, FileMode.Create, FileAccess.Write);
exportData.WriteTo(file);
file.Close();
}
var errorMessage = "you can return the errors in here!";
//return the Excel file name
return Json(new { fileName = fileName, errorMessage = "" });
}
luego crea la acción de descarga
[HttpGet]
[DeleteFileAttribute] //Action Filter, it will auto delete the file after download,
//I will explain it later
public ActionResult Download(string file)
{
//get the temp folder and file path in server
string fullPath = Path.Combine(Server.MapPath("~/temp"), file);
//return the file for download, this is an Excel
//so I set the file content type to "application/vnd.ms-excel"
return File(fullPath, "application/vnd.ms-excel", file);
}
si desea eliminar el archivo después de descargarlo, cree esto
public class DeleteFileAttribute : ActionFilterAttribute
{
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
filterContext.HttpContext.Response.Flush();
//convert the current filter context to file and get the file path
string filePath = (filterContext.Result as FilePathResult).FileName;
//delete the file after download
System.IO.File.Delete(filePath);
}
}
y finalmente ajax call de ti MVC Razor view
//I use blockUI for loading...
$.blockUI({ message: ''<h3>Please wait a moment...</h3>'' });
$.ajax({
type: "POST",
url: ''@Url.Action("ExportExcel","YourController")'', //call your controller and action
contentType: "application/json; charset=utf-8",
dataType: "json",
}).done(function (data) {
//console.log(data.result);
$.unblockUI();
//get the file name for download
if (data.fileName != "") {
//use window.location.href for redirect to download action for download the file
window.location.href = "@Url.RouteUrl(new
{ Controller = "YourController", Action = "Download"})/?file=" + data.fileName;
}
});
Recientemente pude lograr esto en MVC (aunque no era necesario usar AJAX) sin crear un archivo físico y pensé que compartiría mi código:
Función de JavaScript súper simple (el botón de datatables.net hace clic en activa esto):
function getWinnersExcel(drawingId) {
window.location = "/drawing/drawingwinnersexcel?drawingid=" + drawingId;
}
Código del controlador C #:
public FileResult DrawingWinnersExcel(int drawingId)
{
MemoryStream stream = new MemoryStream(); // cleaned up automatically by MVC
List<DrawingWinner> winnerList = DrawingDataAccess.GetWinners(drawingId); // simple entity framework-based data retrieval
ExportHelper.GetWinnersAsExcelMemoryStream(stream, winnerList, drawingId);
string suggestedFilename = string.Format("Drawing_{0}_Winners.xlsx", drawingId);
return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml", suggestedFilename);
}
En la clase ExportHelper utilizo una herramienta de terceros ( GemBox.Spreadsheet ) para generar el archivo de Excel y tiene una opción Guardar en Stream. Dicho esto, hay varias maneras de crear archivos de Excel que se pueden escribir fácilmente en una secuencia de memoria.
public static class ExportHelper
{
internal static void GetWinnersAsExcelMemoryStream(MemoryStream stream, List<DrawingWinner> winnerList, int drawingId)
{
ExcelFile ef = new ExcelFile();
// lots of excel worksheet building/formatting code here ...
ef.SaveXlsx(stream);
stream.Position = 0; // reset for future read
}
}
En IE, Chrome y Firefox, el navegador solicita descargar el archivo y no se produce una navegación real.
Utilicé la solución publicada por CSL, pero recomendaría que no almacene los datos del archivo en la sesión durante toda la sesión. Al utilizar TempData, los datos del archivo se eliminan automáticamente después de la próxima solicitud (que es la solicitud GET para el archivo). También puede gestionar la eliminación de los datos del archivo en la sesión en la acción de descarga.
La sesión podría consumir mucha memoria / espacio según el almacenamiento de SessionState y cuántos archivos se exportan durante la sesión y si tiene muchos usuarios.
He actualizado el código del lado del seridor de CSL para usar TempData en su lugar.
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" }
};
}
[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();
}
}