Angular通过ASP.NET Core API下载SQL Server存储的PDF文件损坏问题
问题根源分析
你遇到的核心问题是ASP.NET Core API默认会将byte[]序列化为JSON数组,而不是直接返回原始二进制内容。比如你的report.ReportContent是PDF的二进制字节,API却把它转成了类似[255,216,255,...]这样的JSON字符串返回,前端拿到的其实是这个JSON的字节数据,而非真正的PDF二进制,所以下载后文件必然损坏。
而你用WinForm直接读SQL是正常的,因为直接拿到了原始varbinary(max)的字节流,没有经过JSON序列化这一步。
解决方案
1. 修改ASP.NET Core API代码(关键修复)
不要直接返回byte[],改用FileResult来返回二进制流,同时指定正确的MIME类型和文件名。如果你需要返回整行数据的其他字段,可以考虑封装成包含文件内容和其他信息的对象,但如果只是为了下载PDF,直接返回FileResult更合适:
[HttpGet] [Route("detail/{studentid:int}/{reportSeq:int}")] public async Task<IActionResult> GetStudentAcademicReport2(int StudentID, int ReportSeq) { var report = await _context.AcademicReportDetails.FromSql("select * from [dbo].[ufnStudentAcademicReport] (8213, 8158)").FirstOrDefaultAsync(); if (report == null) { return NotFound(); } // 返回二进制流,指定MIME类型为application/pdf,设置文件名 return File(report.ReportContent, "application/pdf", $"Student_{StudentID}_Report_{ReportSeq}.pdf"); }
如果你确实需要返回整行数据(比如包含报告的其他元信息),可以把文件内容转成Base64字符串,或者在响应头里附加元信息,不过这样前端需要额外处理:
// 可选:返回包含元信息和Base64文件内容的对象 public async Task<ActionResult<AcademicReportDto>> GetStudentAcademicReport2(int StudentID, int ReportSeq) { var report = await _context.AcademicReportDetails.FromSql("select * from [dbo].[ufnStudentAcademicReport] (8213, 8158)").FirstOrDefaultAsync(); if (report == null) { return NotFound(); } var dto = new AcademicReportDto { StudentID = report.StudentID, ReportSeq = report.ReportSeq, ReportContentBase64 = Convert.ToBase64String(report.ReportContent) }; return Ok(dto); }
2. 修正Angular下载代码
针对第一种直接返回FileResult的API,调整Angular代码如下(确保responseType设为blob,并正确处理下载):
downloadReport(studentId: number, reportSeq: number) { this.httpClient.get(`https://localhost:44369/api/values/detail/${studentId}/${reportSeq}`, { responseType: 'blob', observe: 'response' // 可选:如果需要获取响应头里的文件名 }).subscribe(response => { // 从响应头获取文件名(如果API设置了的话) const contentDisposition = response.headers.get('Content-Disposition'); let filename = 'file.pdf'; if (contentDisposition) { const matches = contentDisposition.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/); if (matches != null && matches[1]) { filename = matches[1].replace(/['"]/g, ''); } } const blob = new Blob([response.body], { type: 'application/pdf' }); const url = window.URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.setAttribute('download', filename); document.body.appendChild(link); link.click(); // 清理资源 window.URL.revokeObjectURL(url); document.body.removeChild(link); }); }
如果用的是返回Base64的API,处理方式如下:
downloadReport(studentId: number, reportSeq: number) { this.httpClient.get<AcademicReportDto>(`https://localhost:44369/api/values/detail/${studentId}/${reportSeq}`) .subscribe(dto => { const byteCharacters = atob(dto.ReportContentBase64); const byteNumbers = new Array(byteCharacters.length); for (let i = 0; i < byteCharacters.length; i++) { byteNumbers[i] = byteCharacters.charCodeAt(i); } const byteArray = new Uint8Array(byteNumbers); const blob = new Blob([byteArray], { type: 'application/pdf' }); // 后续下载步骤和上面一致 const url = window.URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.setAttribute('download', `Student_${dto.StudentID}_Report_${dto.ReportSeq}.pdf`); document.body.appendChild(link); link.click(); window.URL.revokeObjectURL(url); document.body.removeChild(link); }); }
3. 修正WinForm通过API下载的代码
同样,原来的API返回的是JSON数组,现在修改后的API返回二进制流,所以调整WinForm代码:
HttpClient http = new HttpClient(); var NewResponse = await http.GetAsync("https://localhost:44369/api/values/detail/8213/8158"); // 确保响应成功 NewResponse.EnsureSuccessStatusCode(); var content = await NewResponse.Content.ReadAsByteArrayAsync(); System.IO.File.WriteAllBytes(@"c:\temp\test111.pdf", content);
额外验证点
- 可以用Postman测试API,直接发送GET请求,看是否能正确下载并打开PDF。如果Postman能正常打开,说明API修复成功,剩下的就是前端/WinForm的调用问题。
- 检查SQL中存储的
ReportContent是否确实是有效的PDF字节流(你已经用WinForm直接读SQL验证过,这一步应该没问题)。
内容的提问来源于stack exchange,提问作者Aussie
相关产品推荐
相关产品推荐

