Pobierz plik Excela przez AJAX MVC


92

Mam duży (ish) formularz w MVC.

Muszę mieć możliwość wygenerowania pliku Excela zawierającego dane z podzbioru tego formularza.

Problem polega na tym, że nie powinno to wpływać na resztę formularza, więc chcę to zrobić przez AJAX. Natknąłem się na kilka pytań na temat SO, które wydają się być powiązane, ale nie do końca rozumiem, co oznaczają odpowiedzi.

Ten wydaje się najbliższy temu, czego szukam: asp-net-mvc-download-excel - ale nie jestem pewien, czy rozumiem odpowiedź, i ma ona teraz kilka lat. Natknąłem się również na inny artykuł (nie mogę go już znaleźć) o używaniu iframe do obsługi pobierania pliku, ale nie jestem pewien, jak to zrobić z MVC.

Mój plik Excela wraca dobrze, jeśli robię pełny post, ale nie mogę go uruchomić z AJAX w mvc.

Odpowiedzi:


217

Nie możesz bezpośrednio zwrócić pliku do pobrania za pośrednictwem wywołania AJAX, więc alternatywnym podejściem jest użycie wywołania AJAX do opublikowania powiązanych danych na serwerze. Następnie możesz użyć kodu po stronie serwera, aby utworzyć plik Excel (zalecałbym do tego użycie EPPlus lub NPOI, chociaż brzmi to tak, jakbyś miał tę część działającą).

UPDATE wrzesień 2016

Moja pierwotna odpowiedź (poniżej) miała ponad 3 lata, więc pomyślałem, że zaktualizuję, ponieważ nie tworzę już plików na serwerze podczas pobierania plików przez AJAX, jednak zostawiłem oryginalną odpowiedź, ponieważ może to być nadal przydatne w zależności od Twoje specyficzne wymagania.

Typowym scenariuszem w moich aplikacjach MVC jest raportowanie za pośrednictwem strony internetowej, która ma skonfigurowane przez użytkownika parametry raportu (zakresy dat, filtry itp.). Gdy użytkownik określi parametry, które wysyła na serwer, generowany jest raport (np. Plik Excela jako dane wyjściowe), a następnie przechowuję wynikowy plik jako tablicę bajtów w TempDatazasobniku z unikalnym odniesieniem. To odwołanie jest przekazywane z powrotem jako wynik Json do mojej funkcji AJAX, która następnie przekierowuje do oddzielnej akcji kontrolera w celu wyodrębnienia danych z TempDataprzeglądarki użytkowników końcowych i pobrania ich do przeglądarki.

Aby podać więcej szczegółów, zakładając, że masz widok MVC, który ma formularz powiązany z klasą Model, wywołajmy Model ReportVM.

Po pierwsze, aby otrzymać przesłany model, wymagana jest akcja kontrolera, przykładem może być:

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

}

Wywołanie AJAX, które wysyła mój formularz MVC do powyższego kontrolera i odbiera odpowiedź, wygląda następująco:

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

Akcja kontrolera obsługująca pobieranie pliku:

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

Inną zmianą, którą można łatwo wprowadzić w razie potrzeby, jest przekazanie typu MIME pliku jako trzeciego parametru, aby jedna akcja kontrolera mogła poprawnie obsługiwać różne formaty plików wyjściowych.

Eliminuje to potrzebę tworzenia i przechowywania jakichkolwiek plików fizycznych na serwerze, więc nie są wymagane żadne procedury porządkowe i po raz kolejny jest to bezproblemowe dla użytkownika końcowego.

Zwróć uwagę, że zaletą używania TempDatazamiast Sessionjest to, że po TempDataodczytaniu dane są usuwane, więc będzie bardziej wydajne pod względem wykorzystania pamięci, jeśli masz dużą liczbę żądań plików. Zobacz najlepsze praktyki TempData .

ORYGINALNA odpowiedź

Nie możesz bezpośrednio zwrócić pliku do pobrania za pośrednictwem wywołania AJAX, więc alternatywnym podejściem jest użycie wywołania AJAX w celu opublikowania powiązanych danych na serwerze. Następnie możesz użyć kodu po stronie serwera, aby utworzyć plik Excel (zalecałbym użycie do tego EPPlus lub NPOI, chociaż brzmi to tak, jakbyś miał tę część działającą).

Gdy plik zostanie utworzony na serwerze, przekaż ścieżkę do pliku (lub tylko nazwę pliku) jako wartość zwracaną do wywołania AJAX, a następnie ustaw JavaScript window.locationna ten adres URL, co spowoduje, że przeglądarka pobierze plik.

Z punktu widzenia użytkowników końcowych operacja pobierania plików przebiega bezproblemowo, ponieważ nigdy nie opuszczają oni strony, z której pochodzi żądanie.

Poniżej znajduje się prosty wymyślony przykład wywołania AJAX, aby to osiągnąć:

$.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;
    }
});
  • url to metoda kontrolera / akcji, w której kod utworzy plik Excel.
  • parametr data zawiera dane json, które zostaną wyodrębnione z formularza.
  • returnValue będzie nazwą pliku nowo utworzonego pliku Excel.
  • Polecenie window.location przekierowuje do metody kontrolera / akcji, która w rzeczywistości zwraca plik do pobrania.

Przykładowa metoda kontrolera dla akcji Download to:

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

3
Wygląda to na dobrą potencjalną opcję, ale zanim to zrobię, czy nie ma innych alternatyw, które nie wymagają najpierw utworzenia pliku na serwerze?
Valuk

4
Nie żebym był tego świadomy - z takiego podejścia korzystałem z powodzeniem wiele razy. Z punktu widzenia użytkowników jest to bezproblemowe, jedyną rzeczą, o której należy pamiętać, jest to, że będziesz potrzebować procedury porządkowej, aby uporządkować tworzone pliki, ponieważ będą one rosły w miarę upływu czasu.
connectedsoftware

7
Tworzenie punktu końcowego '/ Download? File = ...' KRYJE ogromne zagrożenie bezpieczeństwa - nie jestem ekspertem w dziedzinie bezpieczeństwa, ale myślę, że chciałbyś dodać uwierzytelnianie użytkownika, oczyszczanie danych wejściowych, [ValidateAntiForgeryToken] MVC i wspomnieć o innych najlepszych zabezpieczeniach -praktyki do tej odpowiedzi.
Jimmy

2
@CSL Zawsze otrzymuję błąd 0x800a03f6 - Błąd wykonania JavaScript: nieprawidłowy znak w odpowiedzi var = JSON.parse (dane);
Standage

2
Świetnie, dlaczego nie umieścisz starej odpowiedzi na dole? A nowa odpowiedź na górze, żeby ludzie nie
tracili

19

Moje 2 centy - nie musisz przechowywać programu Excel jako fizycznego pliku na serwerze - zamiast tego przechowuj go w pamięci podręcznej (Session). Użyj unikalnie wygenerowanej nazwy dla swojej zmiennej Cache (przechowującej ten plik Excela) - będzie to zwrot twojego (początkowego) wywołania ajax. W ten sposób nie musisz zajmować się problemami z dostępem do plików, zarządzaniem (usuwaniem) plikami, gdy nie są potrzebne itp., A mając plik w pamięci podręcznej, można go szybciej odzyskać.


1
Jak dokładnie byś to zrobił? Brzmi interesująco.
Natalia

2
Przykład byłby miły (mam na myśli, jak przechowywać go w pamięci podręcznej, a nie generować plik Excela).
Tadej

Jak skalowalne jest to jednak? Jeśli użytkownik pobiera kilka dużych raportów?
Zapnologica

Jeśli korzystasz z platformy Azure, sesja będzie działać do momentu wyłączenia ARRAffinity.
JeeShen Lee,

15

Niedawno udało mi się to osiągnąć w MVC (chociaż nie było potrzeby używania AJAX) bez tworzenia fizycznego pliku i pomyślałem, że udostępnię swój kod:

Super prosta funkcja JavaScript (wywołuje to kliknięcie przycisku datatables.net):

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

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

W klasie ExportHelper używam narzędzia innej firmy ( GemBox.Spreadsheet ) do generowania pliku Excel i ma ono opcję Zapisz do strumienia. Mając to na uwadze, istnieje wiele sposobów tworzenia plików Excela, które można łatwo zapisać w strumieniu pamięci.

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

     }
}

W przeglądarkach IE, Chrome i Firefox przeglądarka monituje o pobranie pliku i nie następuje żadna nawigacja.


Miałem podobne podejście. Problem polega na tym, że nie wiesz, kiedy kończy się pobieranie, więc możesz zatrzymać ten cholerny preloader :)
Cătălin Rădoi

8

Najpierw utwórz akcję kontrolera, która utworzy plik programu 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 = "" });
}

następnie utwórz akcję Pobierz

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

jeśli chcesz usunąć plik po pobraniu, utwórz to

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

i wreszcie wywołanie AJAX z widoku Razor MVC

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

Skorzystałem z rozwiązania opublikowanego przez CSL, ale radziłbym nie przechowywać danych pliku w Session podczas całej sesji. Korzystając z TempData, dane pliku są automatycznie usuwane po kolejnym żądaniu (którym jest żądanie GET dla pliku). Możesz także zarządzać usunięciem danych pliku w Sesja w akcji pobierania.

Sesja może zużywać dużo pamięci / miejsca w zależności od pamięci SessionState i liczby plików eksportowanych podczas sesji oraz jeśli masz wielu użytkowników.

Zaktualizowałem kod strony serwera z CSL, aby zamiast tego używał TempData.

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 Zacząłem również używać TempData, Twoja odpowiedź skłoniła mnie do zaktualizowania mojej, aby to odzwierciedlić!
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);
    }


W bloku powodzenia połączenia AJAX, sukces: funkcja (Rdata) {debugger; var bytes = new Uint8Array (Rdata.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 (); },
GVKRAO

some one Zaimplementowałem pobieranie pliku Excel w powyższym linku, działa tylko dla @ html.Beginform () to po małych zmianach potrzebny jest ten kod, do wywołania AJAX Success Block, proszę to sprawdzić, działa dobrze w AJAX CALL
GVKRAO

3
$ .ajax ({
                type: "GET",
                url: "/ Home / Downloadexcel /",
                contentType: "application / json; charset = utf-8",
                data: null,
                success: function (Rdata) {
                    debugger;
                    var bytes = new Uint8Array (Rdata.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 (err) {

                }

            });

1

Zaakceptowana odpowiedź nie do końca działała dla mnie, ponieważ otrzymałem wynik 502 Bad Gateway z wywołania Ajax, mimo że wszystko wydawało się w porządku ze sterownika.

Być może osiągnąłem limit z TempData - nie jestem pewien, ale stwierdziłem, że jeśli użyłem IMemoryCache zamiast TempData , to działało dobrze, więc oto moja dostosowana wersja kodu w zaakceptowanej odpowiedzi:

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

}

Połączenie AJAX pozostaje takie, jak z zaakceptowaną odpowiedzią (nie wprowadziłem żadnych zmian):

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

Akcja kontrolera obsługująca pobieranie pliku:

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

...

Teraz jest dodatkowy kod do konfiguracji MemoryCache ...

Aby użyć "_cache", wstawiłem w konstruktorze kontrolera w następujący sposób:

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

I upewnij się, że masz następujące elementy w ConfigureServices w Startup.cs:

services.AddDistributedMemoryCache();

0

Ten wątek pomógł mi stworzyć własne rozwiązanie, którym się tutaj podzielę. Na początku korzystałem z żądania AJAX GET bez problemów, ale doszło do punktu, w którym długość adresu URL żądania została przekroczona, więc musiałem przełączyć się na POST.

JavaScript wykorzystuje wtyczkę pobierania plików JQuery i składa się z 2 kolejnych wywołań. Jeden POST (aby wysłać parametry) i jeden GET, aby odzyskać plik.

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

Po stronie serwera

    [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

Odpowiedź CSL została zaimplementowana w projekcie, nad którym pracuję, ale problem, który napotkałem, polegał na skalowaniu w poziomie na platformie Azure, zepsuł nasze pobieranie plików. Zamiast tego udało mi się to zrobić za pomocą jednego połączenia AJAX:

SERWER

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

KLIENT (zmodyfikowana wersja pobierania pliku Handle z postu 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));
});

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

nie można otworzyć pliku, Excel po prostu się otwiera i nie zamyka się, dodałem nawet stream.close () tuż przed robj, ale nie działa.
dawncode

0

Może brzmię dość naiwnie i może wzbudzić dość krytykę, ale oto jak to zrobiłem
( nie dotyczy ajaxeksportu, ale nie ma też pełnego ogłoszenia zwrotnego )

Dzięki za ten post i odpowiedź.
Stwórz prosty kontroler

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

A oto widoki ...

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

Cały sens tej sztuczki wydaje się, że wysyłamy formularz ( część widoku Razor), na którym wywołujemy an Action method, który zwraca: a FileResult, i to FileResultzwraca the Excel File..
I do wysyłania wartości filtrów, jak powiedziałem, ( i jeśli chcesz), wysyłam prośbę o wpis do innego działania, jak próbowano opisać.


-1

Używam Asp.Net WebForm i chcę tylko pobrać plik po stronie serwera. Jest dużo artykułów, ale nie mogę znaleźć tylko podstawowej odpowiedzi. Teraz wypróbowałem podstawowy sposób i go otrzymałem.

To mój problem.

Muszę dynamicznie tworzyć wiele przycisków wejściowych w czasie wykonywania. I chcę dodać każdy przycisk do przycisku pobierania z podaniem unikalnego numeru pliku.

Tworzę każdy przycisk w ten sposób:

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

Każdy przycisk wywołuje tę metodę 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;
    }
});

Następnie napisałem podstawową prostą metodę.

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

Generuję ten Form_1, Form_2, Form_3 .... I zamierzam usunąć te stare pliki innym programem. Ale jeśli istnieje sposób, aby po prostu wysłać tablicę bajtów do pobrania pliku, tak jak przy użyciu Response. Chcę tego użyć.

Mam nadzieję, że będzie to przydatne dla każdego.


-1

W formularzu przesyłania

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();
        }
    }
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.