Blazor Server(.NET6)从Azure Blob下载11MB大文件报错
Blazor Server Azure Blob大文件下载超时问题解决
问题现状
基于.NET 6开发的Blazor Server项目,实现Azure Blob存储的文件上传下载功能时,小文件可正常下载,但下载11MB文件时耗时极长,最终抛出异常,浏览器控制台报错。
浏览器控制台错误截图:
当前实现逻辑:通过C#读取Azure Blob流,再通过DotNetStreamReference传递给JavaScript完成下载。
核心原因
- 重复读取Blob资源:原代码同时调用
OpenReadAsync()和DownloadContentAsync(),两次获取Blob数据,浪费服务器资源且增加处理耗时。 - SignalR消息大小限制:Blazor Server依赖SignalR与前端通信,默认的SignalR消息大小限制无法容纳大文件流,直接通过
DotNetStreamReference传递大文件会触发超时或大小超限错误,同时整个文件会被加载到服务器内存,引发性能问题。
解决方案
最优方案:直接返回FileResult(绕过JS互操作)
Blazor Server中下载大文件的最佳实践是直接返回文件流,让浏览器原生处理下载,彻底规避SignalR的消息限制和内存占用问题。
方式1:生成SAS链接直接跳转
在Razor页面中注入NavigationManager,生成Blob的临时SAS链接(私有Blob场景),引导浏览器跳转下载:
// Razor页面注入NavigationManager [Inject] public NavigationManager NavManager { get; set; } private async Task DownloadBlob(string blobName) { BlobContainerClient container = new BlobContainerClient(connectionString, "uploadfiles"); try { BlobClient blobClient = container.GetBlobClient(blobName); if (await blobClient.ExistsAsync()) { // 获取Blob的Content-Type var properties = await blobClient.GetPropertiesAsync(); string contentType = properties.Value.ContentType; string downloadUrl; // 私有Blob生成临时SAS链接,有效期5分钟 if (blobClient.CanGenerateSasUri) { var sasBuilder = new BlobSasBuilder(BlobContainerPermissions.Read, DateTimeOffset.UtcNow.AddMinutes(5)) { BlobName = blobName, BlobContainerName = "uploadfiles" }; downloadUrl = blobClient.GenerateSasUri(sasBuilder).ToString(); } // 公开容器直接使用Blob的公开URL else { downloadUrl = blobClient.Uri.ToString(); } // 跳转至下载链接,浏览器自动触发下载 NavManager.NavigateTo(downloadUrl, true); } } catch (Exception ex) { Console.WriteLine(ex.Message); } }
方式2:通过API控制器返回流
创建API控制器直接返回Blob流,前端通过创建a标签触发下载:
// API控制器代码 [ApiController] [Route("api/blob")] public class BlobController : ControllerBase { private readonly string _connectionString; public BlobController(IConfiguration configuration) { _connectionString = configuration.GetConnectionString("AzureBlobStorage"); } [HttpGet("download/{blobName}")] public async Task<IActionResult> Download(string blobName) { BlobContainerClient container = new BlobContainerClient(_connectionString, "uploadfiles"); BlobClient blobClient = container.GetBlobClient(blobName); if (!await blobClient.ExistsAsync()) { return NotFound(); } var properties = await blobClient.GetPropertiesAsync(); Stream stream = await blobClient.OpenReadAsync(); return File(stream, properties.Value.ContentType, blobName); } }
前端JavaScript代码:
window.downloadFromApi = (blobName) => { const anchor = document.createElement('a'); anchor.href = `/api/blob/download/${blobName}`; anchor.download = blobName ?? ''; anchor.click(); anchor.remove(); }
备选方案:调整SignalR消息大小限制(不推荐)
如果必须保留JS互操作的方式,需修改SignalR配置增大消息限制,同时优化原代码移除重复读取:
- 修改
Program.cs配置:
builder.Services.AddServerSideBlazor() .AddHubOptions(options => { options.MaximumReceiveMessageSize = 50 * 1024 * 1024; // 设置为50MB });
- 优化后的Blazor页面代码:
BlobContainerClient container = new BlobContainerClient(connectionString, "uploadfiles"); try { BlobClient blobClient = container.GetBlobClient(blobName); if (await blobClient.ExistsAsync()) { var properties = await blobClient.GetPropertiesAsync(); string contentType = properties.Value.ContentType; using Stream blobContent = await blobClient.OpenReadAsync(); using var streamRef = new DotNetStreamReference(stream: blobContent); await JS.InvokeVoidAsync("downloadFileFromStream", blobName, streamRef); } } catch (Exception ex) { Console.WriteLine(ex.Message); }
注:此方式仍会将大文件加载到服务器内存,性能较差,仅适合无法使用直接下载方案的场景。
错误说明
浏览器控制台的错误源于SignalR默认消息大小限制,当传递的流数据超过限制时,SignalR连接会中断,进而抛出超时或消息过大的异常。
内容的提问来源于stack exchange,提问作者mnu-nasir
相关产品推荐
相关产品推荐

