Download Excel file via AJAX MVC

后端 未结 14 1761
说谎
说谎 2020-11-22 02:30

I have a large(ish) form in MVC.

I need to be able to generate an excel file containing data from a subset of that form.

The tricky bit is that this shouldn

相关标签:
14条回答
  • 2020-11-22 03:08

    CSL's answer was implemented in a project I'm working on but the problem I incurred was scaling out on Azure broke our file downloads. Instead, I was able to do this with one AJAX call:

    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 (modified version of Handle file download from 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 讨论(0)
  • 2020-11-22 03: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) {
    
                    }
    
                });
    
    0 讨论(0)
  • 2020-11-22 03:11

    I may sound quite naive, and may attract quite a criticism, but here's how I did it,
    (It doesn't involve ajax for export, but it doesn't do a full postback either )

    Thanks for this post and this answer.
    Create a simple controller

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

    And here are the Views..

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

    The whole point of the trick seems that, we are posting a form (a part of the Razor View ) upon which we are calling an Action method, which returns: a FileResult, and this FileResult returns the Excel File..
    And for posting the filter values, as said, ( and if you require to), I am making a post request to another action, as has been attempted to describe..

    0 讨论(0)
  • 2020-11-22 03:15

    I was recently able to accomplish this in MVC (although there was no need to use AJAX) without creating a physical file and thought I'd share my code:

    Super simple JavaScript function (datatables.net button click triggers this):

    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 the ExportHelper class I do use a 3rd party tool (GemBox.Spreadsheet) to generate the Excel file and it has a Save to Stream option. That being said, there are a number of ways to create Excel files that can easily be written to a memory stream.

    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, and Firefox, the browser prompts to download the file and no actual navigation occurs.

    0 讨论(0)
  • 2020-11-22 03:19

    My 2 cents - you don't need to store the excel as a physical file on the server - instead, store it in the (Session) Cache. Use a uniquely generated name for your Cache variable (that stores that excel file) - this will be the return of your (initial) ajax call. This way you don't have to deal with file access issues, managing (deleting) the files when not needed, etc. and, having the file in the Cache, is faster to retrieve it.

    0 讨论(0)
  • 2020-11-22 03:19

    First Create the controller action that will create the Excel File

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

    then create the Download action

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

    if you want to delete the file after downloaded create this

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

    and finally ajax call from you MVC Razor view

    //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 讨论(0)
提交回复
热议问题