Angular + .NET 文件下载异常问题求助
问题
我有一个.NET文件下载接口,URL为https://localhost:7004/api/Files/download:
- 直接在浏览器粘贴该URL可正常触发浏览器下载管理器,支持暂停/取消,但刷新页面不会重新触发下载;
- 在Angular应用中调用该接口时,文件需完整下载完成后才会交给浏览器下载管理器,无法实时显示进度,调试发现
subscribe回调仅在下载完成后触发。已尝试blob和arrayBuffer作为responseType,问题仍存在。
相关代码如下:
.NET端点代码
private readonly string _filePath = "C:/Users/shadi/Desktop/file.zip"; [HttpGet("download")] public IActionResult DownloadFile() { if (!System.IO.File.Exists(_filePath)) { return NotFound(); } var fileName = "file.zip"; var fileStream = new FileStream(_filePath, FileMode.Open, FileAccess.Read); var fileLength = new FileInfo(_filePath).Length; Response.Headers.Add("Content-Disposition", $"attachment; filename={fileName}"); Response.Headers.Add("Content-Length", fileLength.ToString()); Response.Headers.Add("Content-Type", "application/octet-stream"); return new FileStreamResult(fileStream, "application/octet-stream"); }
Angular服务代码
downloadFile(): Observable<Blob> { const url = `${this.filesUrl}/download` return this.http.get(url, { responseType: 'blob' }); }
Angular组件代码
download(): void { this.fileService.downloadFile().subscribe((res: Blob) => { const url = window.URL.createObjectURL(res); const a = document.createElement('a'); a.href = url; a.download = 'file.zip'; document.body.appendChild(a); a.click(); document.body.removeChild(a); window.URL.revokeObjectURL(url); }); }
解决方案
核心原因
Angular的HttpClient.get默认会等待整个响应完全接收后才触发订阅回调,无法实时获取下载进度。而浏览器直接访问接口时,服务器以流式返回内容,浏览器下载管理器能实时处理流式数据,所以能显示进度、支持暂停。
1. 修改Angular代码实现实时进度监听
要实现实时进度,需要启用reportProgress并将observe设置为'events',这样可以监听下载过程中的各类事件(包括进度事件):
修改后的Angular服务代码
import { HttpClient, HttpEvent, HttpEventType } from '@angular/common/http'; import { Observable } from 'rxjs'; // ... downloadFile(): Observable<HttpEvent<Blob>> { const url = `${this.filesUrl}/download`; return this.http.get(url, { responseType: 'blob', reportProgress: true, observe: 'events' }); }
修改后的Angular组件代码
download(): void { let downloadedBytes = 0; let totalBytes = 0; this.fileService.downloadFile().subscribe({ next: (event) => { // 处理进度事件,可用于更新UI if (event.type === HttpEventType.DownloadProgress) { totalBytes = event.total || 0; downloadedBytes = event.loaded; const progress = Math.round((downloadedBytes / totalBytes) * 100); console.log(`下载进度:${progress}%`); } // 下载完成,触发浏览器下载 else if (event.type === HttpEventType.Response) { const blob = event.body; if (blob) { const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'file.zip'; document.body.appendChild(a); a.click(); document.body.removeChild(a); window.URL.revokeObjectURL(url); } } }, error: (err) => { console.error('下载失败', err); } }); }
2. .NET代码优化(可选)
原.NET代码已经实现了流式返回,不过可以简化为使用PhysicalFile方法,它会自动处理文件流、Content-Length等响应头,代码更简洁:
private readonly string _filePath = "C:/Users/shadi/Desktop/file.zip"; [HttpGet("download")] public IActionResult DownloadFile() { if (!System.IO.File.Exists(_filePath)) { return NotFound(); } var fileName = "file.zip"; // PhysicalFile会自动设置Content-Disposition、Content-Length、Content-Type return PhysicalFile(_filePath, "application/octet-stream", fileName); }
额外说明
- 浏览器直接访问时刷新页面不重新触发下载,是因为浏览器缓存了响应头,可在.NET接口中添加
Response.Headers.Add("Cache-Control", "no-cache, no-store, must-revalidate");禁用缓存,刷新页面就会重新下载。 - 若无需监听进度,只想直接触发浏览器下载管理器,可在Angular中直接打开新窗口访问下载URL:
window.open(${this.filesUrl}/download, '_blank'),这种方式无需等待下载完成,直接交给浏览器处理。
内容的提问来源于stack exchange,提问作者Shadi Aslan
相关产品推荐
相关产品推荐

