Reputation: 133
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
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
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
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