C#(ASP.NET core)でWeb APIを作ってみる（Hello World編）

# はじめに

C#でWeb APIを作ろうとすると、core無しのASP.NETとcore付きのASP.NETがあるみたいですが、core付きのほうが新しそう、という理由だけで、ASP.NET coreでWeb APIの作り方を書いていこうかと思います。ちなみにバージョンは、ASP.NET core 2.1になります。

今回は、とにかくWeb APIを作って動かすやり方の話になります。そのため、プログラムの解説は全くしません。Web APIの話は１回で収まらないため、プログラムの解説は次回以降で書こうと思います。

# 目次

- Hello World編　（ここ）
- [IISデプロイ編](https://qiita.com/rawr/items/f367edaa8061a5988085)　
- ルーティング編　（まだ）
- リクエストパラメータ編　（まだ）
- レスポンス編　（まだ）
- 認証編　（まだ）
- 認可編　（まだ）

# 準備するもの

- Visual Studio 2017
    - これが無いと、ASP.NET core のプロジェクトが作れない
- WinCurl
    - この記事の下のほうに、入手＆導入方法があります。

# 作るAPI

例として作るAPIは、定番のCRUD４パターンを実装をしていきます。

| API | 説明 | リクエストボディ | レスポンスボディ | ステータスコード |
|-----|-----|-----|-----|-----|
| GET /api/todo/ | すべてのToDoリストを取得する。 | （なし）| ToDoアイテムの配列 | 200 |
| GET /api/todo/{id} | {id}で指定したToDoアイテムを取得する。 | （なし）| ToDoアイテム | 200<br>404({id}で指定したTodoアイテムが無い場合) |
| POST /api/todo/ | ToDoを登録する | ToDoアイテム | Todoアイテム | 201 |
| PUT /api/todo/{id} | {id}で指定したToDoアイテムを更新する。 | Todoアイテム | （なし） | 204<br>404({id}で指定したTodoアイテムが無い場合) |
| DELETE /api/todo/{id} | {id}で指定したToDoアイテムを削除する。 | （なし）| （なし） | 204<br>404({id}で指定したTodoアイテムが無い場合) |

# Visual Studioでプロジェクトを作成する

(1) メニューの「ファイル」＞「新規作成」＞「プロジェクト...」を選択します。
![2018-11-14_221330.png](https://qiita-image-store.s3.amazonaws.com/0/208281/31ec4f6b-6e21-83ae-ed4d-6c1d32c2d038.png)

(2) 「Web」から「ASP.NET Core Web アプリケーション」を選択します。プロジェクト名は何でもいいですが、今回の例では`Todo`にしておきます。
![2018-11-14_221432.png](https://qiita-image-store.s3.amazonaws.com/0/208281/87d8b4d7-7050-2371-7612-f1c3eb479630.png)

(3) 続いて、プロジェクトのテンプレート選択画面から、上部にある「.NET Core」のバージョン「ASP.NET Core 2.1」を選択し、テンプレートは「空」を選択します。「Dockerサポートを有効にする」「HTTPS用の構成」はチェックを外して「OK」を押します。
![2018-11-14_221450.png](https://qiita-image-store.s3.amazonaws.com/0/208281/d22b6ffc-80eb-d1b2-d315-fe6df46d69e9.png)

## ポート番号を変えておく（必須ではない）

ローカルでVisual Studioからデバック実行すると、デフォルトでランダムに設定されたポートで起動します。このままでもいいのですが、個人的に50000番台のポートを使っているのが気に喰わないので変えておきます。

(1) プロジェクト名（Todo）を右クリック＞「プロパティ」でプロジェクトの設定画面を開き、「デバッグ」タブを選択すると、「アプリURL」があるので、ポート番号を変更します。番号は何番でもいいのですが、 http://localhost:9999 に変えておきます。
![2018-11-14.png](https://qiita-image-store.s3.amazonaws.com/0/208281/9db5fa91-48b2-551d-2662-de8b678f4353.png)

# 実装する

プロジェクトの作成と設定は完了したので、プログラムを作っていきます。

## モデルを追加する

モデルには、Todoアイテムを表す`TodoItem`クラスを作ります。

Modelsフォルダがないので、右クリック＞「追加」＞「新しいフォルダー」を選択し、`Models`と入力してフォルダを作っておきます。
![2018-11-13_225834.png](https://qiita-image-store.s3.amazonaws.com/0/208281/646db86c-0f60-a050-b6e4-846de91a92be.png)

`Models`フォルダを作ったら、そこを右クリック＞「追加」＞「クラス...」で`TodoItem`クラスを作ります。
![2018-11-13_225948.png](https://qiita-image-store.s3.amazonaws.com/0/208281/c051b790-261b-9186-39aa-bff592f8d3dd.png)

`TodoItem`クラスの実装は、次のようにします。ただの構造体クラス（C#ではPOCOと呼ぶらしい）です。

```csharp:TodoItem.cs
namespace Todo.Models
{
    public class TodoItem
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public bool IsDone { get; set; }
    }
}
```

## コントローラーを追加し、メソッドを実装する（GETだけ）

続いてルーティングの定義と、APIの処理をするクラスを作ります。

プロジェクトを「空」で作ると `Controllers` フォルダもないので、Modelsのときと同様に、右クリック＞「追加」＞「新しいフォルダ」でフォルダを作っておきます。

フォルダを作ったら、`Controllers`フォルダを、右クリック＞「追加」＞「コントローラー...」を選択します。
![2018-11-13_230317.png](https://qiita-image-store.s3.amazonaws.com/0/208281/93fa698f-aba3-7c69-c095-6e2eb51fe6c5.png)

コントローラーの種類を選択する画面では、「APIコントローラー - 空」を選択し、クラス名は `TodoController` にします。
![2018-11-13_230451.png](https://qiita-image-store.s3.amazonaws.com/0/208281/a9068ef7-94b6-d022-86c4-c44ca6f1de77.png)
![2018-11-13_230506.png](https://qiita-image-store.s3.amazonaws.com/0/208281/3a7144d4-f705-9594-7ff4-fcaf22202e65.png)

`TodoController` クラスは次のように実装します。データは、本来はデータベースや他のWeb APIなどから取得しますが、今回はデーターベースを使わないので、コントローラークラスにハードコーディングしてしまいます。また、GETを受け取る２つのメソッド(`/api/todo`と`/api/todo/{id}`)も実装します。

```csharp:TodoController.cs
...
using Todo.Models;

namespace Todo.COntrollers
{
    [Route("api/{controller}")]
    [ApiController]
    public class TotoController : ControllerBase
    {
        // Todoアイテムの初期データ。本来はデータベースなどから取得する。
        private static List<TodoItem> items = new List<TodoItem>() {
            new TodoItem() { Id = 1, Name = @"犬の散歩", IsDone = false, },
            new TodoItem() { Id = 2, Name = @"買い物", IsDone = true, },
            new TodoItem() { Id = 3, Name = @"本棚の修理", IsDone = false },
        };
     
        [HttpGet]
        public ActionResult<List<TodoItem>> GetAll()
            => items;

        [HttpGet("{id}", Name = "Todo")]
        public ActionResult<TodoItem> GetById(int id)
        {
            var item = items.Find(i => i.Id == id);
            if (item == null)
                return NotFound();
            return item;
        }
    }
}
```

## Startup.csを変更する

空のプロジェクトの場合、`StartUp.cs`に必要な起動処理が書かれていないので、これも実装します。`ConfigureServices()`と`Configure()`メソッドの中身を次のように**書き換え**ます。

```csharp:Startup.cs
...
using Microsoft.AspNetCore.Mvc;

namespace Todo
{
    public class StartUp
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc()
                    .SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
             
            app.UseMvc();
        }
    }
}
```

# 試してみる１

これで、GETメソッドだけですが実装完了です。早速試してみましょう。

## Web APIテスト用クライアントを導入する

GETメソッドならブラウザからの確認でもいいのですが、今後POSTやPUTなども試したいので、何か確認用ツールを導入します

RESTful APIをテストするときの定番ツールは `curl` であり、Windowsにも `curl` はあるのですが（WinCurl）、コマンドプロンプトからではどうがんばっても日本語をUTF-8で入力できない（表示はできる）ので、GUIのツールに頼るしかなさそうです。おそらくブラウザのプラグイン、Chrome の postman か Advanced Rest Client あたりになるかと思います。この記事では、`curl` の導入方法と、確認 `curl` のコマンドで書きますが、postman とか使う人は適当に読み替えてください。

### WinCurlをインストールする

いちおう、WinCurlの導入方法を書いておきます。

https://curl.haxx.se/download.html へ行き、下のほうにある「Windows 64 bit」から、ダウンロードします。
![2018-11-14.png](https://qiita-image-store.s3.amazonaws.com/0/208281/87896368-1266-888d-587e-13c65561ecae.png)

ダウンロードしたファイルは、ただのexe（インストーラーではない）なので、C:\Program Filesなど、適当なフォルダに解凍したフォルダごと置き、環境変数PATHを通しておきます（マイコンピューターを、右クリック＞「プロパティ」で、「環境設定」＞「詳細設定」タブ＞「環境変数」の`PATH`に追加する）。

## 試す

Visual Studio上でF5を押して起動し、コマンドプロンプトを起動して`curl`を実行します。F5を押すと、ブラウザが自動的に起動しますが、今は無視してください（じゃまなので、後で起動しないように設定を変更する）。**オプションに `-D -` を付けて、レスポンスヘッダーも出す**ようにしておきます。APIの確認をするとき、レスポンスボディしか見ない人がいますが、ちゃんとレスポンスヘッダーも確認するようにしましょう。

```
> curl -D - -X GET "http://localhost:9999/api/todo"
HTTP/1.1 200 OK
Transfer-Encoding: chunked
Content-Type: application/json; charset=utf-8
Server: Kestrel
X-SourceFiles: =?UTF-8?B?QzpcQXBwbGljYXRpb25cdnNwcm9qZWN0c1xUb2RvXFRvZG9cYXBpXHRvZG8=?=
X-Powered-By: ASP.NET
Date: Wed, 14 Nov 2018 13:36:23 GMT

[{"id":1,"name":"犬の散歩","isDone":false},{"id":2,"name":"買い物","isDone":true},{"id":3,"name":"本棚の修理","isDone":false}]

> curl -D - -X GET "http://localhost:9999/api/todo/1"
HTTP/1.1 200 OK
Transfer-Encoding: chunked
Content-Type: application/json; charset=utf-8
Server: Kestrel
X-SourceFiles: =?UTF-8?B?QzpcQXBwbGljYXRpb25cdnNwcm9qZWN0c1xUb2RvXFRvZG9cYXBpXHRvZG9cMQ==?=
X-Powered-By: ASP.NET
Date: Wed, 14 Nov 2018 13:40:05 GMT

{"id":1,"name":"犬の散歩","isDone":false}

> curl -D - -X GET "http://localhost:9999/api/products/0"
HTTP/1.1 404 Not Found
Server: Kestrel
X-SourceFiles: =?UTF-8?B?QzpcQXBwbGljYXRpb25cdnNwcm9qZWN0c1xUb2RvXFRvZG9cYXBpXHRvZG9cMA==?=
X-Powered-By: ASP.NET
Date: Wed, 14 Nov 2018 13:40:28 GMT
Content-Length: 0
```

確認ポイント：

- ステータスコード
    - ときどき、なんでも200を返すAPIがいる。
- X-xxxヘッダー
    - サーバー情報がはいっているので、なるべくIISの設定を何とかして出さないようにしたほうがいい。今回はVisual Studio上でデバッグで動かしているので、関係ないですが。
- Location
    - Createのとき、生成したリソースのURLが入る。この値がむちゃくちゃだったり、無かったりするときがある。
- Content-Type
    - `application/json`になっているか。
- Content-Length
    - ときどき、むちゃくちゃな値が入っているときがある。また、このヘッダーの代わりに`Transfer-Encoding: chunk`になっている場合がある（これ自体は問題ない）。`Transfer-Encoding`が受け取れない、残念なクライアントがたまにいる。

# 他のメソッドも実装する（POST, PUT, DELETE）

`TodoController`クラスに、POST, PUT, DELETE用のメソッドも追加していきましょう。

```csharp:TodoController.cs
    [Route("api/{controller}")]
    [ApiController]
    public class TotoController : ControllerBase
    {
        ...

        [HttpPost]
        public IActionResult Create(TodoItem item)
        {
            // 新しいTodoItemのIdは、最大値＋１にする
            // 本当はSQLでやる
            item.Id = items.Max(i => i.Id) + 1;
            items.Add(item);
            
            return CreatedAtRoute("Todo", new { id = item.Id }, item);
        }

        [HttpPut("{id}")]
        public IActionResult Update(int id, TodoItem item)
        {
            var target = items.Find(i => i.Id == id);
            if (target == null)
                return NotFound();

            target.Name = item.Name;
            target.IsDone = item.IsDone;

            return NoContent();
        }

        [HttpDelete("{id}")]
        public IActionResult Delete(int id)
        {
            var n = items.RemoveAll(i => i.Id == id);
            if (n == 0)
                return NotFound();
            return NoContent();
        }
    }
```

# 試してみる２

## デバッグ実行のとき、ブラウザ起動しないようにする

`curl` などツールを使う場合、いちいちブラウザが起動してうっとうしいので、起動しないように設定します。

プロジェクトを右クリックしてプロジェクトの設定を開き、「デバッグ」タブを選択すると、「ブラウザの起動」にチェックが入っているので、チェックをはずします。
![2018-11-17_221234.png](https://qiita-image-store.s3.amazonaws.com/0/208281/7c2716cd-c046-c41c-aabc-37e717e3cc85.png)


## 試す

- POST

ボディがある場合は、`Content-Type`ヘッダーを付けないと、エラーになります。
※注　コマンドプロンプトから日本語を入力する場合、エンコードがShift_JISになり、エラーになります。

```
> curl -D - -X POST -H "Content-Type: application/json" -d "{\"name\":\"旅行\", \"isDone\":false}" "http://localhost:9999/api/todo"
HTTP/1.1 201 Created
Transfer-Encoding: chunked
Content-Type: application/json; charset=utf-8
Location: http://localhost:9999/api/Todo/4
Server: Kestrel
X-SourceFiles: =?UTF-8?B?QzpcQXBwbGljYXRpb25cdnNwcm9qZWN0c1xUb2RvXFRvZG9cYXBpXHRvZG8=?=
X-Powered-By: ASP.NET
Date: Sat, 17 Nov 2018 13:01:10 GMT

{"id":4,"name":"旅行","isDone":false}

> curl -D - -X GET "http://localhost:9999/api/todo"
HTTP/1.1 200 OK
Transfer-Encoding: chunked
Content-Type: application/json; charset=utf-8
Server: Kestrel
X-SourceFiles: =?UTF-8?B?QzpcQXBwbGljYXRpb25cdnNwcm9qZWN0c1xUb2RvXFRvZG9cYXBpXFRvZG9cNA==?=
X-Powered-By: ASP.NET
Date: Sat, 17 Nov 2018 13:02:35 GMT

{"id":4,"name":"旅行","isDone":false}
```

ステータスコードが201で、`Location` ヘッダーがあることを確認しましょう。

- PUT

※注　コマンドプロンプトから日本語を入力する場合、エンコードがShift_JISになり、エラーになります。

```
> curl -D - -X PUT -H "Content-Type: application/json" -d "{\"name\":\"犬の散歩\", \"isDone\":true}" "http://localhost:9999/api/todo/1"
HTTP/1.1 204 No Content
Server: Kestrel
X-SourceFiles: =?UTF-8?B?QzpcQXBwbGljYXRpb25cdnNwcm9qZWN0c1xUb2RvXFRvZG9cYXBpXHRvZG9cMQ==?=
X-Powered-By: ASP.NET
Date: Sat, 17 Nov 2018 13:09:47 GMT

> curl -D - -X PUT -H "Content-Type: application/json" -d "{\"name\":\"ほげほげ\", \"isDone\":true}" "http://localhost:9999/api/todo/0"
HTTP/1.1 404 Not Found
Server: Kestrel
X-SourceFiles: =?UTF-8?B?QzpcQXBwbGljYXRpb25cdnNwcm9qZWN0c1xUb2RvXFRvZG9cYXBpXHRvZG9cMA==?=
X-Powered-By: ASP.NET
Date: Sat, 17 Nov 2018 13:10:19 GMT
Content-Length: 0
```

- DELETE

```
> curl -D - -X DELETE "http://localhost:9999/api/todo/１"
HTTP/1.1 204 No Content
Server: Kestrel
X-SourceFiles: =?UTF-8?B?QzpcQXBwbGljYXRpb25cdnNwcm9qZWN0c1xUb2RvXFRvZG9cYXBpXHRvZG9cMQ==?=
X-Powered-By: ASP.NET
Date: Sat, 17 Nov 2018 13:11:13 GMT

> curl -D - -X DELETE "http://localhost:9999/api/todo/0"
HTTP/1.1 404 Not Found
Server: Kestrel
X-SourceFiles: =?UTF-8?B?QzpcQXBwbGljYXRpb25cdnNwcm9qZWN0c1xUb2RvXFRvZG9cYXBpXHRvZG9cMA==?=
X-Powered-By: ASP.NET
Date: Sat, 17 Nov 2018 13:11:33 GMT
Content-Length: 0
```

# まとめ

今回は、動かすことに集中しプログラムの解説を全くしなかったため、プログラムに関しては意味不明だったと思います（特にルーティングとレスポンスの生成）。プログラムの解説は次回以降で行っていく予定です。

Web API は core になって、MVC の枠組みの中の１つとなってしまい、いろいろ作るものが多いという印象です。これが複雑さを上げているように思います。

# 参考文献、サイト

- [Micorsoftのチュートリアルページ（日本語）](https://docs.microsoft.com/ja-jp/aspnet/core/tutorials/first-web-api?view=aspnetcore-2.1)
    - 元ネタはここのチュートリアルです。