Laden Sie die Excel-Datei über AJAX MVC herunter

Ich habe eine große (ish) Form in MVC.

Ich muss in der Lage sein, eine Excel-Datei zu erzeugen, die Daten von einer Teilmenge dieses Formulars enthält.

Das Schwierige daran ist, dass dies den Rest des Formulars nicht beeinflussen sollte und ich möchte es über AJAX machen. Ich bin auf ein paar Fragen zu SO gestoßen, die miteinander verwandt zu sein scheinen, aber ich kann nicht genau herausfinden, was die Antworten bedeuten.

Dieser scheint mir am nächsten zu sein: asp-net-mvc-downloading-excel – aber ich bin mir nicht sicher, ob ich die Antwort verstehe, und sie ist jetzt ein paar Jahre alt. Ich bin auch auf einen anderen Artikel gestoßen (ich kann ihn nicht mehr finden) über die Verwendung eines Iframes, um den Dateidownload zu verarbeiten, aber ich bin mir nicht sicher, wie ich das mit MVC funktioniere.

Meine Excel-Datei ist in Ordnung, wenn ich einen vollständigen Post zurück mache, aber ich kann es nicht mit AJAX in mvc arbeiten.

Sie können eine Datei nicht direkt über einen AJAX-Aufruf zum Download zurücksenden. Alternativ können Sie einen AJAX-Aufruf verwenden, um die entsprechenden Daten an Ihren Server zu senden. Sie können dann serverseitigen Code verwenden, um die Excel-Datei zu erstellen (ich würde empfehlen, hierfür EPPlus oder NPOI zu verwenden, obwohl es sich anhört, als ob Sie dieses Teil haben).

UPDATE September 2016

Meine ursprüngliche Antwort (unten) war über 3 Jahre alt, also dachte ich, ich würde aktualisieren, da ich keine Dateien mehr auf dem Server beim Herunterladen von Dateien über AJAX erstelle, aber ich habe die ursprüngliche Antwort hinterlassen, da sie immer noch von Nutzen ist Ihre spezifischen Anforderungen.

Ein gängiges Szenario in meinen MVC-Anwendungen ist die Berichterstellung über eine Webseite mit einigen vom Benutzer konfigurierten Berichtsparametern (Datumsbereiche, Filter usw.). Wenn der Benutzer die Parameter angegeben hat, die er an den Server TempData , wird der Bericht generiert (z. B. eine Excel-Datei als Ausgabe), und dann wird die resultierende Datei als TempData im TempData Bucket mit einer eindeutigen Referenz TempData . Diese Referenz wird als Json-Ergebnis an meine AJAX-function zurückgegeben, die anschließend zu einer separaten Controller-Aktion umleitet, um die Daten aus TempData zu extrahieren und in den Browser des TempData herunterzuladen.

ReportVM Sie eine MVC View mit einem an eine Model-class gebundenen Formular verwenden ReportVM , rufen Sie das Model ReportVM .

Zunächst ist eine Controller-Aktion erforderlich, um das veröffentlichte Modell zu erhalten. Ein Beispiel wäre:

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

Der AJAX-Aufruf, der mein MVC-Formular an den obigen Controller sendet und die Antwort empfängt, sieht folgendermaßen aus:

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

Die Controller-Aktion für das Herunterladen der Datei:

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

Eine weitere Änderung, die bei Bedarf leicht angepasst werden kann, besteht darin, den MIME-Typ der Datei als dritten Parameter zu übergeben, so dass die eine Controller-Aktion eine Vielzahl von Ausgabedateiformaten korrekt bedienen kann.

Dadurch müssen keine physischen Dateien mehr auf dem Server erstellt und gespeichert werden, so dass keine Routine-Routinen erforderlich sind, und dies ist wiederum für den Endbenutzer nahtlos.

Beachten Sie, dass der Vorteil der Verwendung von TempData anstelle von Session besteht, dass nach TempData Lesen von TempData die Daten gelöscht werden, so dass die Speicherauslastung effizienter ist, wenn Sie eine große Menge an Dateianforderungen haben. Siehe TempData Best Practice .

ORIGINAL Antwort

Sie können eine Datei nicht direkt über einen AJAX-Aufruf zum Download zurücksenden. Alternativ können Sie einen AJAX-Aufruf verwenden, um die entsprechenden Daten an Ihren Server zu senden. Sie können dann serverseitigen Code verwenden, um die Excel-Datei zu erstellen (ich würde empfehlen, hierfür EPPlus oder NPOI zu verwenden, obwohl es sich anhört, als ob Sie dieses Teil haben).

Sobald die Datei auf dem Server erstellt wurde, übergeben Sie den Pfad zur Datei (oder nur den Dateinamen) als Rückgabewert an Ihren AJAX-Aufruf und legen Sie dann die JavaScript window.location auf diese URL fest, die den Browser zum Herunterladen der Datei window.location .

Aus Sicht der Endbenutzer ist die Dateidownloadoperation nahtlos, da sie die Seite, auf der die Anforderung basiert, niemals verlassen.

Im Folgenden finden Sie ein einfaches künstliches Beispiel für einen Ajax-Aufruf, um dies zu erreichen:

 $.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; } }); 
  • Der URL- Parameter ist die Controller / Action-Methode, bei der der Code die Excel-Datei erstellt.
  • Der Datenparameter enthält die JSON-Daten, die aus dem Formular extrahiert werden.
  • returnValue wäre der Dateiname der neu erstellten Excel-Datei.
  • Der Befehl window.location verweist auf die Controller / Action-Methode, die Ihre Datei zum Download zurückgibt.

Eine Beispiel-Controller-Methode für die Download-Aktion wäre:

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

Meine 2 Cent – Sie müssen das Excel nicht als physische Datei auf dem Server speichern – speichern Sie es stattdessen im (Session) Cache. Verwenden Sie einen eindeutig generierten Namen für Ihre Cache-Variable (die diese Excel-Datei speichert) – dies ist die Rückgabe Ihres (anfänglichen) Ajax-Aufrufs. Auf diese Weise müssen Sie nicht mit Dateizugriffsproblemen umgehen, die Dateien verwalten (löschen), wenn sie nicht benötigt werden usw. und die Datei im Cache schneller abrufen.

Ich konnte dies kürzlich in MVC erreichen (obwohl ich AJAX nicht verwenden musste), ohne eine physische Datei zu erstellen, und dachte, ich würde meinen Code teilen:

Super einfache JavaScript function (datatables.net Button Klick triggers dies aus):

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

C # Controller-Code:

  public FileResult DrawingWinnersExcel(int drawingId) { MemoryStream stream = new MemoryStream(); // cleaned up automatically by MVC List 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); } 

In der ExportHelper-class verwende ich ein Tool von Drittanbietern ( GemBox.Spreadsheet ), um die Excel-Datei zu generieren, und es verfügt über die Option “In Stream speichern”. Davon abgesehen gibt es eine Reihe von Möglichkeiten zum Erstellen von Excel-Dateien, die einfach in einen Speicher-Stream geschrieben werden können.

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

In IE, Chrome und Firefox fordert der Browser zum Herunterladen der Datei auf, und es findet keine eigentliche Navigation statt.

Ich habe die von CSL gepostete Lösung verwendet, aber ich würde empfehlen, dass Sie die Dateidaten während der gesamten Sitzung nicht in der Sitzung speichern. Durch die Verwendung von TempData werden die Dateidaten automatisch nach der nächsten Anfrage entfernt (was die GET-Anfrage für die Datei ist). Sie können auch das Entfernen der Dateidaten in der Sitzung beim Herunterladen verwalten.

Die Sitzung könnte viel Speicher / Speicherplatz verbrauchen, abhängig vom Speicher des SessionState und davon, wie viele Dateien während der Sitzung exportiert werden und ob viele Benutzer vorhanden sind.

Ich habe den Serer-Side-Code von CSL aktualisiert, um stattdessen TempData zu verwenden.

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

Zuerst Erstellen Sie die Controller-Aktion, die die Excel-Datei erstellt

 [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 = "" }); } 

Erstellen Sie dann die Download-Aktion

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

Wenn Sie die Datei nach dem Download löschen möchten, erstellen Sie diese

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

und schließlich Ajax Anruf von dir MVC Razor anzeigen

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

Dieser Thread hat mir geholfen, meine eigene Lösung zu erstellen, die ich hier teilen werde. Ich habe zuerst eine GET ajax Anfrage ohne Probleme verwendet, aber es kam zu einem Punkt, wo die Länge der Anfrage URL überschritten wurde, also musste ich zu einem POST wechseln.

Das Javascript verwendet das JQuery-Datei-Download-Plugin und besteht aus 2 aufeinanderfolgenden Aufrufen. Ein POST (um Parameter zu senden) und ein GET, um die Datei zu retreive.

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

Serverseite

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

Ich benutze Asp.Net WebForm und möchte nur eine Datei von der Serverseite herunterladen. Es gibt eine Menge Artikel, aber ich kann nicht nur eine einfache Antwort finden. Jetzt habe ich einen einfachen Weg ausprobiert und verstanden.

Das ist mein Problem.

Ich muss eine Menge Eingabeknopf dynamisch zur Laufzeit erstellen. Und ich möchte jede Taste zum Download-Button mit einer eindeutigen Dateinummer hinzufügen.

Ich erstelle jede Schaltfläche wie folgt:

 fragment += "
";

Auf Formular absenden

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