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

.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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.23 08:25:01