【问题标题】:Returning CSV from .NET Core controller从 .NET Core 控制器返回 CSV
【发布时间】:2018-04-09 03:55:08
【问题描述】:

我无法将 .NET Core API 控制器端点解析为 CSV 下载。我正在使用从 .NET 4.5 控制器中提取的以下代码:

[HttpGet]
[Route("{id:int}")]
public async Task<HttpResponseMessage> Get(int id)
{
    string csv = await reportManager.GetReport(CustomerId, id);
    var response = new HttpResponseMessage(HttpStatusCode.OK);
    response.Content = new StringContent(csv);
    response.Content.Headers.ContentType = new MediaTypeHeaderValue("text/csv");
    response.Content.Headers.ContentDisposition = 
        new ContentDispositionHeaderValue("attachment") { FileName = "report.csv" };
    return response;
}

当我从我的 Angular 4 应用程序中访问此端点时,我收到以下响应写入浏览器:

{
    "version": {
        "major": 1,
        "minor": 1,
        "build": -1,
        "revision": -1,
        "majorRevision": -1,
        "minorRevision": -1
    },
    "content": {
        "headers": [
            {
                "key": "Content-Type",
                "value": [
                    "text/csv"
                ]
            },
            {
                "key": "Content-Disposition",
                "value": [
                    "attachment; filename=11.csv"
                ]
            }
        ]
    },
    "statusCode": 200,
    "reasonPhrase": "OK",
    "headers": [ ],
    "requestMessage": null,
    "isSuccessStatusCode": true
}

我的期望是,当我点击端点时,会提示用户下载 CSV。

我发现this 发布了有关如何在 .NET Core 中“导出”CSV 的帖子。问题是我正在从其源(AWS S3 存储桶)检索内存中的 CSV,而此代码似乎仅在您拥有 IEnumerable&lt;object&gt; 时才有效。

我想知道我的问题是出在请求标头还是响应标头中,其中存在阻止浏览器从我的 API 检索 CSV 的东西。这是我在浏览器控制台中看到的:

【问题讨论】:

  • 另一个注意事项,在这种情况下,通常最好使用输出格式器,例如读取acceptencoding或其他规则。如果您正在处理一个大型项目,您可能需要使用 csv 导出来获取多种方法,并且如果您将创建通用格式,则可以避免将来不必要的工作。可以是中间件中的一层
  • @zxxc 你有什么要补充的吗?您链接到我帖子的链接中引用的文章不是很有帮助。
  • 看起来好像您正在通过 AJAX 获取下载。当您这样做时,您需要将responseType 设置为arraybuffer 并在您的成功方法中从响应数据中创建一个blob。您还需要手动处理实际下载,方法是创建包含该数据的链接并“单击”它。这一切都不会自动为您发生。
  • @ChrisPratt 在网络流量截图中,我直接在浏览器中点击了 URL。

标签: c# asp.net csv asp.net-core


【解决方案1】:

Newcomers to this question please see Svek's answer。最初的问题是关于 http Content-Disposition,但看起来搜索引擎在这里发送通用 .net 核心 csv 查询。 Svek 的回答很好地概述了 .Net Core 可用于从控制器返回 CSV 数据的工具。


强制下载文件而不是内联显示的正确方法是使用Content-Disposition response header. 虽然下面的解决方案有效 (see documentation),但有人指出这可能会产生意想不到的副作用。


旧答案

Content-Type 响应标头设置为application/octet-stream 将强制大多数主流浏览器提示用户保存文件,而不是在窗口中显示。

尝试做这样的事情:

var result = new FileContentResult(myCsvByteArray, "application/octet-stream");
result.FileDownloadName = "my-csv-file.csv";
return result;

See my answer to this similar question for more info

【讨论】:

  • 使用application/octet-stream 是针对诸如exebin 之类的可执行文件...这是一种不好的做法,因为它违反了Web 标准,并且可能会以负面的方式影响您的Web 应用程序。说到网络爬虫和长期可维护性等。有网络标准是有原因的。您应该清楚自己为客户提供的内容。
  • @Svek application/octet-stream 是“这是二进制文件的默认值。”,我会说任何文件都很重要。你反对使用这种内容类型的论点似乎很弱,但你没有错,这是一种不好的做法。无论如何,我已经使用要设置的正确响应标头更新了我的答案,以达到相同的效果。
  • @NeilBostian - 几乎没有其他解释的余地​​。 text/csvtext 文件,而不是 binary 文件...根据网络标准,unknown text filetext/plain -- 并且 -- 未知的二进制文件application/octet-stream。如果您只想显示下载提示,则返回带有byte[]streamFile 类型。不要用Content-Types 破解它——没有必要。
  • @Svek 将文本文件视为二进制文件与将string 视为object 没有区别。在 OP 的情况下,使用 application/octet-streamtext/plain 的可维护性可能无关紧要,如果他特别试图提示用户下载文件,网络爬虫也是如此。这并没有通过尝试将文本文件作为视频传递来违反标准,它只是说“把我当作二进制 blob”,这没有违反任何标准,它正在利用它。
  • @NeilBostian FileResultContent-Disposition 设置为开箱即用的attachment,因为它是用来提示“保存”对话框的。请注意,Content-Type 仍将保持不变并正确为text/csv。确实没有任何论据可以证明使用 application/octet-stream 是合理的,也不应该推荐它。
【解决方案2】:

解决方法:使用FileResult

如果您希望客户端获得“保存文件”对话框,则应使用此选项。

这里有多种选择,如FileContentResultFileStreamResultVirtualFileResultPhysicalFileResult;但它们都源自FileResult - 所以我们将在本示例中使用那个。

public async Task<FileResult> Download()
{
    string fileName = "foo.csv";
    byte[] fileBytes = ... ;

    return File(fileBytes, "text/csv", fileName); // this is the key!
}

如果您更喜欢使用public async Task&lt;IActionResult&gt;,上述方法也可以使用。

关键是你返回一个File类型。

附加:内容处置

FileResult 将自动为attachment 提供正确的Content-Disposition 标头。

如果要在浏览器中打开文件(“内联”),而不是提示“保存文件”对话框(“附件”)。然后您可以通过更改 Content-Disposition 标头值来做到这一点。

例如,我们想在浏览器中显示PDF文件。

public IActionResult Index()
{
    byte[] contents = FetchPdfBytes();
    Response.AddHeader("Content-Disposition", "inline; filename=test.pdf");
    return File(contents, "application/pdf");
}

感谢SO Answer


自定义格式化程序

自定义格式化程序通常是一个不错的选择,因为它们允许客户端询问他们想要的数据类型,例如更流行的 JSON 或不太流行的 XML。

这主要通过提供客户端传递给服务器的 Accept 标头中指定的内容来工作,例如 CSV、XLS、XML、JSON 等。

您想使用"text/csv" 的格式类型,但没有预定义的格式器,因此您必须手动将其添加到输入和输出格式器集合中:

services.AddMvc(options =>
{
    options.InputFormatters.Insert(0, new MyCustomInputFormatter());
    options.OutputFormatters.Insert(0, new MyCustomOutputFormatter());
});

非常简单的自定义格式化程序

这是自定义格式化程序的一个非常简单的版本,它是Microsoft Docs example 提供的精简版。

public class CsvOutputFormatter : TextOutputFormatter
{
    public CsvOutputFormatter()
    {
        SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/csv"));
        SupportedEncodings.Add(Encoding.UTF8);
        SupportedEncodings.Add(Encoding.Unicode);
    }

    protected override bool CanWriteType(Type type)
    {
        return true; // you could be fancy here but this gets the job done.
    }

    public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
    {
        var response = context.HttpContext.Response;
        
        // your magic goes here
        string foo = "Hello World!";

        return response.WriteAsync(foo);
    }
}

强制使用特定格式

// force all actions in the controller
[Produces("text/csv")]
public class FooController
{
    // or apply on to a single action
    [Produces("text/csv")]
    public async Task<IActionResult> Index()
    {
    }
}  

有关更多信息,我建议您阅读:

【讨论】:

  • 是否可以从 Action 返回自定义对象或 ActionResult,但是,如果需要,使用自定义格式化程序拦截它以返回 CSV 文件?我知道我们可以使用客户格式化程序将 CSV 作为文本返回。就我而言,我想返回 JSON 数据,但如果客户端请求 CSV,那么我想返回 CSV 作为 FILE 而不是 CSV 作为文本。我想知道是否可以使用自定义格式化程序?
  • 我相信您的问题在使用 Content-Disposition 时已经得到解答?
  • 能否在自定义格式化程序中添加响应标头?我觉得不行。我相信我必须创建一个中间件来添加 Content-Disposition 的响应头和一个自定义格式化程序以实现我的目的。请建议是否有任何替代或更好的方法。谢谢。
猜你喜欢
  • 1970-01-01
  • 2023-03-25
  • 2018-04-28
  • 2022-01-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-04-28
相关资源
最近更新 更多