.NET Core Web API调用第三方API后,Angular13无法获取.gz文件二进制数据
问题:.NET Core Web API返回HttpResponseMessage导致前端收到序列化JSON而非二进制数据
场景与代码实现
我实现了一个.NET Core Web API方法,调用第三方API获取.gz格式文件的二进制数据,返回给Angular 13前端。
.NET Core API服务代码
public async Task<HttpResponseMessage> DownloadGzippedFile(string segment, string folderPath, string fileName, string token) { using (var httpClient = new System.Net.Http.HttpClient()) { // NOTE: accept all media types httpClient.DefaultRequestHeaders.Accept.Clear(); httpClient.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("*/*")); httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); System.Net.Http.HttpResponseMessage response; try { var fileUrl = $"{{uri}}member/file/download/1.0?segment=" + segment + "&folderPath=" + folderPath + "&filename=" + fileName; response = await httpClient.GetAsync(fileUrl); string value = await response.Content.ReadAsStringAsync(); if (response.IsSuccessStatusCode) return response; else throw new HttpRequestException(response.ReasonPhrase); } catch (Exception ex) { Console.WriteLine(ex.Message); return null; } } }
Angular 13代码
Service层
download(segment:string,folderPath:string,fileName:string) :Observable<Blob> { folderPath='/'+folderPath.slice(0,folderPath.lastIndexOf('/')); const url = 'http://localhost:22721/api/NseDownload/DownloadGzippedFile?segment='+segment+'&folderPath='+folderPath+'&fileName='+fileName+'&token='+localStorage.getItem("token"); return this.http.get(url,{ responseType: 'blob' }); }
Component层
NOTE: import { saveAs } from "file-saver";
download(node:any){ this.db.download('CM',node.folderPath,node.name).subscribe(response=>{ saveAs(response, node.name) }) }
问题现象
前端未获取到二进制数据,反而收到序列化后的HttpResponseMessage JSON结构:
{"version":"1.1","content":{"headers":[{"key":"Content-Disposition","value":["attachment; filename=\"07765_C_member_missing_client_details.csv.gz\""]},{"key":"Expires","value":["Mon, 20 Feb 2023 10:19:19 GMT"]},{"key":"Content-Length","value":["39968"]}],"statusCode":200,"reasonPhrase":"OK","headers":[{"key":"Vary","value":["Origin","Access-Control-Request-Method","Access-Control-Request-Headers","Origin","Access-Control-Request-Method","Access-Control-Request-Headers"]},{"key":"X-Frame-Options","value":["SAMEORIGIN"]},{"key":"X-XSS-Protection","value":["1; mode=block"]},{"key":"X-Forwarded-Proto","value":["https"]},{"key":"X-Content-Type","value":["nosniff"]},{"key":"Strict-Transport-Security","value":["max-age=31536000; includeSubdomains; preload"]},{"key":"Cache-Control","value":["no-store, no-cache, max-age=0"]},{"key":"Pragma","value":["no-cache"]},{"key":"Date","value":["Mon, 20 Feb 2023 10:19:19 GMT"]},{"key":"Connection","value":["keep-alive"]},{"key":"Set-Cookie","value":["HttpOnly; Secure"]}],"trailingHeaders":[],"requestMessage":{"version":"1.1","versionPolicy":0,"content":null,"method":{"method":"GET"},"requestUri":"{uri}/member/file/download/1.0?segment=CM&folderPath=/Reports&filename=07765_details.csv.gz","headers":[{"key":"Accept","value":["*/*"]},{"key":"Authorization","value":["Bearer eyJhbGciOiJSUzI1NiJ9.eyJtZW1iZXJDNSwiaWF0IjoxNjc2ODg4MzA1LCJqdGkiOiIzY2UzMGJjMy0yY2M4LTRjZDEtOWIyMy1jOWY0ZDEwMDgxOTgifQ.Dfn-G4US36sO3F1ekNhQZWk6J8uXH_PBaQGGGwevsNMncZgk4zUUJaC1peFqO9mCBy1UFmyO9ngw-YAP_aEzNg"]},{"key":"traceparent","value":["00-b76ec4bd3d634170ef8bce99e991ce57-76cf6e6800e6d661-00"]}],"properties":{},"options":{}},"isSuccessStatusCode":true}
问题原因
- .NET Core Web API中直接返回
HttpResponseMessage时,框架会自动将该对象序列化为JSON,而非透传原始二进制响应内容。 - 代码中冗余的
await response.Content.ReadAsStringAsync()操作,会将二进制文件内容读取为字符串,破坏原始二进制数据结构。
解决方法
1. 修改.NET Core API方法,返回标准文件流
将返回类型改为Task<IActionResult>,使用.NET Core内置的File方法返回二进制流,同时保留第三方响应的关键头信息:
public async Task<IActionResult> DownloadGzippedFile(string segment, string folderPath, string fileName, string token) { using (var httpClient = new HttpClient()) { httpClient.DefaultRequestHeaders.Accept.Clear(); httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("*/*")); httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); try { var fileUrl = $"{{uri}}member/file/download/1.0?segment={segment}&folderPath={folderPath}&filename={fileName}"; var response = await httpClient.GetAsync(fileUrl); if (!response.IsSuccessStatusCode) { throw new HttpRequestException(response.ReasonPhrase); } // 获取第三方响应的原始二进制流 var contentStream = await response.Content.ReadAsStreamAsync(); // 从响应头中提取文件名(优先用第三方返回的文件名) var contentDisposition = response.Content.Headers.ContentDisposition; var targetFileName = contentDisposition?.FileNameStar ?? contentDisposition?.FileName ?? fileName; // 返回二进制文件流,指定正确的MIME类型 return File(contentStream, "application/gzip", targetFileName); } catch (Exception ex) { Console.WriteLine(ex.Message); return StatusCode(StatusCodes.Status500InternalServerError, ex.Message); } } }
2. 优化Angular代码(可选但推荐)
使用HttpParams构建查询参数,避免手动拼接URL的编码问题,同时添加错误处理:
修改后的Service代码
import { HttpClient, HttpParams } from '@angular/common/http'; import { catchError } from 'rxjs/operators'; download(segment: string, folderPath: string, fileName: string): Observable<Blob> { folderPath = '/' + folderPath.slice(0, folderPath.lastIndexOf('/')); const params = new HttpParams() .set('segment', segment) .set('folderPath', folderPath) .set('fileName', fileName) .set('token', localStorage.getItem("token") || ''); return this.http.get('http://localhost:22721/api/NseDownload/DownloadGzippedFile', { responseType: 'blob', params }).pipe( catchError(error => { console.error('下载失败:', error); throw error; }) ); }
修改后的Component代码
download(node: any){ this.db.download('CM', node.folderPath, node.name).subscribe({ next: (response) => { saveAs(response, node.name); }, error: () => { alert('文件下载失败,请稍后重试'); } }); }
内容的提问来源于stack exchange,提问作者Trilok
相关产品推荐
相关产品推荐

