Download Excel file via AJAX MVC

后端 未结 14 1791
说谎
说谎 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:01

    I used the solution posted by CSL but I would recommend you dont store the file data in Session during the whole session. By using TempData the file data is automatically removed after the next request (which is the GET request for the file). You could also manage removal of the file data in Session in download action.

    Session could consume much memory/space depending on SessionState storage and how many files are exported during the session and if you have many users.

    I've updated the serer side code from CSL to use TempData instead.

    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 讨论(0)
  • 2020-11-22 03:01

    This thread helped me create my own solution that I will share here. I was using a GET ajax request at first without issues but it got to a point where the request URL length was exceeded so I had to swith to a POST.

    The javascript uses JQuery file download plugin and consists of 2 succeeding calls. One POST (To send params) and one GET to retreive the file.

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

    Server side

        [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 讨论(0)
  • 2020-11-22 03:03

    The accepted answer didn't quite work for me as I got a 502 Bad Gateway result from the ajax call even though everything seemed to be returning fine from the controller.

    Perhaps I was hitting a limit with TempData - not sure, but I found that if I used IMemoryCache instead of TempData, it worked fine, so here is my adapted version of the code in the accepted answer:

    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 call remains as with the accepted answer (I made no changes):

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

    The controller action to handle the downloading of the file:

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

    ...

    Now there is some extra code for setting up MemoryCache...

    In order to use "_cache" I injected in the constructor for the controller like so:

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

    And make sure you have the following in ConfigureServices in Startup.cs:

    services.AddDistributedMemoryCache();
    
    0 讨论(0)
  • 2020-11-22 03:07
      $.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);
        }
    
    0 讨论(0)
  • 2020-11-22 03:07

    I am using Asp.Net WebForm and just I wanna to download a file from server side. There is a lot article but I cannot find just basic answer. Now, I tried a basic way and got it.

    That's my problem.

    I have to create a lot of input button dynamically on runtime. And I want to add each button to download button with giving an unique fileNumber.

    I create each button like this:

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

    Each button call this ajax method.

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

    Then I wrote a basic simple method.

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

    I am generating this Form_1, Form_2, Form_3.... And I am going to delete this old files with another program. But if there is a way to just sending byte array to download file like using Response. I wanna to use it.

    I hope this will be usefull for anyone.

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

    You can't directly return a file for download via an AJAX call so, an alternative approach is to to use an AJAX call to post the related data to your server. You can then use server side code to create the Excel File (I would recommend using EPPlus or NPOI for this although it sounds as if you have this part working).

    UPDATE September 2016

    My original answer (below) was over 3 years old, so I thought I would update as I no longer create files on the server when downloading files via AJAX however, I have left the original answer as it may be of some use still depending on your specific requirements.

    A common scenario in my MVC applications is reporting via a web page that has some user configured report parameters (Date Ranges, Filters etc.). When the user has specified the parameters they post them to the server, the report is generated (say for example an Excel file as output) and then I store the resulting file as a byte array in the TempData bucket with a unique reference. This reference is passed back as a Json Result to my AJAX function that subsequently redirects to separate controller action to extract the data from TempData and download to the end users browser.

    To give this more detail, assuming you have a MVC View that has a form bound to a Model class, lets call the Model ReportVM.

    First, a controller action is required to receive the posted model, an example would be:

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

    The AJAX call that posts my MVC form to the above controller and receives the response looks like this:

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

    The controller action to handle the downloading of the file:

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

    One other change that could easily be accommodated if required is to pass the MIME Type of the file as a third parameter so that the one Controller action could correctly serve a variety of output file formats.

    This removes any need for any physical files to created and stored on the server, so no housekeeping routines required and once again this is seamless to the end user.

    Note, the advantage of using TempData rather than Session is that once TempData is read the data is cleared so it will be more efficient in terms of memory usage if you have a high volume of file requests. See TempData Best Practice.

    ORIGINAL Answer

    You can't directly return a file for download via an AJAX call so, an alternative approach is to to use an AJAX call to post the related data to your server. You can then use server side code to create the Excel File (I would recommend using EPPlus or NPOI for this although it sounds as if you have this part working).

    Once the file has been created on the server pass back the path to the file (or just the filename) as the return value to your AJAX call and then set the JavaScript window.location to this URL which will prompt the browser to download the file.

    From the end users perspective, the file download operation is seamless as they never leave the page on which the request originates.

    Below is a simple contrived example of an ajax call to achieve this:

    $.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 parameter is the Controller/Action method where your code will create the Excel file.
    • data parameter contains the json data that would be extracted from the form.
    • returnValue would be the file name of your newly created Excel file.
    • The window.location command redirects to the Controller/Action method that actually returns your file for download.

    A sample controller method for the Download action would be:

    [HttpGet]
    public virtual ActionResult Download(string file)
    {   
      string fullPath = Path.Combine(Server.MapPath("~/MyFiles"), file);
      return File(fullPath, "application/vnd.ms-excel", file);
    }
    
    0 讨论(0)
提交回复
热议问题