Blazor - Downloading Excel File

Blazor - Downloading Excel File

Using Javascript and ClosedXML

Working on a project at work I have a requirnment for creating and downloading an excel file.

This has proven to be confusing but in the end with the help of our friend google I managed to find and implement the code needed using Javascript in blazor to do this so adding here for my memory and just in case i tmay help anyone else in the future.

First the Javascript file method:

function saveAsFile(filename, bytesBase64) {
    var link = document.createElement('a');
    link.download = filename;
    link.href = "data:application/octet-stream;base64," + bytesBase64;
    document.body.appendChild(link); // Needed for Firefox
    link.click();
    document.body.removeChild(link);
}

Then in the blazor file or class file you would call as

await JSRuntime.InvokeAsync<object>("saveAsFile", fileName, Convert.ToBase64String(content));

It needs (if I have understood this correctly) the content to be byte format so for my excel file I used ClosedXML to create the excel file then at the end exported so it donwloaded to the users selected download location.

What I essentially have in my project is a:

  • Razor page which has a table of requests with an export button which calls a method from a custom excel class. This custom excel class is injected into this razor page.

  • The custom excel class uses ClosedXML to create the excel file as required with data, formatting etc.

  • In the startup the custom excel class is injected as a service so the razor page can inject it.

Custom Class Code:

using System.Threading.Tasks;
using System.IO;
using Microsoft.JSInterop;


namespace SACOMaintenance.Blazor.Server.Services
{
    public class MaintenanceRequestsExcel
    {

        private readonly IJSRuntime JSRuntime;

        public MaintenanceRequestsExcel(IJSRuntime jSRuntime)
        {
            JSRuntime = jSRuntime;
        }

            using (var workbook = new XLWorkbook())
            {
                var worksheet = workbook.Worksheets.Add("Maint Requests All");

                worksheet.Cell("A1").Value = "All Maintenance Requests";
                worksheet.Cell("A1").Style.Font.Bold = true;
                worksheet.Cell("A1").Style.Font.FontSize = 20;
                worksheet.Range("A1", "E1").Merge(true);

                worksheet.Cell("A2").Value = "Id";
                worksheet.Cell("A2").Style.Alignment.SetHorizontal(XLAlignmentHorizontalValues.Center);
                worksheet.Cell("A2").Style.Font.Bold = true;

                worksheet.Cell("B2").Value = "Details";
                worksheet.Cell("B2").Style.Alignment.SetHorizontal(XLAlignmentHorizontalValues.Center);
                worksheet.Cell("B2").Style.Font.Bold = true;

                worksheet.Cell("C2").Value = "Date Raised";
                worksheet.Cell("C2").Style.Alignment.SetHorizontal(XLAlignmentHorizontalValues.Center);
                worksheet.Cell("C2").Style.Font.Bold = true;

                worksheet.Cell("D2").Value = "Equipment Name";
                worksheet.Cell("D2").Style.Alignment.SetHorizontal(XLAlignmentHorizontalValues.Center);
                worksheet.Cell("D2").Style.Font.Bold = true;

                worksheet.Cell("E2").Value = "Status";
                worksheet.Cell("E2").Style.Alignment.SetHorizontal(XLAlignmentHorizontalValues.Center);
                worksheet.Cell("E2").Style.Font.Bold = true;

                worksheet.Column(1).Style.Alignment.SetHorizontal(XLAlignmentHorizontalValues.Center);
                worksheet.Column(3).Style.Alignment.SetHorizontal(XLAlignmentHorizontalValues.Center);
                worksheet.Column(4).Style.Alignment.SetHorizontal(XLAlignmentHorizontalValues.Center);
                worksheet.Column(5).Style.Alignment.SetHorizontal(XLAlignmentHorizontalValues.Center);

                worksheet.Range("A2", "E2").SetAutoFilter();

                //loop through the collection and put the items in columns then increment to the next row
                var i = 3; //declare the row number to start

                //loop through the items in the list
                foreach(var item in exportList)
                {
                    worksheet.Cell("A" + i).Value = item.Id;
                    worksheet.Cell("B" + i).Value = item.RequestDetails;
                    worksheet.Cell("C" + i).Value = item.DateRaised;
                    worksheet.Cell("D" + i).Value = item.Equipment.Name;
                    worksheet.Cell("E" + i).Value = item.Status.StatusName;
                    i++;
                }

                worksheet.Columns("A", "E").AdjustToContents();

                //Download the excel file to the users download default lcoation
                using (var stream = new MemoryStream())
                {
                    workbook.SaveAs(stream);
                    var content = stream.ToArray();

                    var fileName = "Maintenance Request Export " + DateTime.Now.ToString("dd-MM-yyyy") + " .xlsx"; //TODO: see if this format can be changed in the app settings?
                    await JSRuntime.InvokeAsync<object>("saveAsFile", fileName, Convert.ToBase64String(content));
                } 
            }
        }
    }  
}

So the Javascript is add from a using statment so it can call the relevant javascript file from this line

using Microsoft.JSInterop;

Then the IJSRuntime is injected into the class and constructor.

 private readonly IJSRuntime JSRuntime;

        public MaintenanceRequestsExcel(IJSRuntime jSRuntime)
        {
            JSRuntime = jSRuntime;
        }

The part of the method that creates the excel and download with the Javascript function is

using (var stream = new MemoryStream())
{
       workbook.SaveAs(stream);
        var content = stream.ToArray();

        var fileName = "Maintenance Request Export " + DateTime.Now.ToString("dd-MM-yyyy") + " .xlsx"; //TODO: see if this format can be changed in the app settings?
        await JSRuntime.InvokeAsync<object>("saveAsFile", fileName, Convert.ToBase64String(content));
}

In the startup class in blazor the excel class is added as a service

services.AddScoped<MaintenanceRequestsExcel>();

In the razor page file I call the excel class method to run the export function and download the file. The method is tied to an onclick event of a button.

public async Task ExportExcelFile()
 {
        await ExcelExport.ExportListToExcel(maintReqListViewModel.requests);
 }

Here is a quick video of it in action where it is exporting the table shown into an excel file.