Téléchargez le fichier Excel via AJAX MVC

J’ai un grand (ish) forme dans MVC.

Je dois être capable de générer un fichier Excel contenant des données d’un sous-ensemble de ce formulaire.

La difficulté réside dans le fait que cela ne devrait pas affecter le rest du formulaire et que je souhaite le faire via AJAX. J’ai rencontré quelques questions sur SO qui semblent liées, mais je ne peux pas tout à fait comprendre ce que les réponses signifient.

Celui-ci semble le plus proche de ce que je recherche après: asp-net-mvc-download-excel – mais je ne suis pas sûr de comprendre la réponse, et il a déjà quelques années. Je suis également tombé sur un autre article (je ne le trouve plus) sur l’utilisation d’un iframe pour gérer le téléchargement du fichier, mais je ne sais pas comment le faire fonctionner avec MVC.

Mon fichier Excel retourne bien si je fais un post complet mais je ne peux pas le faire fonctionner avec AJAX dans mvc.

Vous ne pouvez pas retourner directement un fichier au téléchargement via un appel AJAX. Une autre approche consiste à utiliser un appel AJAX pour publier les données connexes sur votre serveur. Vous pouvez ensuite utiliser le code côté serveur pour créer le fichier Excel (je vous recommanderais d’utiliser EPPlus ou NPOI pour cela bien que cela puisse sembler si cette partie fonctionne).

MISE À JOUR Septembre 2016

Ma réponse originale (ci-dessous) datait de plus de 3 ans. Je pensais donc que je mettrais à jour car je ne crée plus de fichiers sur le serveur lors du téléchargement de fichiers via AJAX. Cependant, j’ai laissé la réponse originale car elle pourrait encore être utile, selon vos exigences spécifiques.

Un scénario courant dans mes applications MVC consiste à générer des rapports via une page Web comportant certains parameters de rapport configurés par l’utilisateur (plages de dates, filtres, etc.). Lorsque l’utilisateur a spécifié les parameters qu’il envoie au serveur, le rapport est généré (par exemple, un fichier Excel en sortie), puis je stocke le fichier résultant sous forme de tableau d’octets dans le TempData avec une référence unique. Cette référence est renvoyée en tant que résultat Json à ma fonction AJAX qui redirige ensuite vers une action distincte du contrôleur afin d’extraire les données de TempData et de les télécharger vers le navigateur de l’utilisateur final.

Pour vous donner plus de détails, en supposant que votre vue MVC ait un formulaire lié à une classe Model, appelons Model ReportVM .

Premièrement, une action du contrôleur est requirejse pour recevoir le modèle posté. Voici un exemple:

 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 ssortingng handle = Guid.NewGuid().ToSsortingng(); 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" } }; } 

L’appel AJAX qui poste mon formulaire MVC sur le contrôleur ci-dessus et reçoit la réponse se présente comme suit:

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

L’action du contrôleur pour gérer le téléchargement du fichier:

 [HttpGet] public virtual ActionResult Download(ssortingng fileGuid, ssortingng 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(); } } 

Un autre changement qui pourrait facilement être pris en compte si nécessaire consiste à passer le type MIME du fichier en tant que troisième paramètre, de sorte que l’action d’un contrôleur puisse correctement servir une variété de formats de fichier de sortie.

Cela supprime la nécessité de créer et de stocker des fichiers physiques sur le serveur. Ainsi, aucune routine de maintenance n’est requirejse et, encore une fois, cette opération est transparente pour l’utilisateur final.

Notez que l’avantage d’utiliser TempData plutôt que Session est que, une fois que TempData est lu, les données sont effacées, ce qui optimise l’utilisation de la mémoire si le volume de demandes de fichiers est élevé. Voir TempData Best Practice .

Réponse originale

Vous ne pouvez pas retourner directement un fichier au téléchargement via un appel AJAX. Une autre approche consiste à utiliser un appel AJAX pour publier les données connexes sur votre serveur. Vous pouvez ensuite utiliser le code côté serveur pour créer le fichier Excel (je vous recommanderais d’utiliser EPPlus ou NPOI pour cela bien que cela puisse sembler comme si cette partie fonctionnait).

Une fois le fichier créé sur le serveur, renvoyez le chemin d’access au fichier (ou simplement le nom du fichier) comme valeur de retour à votre appel AJAX, puis définissez le window.location JavaScript sur cette URL, ce qui invitera le navigateur à télécharger le fichier. .

Du sharepoint vue de l’utilisateur final, l’opération de téléchargement de fichier est transparente, car ils ne quittent jamais la page sur laquelle la demande provient.

Vous trouverez ci-dessous un exemple simple et artificiel d’appel ajax:

 $.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; } }); 
  • Le paramètre url est la méthode contrôleur / action où votre code créera le fichier Excel.
  • Le paramètre data contient les données JSON qui seraient extraites du formulaire.
  • returnValue serait le nom du fichier Excel que vous venez de créer.
  • La commande window.location redirige vers la méthode Controller / Action qui renvoie le fichier à télécharger.

Un exemple de méthode de contrôleur pour l’action de téléchargement serait:

 [HttpGet] public virtual ActionResult Download(ssortingng file) { ssortingng fullPath = Path.Combine(Server.MapPath("~/MyFiles"), file); return File(fullPath, "application/vnd.ms-excel", file); } 

Mes 2 centimes – vous n’avez pas besoin de stocker Excel sous forme de fichier physique sur le serveur – mais stockez-le dans le cache (de session). Utilisez un nom généré de manière unique pour votre variable de cache (qui stocke ce fichier excel) – ce sera le retour de votre appel ajax (initial). Ainsi, vous n’avez pas à traiter les problèmes d’access aux fichiers, à gérer (supprimer) les fichiers lorsque vous n’en avez pas besoin, etc., et, ayant le fichier dans le cache, il est plus rapide de le récupérer.

J’ai récemment pu accomplir cela dans MVC (bien qu’il ne soit pas nécessaire d’utiliser AJAX) sans créer un fichier physique et je pensais partager mon code:

Fonction JavaScript très simple (un clic sur le bouton datatables.net déclenche cette opération):

 function getWinnersExcel(drawingId) { window.location = "/drawing/drawingwinnersexcel?drawingid=" + drawingId; } 

Code du contrôleur C #:

  public FileResult DrawingWinnersExcel(int drawingId) { MemoryStream stream = new MemoryStream(); // cleaned up automatically by MVC List winnerList = DrawingDataAccess.GetWinners(drawingId); // simple entity framework-based data resortingeval ExportHelper.GetWinnersAsExcelMemoryStream(stream, winnerList, drawingId); ssortingng suggestedFilename = ssortingng.Format("Drawing_{0}_Winners.xlsx", drawingId); return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml", suggestedFilename); } 

Dans la classe ExportHelper, j’utilise un outil tiers ( GemBox.Spreadsheet ) pour générer le fichier Excel. Il comporte une option Enregistrer dans le stream. Cela étant dit, il existe plusieurs façons de créer des fichiers Excel faciles à écrire dans un stream de mémoire.

 public static class ExportHelper { internal static void GetWinnersAsExcelMemoryStream(MemoryStream stream, List 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 } } 

Dans IE, Chrome et Firefox, le navigateur vous invite à télécharger le fichier. Aucune navigation réelle ne se produit.

J’ai utilisé la solution publiée par CSL mais je vous recommande de ne pas stocker les données du fichier dans Session pendant toute la session. En utilisant TempData, les données du fichier sont automatiquement supprimées après la demande suivante (qui est la demande GET du fichier). Vous pouvez également gérer la suppression des données du fichier dans Session dans l’action de téléchargement.

La session peut utiliser beaucoup de mémoire / espace en fonction du stockage SessionState et du nombre de fichiers exportés au cours de la session et si vous avez plusieurs utilisateurs.

J’ai mis à jour le code côté sereur à partir de CSL pour utiliser TempData à la place.

 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 ssortingng handle = Guid.NewGuid().ToSsortingng() 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(ssortingng fileGuid, ssortingng 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(); } } 

Créez d’abord l’action du contrôleur qui créera le fichier Excel

 [HttpPost] public JsonResult ExportExcel() { DataTable dt = DataService.GetData(); var fileName = "Excel_" + DateTime.Now.ToSsortingng("yyyyMMddHHmm") + ".xls"; //save the file to server temp folder ssortingng 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 = "" }); } 

puis créez l’action Télécharger

 [HttpGet] [DeleteFileAtsortingbute] //Action Filter, it will auto delete the file after download, //I will explain it later public ActionResult Download(ssortingng file) { //get the temp folder and file path in server ssortingng 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 vous voulez supprimer le fichier une fois téléchargé, créez ceci

 public class DeleteFileAtsortingbute : ActionFilterAtsortingbute { public override void OnResultExecuted(ResultExecutedContext filterContext) { filterContext.HttpContext.Response.Flush(); //convert the current filter context to file and get the file path ssortingng filePath = (filterContext.Result as FilePathResult).FileName; //delete the file after download System.IO.File.Delete(filePath); } } 

et enfin appel ajax de votre vue MVC Razor

 //I use blockUI for loading... $.blockUI({ message: '

Please wait a moment...

' }); $.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; } });

Ce fil m’a aidé à créer ma propre solution que je partagerai ici. J’utilisais une requête GET ajax au début sans problèmes, mais la longueur de l’URL de la requête a été dépassée et j’ai donc dû passer à un POST.

Le javascript utilise le plugin de téléchargement de fichier JQuery et consiste en 2 appels successifs. Un POST (Pour envoyer des parameters) et un GET pour récupérer le fichier.

  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.ssortingngify(data), success: download.bind(this), fail: onFail.bind(this) }); 

Du côté serveur

  [HttpPost] public ssortingng MassExportDocuments(MassExportDocumentsInput input) { // Save query for file download use var guid = Guid.NewGuid(); HttpContext.Current.Cache.Insert(guid.ToSsortingng(), input, null, DateTime.Now.AddMinutes(5), Cache.NoSlidingExpiration); return guid.ToSsortingng(); } [HttpGet] public async Task MassExportDocuments([FromUri] Guid guid) { //Get params from cache, generate and return var model = (MassExportDocumentsInput)HttpContext.Current.Cache[guid.ToSsortingng()]; ..... // Document generation // to determine when file is downloaded HttpContext.Current .Response .SetCookie(new HttpCookie("fileDownload", "true") { Path = "/" }); return FileResult(memoryStream, "documents.zip", "application/zip"); } 

La réponse de CSL a été implémentée dans un projet sur lequel je travaillais, mais le problème que j’ai rencontré était celui de la montée en charge sur Azure, nos téléchargements de fichiers ont été interrompus. Au lieu de cela, j’ai pu le faire avec un appel AJAX:

SERVEUR

 [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); ssortingng fileName = "Invoice.xlsx"; return File(fileBytes, System.Net.Mime.MediaTypeNames.Application.Octet, fileName); } 

CLIENT (version modifiée du téléchargement du fichier Handle depuis ajax post )

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

using 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); } 
 $ .ajax ({
                 type: "GET",
                 url: "/ Home / Downloadexcel /",
                 contentType: "application / json; charset = utf-8",
                 data: null,
                 succès: fonction (Rdata) {
                     débogueur;
                     var bytes = new Uint8Array (Rdata.FileContents); 
                     var blob = new Blob ([octets], {type: "application / vnd.openxmlformats-officedocument.spreadsheetml.sheet"});
                     var link = document.createElement ('a');
                     link.href = window.URL.createObjectURL (object blob);
                     link.download = "myFileName.xlsx";
                     link.click ();
                 },
                 erreur: fonction (err) {

                 }

             });

J’utilise Asp.Net WebForm et je veux juste télécharger un fichier côté serveur. Il y a beaucoup d’articles mais je ne trouve pas de réponse simple. Maintenant, j’ai essayé un moyen de base et l’ai eu.

C’est mon problème.

Je dois créer beaucoup de boutons de saisie dynamicment au moment de l’exécution. Et je veux append chaque bouton pour télécharger le bouton avec un numéro de fichier unique.

Je crée chaque bouton comme ceci:

 fragment += "
";

Sur le formulaire de soumission

 public ActionResult ExportXls() { var filePath=""; CommonHelper.WriteXls(filePath, "Text.xls"); } public static void WriteXls(ssortingng filePath, ssortingng targetFileName) { if (!Ssortingng.IsNullOrEmpty(filePath)) { HttpResponse response = HttpContext.Current.Response; response.Clear(); response.Charset = "utf-8"; response.ContentType = "text/xls"; response.AddHeader("content-disposition", ssortingng.Format("attachment; filename={0}", targetFileName)); response.BinaryWrite(File.ReadAllBytes(filePath)); response.End(); } }