2013-05-21 19 views
62

Mam dużą (ish) formularz w MVC.Pobierz plik Excel przez AJAX MVC

Potrzebuję być w stanie wygenerować plik excel zawierający dane z podzestawu tego formularza.

Trudne jest to, że nie powinno to wpływać na resztę formularza, więc chcę to zrobić za pośrednictwem AJAX. Znalazłem kilka pytań na temat SO, które wydają się być powiązane, ale nie mogę całkiem zrozumieć, co oznaczają odpowiedzi.

Ten wydaje się być najbliższy temu, czego szukam: asp-net-mvc-downloading-excel - ale nie jestem pewien, czy rozumiem odpowiedź i teraz ma już kilka lat. Natknąłem się także na inny artykuł (nie mogę go już znaleźć) na temat używania elementu iframe do obsługi pobierania pliku, ale nie jestem pewien, jak to działa z MVC.

Mój plik excel zwraca się dobrze, jeśli robię pełny post z powrotem, ale nie mogę uzyskać pracy z AJAX w mvc.

Odpowiedz

131

Nie można bezpośrednio zwrócić pliku do pobrania za pośrednictwem wywołania AJAX, więc alternatywnym podejściem jest użycie wywołania AJAX, aby opublikować powiązane dane na serwerze. Następnie możesz użyć kodu po stronie serwera, aby utworzyć plik Excel (polecam używanie EPPlus lub NPOI, chociaż brzmi to tak, jakby działała ta część).

UPDATE September 2016

Mój oryginalny odpowiedzi (poniżej) był ponad 3 lat, więc myślałem, że chciałbym aktualizować jak już nie tworzyć plików na serwerze, podczas pobierania plików za pośrednictwem AJAX jednak zostawiłem oryginalną odpowiedź jak może być nadal przydatny w zależności od twoich konkretnych wymagań.

Typowym scenariuszem w moich aplikacjach MVC jest raportowanie za pośrednictwem strony internetowej, która ma niektóre skonfigurowane przez użytkownika parametry raportu (zakresy dat, filtry itp.). Gdy użytkownik określi parametry, które przesyłają na serwer, generowany jest raport (np. Jako plik wyjściowy Excela), a następnie przechowuję wynikowy plik jako tablicę bajtów w wiadrze TempData z unikalnym odniesieniem. To odwołanie jest przekazywane jako wynik Json do mojej funkcji AJAX, która następnie przekierowuje do oddzielnego działania kontrolera, aby wyodrębnić dane z TempData i pobrać do przeglądarki użytkowników końcowych.

Aby uzyskać więcej szczegółów, zakładając, że masz widok MVC, który ma formularz powiązany z klasą Model, możesz zadzwonić do Modelu ReportVM.

pierwsze, wymagana jest akcja kontroler otrzymywać zamieszczonych modelu, 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łania AJAX, że posty mojego formularza MVC do powyższego sterownika 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 kontroler do obsługi pobierania 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ą inną zmianą, którą można łatwo zastosować, jeśli jest to wymagane, jest przekazanie typu MIME pliku jako trzeciego parametru, aby działanie jednego kontrolera mogło poprawnie obsługiwać różne formaty plików wyjściowych.

Dzięki temu nie trzeba tworzyć żadnych plików fizycznych na serwerze, więc nie trzeba wykonywać żadnych procedur porządkowych, co po raz kolejny jest bezproblemowe dla użytkownika końcowego.

Uwaga, zaletą korzystania TempData zamiast Session jest to, że raz TempData są odczytywane dane są usuwane tak będzie bardziej wydajny pod względem zużycia pamięci, jeśli masz dużą ilość wniosków plików. Zobacz TempData Best Practice.

ORIGINAL Answer

Nie można bezpośrednio zwrócić plik do pobrania za pośrednictwem wywołania AJAX Więc Alternatywnym podejściem jest użycie wywołanie AJAX aby umieścić odpowiednie dane do serwera. Następnie możesz użyć kodu po stronie serwera, aby utworzyć plik Excel (polecam używanie EPPlus lub NPOI, chociaż brzmi to tak, jakby działała ta część).

Po utworzeniu pliku na serwerze należy przekazać ścieżkę do pliku (lub tylko nazwę pliku) jako wartość zwracaną do wywołania AJAX, a następnie ustawić kod JavaScript window.location na ten adres URL, który będzie monitował przeglądarkę do pobrania plik.

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

Poniżej znajduje się prosty wymyślony przykład wywołania AJAX do osiągnięcia tego celu:

$.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 parametrem jest metoda Controller/Action gdzie Twój kod utworzy plik Excela.
  • Parametr data zawiera dane json, które zostałyby wyodrębnione z formularza.
  • returnValue będzie nazwą pliku nowo utworzonego pliku Excel.
  • Polecenie window.location powoduje przekierowanie do metody Controller/Action, która faktycznie zwraca plik do pobrania.

Próbka metoda kontroler dla działania Pobrać byłoby:

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

To wygląda jak duży potencjał możliwości, ale zanim pójdę do przodu z nim, są tam żadnych innych alternatyw, które nie dotyczą tworzenia pliku na na początku serwer? – Valuk

+2

Nie jestem tego świadomy - to podejście wielokrotnie stosowałem z powodzeniem. Z punktu widzenia użytkowników jest to płynne, jedyną rzeczą, na którą należy zwrócić uwagę, jest to, że będziesz potrzebował procedury porządkowej, aby uporządkować pliki, które są tworzone, ponieważ będą się montować w miarę upływu czasu. – CSL

+0

http://johnculviner.com/jquery-file-download-plugin-for-ajax-like-feature-rich-file-downloads/ –

18

Moje 2 centów - nie trzeba przechowywać Excel jako plik na serwerze fizycznym - zamiast tego, należy go przechowywać w pamięć podręczna (sesja). Użyj unikalnie wygenerowanej nazwy dla zmiennej Cache (przechowującej ten plik Excela) - będzie to powrót twojego (początkowego) wywołania ajax. W ten sposób nie musisz radzić sobie z problemami z dostępem do plików, zarządzać (usuwać) plikami, gdy nie są potrzebne, itd., A posiadanie pliku w pamięci podręcznej jest szybsze do odzyskania.

+1

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

+1

OK. To zrobiłem. To wspaniale. Dziękuję bardzo za twoją podpowiedź! :) – Natalia

+0

Przykład byłby miły (mam na myśli sposób przechowywania go w pamięci podręcznej, nie generując pliku excel). – JedatKinports

9

Byłem niedawno w stanie tego dokonać w MVC (choć nie było potrzeby korzystania AJAX) bez tworzenia pliku fizycznego i myślałem, że będę dzielić mój kod:

Super prostą funkcję JavaScript (datatables.net przycisk kliknij wyzwalaczy to):

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

kod C# Kontroler:

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 robię użyć narzędzia 3rd Party (GemBox.Spreadsheet), aby wygenerować plik Excel i ma opcję Zapisz w strumieniu. Mając to na uwadze, istnieje wiele sposobów tworzenia plików Excel, 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 Internet Explorer, Chrome i Firefox przeglądarka monituje o pobranie pliku i nie następuje faktyczna nawigacja.

0

Ten wątek pomógł mi stworzyć własne rozwiązanie, które udostępnię tutaj. Używałem najpierw żądania GET ajax bez problemów, ale doszło do przekroczenia długości adresu URL żądania, więc musiałem przejść do POST.

Javascript używa wtyczki do pobierania plików JQuery i składa się z 2 następujących po sobie połączeń. 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) 
    }); 

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

użyłem rozwiązania zamieszczonych przez CSL, ale polecam dont przechowywania danych plików w sesji podczas całej sesji. Za pomocą TempData dane pliku są automatycznie usuwane po następnym żądaniu (co jest żądaniem GET dla pliku). Można również zarządzać usuwaniem danych pliku w sesji w akcji pobierania.

Sesja może zająć dużo pamięci/miejsca w zależności od miejsca przechowywania SessionState i liczby plików wyeksportowanych podczas sesji oraz użytkowników z wieloma użytkownikami.

Zaktualizowałem kod boczny serwera CSL, aby zamiast niego 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(); 
    } 
} 
+0

@Nichlas Zacząłem używać również TempData, Twoja odpowiedź skłoniła mnie do aktualizacji mojej, aby to odzwierciedlić! – CSL

0

Używam Asp.Net WebForm i po prostu chcę pobrać plik od strony serwera. Jest dużo artykułów, ale nie mogę znaleźć prostej odpowiedzi. Teraz próbowałem podstawowy sposób i dostałem to.

To jest mój problem.

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

tworzę każdy przycisk tak:

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

Każde wywołanie tej metody ajax przycisk.

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

Wtedy pisałem podstawową metodę prostą.

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

jestem generowania tego Form_1, Form_2, Form_3 .... I mam zamiar usunąć to stare pliki z innego programu. Ale jeśli istnieje sposób na wysłanie tablicy bajtów, aby pobrać plik, np. Za pomocą odpowiedzi. Chcę z niego korzystać.

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

0

Na Złożenie

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

najpierw utworzyć działania kontrolera, która stworzy plików 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 utworzyć Pobierz działanie

[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 utworzyć tę

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 ajax połączeń z was widok MVC Razor

//I use blockUI for loading... 
$.blockUI({ message: '<h3>Please wait a moment...</h3>' });  
$.ajax({ 
    type: "POST", 
    url: '@Url.Action("ExportExcel","YourController")', //call your controller and action 
    contentType: "application/json; charset=utf-8", 
    dataType: "json", 
}).done(function (data) { 
    //console.log(data.result); 
    $.unblockUI(); 

    //get the file name for download 
    if (data.fileName != "") { 
     //use window.location.href for redirect to download action for download the file 
     window.location.href = "@Url.RouteUrl(new 
      { Controller = "YourController", Action = "Download"})/?file=" + data.fileName; 
    } 
});