从.NET Core控制器返回CSV

im1*_*ike 15 c# csv asp.net asp.net-core

我无法将.NET Core API Controller端点解析为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;
}
Run Code Online (Sandbox Code Playgroud)

当我从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
}
Run Code Online (Sandbox Code Playgroud)

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

我发现这篇文章介绍了如何在.NET Core中"导出"CSV.问题是我正在从它的源(AWS S3存储桶)中检索CSV内存,而这个代码似乎只有在你有一个时才能工作IEnumerable<object>.

我想知道我的问题是在请求还是响应标头中,哪里有阻止浏览器从我的API检索CSV的东西.这是我在浏览器控制台中看到的内容:

在此输入图像描述

Sve*_*vek 15

解决方案:使用 FileResult

如果您希望客户端获取" 保存文件 "对话框,则应使用此选项.

有很多种,从这里选择,例如FileContentResult,FileStreamResult,VirtualFileResult,PhysicalFileResult,但它们都源自FileResult- 所以我们将在这个例子中使用那个.

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

    return File(fileBytes, "text/csv", fileName); // this is the key!
}
Run Code Online (Sandbox Code Playgroud)

public async Task<IActionResult>如果你喜欢使用它,上面也可以使用.

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

额外:内容处理

FileResult会自动提供正确的Content-Dispositionattachment.

如果要在浏览器中打开文件("内联"),而不是提示"保存文件"对话框("附件").然后,您可以通过更改Content-Disposition标头值来实现.

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

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

相信这个SO答案


自定义格式化程序

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

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

由于您使用的格式类型"text/csv"- 当前,当您使用预先捆绑的选项(如FileResult...)时,没有输入/输出格式化器.因此,您必须将其添加,如下所示:

services.AddMvc(options =>
{
    options.InputFormatters.Insert(0, new MyCustomInputFormatter());
    options.OutputFormatters.Insert(0, new MyCustomOutputFormatter());
});
Run Code Online (Sandbox Code Playgroud)

非常简单的自定义格式器

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

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

强制特定格式

// 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()
    {
    }
}  
Run Code Online (Sandbox Code Playgroud)

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


Nei*_*eil 6

这个问题的新手请看 Svek 的回答。最初的问题是关于 http Content-Disposition,但看起来搜索引擎在这里发送通用的 .net core csv 查询。Svek 的回答很好地概述了 .Net Core 可用于从控制器返回 CSV 数据的工具。


强制下载文件而不是内联显示的正确方法是使用Content-Disposition响应标头。虽然以下解决方案有效(请参阅文档),但有人指出这可能会产生意想不到的副作用。


旧答案

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

尝试做这样的事情:

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

有关更多信息,请参阅我对这个类似问题的回答

  • 使用`application/octet-stream` 是针对诸如`exe` 或`bin` 之类的可执行文件...这是不好的做法,因为它违反了网络标准,并且在涉及网络爬虫时可能会以负面方式影响您的网络应用程序和长期可维护性等。有网络标准是有原因的。您应该清楚您为客户提供的内容。 (4认同)
  • @Svek `application/octet-stream` 是“这是二进制文件的默认值。”,我会说任何文件都很重要。您反对使用这种内容类型的论点似乎很弱,但您并没有错,这是一种不好的做法。无论如何,我已经使用正确的响应标头更新了我的答案,以达到相同的效果。 (2认同)
  • @NeilBostian - 几乎没有其他解释的空间。`text/csv` 是 **text** 文件,而不是 **binary** 文件...根据网络标准,**未知文本文件** 是 `text/plain` -- 和 -- **未知的二进制文件**是`application/octet-stream`。如果您只想显示下载提示,则返回带有 `byte[]` 或 `stream` 的 `File` 类型。不要用`Content-Types`来破解它——没有必要。 (2认同)