ASP.NET Core と VS2017 を使用して Windows 上に Web API を構築する

本稿では、「タスク管理」リストを操作するための RESTful API の構築手順を解説します。UI は作成しません。

概要

本チュートリアルで作成する API の仕様は次のとおりです。

エンドポイント 機能説明 リクエストボディ レスポンスボディ
GET /api/tasks 全タスクの取得 なし タスクオブジェクトの配列
GET /api/tasks/{id} 指定IDのタスク取得 なし タスクオブジェクト
POST /api/tasks 新規タスク登録 タスクオブジェクト 登録されたタスクオブジェクト
PUT /api/tasks/{id} 既存タスク更新 タスクオブジェクト なし
DELETE /api/tasks/{id} タスク削除 なし なし

アーキテクチャ構成は以下の通りです。

  • フロントエンド層は Web API を介してアクセスします(モバイルアプリ、ブラウザなど)。本稿ではクライアント開発はせず、Postman または curl を使用して動作確認を行います。
  • モデルはアプリケーション内のデータを表現するオブジェクトです。本例ではタスクオブジェクトのみを定義します。
  • コントローラーは HTTP リクエストを処理し、HTTP レスポンスを生成するコンポーネントです。本アプリケーションでは単一のコントローラーを実装します。
  • チュートリアルの簡略化のため、永続化データベースは使用せず、メモリ上のデータストアで実装します。

動作環境

事前に以下のソフトウェアをインストールしてください。

  • .NET Core 2.0.0 SDK 以降
  • Visual Studio 2017 15.3 以降(ASP.NET および Web 開発ワークロードインストール済み)

プロジェクトの生成

Visual Studio で「ファイル」メニューから「新規作成」→「プロジェクト」を選択します。

「ASP.NET Core Web アプリケーション (.NET Core)」テンプレートを選択し、プロジェクト名を「TaskApi」として「OK」をクリックします。

「新規 ASP.NET Core Web アプリケーション - TaskApi」ダイアログで「Web API」テンプレートを選択し、「OK」をクリックします。Docker サポートは有効にしません。

アプリケーションの起動

Visual Studio で Ctrl+F5 を押下してデバッグなしで起動します。ブラウザーが自動的に起動し http://localhost:port/api/values にアクセスします(ポート番号は自動的に割り当てられます)。Chrome、Edge、Firefox では以下が表示されます。

["value1","value2"]

モデルの定義

モデルクラスはアプリケーションのデータを表現するものです。本例ではタスクオブジェクトのみを定義します。

「Models」フォルダーを作成します。ソリューションエクスプローラーでプロジェクトを右クリックし、「追加」→「新しいフォルダー」を選択、フォルダ名を「Models」とします。

モデルクラスはプロジェクト内の任意の位置に配置可能ですが、慣例として「Models」フォルダーに格納します。

TaskItem クラスを追加します。「Models」フォルダーを右クリックし、「追加」→「クラス」を選択、クラス名を「TaskItem」として「追加」をクリックします。

生成されたコードを以下のように置換します。

C#

namespace TaskApi.Models
{
    public class TaskItem
    {
        public long Id { get; set; }
        public string Title { get; set; }
        public bool IsDone { get; set; }
    }
}

TaskItem インスタンス生成時、データストアにより一意の Id が自動採番されます。

DbContext の実装

DbContext は Entity Framework Core の機能を取りまとめるメインクラスです。Microsoft.EntityFrameworkCore.DbContext を継承して実装します。

TaskContext クラスを追加します。「Models」フォルダーを右クリックし、「追加」→「クラス」を選択、クラス名を「TaskContext」として「追加」をクリックします。

生成されたコードを以下のように置換します。

C#

using Microsoft.EntityFrameworkCore;

namespace TaskApi.Models
{
    public class TaskContext : DbContext
    {
        public TaskContext(DbContextOptions<TaskContext> options)
            : base(options)
        {
        }

        public DbSet<TaskItem> Tasks { get; set; }
    }
}

DbContext のサービス登録

DbContext を DI コンテナに登録し、コントローラーから利用可能な状態にします。依存性注入の機能を用いて DbContext をサービスコンテナに登録します。Startup.cs ファイルの中身を以下で置換します。

C#

using Microsoft.AspNetCore.Builder;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using TaskApi.Models;

namespace TaskApi
{
    public class Startup
    {       
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<TaskContext>(opt => opt.UseInMemoryDatabase("TaskDatabase"));
            services.AddMvc();
        }

        public void Configure(IApplicationBuilder app)
        {
            app.UseMvc();
        }
    }
}

上記コードでは未使用のインポートを削除し、メモリデータベースをサービスコンテナに登録しています。

コントローラーの実装

ソリューションエクスプローラーで「Controllers」フォルダーを右クリックし、「追加」→「新しい項目の追加」を選択します。「Web API コントローラー クラス」テンプレートを選択し、クラス名を「TaskController」として「追加」をクリックします。

生成されたコードを以下のように置換します。

C#

using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using TaskApi.Models;
using System.Linq;

namespace TaskApi.Controllers
{
    [Route("api/[controller]")]
    public class TaskController : Controller
    {
        private readonly TaskContext _dbContext;

        public TaskController(TaskContext context)
        {
            _dbContext = context;

            if (!_dbContext.Tasks.Any())
            {
                _dbContext.Tasks.Add(new TaskItem { Title = "最初のタスク" });
                _dbContext.SaveChanges();
            }
        }       
    }
}

上記コードでは空のコントローラークラスを定義しています。以降のセクションで API メソッドを順次追加します。コンストラクタでは依存性注入により DbContext (TaskContext) を受け取り、各 CRUD メソッドで利用可能にします。初期データとして存在しない場合は一件目のタスクを追加します。

全タスクの取得

TaskController クラスに以下のメソッドを追加し、全タスク取得機能を実装します。

C#

[HttpGet]
public IEnumerable<TaskItem> GetAll()
{
    return _dbContext.Tasks.ToList();
}

[HttpGet("{id}", Name = "GetTask")]
public IActionResult GetById(long id)
{
    var task = _dbContext.Tasks.FirstOrDefault(t => t.Id == id);
    if (task == null)
    {
        return NotFound();
    }
    return new ObjectResult(task);
}

これらのメソッドは以下の GET エンドポイントを実現します。

  • GET /api/tasks
  • GET /api/tasks/{id}

GetAll メソッドの HTTP レスポンス例は以下の通りです。

HTTP/1.1 200 OK
   Content-Type: application/json; charset=utf-8
   Server: Microsoft-IIS/10.0
   Date: Thu, 18 Jun 2015 20:51:10 GMT
   Content-Length: 82

   [{"Id":1, "Title":"最初のタスク","IsDone":false}]

本チュートリアルの後半で Postman または curl を使用して HTTP レスポンスを確認する手順を解説します。

ルーティングと URL パス

[HttpGet] 属性は HTTP GET メソッドを示します。各メソッドの URL パスは以下の規則で構築されます。

コントローラーのルーティング属性でテンプレート文字列を指定します。

C#

namespace TaskApi.Controllers
{
    [Route("api/[controller]")]
    public class TaskController : Controller
    {
        private readonly TaskContext _dbContext;

[controller] はコントローラークラス名から「Controller」サフィックスを除いた名前に置換されます。本例では「Task」となり、URL パスは /api/task となります。ASP.NET Core のルーティングは大文字小文字を区別しません。

[HttpGet] 属性に追加のルートテンプレート(例: [HttpGet("/products")])が指定されている場合、URL パスに連結されます。本例ではテンプレートは指定していません。詳細については属性ルーティングのドキュメントを参照してください。

GetById メソッドでは以下のように定義しています。

C#

[HttpGet("{id}", Name = "GetTask")]
public IActionResult GetById(long id)

"{id}" はタスク ID のプレースホルダー変数です。GetById メソッド呼び出し時、URL の {id} 部分の値がパラメータ id に割り当てられます。

Name = "GetTask" は名前付きルートを定義しており、HTTP レスポンス内からこのルートへのリンク生成が可能になります。後述の例で使用します。

戻り値の型

GetAll メソッドは IEnumerable<TaskItem> を返します。MVC はオブジェクトを自動的に JSON にシリアライズし、レスポンスボディに書き込みます。例外が発生しない場合のステータスコードは 200 です。

GetById メソッドは IActionResult を返し、複数の戻り値型に対応します。

  • リクエストされた ID に一致するタスクが存在しない場合、404 エラーを返します(NotFound())。
  • タスクが存在する場合、JSON レスポンスボディを含む 200 を返します(ObjectResult(task))。

アプリケーションの実行

Visual Studio で Ctrl+F5 を押下して起動します。ブラウザーが自動的に開き http://localhost:port/api/values にアクセスします。Chrome、Edge、Firefox ではデータが表示されます。IE ではファイルを開くか保存するダイアログが表示されます。作成した Task コントローラーにアクセスするには http://localhost:port/api/tasks に移動してください。

タスク登録機能の実装

以下のメソッドをコントローラーに追加し、タスク登録機能を実装します。

C#

[HttpPost]
public IActionResult Register([FromBody] TaskItem newTask)
{
    if (newTask == null)
    {
        return BadRequest();
    }

    _dbContext.Tasks.Add(newTask);
    _dbContext.SaveChanges();

    return CreatedAtRoute("GetTask", new { id = newTask.Id }, newTask);
}

このメソッドは [HttpPost] 属性で示される HTTP POST メソッドです。[FromBody] 属性により HTTP リクエストボディからタスクオブジェクトを取得します。

CreatedAtRoute メソッドは 201 Created レスポンスを返します。これはサーバーで新規リソースが作成された際の標準的な HTTP ステータスコードです。また、Location ヘッダーにも新規リソースの URI が設定されます。

Postman での登録リクエスト確認

Postman を使用して登録リクエストを送信する手順は以下の通りです。

  • HTTP メソッドを POST に設定
  • 「Body」タブを選択
  • 「raw」オプションを選択
  • タイプを JSON に設定
  • エディタでタスクオブジェクトを入力

JSON

{
    "title":"犬を散歩させる",
    "isDone":true
}

「Send」をクリックし、下部の「Headers」タブから「Location」ヘッダーの値をコピーします。

この Location ヘッダーの URI を使用すると、作成したリソースにアクセスできます。名前付きルート "GetTask"GetById メソッドに対応しています。

C#

[HttpGet("{id}", Name = "GetTask")]
public IActionResult GetById(long id)

タスク更新機能の実装

以下のメソッドを追加し、タスク更新機能を実装します。

C#

[HttpPut("{id}")]
public IActionResult Modify(long id, [FromBody] TaskItem taskData)
{
    if (taskData == null || taskData.Id != id)
    {
        return BadRequest();
    }

    var existingTask = _dbContext.Tasks.FirstOrDefault(t => t.Id == id);
    if (existingTask == null)
    {
        return NotFound();
    }

    existingTask.IsDone = taskData.IsDone;
    existingTask.Title = taskData.Title;

    _dbContext.Tasks.Update(existingTask);
    _dbContext.SaveChanges();
    return new NoContentResult();
}

Modify メソッドは Register と類似していますが、HTTP PUT を使用します。レスポンスは 204 No Content です。HTTP 仕様では、PUT リクエストではクライアントが更新済みエンティティ全体を送信する必要があります。部分更新をサポートする場合は HTTP PATCH を使用します。

タスク削除機能の実装

以下のメソッドを追加し、タスク削除機能を実装します。

C#

[HttpDelete("{id}")]
public IActionResult Remove(long id)
{
    var task = _dbContext.Tasks.FirstOrDefault(t => t.Id == id);
    if (task == null)
    {
        return NotFound();
    }

    _dbContext.Tasks.Remove(task);
    _dbContext.SaveChanges();
    return new NoContentResult();
}

削除成功時は 204 No Content レスポンスを返します。

タグ: asp.net-core web-api REST entity-framework-core visual-studio-2017

6月16日 23:36 投稿