.NET Framework 4.8迁移.NET 6后ActionResult无法返回PDF文件
我正在将应用从.NET Framework 4.8迁移到.NET 6,因部分.NET类在新版本中过时遇到问题。我的控制器中有一个端点应返回PDF文件,但当前仅返回包含少量值的JSON文件。
.NET 6项目相关代码
端点方法
[HttpPost] public ActionResult DownloadFile([FromForm] string data, [FromForm] string fileType) { try { if (!string.IsNullOrWhiteSpace(data)) { return GenerateReportDocument(data, fileType); } } catch (Exception ex) { logger.LogError(ex, $"Unexpected error occured in {nameof(DownloadFile)}."); return StatusCode(500); } return NoContent(); }
GenerateReportDocument方法
private ActionResult GenerateReportDocument(string data, string fileType) { var specificationString = specificationGeneratorService.GenerateSpecificationString(JObject.Parse(data)); logger.LogWarning($"Check images in specificationString: '{specificationString}'"); if (string.IsNullOrWhiteSpace(specificationString)) { specificationString = "<p></p>"; } var reportGenerator = generateReportDocuments.SingleOrDefault(r => r.FileType.ToLower().Equals(fileType.ToLower())); if (reportGenerator is not null) { return Ok(reportGenerator.GenerateReportDocument(SpecificationFileName, specificationString)); } return NoContent(); }
第三方调用方法
public HttpContent GenerateReportDocument(string fileName, string specificationString) { var requestContent = new StringContent(JsonConvert.SerializeObject(new { Html = specificationString })); requestContent.Headers.ContentType = new MediaTypeHeaderValue("application/json"); var httpResponse = Flurl.Url.Combine(_abcPdfOptions.PdfConverterUrl, "pdf/convertfromhtmltopdf") .PostAsync(requestContent).GetAwaiter().GetResult(); HttpContent httpContent = httpResponse.Content; httpContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") { FileName = $"{fileName}.{FileExt}", }; httpContent.Headers.ContentType = new MediaTypeHeaderValue("application/pdf"); return httpContent; }
.NET Framework 4.8中正常工作的对应代码
端点方法
[HttpPost] public HttpResponseMessage DownloadFile(HttpRequestMessage requestMessage) { try { var specification = requestMessage.Content.ReadAsFormDataAsync().Result; string data = specification.Get("Data"); string fileType = specification.Get("FileType"); if (!string.IsNullOrWhiteSpace(data)) { return GenerateReportDocument(data, fileType); } } catch (Exception ex) { logger.LogError(ex, $"Unexpected error occured in {nameof(DownloadFile)}."); return new HttpResponseMessage(HttpStatusCode.InternalServerError); } return new HttpResponseMessage(HttpStatusCode.NoContent); }
GenerateReportDocument方法
private HttpResponseMessage GenerateReportDocument(string data, string fileType) { var specificationString = specificationGeneratorService.GenerateSpecificationString(JObject.Parse(data)); logger.LogWarning($"Check images in specificationString: '{specificationString}'"); if (string.IsNullOrWhiteSpace(specificationString)) { specificationString = "<p></p>"; } var reportGenerator = generateReportDocuments.SingleOrDefault(r => r.FileType.ToLower().Equals(fileType.ToLower())); if (reportGenerator != null) { return new HttpResponseMessage(HttpStatusCode.OK) { Content = reportGenerator.GenerateReportDocument(SpecificationFileName, specificationString), }; } return new HttpResponseMessage(HttpStatusCode.NoContent); }
第三方调用方法
public HttpContent GenerateReportDocument(string fileName, string specificationString) { var requestContent = new StringContent(JsonConvert.SerializeObject(new { Html = specificationString })); requestContent.Headers.ContentType = new MediaTypeHeaderValue("application/json"); var httpResponse = Flurl.Url.Combine(_abcPdfOptions.PdfConverterUrl, "pdf/convertfromhtmltopdf") .PostAsync(requestContent).GetAwaiter().GetResult(); HttpContent httpContent = httpResponse.Content; httpContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") { FileName = $"{fileName}.{FileExt}", }; httpContent.Headers.ContentType = new MediaTypeHeaderValue("application/pdf"); return httpContent; }
我尝试过修改代码,将方法改为IActionResult或HttpResponse类型,也调整了返回类型和参数,但问题仍未解决。请问我哪里出错了?ActionResult是否适合我当前的需求?
核心问题
在.NET 6中,直接返回Ok(HttpContent)时,ASP.NET Core会把HttpContent对象序列化为JSON,而非将其内容作为响应体输出——这就是你得到JSON而非PDF的原因。
.NET Framework Web API中返回HttpResponseMessage时,框架会直接处理HttpContent内容,但ASP.NET Core的ActionResult体系需要使用专门的结果类型处理文件流或外部响应内容。
修改方案
1. 调整GenerateReportDocument方法的返回逻辑
替换Ok(HttpContent)为ASP.NET Core支持的FileStreamResult,修改后的代码如下:
private async Task<IActionResult> GenerateReportDocument(string data, string fileType) { var specificationString = specificationGeneratorService.GenerateSpecificationString(JObject.Parse(data)); logger.LogWarning($"Check images in specificationString: '{specificationString}'"); if (string.IsNullOrWhiteSpace(specificationString)) { specificationString = "<p></p>"; } var reportGenerator = generateReportDocuments.SingleOrDefault(r => r.FileType.ToLower().Equals(fileType.ToLower())); if (reportGenerator is not null) { var httpContent = await reportGenerator.GenerateReportDocument(SpecificationFileName, specificationString); // 读取PDF流 var stream = await httpContent.ReadAsStreamAsync(); // 获取响应头中的文件名和内容类型 var fileName = httpContent.Headers.ContentDisposition?.FileName ?? $"default.{FileExt}"; var contentType = httpContent.Headers.ContentType?.MediaType ?? "application/pdf"; return File(stream, contentType, fileName); } return NoContent(); }
2. 更新端点方法为异步
由于上述方法使用异步操作,端点方法需同步改为异步:
[HttpPost] public async Task<IActionResult> DownloadFile([FromForm] string data, [FromForm] string fileType) { try { if (!string.IsNullOrWhiteSpace(data)) { return await GenerateReportDocument(data, fileType); } } catch (Exception ex) { logger.LogError(ex, $"Unexpected error occured in {nameof(DownloadFile)}."); return StatusCode(500); } return NoContent(); }
3. 优化第三方调用的异步方式
原代码中.GetAwaiter().GetResult()会导致同步阻塞,改为异步调用避免死锁:
public async Task<HttpContent> GenerateReportDocument(string fileName, string specificationString) { var requestContent = new StringContent(JsonConvert.SerializeObject(new { Html = specificationString })); requestContent.Headers.ContentType = new MediaTypeHeaderValue("application/json"); var httpResponse = await Flurl.Url.Combine(_abcPdfOptions.PdfConverterUrl, "pdf/convertfromhtmltopdf") .PostAsync(requestContent); HttpContent httpContent = httpResponse.Content; httpContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") { FileName = $"{fileName}.{FileExt}", }; httpContent.Headers.ContentType = new MediaTypeHeaderValue("application/pdf"); return httpContent; }
关于ActionResult的适用性
ActionResult完全适合你的需求,ASP.NET Core中的FileResult(包括FileStreamResult、FileContentResult等)专门用于处理文件下载场景,能正确设置响应头、处理文件流,比直接返回HttpResponseMessage更符合ASP.NET Core的设计范式。
内容的提问来源于stack exchange,提问作者cowefe

