web-dev-qa-db-ja.com

.NET CoreコントローラーからCSVを返す

.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をダウンロードするように求められることです。

.NET CoreでCSVを「エクスポート」する方法に関する this の投稿を見つけました。問題は、ソース(AWS S3バケット)からCSVインメモリを取得しているのに対して、このコードはIEnumerable<object>

ブラウザーがAPIからCSVを取得するのを妨げる何かがある、リクエストまたはレスポンスヘッダーに問題があるのではないかと思っています。ブラウザコンソールに表示される内容は次のとおりです。

enter image description here

14
im1dermike

表示されるインラインの代わりにファイルを強制的にダウンロードする適切な方法は、 Content-Disposition応答ヘッダーを使用することです。 以下の解決策は機能します( ドキュメントを参照してください ).


古い答え

Content-Type応答ヘッダーをapplication/octet-streamに設定すると、ほとんどの主要なブラウザーは、ファイルをウィンドウに表示する代わりに保存するようユーザーに要求します。

次のようなことを試してください:

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

詳細については、この同様の質問に対する私の答えを参照してください

4
Neil

解決策:FileResultを使用します

これは、クライアントに「Save File」ダイアログボックスを取得させる場合に使用する必要があります。

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>を使用する場合も上記は機能します。

重要な点は、aFiletype。

追加:コンテンツの処理

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など、データが必要なタイプを要求できるためです。

これは主に、CSV、XLS、XML、JSONなど、クライアントがサーバーに渡すAcceptヘッダーで指定されたコンテンツを提供することで機能します。

"text/csv"のフォーマットタイプを使用したいが、このための事前に定義されたフォーマッターがないため、入力および出力フォーマッターコレクションに手動で追加する必要があります。

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

非常にシンプルなカスタムフォーマッター

これは、カスタムフォーマッターの非常に単純なバージョンです。これは、 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);
    }
}

特定の形式を強制する

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

詳細については、以下を読むことをお勧めします。

14
Svek