从.NET Core控制器返回CSV

时间:2017-10-27 13:57:31

标签: c# asp.net csv 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;
}

当我从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关于如何&#34; export&#34;的帖子.NET Core中的CSV。问题是我从它的源(AWS S3存储桶)中检索内存中的CSV,而此代码似乎仅在您有IEnumerable<object>时才有效。

我想知道我的问题是否存在于请求或响应标头中,哪些内容阻止浏览器从我的API中检索CSV。这是我在浏览器控制台中看到的内容:

enter image description here

2 个答案:

答案 0 :(得分:9)

解决方案:使用FileResult

如果您希望客户获得&#34; 保存文件&#34;对话框。

此处有多种选择,例如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<IActionResult>,则上述内容也适用。

     

关键是您返回 File 类型。

额外:内容处置

FileResult会自动为Content-Disposition提供正确的attachment标题。

如果要在浏览器中打开文件(&#34;内联&#34;),而不是提示&#34;保存文件&#34;对话框(&#34;附件&#34;)。然后,您可以通过更改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" - 目前,当您使用预先捆绑的选项(例如AddMvc())时,没有输入/输出格式化程序...所以,你必须加入,如下:

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

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

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

//using Microsoft.AspNetCore.Mvc.Formatters;

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()
    {
    }
}  

有关详细信息,我建议您阅读:

答案 1 :(得分:4)

强制下载文件而不是内联显示的正确方法是使用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