通过 AJAX MVC 下载 Excel 文件

Download Excel file via AJAX MVC

提问人:Valuk 提问时间:5/21/2013 最后编辑:CommunityValuk 更新时间:11/15/2022 访问量:319816

问:

我在MVC中有一个大(ish)形式。

我需要能够生成一个 excel 文件,其中包含来自该表单子集的数据。

棘手的一点是,这不应该影响表单的其余部分,所以我想通过 AJAX 来做到这一点。我在 SO 上遇到了一些似乎相关的问题,但我不太清楚答案的含义。

这个似乎最接近我所追求的:asp-net-mvc-downloading-excel - 但我不确定我是否理解响应,而且它已经有几年的历史了。我还遇到了另一篇关于使用 iframe 处理文件下载的文章(再也找不到了),但我不确定如何使用 MVC 来解决这个问题。

如果我正在进行完整的回发,我的 excel 文件返回正常,但我无法让它在 mvc 中使用 AJAX。

C# jQuery ASP.NET-MVC vb.net 导出到 Excel

评论


答:

240赞 connectedsoftware 5/21/2013 #1

您无法通过 AJAX 调用直接返回文件以供下载,因此,另一种方法是使用 AJAX 调用将相关数据发布到服务器。然后,您可以使用服务器端代码来创建 Excel 文件(我建议使用 EPPlus 或 NPOI 来执行此操作,尽管听起来好像您有这部分工作)。

更新:2016 年 9 月

我的原始答案(如下)已经有 3 年多了,所以我想我会更新,因为在通过 AJAX 下载文件时我不再在服务器上创建文件,但是,我保留了原始答案,因为它可能仍然有一些用处,具体取决于您的具体要求。

我的 MVC 应用程序中的一个常见方案是通过具有一些用户配置的报告参数(日期范围、筛选器等)的网页进行报告。当用户指定了参数并将其发布到服务器时,将生成报告(例如,将 Excel 文件作为输出),然后我将生成的文件作为字节数组存储在具有唯一引用的存储桶中。此引用作为 Json 结果传递回我的 AJAX 函数,该函数随后重定向到单独的控制器操作,以从中提取数据并下载到最终用户浏览器。TempDataTempData

为了更详细地介绍这一点,假设您有一个 MVC 视图,该视图具有绑定到 Model 类的表单,让我们调用 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();
   }
}

如果需要,可以轻松适应的另一个更改是将文件的 MIME 类型作为第三个参数传递,以便一个控制器操作可以正确地提供各种输出文件格式。

这消除了在服务器上创建和存储任何物理文件的需要,因此不需要内务处理程序,这对最终用户来说再次是无缝的。

请注意,使用 instead 而不是的优点是,一旦读取数据,数据就会被清除,因此,如果您有大量文件请求,则在内存使用方面会更有效。请参阅 TempData 最佳实践TempDataSessionTempData

原始答案

您无法通过 AJAX 调用直接返回文件以供下载,因此,另一种方法是使用 AJAX 调用将相关数据发布到服务器。然后,您可以使用服务器端代码来创建 Excel 文件(我建议使用 EPPlus 或 NPOI 来执行此操作,尽管听起来好像您有这部分工作)。

在服务器上创建文件后,将文件的路径(或仅文件名)作为返回值传回 AJAX 调用,然后将 JavaScript 设置为此 URL,这将提示浏览器下载文件。window.location

从最终用户的角度来看,文件下载操作是无缝的,因为他们永远不会离开发出请求的页面。

下面是一个简单的 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 参数是代码将在其中创建 Excel 文件的 Controller/Action 方法。
  • data 参数包含将从表单中提取的 JSON 数据。
  • returnValue 将是新创建的 Excel 文件的文件名。
  • window.location 命令重定向到实际返回文件以供下载的 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);
}

评论

3赞 Valuk 5/21/2013
这看起来是一个很好的潜在选择,但在我继续之前,是否没有其他替代方案不涉及首先在服务器上创建文件?
4赞 connectedsoftware 5/22/2013
我不知道 - 这种方法我已经成功使用了很多次。从用户的角度来看,它是无缝的,唯一需要注意的是,您需要一个家政程序来整理创建的文件,因为它们会随着时间的推移而安装。
7赞 Jimmy 11/24/2015
创建端点“/Download?file=...”SCREAMS 巨大的安全风险 - 我不是安全专家,但我认为你会想添加用户身份验证、输入卫生、MVC 的 [ValidateAntiForgeryToken] 并提及其他安全最佳实践到这个答案中。
2赞 Standage 3/7/2017
@CSL我总是收到错误0x800a03f6 - JavaScript 运行时错误:var 响应上的字符无效 = JSON.parse(data);
2赞 goamn 11/14/2017
太好了,你为什么不把旧的答案放在底部呢?以及顶部的新答案,这样人们就不会浪费时间
19赞 Luchian 3/6/2014 #2

我的 2 美分 - 您不需要将 excel 作为物理文件存储在服务器上 - 而是将其存储在(会话)缓存中。为您的 Cache 变量(存储该 excel 文件)使用唯一生成的名称 - 这将是(初始)ajax 调用的返回值。这样,您就不必处理文件访问问题,在不需要时管理(删除)文件等,并且将文件放在缓存中可以更快地检索它。

评论

1赞 Natalia 12/16/2014
你到底会怎么做?听起来很有趣。
2赞 ttt 11/23/2017
举个例子会很好(我的意思是如何将其存储在缓存中,而不是生成 excel 文件)。
0赞 Zapnologica 2/22/2018
但是,这有多大的可扩展性?如果用户正在下载多个大型报告?
0赞 JeeShen Lee 9/11/2018
如果你在 Azure 上,会话将一直工作,直到你关闭 ARRAffinity。
18赞 Andy S 10/2/2015 #3

我最近能够在 MVC 中完成此操作(尽管无需使用 AJAX),而无需创建物理文件,并认为我会分享我的代码:

超级简单的 JavaScript 函数(datatables.net 按钮点击触发):

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 文件,并且它有一个“保存到流”选项。话虽如此,有多种方法可以创建可以轻松写入内存流的 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 中,浏览器会提示下载文件,并且不会进行实际导航。

评论

2赞 Cătălin Rădoi 10/6/2020
我也有类似的方法。问题是你不知道下载什么时候结束,所以你可以停止那个该死的预加载器:)
0赞 Machinegon 7/12/2016 #4

这个线程帮助我创建了自己的解决方案,我将在这里分享。起初我使用 GET ajax 请求没有问题,但它达到了超出请求 URL 长度的地步,所以我不得不使用 POST。

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");
    }
7赞 Niclas 8/5/2016 #5

我使用了 CSL 发布的解决方案,但我建议您在整个会话期间不要将文件数据存储在 Session 中。通过使用 TempData,在下一个请求(即文件的 GET 请求)之后,将自动删除文件数据。您还可以在下载操作中的会话中管理文件数据的删除。

会话可能会占用大量内存/空间,具体取决于 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();
   }
}

评论

0赞 connectedsoftware 11/23/2016
@Nichlas我也开始使用 TempData,您的回答促使我更新我的答案以反映这一点!
-1赞 Can OTUR 8/25/2016 #6

我正在使用 Asp.Net WebForm,只是想从服务器端下载文件。有很多文章,但我找不到基本的答案。 现在,我尝试了一种基本的方法并得到了它。

那是我的问题。

我必须在运行时动态创建大量输入按钮。我想将每个按钮添加到下载按钮,并给出一个唯一的fileNumber。

我像这样创建每个按钮:

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

每个按钮都调用此 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;
    }
});

然后我写了一个基本的简单方法。

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

我正在生成这个Form_1,Form_2,Form_3......我将使用另一个程序删除这些旧文件。但是,如果有一种方法可以像使用Response一样发送字节数组来下载文件。我想使用它。

我希望这对任何人都有用。

-1赞 Rajesh Kumar 1/7/2017 #7

提交表格时

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();
        }
    }
8赞 Elvin Acevedo 7/1/2017 #8

首先,创建将创建 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 = "" });
}

,然后创建“下载”操作

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

如果要在下载后删除文件,请创建此文件

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 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;
    }
});
0赞 wilsjd 10/6/2018 #9

CSL 的答案是在我正在处理的一个项目中实现的,但我遇到的问题是在 Azure 上横向扩展破坏了我们的文件下载。相反,我能够通过一个 AJAX 调用来做到这一点:

服务器

[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处理从 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));
});
5赞 G.V.K.RAO 10/6/2018 #10

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

评论

0赞 G.V.K.RAO 10/6/2018
在 AJAX CALL Success Block 中,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”;链接.click();},
0赞 G.V.K.RAO 10/6/2018
有人在上面的链接中实现了Excel文件下载,它仅适用于@html。Beginform() 然后在小的更改后需要该代码,对于 AJAX 调用成功块,请检查它,它在 AJAX CALL 中工作正常
3赞 G.V.K.RAO 10/6/2018 #11
$.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) {

                }

            });
2赞 egmfrs 6/10/2019 #12

接受的答案对我不太有效,因为我从 ajax 调用中得到了 502 Bad Gateway 结果,即使一切似乎都从控制器返回正常。

也许我在 TempData 上达到了极限 - 不确定,但我发现如果我使用 IMemoryCache 而不是 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();

        //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调用与接受的答案相同(我没有进行任何更改):

$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 (_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
    }
}

...

现在有一些额外的代码用于设置 MemoryCache...

为了使用“_cache”,我注入了控制器的构造函数,如下所示:

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

并确保在 Startup.cs 的 ConfigureServices 中具有以下内容:

services.AddDistributedMemoryCache();
1赞 Irf 9/9/2019 #13

我可能听起来很幼稚,并且可能会招致相当多的批评,但这是我是如何做到的,(它不涉及导出的ajax
但它也没有做完整的回发
)

感谢您的这篇文章这个答案。
创建一个简单的控制器

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

这里是视图..

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

这个技巧的全部意义似乎是,我们正在发布一个表单(Razor View的一部分),我们在其上调用一个,它返回:FileResult,而这个FileResult返回Excel文件
为了发布过滤器值,如前所述(如果需要),我正在向另一个操作发出发布请求,正如试图描述的那样。
Action method

3赞 Rinku Choudhary 11/5/2019 #14
  $.ajax({
    global: false,
    url: SitePath + "/User/ExportTeamMembersInExcel",
    "data": { 'UserName': UserName, 'RoleId': RoleId, UserIds: AppraseeId },
    "type": "POST",
    "dataType": "JSON",
   "success": function (result) {
        
        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);
    }

评论

0赞 dawncode 7/24/2020
无法打开文件,excel 只是打开而不是自行关闭,我什至在 robj 之前添加了 stream.close() 但不起作用。
0赞 David C 6/16/2023
这对我来说非常有效
1赞 Akbar 11/15/2022 #15

这对我有用。确保从控制器操作返回一个文件,其内容类型为“application/vnd.openxmlformats-officedocument.spreadsheetml.sheet”,文件名为“List.xlsx”,应与 AJAX 成功调用中的相同。我使用 ClosedXML NuGet 包来生成 excel 文件。

$.ajax({
    url: "Home/Export",
    type: 'GET',      
    contentType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
    xhrFields: { responseType: 'blob' },
    success: function (data) {
        var a = document.createElement('a');
        var url = window.URL.createObjectURL(data);
        a.href = url;
        a.download = 'List.xlsx';
        a.click();
        window.URL.revokeObjectURL(url);
    }
});