通过AJAX MVC下载Excel文件
我在MVC中有一个很大的(ish)表单。
我需要能够生成包含该表单子集的数据的Excel文件。
棘手的是,这不应该影响其余的forms,所以我想通过AJAX做到这一点。 我已经遇到了几个关于SO的问题,但是我无法弄清楚答案的意思。
这一个似乎是最接近我后: asp-net-mvc-downloads-excel – 但我不知道我明白了答案,现在已经是几年了。 我也遇到了另一篇文章(无法find它)有关使用iframe来处理文件下载,但我不知道如何得到这与MVC的工作。
我的excel文件返回罚款,如果我正在做一个完整的回发,但我不能得到它与MVC AJAX的工作。
您不能通过AJAX调用直接返回文件进行下载,因此,另一种方法是使用AJAX调用将相关数据发布到您的服务器。 然后,您可以使用服务器端代码来创buildExcel文件(我会build议使用EPPlus或NPOI,虽然听起来好像你有这部分工作)。
一旦在服务器上创build了文件,将path返回到文件(或者仅仅是文件名),作为AJAX调用的返回值,然后将JavaScript window.location
设置为这个URL,它将提示浏览器下载文件。
从最终用户的angular度来看,文件下载操作是无缝的,因为他们从不离开发出请求的页面。
下面是一个简单的人为的例子ajax调用来实现这个:
$.ajax({ type: 'POST', url: '/Reports/ExportMyData', data: '{ "dataprop1": "test", "dataprop2" : "test2" }', contentType: 'application/json; charset=utf-8', dataType: 'json', success: function (returnValue) { window.location = '/Reports/Download?file=' + returnValue; } });
- url参数是您的代码将创buildExcel文件的Controller / Action方法。
- 数据参数包含将从表单中提取的json数据。
- returnValue将是您新创build的Excel文件的文件名。
- window.location命令redirect到实际返回文件下载的Controller / Action方法。
下载操作的示例控制器方法是:
[HttpGet] public virtual ActionResult Download(string file) { string fullPath = Path.Combine(Server.MapPath("~/MyFiles"), file); return File(fullPath, "application/vnd.ms-excel", file); }
2016年9月更新
我原来的答案是3岁以上,所以我想我会更新,因为我不再通过AJAX下载文件时,在服务器上创build文件,但是,我已经留在上面的原始答案,因为它可能有一些使用仍然取决于具体要求。
我的MVC应用程序中的一个常见情况是通过具有用户configuration的报告参数(date范围,filter等)的网页进行报告。 当用户指定了参数后,将它们发布到服务器,生成报表(例如,将一个Excel文件作为输出),然后将生成的文件作为字节数组存储在具有唯一引用的TempData
存储桶中。 此引用作为Json结果传递给我的AJAX函数,随后将其redirect到单独的控制器操作,以便从TempData
提取数据并下载到最终用户浏览器。
为了给出更多的细节,假设你有一个绑定到Model类的MVC视图,可以调用Model ReportVM
。
首先,需要一个控制器动作来接收发布的模型,一个例子是:
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" } }; }
将我的MVC表单发布到上述控制器并接收响应的AJAX调用如下所示:
$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; } })
控制器处理文件下载的操作:
[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(); } }
如果需要,另一个可以容易调整的更改是将文件的MIMEtypes作为第三个parameter passing,以便一个Controller操作可以正确地提供各种输出文件格式。
这消除了在服务器上创build和存储任何物理文件的需要,因此不需要pipe理例程,并且再次对最终用户来说是无缝的。
请注意,使用TempData
而不是Session
的优点是,一旦TempData
被读取,数据将被清除,因此如果您有大量的文件请求,它将在内存使用方面更加高效。 请参阅TempData最佳实践 。
我的2美分 – 你不需要将excel作为物理文件存储在服务器上,而是将其存储在(会话)caching中。 为您的cachingvariables(存储该文件)使用一个唯一生成的名称 – 这将是您的(初始)ajax调用的返回。 这样,您不必处理文件访问问题,在不需要的时候pipe理(删除)文件等,并且使caching中的文件更快地检索文件。
我最近能够在MVC中完成这项工作(虽然没有必要使用AJAX),但没有创build一个物理文件,我想我会分享我的代码:
超级简单的JavaScript函数(datatables.netbutton点击触发这个):
function getWinnersExcel(drawingId) { window.location = "/drawing/drawingwinnersexcel?drawingid=" + drawingId; }
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); }
在ExportHelper类中,我使用第三方工具( GemBox.Spreadsheet )来生成Excel文件,并具有“保存到stream”选项。 也就是说,有很多方法可以创build可轻松写入内存stream的Excel文件。
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 } }
在IE,Chrome和Firefox中,浏览器会提示下载文件,并且不会进行实际导航。
我使用CSL公布的解决scheme,但我build议你不要在整个会话期间存储会话中的文件数据。 通过使用TempData,文件数据在下一个请求(这是对文件的GET请求)后自动删除。 您也可以在下载操作中pipe理删除会话中的文件数据。
会话可能会消耗大量内存/空间,具体取决于SessionState存储以及会话期间导出的文件数量以及是否有许多用户。
我已经更新了CSL的serer端代码来使用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(); } }
首先创build将创buildExcel文件的控制器操作
[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 = "" }); }
然后创build“下载”操作
[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); }
如果你想删除下载后创build这个文件
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); } }
最后ajax从你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; } });
这个线程帮助我创build了我自己的解决scheme,我将在这里分享。 我刚开始使用GET ajax请求没有问题,但它已经超过了请求URL长度,所以我不得不开始开机自检。
JavaScript使用JQuery文件下载插件,并由2个成功的调用组成。 一个POST(发送参数)和一个GET来检索文件。
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) });
服务器端
[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"); }
我正在使用Asp.Net WebForm,只是我想从服务器端下载一个文件。 有很多文章,但我找不到基本的答案。 现在,我尝试了一个基本的方法,并得到它。
这是我的问题。
我必须在运行时dynamic地创build大量的inputbutton。 我想添加每个button下载button与给出一个唯一的fileNumber。
我创build每个button像这样:
fragment += "<div><input type=\"button\" value=\"Create Excel\" onclick=\"CreateExcelFile(" + fileNumber + ");\" /></div>";
在提交表单上
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(); } }