はじめに
C#でWeb APIを作ろうとすると、core無しのASP.NETとcore付きのASP.NETがあるみたいですが、core付きのほうが新しそう、という理由だけで、ASP.NET coreでWeb APIの作り方を書いていこうかと思います。ちなみにバージョンは、ASP.NET core 2.1になります。
今回は、とにかくWeb APIを作って動かすやり方の話になります。そのため、プログラムの解説は全くしません。Web APIの話は1回で収まらないため、プログラムの解説は次回以降で書こうと思います。
目次
- Hello World編 (ここ)
- IISデプロイ編
- ルーティング編 (まだ)
- リクエストパラメータ編 (まだ)
- レスポンス編 (まだ)
- 認証編 (まだ)
- 認可編 (まだ)
準備するもの
- Visual Studio 2017
- これが無いと、ASP.NET core のプロジェクトが作れない
- WinCurl
- この記事の下のほうに、入手&導入方法があります。
作るAPI
例として作るAPIは、定番のCRUD4パターンを実装をしていきます。
API | 説明 | リクエストボディ | レスポンスボディ | ステータスコード |
---|---|---|---|---|
GET /api/todo/ | すべてのToDoリストを取得する。 | (なし) | ToDoアイテムの配列 | 200 |
GET /api/todo/{id} | {id}で指定したToDoアイテムを取得する。 | (なし) | ToDoアイテム | 200 404({id}で指定したTodoアイテムが無い場合) |
POST /api/todo/ | ToDoを登録する | ToDoアイテム | Todoアイテム | 201 |
PUT /api/todo/{id} | {id}で指定したToDoアイテムを更新する。 | Todoアイテム | (なし) | 204 404({id}で指定したTodoアイテムが無い場合) |
DELETE /api/todo/{id} | {id}で指定したToDoアイテムを削除する。 | (なし) | (なし) | 204 404({id}で指定したTodoアイテムが無い場合) |
Visual Studioでプロジェクトを作成する
(1) メニューの「ファイル」>「新規作成」>「プロジェクト...」を選択します。
(2) 「Web」から「ASP.NET Core Web アプリケーション」を選択します。プロジェクト名は何でもいいですが、今回の例ではTodo
にしておきます。
(3) 続いて、プロジェクトのテンプレート選択画面から、上部にある「.NET Core」のバージョン「ASP.NET Core 2.1」を選択し、テンプレートは「空」を選択します。「Dockerサポートを有効にする」「HTTPS用の構成」はチェックを外して「OK」を押します。
ポート番号を変えておく(必須ではない)
ローカルでVisual Studioからデバック実行すると、デフォルトでランダムに設定されたポートで起動します。このままでもいいのですが、個人的に50000番台のポートを使っているのが気に喰わないので変えておきます。
(1) プロジェクト名(Todo)を右クリック>「プロパティ」でプロジェクトの設定画面を開き、「デバッグ」タブを選択すると、「アプリURL」があるので、ポート番号を変更します。番号は何番でもいいのですが、 http://localhost:9999 に変えておきます。
実装する
プロジェクトの作成と設定は完了したので、プログラムを作っていきます。
モデルを追加する
モデルには、Todoアイテムを表すTodoItem
クラスを作ります。
Modelsフォルダがないので、右クリック>「追加」>「新しいフォルダー」を選択し、Models
と入力してフォルダを作っておきます。
Models
フォルダを作ったら、そこを右クリック>「追加」>「クラス...」でTodoItem
クラスを作ります。
TodoItem
クラスの実装は、次のようにします。ただの構造体クラス(C#ではPOCOと呼ぶらしい)です。
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
フォルダを、右クリック>「追加」>「コントローラー...」を選択します。
コントローラーの種類を選択する画面では、「APIコントローラー - 空」を選択し、クラス名は TodoController
にします。
TodoController
クラスは次のように実装します。データは、本来はデータベースや他のWeb APIなどから取得しますが、今回はデーターベースを使わないので、コントローラークラスにハードコーディングしてしまいます。また、GETを受け取る2つのメソッド(/api/todo
と/api/todo/{id}
)も実装します。
...
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()
メソッドの中身を次のように書き換えます。
...
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();
}
}
}
試してみる1
これで、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」から、ダウンロードします。
ダウンロードしたファイルは、ただの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用のメソッドも追加していきましょう。
[Route("api/{controller}")]
[ApiController]
public class TotoController : ControllerBase
{
...
[HttpPost]
public IActionResult Create(TodoItem item)
{
// 新しいTodoItemのIdは、最大値+1にする
// 本当は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();
}
}
試してみる2
デバッグ実行のとき、ブラウザ起動しないようにする
curl
などツールを使う場合、いちいちブラウザが起動してうっとうしいので、起動しないように設定します。
プロジェクトを右クリックしてプロジェクトの設定を開き、「デバッグ」タブを選択すると、「ブラウザの起動」にチェックが入っているので、チェックをはずします。
試す
- 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/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: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 の枠組みの中の1つとなってしまい、いろいろ作るものが多いという印象です。これが複雑さを上げているように思います。
参考文献、サイト
-
Micorsoftのチュートリアルページ(日本語)
- 元ネタはここのチュートリアルです。