Abdullah
Abdullah

Reputation: 133

Is there a way to get a file stream to download to the browser in Blazor?

I'm trying out a few things with Blazor and I'm still new to it. I'm trying to get a file stream to download to the browser. What's the best way to download a file from Blazor to browser?

I've tried using a method in my razor view that returns a stream but that didn't work.

//In my Blazor view
@code{
    private FileStream Download()
    {
        //get path + file name
        var file = @"c:\path\to\my\file\test.txt";
        var stream = new FileStream(test, FileMode.OpenOrCreate);
        return stream;
    }
}

The code above doesn't give me anything, not even an error

Upvotes: 8

Views: 16239

Answers (3)

Grandizer
Grandizer

Reputation: 3025

I wound up doing it a different way, not needing NavigationManager. It was partially taken from the Microsoft Docs here. In my case I needed to render an Excel file (using EPPlus) but that is irrelevant. I just needed to return a Stream to get my result.

On my Blazor page or component when a button is clicked:

public async Task GenerateFile()
{
  var fileStream = ExcelExportService.GetExcelStream(exportModel);

  using var streamRef = new DotNetStreamReference(stream: fileStream);

  await jsRuntime.InvokeVoidAsync("downloadFileFromStream", "Actual File Name.xlsx", streamRef);
}

The GetExcelStream is the following:

public static Stream GetExcelStream(ExportModel exportModel)
{
  var result = new MemoryStream();

  ExcelPackage.LicenseContext = LicenseContext.Commercial;
  var fileName = @$"Gets Overwritten";

  using (var package = new ExcelPackage(fileName))
  {
    var sheet = package.Workbook.Worksheets.Add(exportModel.SomeUsefulName);
    var rowIndex = 1;

    foreach (var dataRow in exportModel.Rows)
    {
      ...
      // Add rows and cells to the worksheet
      ...
    }

    sheet.Cells.AutoFitColumns();

    package.SaveAs(result);
  }

  result.Position = 0; // This is required or no data is in result

  return result;
}

This JavaScript is in the link above, but adding it here as the only other thing I needed.

window.downloadFileFromStream = async (fileName, contentStreamReference) => {
  const arrayBuffer = await contentStreamReference.arrayBuffer();
  const blob = new Blob([arrayBuffer]);
  const url = URL.createObjectURL(blob);
  const anchorElement = document.createElement("a");

  anchorElement.href = url;
  anchorElement.download = fileName ?? "";
  anchorElement.click();
  anchorElement.remove();
  URL.revokeObjectURL(url);
}

Upvotes: 2

dotnetspark
dotnetspark

Reputation: 581

Although the above answer is technically correct, if you need to pass in a model -POST-, then NavigationManager won't work. In which case you, must likely end up using HttpClient component. If so wrap the response.Content -your stream- in a DotNetStreamReference instance - new DotNetStreamReference(response.Content). This will create a ReadableStream. Then create the blob with the content. Keep in mind DotNetStreamReference was recently introduced with .NET 6 RC1. As of now the most efficient way. Otherwise, you can use fetch API and create a blob from the response.

Upvotes: 1

Softlion
Softlion

Reputation: 12625

Another solution is to add a simple api controller endpoint using endpoints.MapControllerRoute. This will work only with server side blazor though.

Ex:

endpoints.MapBlazorHub();
endpoints.MapControllerRoute("mvc", "{controller}/{action}");
endpoints.MapFallbackToPage("/_Host");

Then add a controller. For example:

public class InvoiceController : Controller
{
    [HttpGet("~/invoice/{sessionId}")]
    public async Task<IActionResult> Invoice(string sessionId, CancellationToken cancel)
    {
        return File(...);
    }
}

Usage in a .razor file:

async Task GetInvoice()
{
    ...
    Navigation.NavigateTo($"/invoice/{orderSessionId}", true);
}

Upvotes: 11

Related Questions