skip to Main Content

I am using ASP.NET Core and ClosedXML to generate an Excel export.

Export Method 1

Here is the controller method that DOES return an .xlsx file to the browser. The method gets called from a .cshtml page:

<form id="downloadLoanEvents" asp-action="DownloadLoanEventsLog" asp-controller="Lender" asp-route-id="@Model.LoanFileId" method="post">
    <div class="form-group">
        <input type="submit" value="Submit" />
    </div>
</form>

This is the DownloadLoanEventsLog controller method:

[HttpPost]
public async Task<IActionResult> DownloadLoanEventsLog(string id)
{
    var user = await _userManager.GetUserAsync(HttpContext.User);
    var loan = _unitOfWork.LoanFiles.GetById(id);

    var list = eventLogService.GetLoanFileEventLog(id, _unitOfWork, user);
    string contentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
    string fileName = $"Loan-{loan.LoanNumber}-EventsLog.xlsx";

    try
    {
        using (var workbook = new XLWorkbook())
        {
            IXLWorksheet worksheet = workbook.Worksheets.Add("Events");
            worksheet.Cell(1, 1).Value = "EventDate";
            worksheet.Cell(1, 2).Value = "LoanNumber";

            int index = 2;

            foreach (var row in list)
            {
                worksheet.Cell(index, 1).Value = row.EventDateTimeDisplay;
                worksheet.Cell(index, 2).Value = row.LoanNumber;
                index++;
            }

            using (var stream = new MemoryStream())
            {
                workbook.SaveAs(stream);
                var content = stream.ToArray();
                return File(content, contentType, fileName);
            }
        }
    }
    catch (Exception ex)
    {
        return RedirectToAction("CloseLoan", new { id = id }).WithWarning("", "Exporting Loan File Events Failed");
    }
    finally
    {
    }
}

The DownloadLoanEventLog method works great. I quickly get an expected Excel file to the browser.


Export Method 2

Here is the controller method that DOES NOT return an .xlsx file to the browser.

The method is nearly identical to the 1st method, but this export kicks off in Ajax call to send a json array of data to the controller.

var rows = $("#report").jqxGrid('getrows');

var loans = [];

for (var i = 0; i < rows.length; i++) {
    var row = rows[i];
    loans.push({
        "loanNumber": row.loanNumber,
        "loanType": row.loanType,
    });
}

var jqxhr = $.ajax({
    data: JSON.stringify(loans),
    contentType: "application/json",
    url: "/Reports/ExportLoansReport",
    type: "POST"
});

This is the ExportLoansReport controller method:

[HttpPost]
public IActionResult ExportLoansReport([FromBody] List<LoansReportExportViewModel> model)
{
    string contentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
    string fileName = $"Loans-Export.xlsx";

    try
    {
        using (var workbook = new XLWorkbook())
        {
            IXLWorksheet worksheet = workbook.Worksheets.Add("Loans");
            worksheet.Cell(1, 1).Value = "LoanNumber";
            worksheet.Cell(1, 2).Value = "LoanType";
            
            int index = 2;

            foreach (var row in model)
            {
                worksheet.Cell(index, 1).Value = row.LoanNumber;
                worksheet.Cell(index, 2).Value = row.LoanType;
                
                index++;
            }

            using (var stream = new MemoryStream())
            {
                workbook.SaveAs(stream);
                var content = stream.ToArray();
                return File(content, contentType, fileName);
            }
        }
    }
    catch(Exception ex)
    {
        return Json("some fail msg");
    }
}

ExportLoansReport gets a valid json object in the [FromBody] parameter, and the code does not throw any exception at all. Once return File fires, nothing happens, no file is returned to the browser.

I suspect this issue is because the request was made from the client side but I am not sure how to fix it.

2

Answers


  1. Chosen as BEST ANSWER

    What I ended up doing was creating another controller method that got called from JS in the cshtml file that returned the FileResult.

    JS to download a file export:

    var loans = [];
    
    for (var i = 0; i < rows.length; i++) {
        var row = rows[i];
        loans.push({
            "loanNumber": row.loanNumber,
            "borrowerName": row.borrowerName,
            "dateApplicationSubmitted": row.dateApplicationSubmitted,
            "completed": row.completed
        });
    }
    
    var jqxhr = $.ajax({
        data: JSON.stringify(loans),
        contentType: "application/json",
        url: "/Reports/ExportIntakeReport",
        type: "POST"
    });
    
    jqxhr.done(function (data) {
        if (data == 'success') {
            window.location.href = '@Url.Action("DownloadExportReport", "Reports")';
            ShowInfoMessage('Report file exported successfully.', 'success');
        }
        else {
            ShowInfoMessage('Report file export failed.', 'error');
        }
    });
    
    jqxhr.fail(function () {
        ShowInfoMessage('Report file export failed.', 'error');
    });
    

    The method to create the file:

    [HttpPost]
    public IActionResult ExportIntakeReport([FromBody] List<IntakeReportExportViewModel> model)
    {
        string contentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
    
        string path = Path.Combine(_environment.WebRootPath, "uploads");
        if (!Directory.Exists(path))
        {
            Directory.CreateDirectory(path);
        }
        string fileName = $"FHA-Loans-Questions-Export.xlsx";
        string fileNameWithPath = Path.Combine(path, fileName);
    
        HttpContext.Session.SetString("ExportFileName", fileName);
    
        try
        {
            using (var workbook = new XLWorkbook())
            {
                IXLWorksheet worksheet = workbook.Worksheets.Add("Loans");
                worksheet.Cell(1, 1).Value = "LoanNumber";
                worksheet.Cell(1, 2).Value = "BorrowerName";
                worksheet.Cell(1, 3).Value = "DateApplicationSubmitted";
                worksheet.Cell(1, 4).Value = "Completed";
    
                int index = 2;
                foreach (var row in model)
                {
                    worksheet.Cell(index, 1).Value = row.LoanNumber;
                    worksheet.Cell(index, 2).Value = row.BorrowerName;
                    worksheet.Cell(index, 3).Value = row.DateApplicationSubmitted;
                    worksheet.Cell(index, 4).Value = row.Completed;
                    
                    index++;
                }
                using (var stream = new MemoryStream())
                {
                    workbook.SaveAs(stream);
                    using (var fileStream = new FileStream(fileNameWithPath, FileMode.Create, FileAccess.Write))
                    {
                        stream.WriteTo(fileStream);
                        fileStream.Flush();
                    }
                }
            }
            return Json("success");
        }
        catch (Exception ex)
        {
            return Json("error");
        }
    }
    

    Above method creates the file, saves to a directory, and returns success to the javascript function.

    Then, if the javascript gets a success back, then the "DownloadExportReport" gets called to receive the file in the browser.

    [HttpGet]
    public FileResult DownloadExportReport()
    {
        string contentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
    
        string fileName = HttpContext.Session.GetString("ExportFileName");
    
        // find file confirm exists
        string path = Path.Combine(_environment.WebRootPath, "uploads");
        string fileNameWithPath = Path.Combine(path, fileName);
    
        var file = new FileInfo(fileNameWithPath);
        if (file.Exists)
        {
            byte[] fileBytes = System.IO.File.ReadAllBytes(fileNameWithPath);
    
            // delete the file
            file.Delete();
    
            // clear session
            HttpContext.Session.Remove("ExportFileName");
    
            return File(fileBytes, contentType);
        }
        return null;
    }
    

    Thanks for all who helped.


  2. It seems that ajax itself is not designed to support downloading file scenarios. You might want to have a try with ajaxfiledownload package which supports fetching files from server in an asynchronous way.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search