You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 08:06:57