Blazor Server端大文件带进度下载及多文件同时下载问题咨询
Blazor Server 多文件带进度下载解决方案
问题根因梳理
- 第一种方案:全量读取文件字节通过JSInterop传输,浏览器感知不到传输过程,因此无原生下载进度,且大文件会大量占用服务端和客户端内存,本身就不适用于大文件场景
- 第二种方案:
Http.GetAsync是服务端发起的HTTP请求,和客户端浏览器完全无关,自然不会触发浏览器的下载行为 - 第三种方案:循环调用
open打开新标签会触发浏览器的弹窗拦截规则,因此仅第一个文件能正常下载
具体实现方案
1. 优化后端下载接口
首先修改控制器代码,避免全量读取文件到内存,改用流式返回,降低服务端内存消耗:
[HttpGet("download/model/{JobId}/{fileName}")] public IActionResult DownloadFile([FromRoute] string JobId, [FromRoute] string fileName) { if (string.IsNullOrEmpty(JobId) || string.IsNullOrEmpty(fileName)) { return BadRequest(); } var FolderPath = $"xxxx"; var filePath = Path.Combine(environment.WebRootPath, FolderPath, fileName); if (!System.IO.File.Exists(filePath)) { return NotFound(); } // 直接返回文件流,不读取全量字节 return PhysicalFile(filePath, "application/octet-stream", fileName); }
2. 新增批量下载JS方法
在wwwroot/js目录下新增下载工具方法,通过动态生成a标签触发下载,不会触发弹窗拦截:
window.batchDownloadFiles = function (downloadUrls) { downloadUrls.forEach(url => { const a = document.createElement('a'); a.href = url; a.download = ''; // 让浏览器使用接口返回的文件名 document.body.appendChild(a); a.click(); document.body.removeChild(a); }); }
记得在_Host.cshtml中引入该JS文件。
3. Blazor端调用代码
修改下载方法,调用上面的JS方法批量传入所有下载地址:
public async Task DownloadFiles() { // 构造所有下载地址 var downloadUrls = selectedFiles.Select(file => $"Download/Model/{JobId}/{file.Name}").ToList(); // 调用JS批量下载 await JSRuntime.InvokeVoidAsync("batchDownloadFiles", downloadUrls); }
自定义页面内下载进度(可选)
如果需要在Blazor页面内显示每个文件的下载进度,而不是依赖浏览器自带的下载栏进度,可以用JS的fetch API读取流进度,回调到Blazor更新UI:
- 新增JS方法:
window.downloadFileWithProgress = async function (dotNetRef, url, fileIndex) { const response = await fetch(url); const total = response.headers.get('content-length'); const reader = response.body.getReader(); let received = 0; const chunks = []; while(true) { const {done, value} = await reader.read(); if (done) break; chunks.push(value); received += value.length; const progress = Math.round((received / total) * 100); // 回调Blazor方法更新进度 await dotNetRef.invokeMethodAsync('UpdateDownloadProgress', fileIndex, progress); } // 下载完成触发保存 const blob = new Blob(chunks); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); const contentDisposition = response.headers.get('content-disposition'); const fileName = contentDisposition.match(/filename="?([^"]+)"?/)[1]; a.download = fileName; a.click(); URL.revokeObjectURL(a.href); }
- Blazor端代码:
// 定义进度存储属性 public List<int> DownloadProgress { get; set; } = new(); [JSInvokable] public void UpdateDownloadProgress(int fileIndex, int progress) { DownloadProgress[fileIndex] = progress; StateHasChanged(); } public async Task DownloadFilesWithCustomProgress() { DownloadProgress.Clear(); var dotNetRef = DotNetObjectReference.Create(this); for (var i = 0; i < selectedFiles.Count; i++) { DownloadProgress.Add(0); var url = $"Download/Model/{JobId}/{selectedFiles[i].Name}"; // 需要并发下载可以去掉当前await,按实际需求调整 await JSRuntime.InvokeVoidAsync("downloadFileWithProgress", dotNetRef, url, i); } }
内容的提问来源于stack exchange,提问作者Sonny Hansen
相关产品推荐
相关产品推荐

