Descargar archivo de Excel a través de AJAX MVC

92

Tengo un formulario 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 al resto del formulario, por lo que quiero hacerlo a través de AJAX. Me he encontrado 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 del archivo, 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 hacer que funcione con AJAX en mvc.

Valuk
fuente

Respuestas:

215

No puede devolver directamente un archivo para su descarga a través de una llamada AJAX, por lo que un enfoque alternativo es usar 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 de Excel (recomendaría usar EPPlus o NPOI para esto, aunque parece que tiene esta parte funcionando).

ACTUALIZACIÓN Septiembre de 2016

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

Un escenario común en mis aplicaciones 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 (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 un 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 de los usuarios finales.

Para dar más detalles, asumiendo 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 la única acción del controlador pueda servir correctamente una variedad de formatos de archivo 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 en TempDatalugar de Sessiones que una vez que TempDatase leen, 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 su descarga a través de una llamada AJAX, por lo que un enfoque alternativo es usar 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 de Excel (recomendaría usar EPPlus o NPOI para esto, aunque parece que tiene esta parte 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 el JavaScript window.locationen esta URL que le pedirá 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 inventado 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 Controller / Action donde su código creará el archivo 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 Controller / Action que realmente devuelve su archivo para su descarga.

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);
}
software conectado
fuente
3
Esta parece una buena opción potencial, pero antes de seguir adelante, ¿no hay otras alternativas que no impliquen la creación del archivo en el servidor primero?
Valuk
4
No que yo sepa, este enfoque lo he utilizado con éxito muchas veces. Desde la perspectiva de los usuarios, es perfecto, lo único que debe tener en cuenta es que necesitará una rutina de limpieza para ordenar los archivos que se crean, ya que se acumularán con el tiempo.
software conectado
7
Creando un endpoint '/ Download? File = ...' GRITA riesgo de seguridad masivo: no soy un experto en seguridad, pero creo que le gustaría agregar autenticación de usuario, saneamiento de entrada, [ValidateAntiForgeryToken] de MVC y mencionar otros aspectos de seguridad -prácticas a esta respuesta.
Jimmy
2
@CSL Siempre recibo el error 0x800a03f6 - Error de tiempo de ejecución de JavaScript: carácter no válido en la respuesta var = JSON.parse (datos);
Standage
2
Genial, ¿por qué no pones la respuesta anterior al final? Y la nueva respuesta en la parte superior, para que la gente no pierda tiempo
goamn
19

Mis 2 centavos: no necesita almacenar el Excel como un archivo físico en el servidor; en su lugar, guárdelo en la caché (Sesión). Use un nombre generado de forma única 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
fuente
1
¿Cómo exactamente harías eso? Suena interesante.
Natalia
2
Un ejemplo sería bueno (me refiero a cómo almacenarlo en el caché, no generar el archivo de Excel).
Tadej
Sin embargo, ¿qué tan escalable es esto? ¿Si un usuario está descargando varios informes grandes?
Zapnologica
Si está en Azure, la sesión funcionará HASTA que desactive ARRAffinity.
JeeShen Lee
14

Recientemente pude lograr esto en MVC (aunque no había necesidad de usar AJAX) sin crear un archivo físico y pensé en compartir mi código:

Función JavaScript súper simple (el clic del botón datatables.net lo activa):

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 formas de crear archivos de Excel que se pueden escribir fácilmente en un flujo 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.

Andy S
fuente
8

Primero cree 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 Descargar

[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 este

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 llamada ajax desde su vista MVC Razor

//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;
    }
});
Elvin Acevedo
fuente
7

Usé la solución publicada por CSL, pero le recomendaría que no almacene los datos del archivo en Session durante toda la sesión. Al usar TempData, los datos del archivo se eliminan automáticamente después de la siguiente solicitud (que es la solicitud GET del archivo). También puede gestionar la eliminación de los datos del archivo en Sesión en la acción de descarga.

La sesión podría consumir mucha memoria / espacio dependiendo del almacenamiento de SessionState y cuántos archivos se exportan durante la sesión y si tiene muchos usuarios.

Actualicé 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();
   }
}
Niclas
fuente
@Nichlas También comencé a usar TempData, ¡tu respuesta me impulsó a actualizar la mía para reflejar esto!
conectado
5

usando ClosedXML.Excel;

   public ActionResult Downloadexcel()
    {   
        var Emplist = JsonConvert.SerializeObject(dbcontext.Employees.ToList());
        DataTable dt11 = (DataTable)JsonConvert.DeserializeObject(Emplist, (typeof(DataTable)));
        dt11.TableName = "Emptbl";
        FileContentResult robj;
        using (XLWorkbook wb = new XLWorkbook())
        {
            wb.Worksheets.Add(dt11);
            using (MemoryStream stream = new MemoryStream())
            {
                wb.SaveAs(stream);
                var bytesdata = File(stream.ToArray(), "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "myFileName.xlsx");
                robj = bytesdata;
            }
        }


        return Json(robj, JsonRequestBehavior.AllowGet);
    }
GVKRAO
fuente
En AJAX CALL Success Block, éxito: función (Rdata) {depurador; var bytes = nuevo Uint8Array (Rdata.FileContents); var blob = new Blob ([bytes], {tipo: "application / vnd.openxmlformats-officedocument.spreadsheetml.sheet"}); var link = document.createElement ('a'); link.href = window.URL.createObjectURL (blob); link.download = "myFileName.xlsx"; link.click (); },
GVKRAO
alguien ha implementado la descarga de archivos de Excel en el enlace anterior, funciona solo para @ html.Beginform () luego, después de pequeños cambios, se necesita ese código, para AJAX llamar a Success Block,
verifíquelo
3
$ .ajax ({
                tipo: "OBTENER",
                url: "/ Inicio / Downloadexcel /",
                contentType: "application / json; charset = utf-8",
                datos: nulo,
                éxito: función (Rdata) {
                    depurador
                    var bytes = nuevo Uint8Array (Rdata.FileContents); 
                    var blob = new Blob ([bytes], {tipo: "application / vnd.openxmlformats-officedocument.spreadsheetml.sheet"});
                    var link = document.createElement ('a');
                    link.href = window.URL.createObjectURL (blob);
                    link.download = "myFileName.xlsx";
                    link.click ();
                },
                error: función (err) {

                }

            });
GVKRAO
fuente
1

La respuesta aceptada no funcionó del todo para mí, ya que obtuve un resultado de 502 Bad Gateway de la llamada ajax a pesar de que todo parecía estar volviendo bien desde el controlador.

Quizás estaba alcanzando un límite con TempData, no estoy seguro, pero descubrí que si usaba IMemoryCache en lugar de TempData , funcionaba bien, así que aquí está mi versión adaptada del código en la respuesta aceptada:

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

        //This is an equivalent to tempdata, but requires manual cleanup
        _cache.Set(handle, memoryStream.ToArray(), 
                    new MemoryCacheEntryOptions().SetSlidingExpiration(TimeSpan.FromMinutes(10))); 
                    //(I'd recommend you revise the expiration specifics to suit your application)

   }      

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

}

La llamada AJAX permanece como con la respuesta aceptada (no hice cambios):

$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 (_cache.Get<byte[]>(fileGuid) != null)
    {
        byte[] data = _cache.Get<byte[]>(fileGuid);
        _cache.Remove(fileGuid); //cleanup here as we don't need it in cache anymore
        return File(data, "application/vnd.ms-excel", fileName);
    }
    else
    {
        // Something has gone wrong...
        return View("Error"); // or whatever/wherever you want to return the user
    }
}

...

Ahora hay un código adicional para configurar MemoryCache ...

Para usar "_cache", inyecté en el constructor para el controlador así:

using Microsoft.Extensions.Caching.Memory;
namespace MySolution.Project.Controllers
{
 public class MyController : Controller
 {
     private readonly IMemoryCache _cache;

     public LogController(IMemoryCache cache)
     {
        _cache = cache;
     }

     //rest of controller code here
  }
 }

Y asegúrese de tener lo siguiente en ConfigureServices en Startup.cs:

services.AddDistributedMemoryCache();
egmfrs
fuente
0

Este hilo me ayudó a crear mi propia solución que compartiré aquí. Al principio, estaba usando una solicitud GET ajax sin problemas, pero llegó a un punto en el que se excedió la longitud de la URL de la solicitud, por lo que tuve que cambiar a una POST.

El javascript utiliza el complemento de descarga de archivos JQuery y consta de 2 llamadas sucesivas. Un POST (Para enviar parámetros) 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");
    }
Machinegon
fuente
0

La respuesta de CSL se implementó en un proyecto en el que estoy trabajando, pero el problema en el que incurrí fue escalar en Azure rompió nuestras descargas de archivos. En cambio, pude hacer esto con una llamada AJAX:

SERVIDOR

[HttpPost]
public FileResult DownloadInvoice(int id1, int id2)
{
    //necessary to get the filename in the success of the ajax callback
    HttpContext.Response.Headers.Add("Access-Control-Expose-Headers", "Content-Disposition");

    byte[] fileBytes = _service.GetInvoice(id1, id2);
    string fileName = "Invoice.xlsx";
    return File(fileBytes, System.Net.Mime.MediaTypeNames.Application.Octet, fileName);
}

CLIENTE (versión modificada de la descarga del archivo Handle desde la publicación ajax )

$("#downloadInvoice").on("click", function() {
    $("#loaderInvoice").removeClass("d-none");

    var xhr = new XMLHttpRequest();
    var params = [];
    xhr.open('POST', "@Html.Raw(Url.Action("DownloadInvoice", "Controller", new { id1 = Model.Id1, id2 = Model.Id2 }))", 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 = typeof File === 'function'
                ? new File([this.response], filename, { type: type })
                : 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 = downloadUrl;
                    } else {
                        a.href = downloadUrl;
                        a.download = filename;
                        document.body.appendChild(a);
                        a.click();
                    }
                } else {
                    window.location = downloadUrl;

                }

                setTimeout(function() {
                        URL.revokeObjectURL(downloadUrl);
                    $("#loaderInvoice").addClass("d-none");
                }, 100); // cleanup
            }
        }
    };
    xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
    xhr.send($.param(params));
});
wilsjd
fuente
0
  $.ajax({
    global: false,
    url: SitePath + "/User/ExportTeamMembersInExcel",
    "data": { 'UserName': UserName, 'RoleId': RoleId, UserIds: AppraseeId },
    "type": "POST",
    "dataType": "JSON",
   "success": function (result) {
        debugger
        var bytes = new Uint8Array(result.FileContents);
        var blob = new Blob([bytes], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" });
        var link = document.createElement('a');
        link.href = window.URL.createObjectURL(blob);
        link.download = "myFileName.xlsx";
        link.click();
      },
    "error": function () {
        alert("error");
    }
})


[HttpPost]
    public JsonResult ExportTeamMembersInExcel(string UserName, long? RoleId, string[] UserIds)
    {
        MemoryStream stream = new MemoryStream();
        FileContentResult robj;
        DataTable data = objuserservice.ExportTeamToExcel(UserName, RoleId, UserIds);
        using (XLWorkbook wb = new XLWorkbook())
        {
            wb.Worksheets.Add(data, "TeamMembers");
            using (stream)
            {
                wb.SaveAs(stream);
            }
        }
        robj = File(stream.ToArray(), System.Net.Mime.MediaTypeNames.Application.Octet, "TeamMembers.xlsx");
        return Json(robj, JsonRequestBehavior.AllowGet);
    }
rinku Choudhary
fuente
no se puede abrir el archivo, el Excel simplemente se abre y luego no se cierra solo, incluso agregué stream.close () justo antes de robj pero no funciona.
dawncode
0

Puedo sonar bastante ingenuo y puedo atraer bastantes críticas, pero así es como lo hice
( no implica la ajaxexportación, pero tampoco hace una devolución de datos completa )

Gracias por esta publicación y esta respuesta.
Crea un controlador simple

public class HomeController : Controller
{               
   /* A demo action
    public ActionResult Index()
    {           
        return View(model);
    }
   */
    [HttpPost]
    public FileResult ExportData()
    {
        /* An example filter
        var filter = TempData["filterKeys"] as MyFilter;
        TempData.Keep();            */
        var someList = db.GetDataFromDb(/*filter*/) // filter as an example

    /*May be here's the trick, I'm setting my filter in TempData["filterKeys"] 
     in an action,(GetFilteredPartial() illustrated below) when 'searching' for the data,
     so do not really need ajax here..to pass my filters.. */

     //Some utility to convert list to Datatable
     var dt = Utility.ConvertToDataTable(someList); 

      //  I am using EPPlus nuget package 
      using (ExcelPackage pck = new ExcelPackage())
      {
          ExcelWorksheet ws = pck.Workbook.Worksheets.Add("Sheet1");
          ws.Cells["A1"].LoadFromDataTable(dt, true);

            using (var memoryStream = new MemoryStream())
            {                   
              pck.SaveAs(memoryStream);
              return File(memoryStream.ToArray(),
              "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
              "ExportFileName.xlsx");                    
            }                
        }   
    }

    //This is just a supporting example to illustrate setting up filters ..        
   /* [HttpPost]
    public PartialViewResult GetFilteredPartial(MyFilter filter)
    {            
        TempData["filterKeys"] = filter;
        var filteredData = db.GetConcernedData(filter);
        var model = new MainViewModel();
        model.PartialViewModel = filteredData;

        return PartialView("_SomePartialView", model);
    } */     
} 

Y aquí están las Vistas ...

/*Commenting out the View code, in order to focus on the imp. code     
 @model Models.MainViewModel
 @{Layout...}     

      Some code for, say, a partial View  
      <div id="tblSampleBody">
        @Html.Partial("_SomePartialView", Model.PartialViewModel)
      </div>
  */                                                       
//The actual part.. Just **posting** this bit of data from the complete View...
//Here, you are not posting the full Form..or the complete View
   @using (Html.BeginForm("ExportData", "Home", FormMethod.Post))
    {
        <input type="submit" value="Export Data" />
    }
//...
//</div>

/*And you may require to pass search/filter values.. as said in the accepted answer..
That can be done while 'searching' the data.. and not while
 we need an export..for instance:-             

<script>             
  var filterData = {
      SkipCount: someValue,
      TakeCount: 20,
      UserName: $("#UserName").val(),
      DepartmentId: $("#DepartmentId").val(),     
   }

  function GetFilteredData() {
       $("#loader").show();
       filterData.SkipCount = 0;
       $.ajax({
          url: '@Url.Action("GetFilteredPartial","Home")',
          type: 'POST',
          dataType: "html",
          data: filterData,
          success: function (dataHTML) {
          if ((dataHTML === null) || (dataHTML == "")) {
              $("#tblSampleBody").html('<tr><td>No Data Returned</td></tr>');
                $("#loader").hide();
            } else {
                $("#tblSampleBody").html(dataHTML);                    
                $("#loader").hide();
            }
        }
     });
   }    
</script>*/

El objetivo de la truco parece que, estamos publicando una forma (una parte de la maquinilla de afeitar Vista) sobre la que estamos pidiendo una Action method, que devuelve: una FileResult, y esta FileResultdevuelve the Excel File..
Y para la publicación de los valores de filtro, como se ha dicho, ( y si lo necesita), estoy realizando una solicitud de publicación para otra acción, como se ha intentado describir.

Irfaan
fuente
-1

Estoy usando Asp.Net WebForm y solo quiero descargar un archivo del lado del servidor. Hay muchos artículos, pero no puedo encontrar una respuesta básica. Ahora, probé una forma básica y lo conseguí.

Ese es mi problema.

Tengo que crear muchos botones de entrada dinámicamente en tiempo de ejecución. Y quiero agregar cada botón al botón de descarga dando un número de archivo único.

Creo cada botón así:

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 archivos antiguos con otro programa. Pero si hay una manera de simplemente enviar una matriz de bytes para descargar el archivo, como usar Response. Quiero usarlo.

Espero que esto sea útil para cualquiera.

¿Puede OTUR
fuente
-1

En formulario de envío

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