【C#】ASP.NET Core で CSV ファイルを返す Web API を作る

## 概要

`ASP.NET Core`で`Web API`を開発しクライアントに`CSV`ファイルを返すエンドポイントを作ってみます。
`TODO`アプリで`DB`から取得した`TODO`一覧を`CSV`ファイルとしてダウンロードできる機能を想定します。


## プロジェクト構築

`.NET Core CLI`でプロジェクトのひな型を構築します。

```bash
# .NET Core SDK バージョンを確認
$ dotnet --version
2.1.502

# Web API プロジェクトを生成
$ dotnet new web api -o WebApiCsv

$ cd WebApiCsv
# CsvHelperパッケージをインストール
$ dotnet add package CsvHelper
```

`csproj`ファイルは以下の通りです。

```xml:WebApiCsv.csproj
<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>netcoreapp2.1</TargetFramework>
    <!--ついでに言語バージョンを C#7.3 に変更-->
    <LangVersion>7.3</LangVersion>
  </PropertyGroup>

  <ItemGroup>
    <Folder Include="wwwroot\" />
  </ItemGroup>

  <ItemGroup>
    <PackageReference Include="CsvHelper" Version="12.1.2" />
    <PackageReference Include="Microsoft.AspNetCore.App" />
    <PackageReference Include="Microsoft.AspNetCore.Razor.Design" Version="2.1.2" PrivateAssets="All" />
  </ItemGroup>

</Project>
```

## 開発

`Todo`モデルを作成します。

```csharp:Models/Todo.cs
namespace WebApiCsv.Models {
  public class Todo {
    public int ID { get; set; }
    public string Title { get; set; }
    public bool IsDone { get; set; }
  }
}
```

データ取得を行うリポジトリを作成します。
今回は実際に`DB`にアクセスせず、ダミーデータを返します。

```csharp:Data/TodoRepository.cs
using System.Collections.Generic;
using WebApiCsv.Models;

namespace WebApiCsv.Data {
  public class TodoRepository {
    // ダミーデータ
    static List<Todo> todos { get; } = new List<Todo> {
      new Todo { ID = 1, Title = "CSV返却の記事", IsDone = false },
      new Todo { ID = 2, Title = "IoCの記事", IsDone = false },
    };

    public List<Todo> List() => this.todos;
  }
}
```

`Todo`一覧`CSV`データを取得するサービスクラスを作成します。

```csharp:Services/TodoService.cs
using CsvHelper;
using System.Collections.Generic;
using System.IO;
using WebApiCsv.Data;
using WebApiCsv.Models;

namespace WebApiCsv.Services {
  public class TodoService {
    // サンプルなので DI せず直にインスタンス化
    readonly TodoRepository repository = new TodoRepository();

    public byte[] GetCsvContents() {
      List<Todo> todos = repository.List();

      using (var memory = new MemoryStream())
      using (var writer = new StreamWriter(memory))
      using (var csv = new CsvWriter(writer)) {
        csv.WriteRecords(todos);
        writer.Flush(); //StreamWriter.Flush を呼ばないとバッファーからメモリに書き込まれないので注意
        return memory.ToArray();
      }
    }
  }
}
```

最後にリクエストを受け付ける`Controller`クラスを作成します。

```csharp:Controllers/TodosController.cs
using Microsoft.AspNetCore.Mvc;
using System;
using WebApiCsv.Services;

namespace WebApiCsv.Controllers {
  [Route("api/[controller]")]
  [ApiController]
  public class TodosController : ControllerBase {
    // サンプルなので DI せず直にインスタンス化
    readonly TodoService service = new TodoService();

    [HttpGet("export")]
    public FileContentResult Export() {
      byte[] csv = service.GetCsvContents();
      string fileName = $"ToDoList-{DateTime.Now:yyyyMMdd_HHmmss}.csv";
      return File(csv, "text/csv", fileName);
    }
  }
}
```

サーバーを立ち上げます。


```bash
$ dotnet run
Hosting environment: Development
Content root path: ~/WebApiCsv
Now listening on: https://localhost:5001
Now listening on: http://localhost:5000
Application started. Press Ctrl+C to shut down.
```

`https://localhost:{ポート番号}/api/todos/export`にアクセスすると`CSV`ファイルをダウンロードできます。

```:ToDoList-20190216_000315.csv
ID,Title,IsDone
1,CSV返却の記事,False
2,IoCの記事,False
```

### 項目をダブルクォートで囲む

```diff:Services/TodoServiceから抜粋
  public byte[] GetCsvContents() {
    List<Todo> todos = repository.List();

    using (var memory = new MemoryStream())
    using (var writer = new StreamWriter(memory))
    using (var csv = new CsvWriter(writer)) {
+     csv.Configuration.ShouldQuote = (field, context) => true;
      csv.WriteRecords(todos);
      writer.Flush();
      return memory.ToArray();
    }
  }
}
```

```ToDoList-20190222_010807.csv
"ID","Title","IsDone"
"1","CSV返却の記事","False"
"2","IoCの記事","False"
```

### ヘッダーのカラム名を変えたい



```diff:Models/Todo.cs
+ using CsvHelper.Configuration.Attributes;
  
  namespace WebApiCsv.Models {
    public class Todo {
      public int ID { get; set; }

+     [Name("タイトル")]
      public string Title { get; set; }

+     [Name("済")]
      public bool IsDone { get; set; }
    }
  }
```

```csv:ToDoList-20190222_011222.csv
"ID","タイトル","済"
"1","CSV返却の記事","False"
"2","IoCの記事","False"
```