Laden Sie die Excel-Datei über AJAX MVC herunter


91

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

Ich muss in der Lage sein, eine Excel-Datei mit Daten aus einer Teilmenge dieses Formulars zu generieren.

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

Dieser scheint dem am nächsten zu sein, wonach ich suche: asp-net-mvc-downloading-excel - aber ich bin nicht sicher, ob ich die Antwort verstehe, und er ist jetzt ein paar Jahre alt. Ich bin auch auf einen anderen Artikel gestoßen (kann ihn nicht mehr finden), in dem es darum geht, einen Iframe für den Dateidownload zu verwenden, bin mir aber nicht sicher, wie ich das mit MVC zum Laufen bringen kann.

Meine Excel-Datei kehrt in Ordnung zurück, wenn ich einen vollständigen Beitrag zurückschicke, aber ich kann ihn nicht mit AJAX in MVC zum Laufen bringen.

Antworten:


213

Sie können eine Datei nicht direkt über einen AJAX-Aufruf zum Herunterladen zurückgeben. Ein alternativer Ansatz besteht darin, einen AJAX-Aufruf zu verwenden, um die zugehörigen Daten auf Ihrem Server zu veröffentlichen. Sie können dann serverseitigen Code verwenden, um die Excel-Datei zu erstellen (ich würde empfehlen, dafür EPPlus oder NPOI zu verwenden, obwohl es so klingt, als ob dieser Teil funktioniert).

UPDATE September 2016

Meine ursprüngliche Antwort (unten) war über 3 Jahre alt, daher dachte ich, ich würde sie aktualisieren, da ich beim Herunterladen von Dateien über AJAX keine Dateien mehr auf dem Server erstelle. Ich habe jedoch die ursprüngliche Antwort belassen, da sie möglicherweise noch von Nutzen ist Ihre spezifischen Anforderungen.

Ein häufiges 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 sendet, wird der Bericht generiert (z. B. eine Excel-Datei als Ausgabe), und dann speichere ich die resultierende Datei als Byte-Array im TempDataBucket mit einer eindeutigen Referenz. Diese Referenz wird als Json-Ergebnis an meine AJAX-Funktion zurückgegeben, die anschließend zu einer separaten Controller-Aktion umleitet, um die Daten aus TempDatadem Browser des Endbenutzers zu extrahieren und in diesen herunterzuladen.

Um dies näher zu erläutern, rufen wir das Modell auf, vorausgesetzt, Sie haben eine MVC-Ansicht, deren Formular an eine Modellklasse gebunden ist 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 oben genannten 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 zum 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 problemlos vorgenommen werden kann, besteht darin, den MIME-Typ der Datei als dritten Parameter zu übergeben, damit die eine Controller-Aktion eine Vielzahl von Ausgabedateiformaten korrekt bedienen kann.

Auf diese Weise müssen keine physischen Dateien mehr erstellt und auf dem Server gespeichert werden, sodass keine Reinigungsroutinen erforderlich sind. Dies ist wiederum für den Endbenutzer nahtlos.

Beachten Sie, dass der Vorteil der Verwendung TempDataund nicht darin Sessionbesteht, dass TempDatadie Daten nach dem Lesen gelöscht werden, sodass die Speichernutzung effizienter ist, wenn Sie eine große Anzahl von Dateianforderungen haben. Siehe TempData Best Practice .

ORIGINAL Antwort

Sie können eine Datei nicht direkt über einen AJAX-Aufruf zum Herunterladen zurückgeben. Ein alternativer Ansatz besteht darin, einen AJAX-Aufruf zu verwenden, um die zugehörigen Daten auf Ihrem Server zu veröffentlichen. Sie können dann serverseitigen Code verwenden, um die Excel-Datei zu erstellen (ich würde empfehlen, dafür EPPlus oder NPOI zu verwenden, obwohl es so klingt, als ob dieser Teil funktioniert).

Nachdem die Datei auf dem Server erstellt wurde, geben Sie den Pfad zur Datei (oder nur den Dateinamen) als Rückgabewert für Ihren AJAX-Aufruf zurück und setzen Sie das JavaScript window.locationauf diese URL, die den Browser zum Herunterladen der Datei auffordert.

Aus Sicht der Endbenutzer ist der Dateidownload-Vorgang nahtlos, da sie die Seite, von der die Anforderung stammt, nie verlassen.

Im Folgenden finden Sie ein einfaches 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 Parameter url ist die Controller / Action-Methode, mit der Ihr Code die Excel-Datei erstellt.
  • Der Datenparameter enthält die JSON-Daten, die aus dem Formular extrahiert werden würden.
  • returnValue ist der Dateiname Ihrer neu erstellten Excel-Datei.
  • Der Befehl window.location leitet zur Controller / Action-Methode weiter, die Ihre Datei tatsächlich 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);
}

3
Dies scheint eine gute mögliche Option zu sein, aber bevor ich damit fortfahre, gibt es keine anderen Alternativen, bei denen die Datei nicht zuerst auf dem Server erstellt wird?
Valuk

4
Nicht, dass mir das bewusst wäre - diesen Ansatz habe ich schon oft erfolgreich angewendet. Aus Anwendersicht ist es nahtlos. Das einzige, was Sie beachten müssen, ist, dass Sie eine Reinigungsroutine benötigen, um die erstellten Dateien aufzuräumen, da sie im Laufe der Zeit bereitgestellt werden.
Connectedsoftware

6
Erstellen eines Endpunkts '/ Download? File = ...' SCHREIEN massives Sicherheitsrisiko - Ich bin kein Sicherheitsexperte, aber ich würde denken, Sie möchten Benutzerauthentifizierung, Eingabesanierung, MVCs [ValidateAntiForgeryToken] hinzufügen und andere Sicherheitsmaßnahmen am besten erwähnen -Praktiken zu dieser Antwort.
Jimmy

2
@CSL Ich erhalte immer den Fehler 0x800a03f6 - JavaScript-Laufzeitfehler: Ungültiges Zeichen in der var response = JSON.parse (Daten);
Standage

2
Großartig, warum setzen Sie die alte Antwort nicht ganz unten ein? Und die neue Antwort oben, damit die Leute keine Zeit verschwenden
goamn

19

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


1
Wie genau würdest du das machen? Klingt interessant.
Natalia

2
Ein Beispiel wäre schön (ich meine, wie man es im Cache speichert, ohne die Excel-Datei zu generieren).
Tadej

Wie skalierbar ist das allerdings? Wenn ein Benutzer mehrere große Berichte herunterlädt?
Zapnologica

Wenn Sie sich in Azure befinden, funktioniert die Sitzung, bis Sie ARRAffinity deaktivieren.
JeeShen Lee

13

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

Super einfache JavaScript-Funktion (das Klicken auf die Schaltfläche datatables.net löst 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<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);
    }

In der ExportHelper-Klasse verwende ich ein Drittanbieter-Tool ( GemBox.Spreadsheet ), um die Excel-Datei zu generieren, und es verfügt über die Option In Stream speichern. Abgesehen davon gibt es eine Reihe von Möglichkeiten, Excel-Dateien zu erstellen, die einfach in einen Speicherstrom geschrieben werden können.

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

     }
}

In IE, Chrome und Firefox fordert der Browser zum Herunterladen der Datei auf, und es erfolgt keine tatsächliche Navigation.


8

Erstellen Sie zuerst die Controller-Aktion, mit der die Excel-Datei erstellt wird

[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 Herunterladen 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 Ihnen MVC Razor Ansicht

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

7

Ich habe die von CSL bereitgestellte Lösung verwendet, aber ich würde empfehlen, die Dateidaten nicht während der gesamten Sitzung in der Sitzung zu speichern. Bei Verwendung von TempData werden die Dateidaten nach der nächsten Anforderung (der GET-Anforderung für die Datei) automatisch entfernt. Sie können auch das Entfernen der Dateidaten in Sitzung in Download-Aktion verwalten.

Die Sitzung kann viel Speicherplatz beanspruchen, abhängig vom SessionState-Speicher und der Anzahl der exportierten Dateien während der Sitzung und wenn Sie viele Benutzer haben.

Ich habe den sererseitigen 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();
   }
}

@Nichlas Ich hatte auch angefangen, TempData zu verwenden. Ihre Antwort veranlasste mich, meine zu aktualisieren, um dies widerzuspiegeln!
connectedsoftware

5

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


Im AJAX CALL Success Block ist success: function (Rdata) {debugger; var bytes = new Uint8Array (Rdata.FileContents); var blob = neuer Blob ([Bytes], {Typ: "application / vnd.openxmlformats-officedocument.spreadsheetml.sheet"}); var link = document.createElement ('a'); link.href = window.URL.createObjectURL (blob); link.download = "myFileName.xlsx"; link.click (); },
GVKRAO

Jemand hat den Download von Excel-Dateien unter dem obigen Link implementiert. Er funktioniert nur für @ html.Beginform (). Nach kleinen Änderungen ist dieser Code erforderlich. Für AJAX-Aufruf Success Block. Bitte
überprüfen

3
$ .ajax ({
                Typ: "GET",
                URL: "/ Home / Downloadexcel /",
                contentType: "application / json; charset = utf-8",
                Daten: null,
                Erfolg: Funktion (Rdata) {
                    Debugger;
                    var bytes = new Uint8Array (Rdata.FileContents); 
                    var blob = neuer Blob ([Bytes], {Typ: "application / vnd.openxmlformats-officedocument.spreadsheetml.sheet"});
                    var link = document.createElement ('a');
                    link.href = window.URL.createObjectURL (blob);
                    link.download = "myFileName.xlsx";
                    link.click ();
                },
                Fehler: Funktion (err) {

                }}

            });

1

Die akzeptierte Antwort funktionierte für mich nicht ganz, da ich durch den Ajax-Anruf ein 502 Bad Gateway- Ergebnis erhielt, obwohl vom Controller alles in Ordnung zu sein schien.

Vielleicht habe ich mit TempData ein Limit erreicht - nicht sicher, aber ich habe festgestellt, dass es gut funktioniert hat , wenn ich IMemoryCache anstelle von TempData verwendet habe. Hier ist meine angepasste Version des Codes in der akzeptierten Antwort:

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

}

AJAX-Anruf bleibt wie bei der akzeptierten Antwort (ich habe keine Änderungen vorgenommen):

$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 zum Herunterladen der Datei:

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

...

Jetzt gibt es einen zusätzlichen Code zum Einrichten von MemoryCache ...

Um "_cache" zu verwenden, habe ich den Konstruktor für den Controller wie folgt eingefügt:

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

Stellen Sie unter ConfigureServices in Startup.cs Folgendes sicher:

services.AddDistributedMemoryCache();

0

Dieser Thread hat mir geholfen, meine eigene Lösung zu erstellen, die ich hier teilen werde. Ich habe zunächst eine GET-Ajax-Anfrage ohne Probleme verwendet, aber es kam zu einem Punkt, an dem die Länge der Anforderungs-URL überschritten wurde, sodass ich zu einem POST wechseln musste.

Das Javascript verwendet das JQuery-Datei-Download-Plugin und besteht aus 2 aufeinander folgenden Aufrufen. Ein POST (zum Senden von Parametern) und ein GET zum Abrufen der Datei.

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

0

Die Antwort von CSL wurde in einem Projekt implementiert, an dem ich arbeite, aber das Problem, das ich beim Skalieren in Azure hatte, hat unsere Dateidownloads unterbrochen. Stattdessen konnte ich dies mit einem AJAX-Aufruf tun:

SERVER

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

CLIENT (modifizierte Version des Handle-Dateidownloads von 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] 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));
});

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

Ich kann die Datei nicht öffnen, das Excel öffnet sich einfach und schließt sich dann nicht einfach selbst. Ich habe sogar stream.close () kurz vor robj hinzugefügt, aber es funktioniert nicht.
Dawncode

0

Ich mag ziemlich naiv klingen und eine ziemliche Kritik auf mich ziehen, aber hier ist, wie ich es gemacht habe
( es beinhaltet keinen ajaxExport, aber es macht auch kein vollständiges Postback )

Danke für diesen Beitrag und diese Antwort.
Erstellen Sie eine einfache Steuerung

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

Und hier sind die Ansichten ..

/*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>*/

Der ganze Sinn des Tricks scheint , dass wir ein Formular veröffentlichen (ein Teil der Razor - Ansicht) , auf das wir fordern ein Action method, die zurückkehrt: ein FileResult, und diese FileResultzurückkehrt the Excel File..
Und die Filterwerte für die Buchung, wie gesagt, ( und wenn Sie es benötigen), mache ich eine Post-Anfrage zu einer anderen Aktion, wie versucht wurde zu beschreiben ..


-1

Ich verwende Asp.Net WebForm und möchte nur eine Datei vom Server herunterladen. Es gibt viele Artikel, aber ich kann nicht nur eine grundlegende Antwort finden. Jetzt habe ich einen einfachen Weg ausprobiert und ihn verstanden.

Das ist mein Problem.

Ich muss zur Laufzeit viele Eingabeschaltflächen dynamisch erstellen. Und ich möchte jede Schaltfläche zum Herunterladen mit einer eindeutigen Dateinummer hinzufügen.

Ich erstelle jeden Button so:

fragment += "<div><input type=\"button\" value=\"Create Excel\" onclick=\"CreateExcelFile(" + fileNumber + ");\" /></div>";

Jede Schaltfläche ruft diese Ajax-Methode auf.

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

Dann habe ich eine einfache Methode geschrieben.

[WebMethod]
public static string CreateExcelFile2(string fileNumber)
{
    string filePath = string.Format(@"Form_{0}.xlsx", fileNumber);
    return filePath;
}

Ich generiere dieses Form_1, Form_2, Form_3 ... und ich werde diese alten Dateien mit einem anderen Programm löschen. Aber wenn es eine Möglichkeit gibt, nur ein Byte-Array zum Herunterladen von Dateien zu senden, wie mit Response. Ich möchte es benutzen.

Ich hoffe, dass dies für jeden nützlich sein wird.


-1

Auf Formular senden

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();
        }
    }
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.