In a Blazor project, can I download a file without js interop?

1 day ago 3
ARTICLE AD BOX

The premise

I'm working on a Blazor program which handles a database of companies and related CRUD operations.

I'm on .NET 8.0, writing on Visual Studio Community 2026 v. 18.3.0, with InteractiveServer as the default solution-wide render mode, managing .xls/.xlsx files via ClosedXML 0.102.0.

The goal

I want to implement a function that allows the user to download a .xlsx file enumerating all the companies that meet the most recently set filters.

Such a function is already provided by an external API (see below) which is working as intended alongside the MVC version of the same program.

The issue

May I achieve the desired outcome without resorting to JavaScript interoperability?

It is a question borne out of sheer curiosity because I'm learning Blazor as I write (and I'm not an expert in .js and C# either), therefore I want to keep it as "pure" as possible. I've researched the subject for a couple hours and it would appear I'm forced to resort to js.

The code

Here's the current iteration of the function inside the razor component, invoking ApiServices to download the file:

public async Task DownloadExcelResults() { FiltersDto filters = Filters; filters.PageNumber = 1; filters.PageSize = int.MaxValue; var excelFile = await _service.GetCompaniesExcelAsync(filters); // Downloads the .xlsx file from the API response return FileResult(excelFile, "application/vnd.ms-excel", "Companies.xlsx"); }

File and FileResult do not work as return types because they're static types in this context, which was a head-scratcher when first copy-pasting the function from the MVC version.

Here's the ApiServices corresponding function:

public async Task<Stream> GetCompaniesExcelAsync(FiltersDto filters) { var request = new RestRequest("company/export", Method.Post); request.AddJsonBody(filters); var response = await _client.ExecuteAsync(request); if (!response.IsSuccessful) { throw new Exception($"API error: {response.ErrorMessage}"); } return new MemoryStream(response.RawBytes); }

And here's the API function itself (I'm skipping over the population of the .xlsx file because it works as intended and I don't think it's related to this particular issue):

[HttpPost("export")] public async Task<IActionResult> ExportCompanies(FiltersDto filters) { // Populates the sheet // ... // Creates an .xls file via ClosedXML var wb = new XLWorkbook(); // MemoryStream for handling the file var ms = new MemoryStream(); // Populates the sheet // ... wb.SaveAs(ms); var filename = $"{DateTime.Now:yyyyMMddHHmmss}_{"Companies"}.xlsx"; return File(ms.ToArray(), "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", filename); }
Read Entire Article