チュートリアル: ASP.NET Core で Web API を作成する

作成者: Rick AndersonKirk LarkinMike Wasson

このチュートリアルでは、ASP.NET Core で Web API をビルドする方法の基本について説明します。

このチュートリアルでは、次の作業を行う方法について説明します。

  • Web API プロジェクトを作成する。
  • モデル クラスとデータベース コンテキストを追加する。
  • CRUD メソッドを使用してコントローラーのスキャフォールディング。
  • ルーティング、URL パス、戻り値を構成する。
  • Postman で Web API を呼び出す。

最後に、データベースに格納されている "To Do" アイテムを管理できる Web API が作成されます。

概要

このチュートリアルでは、次の API を作成します。

API 説明 要求本文 応答本文
GET /api/todoitems すべての To Do アイテムを取得します。 None To Do アイテムの配列
GET /api/todoitems/{id} ID でアイテムを取得します。 None To Do アイテム
POST /api/todoitems 新しいアイテムを追加します。 To Do アイテム To Do アイテム
PUT /api/todoitems/{id} 既存のアイテムを更新します。  To Do アイテム None
DELETE /api/todoitems/{id}     アイテムを削除します     None None

次の図は、アプリのデザインを示しています。

クライアントは、左側のボックスに表されます。 要求を送信し、アプリケーション (右側に描画されたボックス) から応答を受信します。 アプリケーション ボックス内の 3 つのボックスは、コントローラー、モデル、およびデータ アクセス レイヤーを表しています。 要求はアプリケーションのコントローラーに送られ、コントローラーとデータ アクセス レイヤー間で読み取り/書き込み操作が行われます。 モデルはシリアル化され、応答でクライアントに返されます。

必須コンポーネント

Web プロジェクトの作成

  • [ファイル] メニューで、 [新規作成] > [プロジェクト] の順に選択します。
  • [ASP.NET Core Web API] テンプレートを選択し、 [次へ] をクリックします。
  • プロジェクトに「TodoApi」という名前を付け、 [作成] をクリックします。
  • [新しい ASP.NET Core Web アプリケーションを作成する] ダイアログで、 [.NET Core][ASP.NET Core 5.0] が選択されていることを確認します。 API テンプレートを選択し、 [作成] をクリックします。

VS の [新しいプロジェクト] ダイアログ

プロジェクトをテストする

プロジェクト テンプレートにより、Swagger をサポートする WeatherForecast API が作成されます。

Ctrl + F5 キーを押して、デバッガーなしで実行します。

SSL を使用するようにプロジェクトがまだ構成されていない場合、Visual Studio に次のダイアログが表示されます。

このプロジェクトは SSL を使用するように構成されています。 ブラウザーに SSL 警告を表示しないようにするために、IIS Express によって生成された自己署名証明書を信頼することを選択できます。 IIS Express SSL 証明書を信頼しますか。

IIS Express SSL 証明書を信頼する場合、[はい] を選択します。

次のダイアログが表示されます。

セキュリティ警告のダイアログ

開発証明書を信頼することに同意する場合は、 [はい] を選択します。

Firefox ブラウザーを信頼する方法の詳細については、「Firefox SEC_ERROR_INADEQUATE_KEY_USAGE 証明書エラー」を参照してください。

Visual Studio により、以下が起動されます。

  • IIS Express Web サーバー。
  • 既定のブラウザーです。https://localhost:<port>/swagger/index.html に移動します。<port> はランダムに選択されるポート番号です。

Swagger ページ /swagger/index.html が表示されます。 [取得] > [試してみる] > [実行] を選択します。 ページに以下が表示されます。

  • WeatherForecast API をテストするための Curl コマンド。
  • WeatherForecast API をテストする URL。
  • 応答コード、本文、およびヘッダー。
  • メディアの種類と、値とスキーマの例を含むドロップ ダウン リスト ボックス。

Swagger ページが表示されない場合は、こちらの GitHub イシューを参照してください。

Swagger は、Web API の有用なドキュメントやヘルプ ページを生成するために使用されます。 このチュートリアルでは、Web API の作成について説明します。 Swagger の詳細については、Swagger/OpenAPI を使用する ASP.NET Core Web API のドキュメント を参照してください。

ブラウザーで 要求 URL をコピーして貼り付けます: https://localhost:<port>/WeatherForecast

次のような JSON が返されます。

[
    {
        "date": "2019-07-16T19:04:05.7257911-06:00",
        "temperatureC": 52,
        "temperatureF": 125,
        "summary": "Mild"
    },
    {
        "date": "2019-07-17T19:04:05.7258461-06:00",
        "temperatureC": 36,
        "temperatureF": 96,
        "summary": "Warm"
    },
    {
        "date": "2019-07-18T19:04:05.7258467-06:00",
        "temperatureC": 39,
        "temperatureF": 102,
        "summary": "Cool"
    },
    {
        "date": "2019-07-19T19:04:05.7258471-06:00",
        "temperatureC": 10,
        "temperatureF": 49,
        "summary": "Bracing"
    },
    {
        "date": "2019-07-20T19:04:05.7258474-06:00",
        "temperatureC": -1,
        "temperatureF": 31,
        "summary": "Chilly"
    }
]

launchUrl を更新する

Properties\launchSettings.json で、launchUrl"swagger" から "api/todoitems" に更新します。

"launchUrl": "api/todoitems",

Swagger が削除されるため、上記のマークアップにより、開始される URL が、次のセクションで追加されるコントローラーの GET メソッドに変更されます。

モデル クラスの追加

モデル は、アプリが管理するデータを表すクラスのセットです。 このアプリのモデルは、単一の TodoItem クラスです。

  • ソリューション エクスプローラー で、プロジェクトを右クリックします。 [追加] > [新しいフォルダー] の順に選択します。 フォルダーに「 Models 」という名前を付けます。

  • Models フォルダーを右クリックして、 [追加] > [クラス] の順に選択します。 クラスに「TodoItem」という名前を付け、 [追加] を選択します。

  • テンプレート コードを次のコードに置き換えます。

namespace TodoApi.Models
{
    public class TodoItem
    {
        public long Id { get; set; }
        public string Name { get; set; }
        public bool IsComplete { get; set; }
    }
}

Id プロパティは、リレーショナル データベース内の一意のキーとして機能します。

モデル クラスはプロジェクト内のどこでも使用できますが、慣例により Models フォルダーが使用されます。

データベース コンテキストの追加

データベース コンテキスト は、データ モデルに対して Entity Framework 機能を調整するメイン クラスです。 このクラスは Microsoft.EntityFrameworkCore.DbContext クラスから派生させて作成します。

NuGet パッケージを追加する

  • [ツール] メニューで [NuGet パッケージ マネージャー]、[ソリューションの NuGet パッケージの管理] の順に選択します。
  • [参照] タブを選択し、検索ボックスに「Microsoft.EntityFrameworkCore.InMemory」と入力します。
  • 左側のウィンドウで Microsoft.EntityFrameworkCore.InMemory を選択します。
  • 右側のウィンドウで [プロジェクト] チェックボックスをオンにして、 [インストール] を選択します。

NuGet パッケージ マネージャー

TodoContext データベースコンテキストの追加

  • Models フォルダーを右クリックして、 [追加] > [クラス] の順に選択します。 クラスに「TodoContext」という名前を付け、 [追加] をクリックします。
  • 次のコードを入力します。

    using Microsoft.EntityFrameworkCore;
    
    namespace TodoApi.Models
    {
        public class TodoContext : DbContext
        {
            public TodoContext(DbContextOptions<TodoContext> options)
                : base(options)
            {
            }
    
            public DbSet<TodoItem> TodoItems { get; set; }
        }
    }
    

データベース コンテキストの登録

ASP.NET Core で、サービス (DB コンテキストなど) を依存関係の挿入 (DI)コンテナーに登録する必要があります。 コンテナーは、コントローラーにサービスを提供します。

次のコードを使用して Startup.cs を更新します。

// Unused usings removed
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.EntityFrameworkCore;
using TodoApi.Models;

namespace TodoApi
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();

            services.AddDbContext<TodoContext>(opt =>
                                               opt.UseInMemoryDatabase("TodoList"));
            //services.AddSwaggerGen(c =>
            //{
            //    c.SwaggerDoc("v1", new OpenApiInfo { Title = "TodoApi", Version = "v1" });
            //});
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                //app.UseSwagger();
                //app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "TodoApi v1"));
            }

            app.UseHttpsRedirection();
            app.UseRouting();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
}

上記のコードでは次の操作が行われます。

  • Swagger 呼び出しを削除します。
  • 不要な using 宣言を削除します。
  • DI コンテナーにデータベース コンテキストを追加します。
  • データベース コンテキストがメモリ内データベースを使用することを指定します。

コントローラーのスキャフォールディング

  • Controllers フォルダーを右クリックします。

  • [追加] > [スキャフォールディングされた新しい項目] を選択します。

  • [Entity Framework を使用したアクションがある API コントローラー] を選択してから、 [追加] を選択します。

  • [Entity Framework を使用したアクションがある API コントローラー] ダイアログで次を実行します。

    • モデル クラスTodoItem (TodoApi.Models) を選択します。
    • データ コンテキスト クラスTodoContext (TodoApi.Models) を選択します。
    • [追加] を選びます。

生成されたコードでは次の操作が行われます。

  • クラスを [ApiController] 属性でマークします。 この属性は、コントローラーが Web API 要求に応答することを示します。 属性によって有効化される特定の動作については、「ASP.NET Core を使って Web API を作成する」 を参照してください。
  • DI を使用して、データベース コンテキスト (TodoContext) をコントローラーに挿入します。 データベース コンテキストは、コントローラーの各 CRUD メソッドで使用されます。

ASP.NET Core テンプレートの対象は次のとおりです。

  • ビューを含むコントローラーには、ルート テンプレートの [action] が含まれます。
  • API コントローラーには、ルート テンプレートの [action] が含まれません。

[action] トークンがルート テンプレート内にない場合、アクション名はルートから除外されます。 つまり、アクションの関連付けられたメソッド名は一致するルートでは使用されません。

PostTodoItem 作成メソッドの更新

nameof 演算子を使用するために、PostTodoItem で return ステートメントを更新します。

// POST: api/TodoItems
[HttpPost]
public async Task<ActionResult<TodoItem>> PostTodoItem(TodoItem todoItem)
{
    _context.TodoItems.Add(todoItem);
    await _context.SaveChangesAsync();

    //return CreatedAtAction("GetTodoItem", new { id = todoItem.Id }, todoItem);
    return CreatedAtAction(nameof(GetTodoItem), new { id = todoItem.Id }, todoItem);
}

[HttpPost] 属性が示すように、上記のコードは HTTP POST メソッドです。 このメソッドは、HTTP 要求の本文から To Do アイテムの値を取得します。

詳細については、「Http[Verb] 属性を使用する属性ルーティング」を参照してください。

CreatedAtAction メソッド:

  • 成功すると、HTTP 201 状態コードが返されます。 HTTP 201 は、サーバーに新しいリソースを作成する HTTP POST メソッドに対する標準の応答です。
  • 応答に Location ヘッダーが追加されます。 Location ヘッダーでは、新しく作成された To Do アイテムの URI が指定されます。 詳細については、「10.2.2 201 Created」を参照してください。
  • GetTodoItem アクションを参照して Location ヘッダーの URI を作成します。 C# の nameof キーワードを使って、CreatedAtAction 呼び出しでアクション名をハードコーディングすることを回避しています。

Postman のインストール

このチュートリアルでは、Postman を使用して Web API をテストします。

  • Postman をインストールします。
  • Web アプリを起動します。
  • Postman を起動します。
  • [SSL 証明書の確認] を無効にします。
    • [ファイル] > [設定] ( [全般] タブ) で、 [SSL 証明書の確認] を無効にします。

      警告

      コントローラーをテストした後、SSL 証明書の検証を再度有効にします。

Postman を使用した PostTodoItem のテスト

  • 新しい要求を作成します。

  • HTTP メソッドを POST に設定します。

  • URI を https://localhost:<port>/api/todoitems に設定します。 たとえば、「 https://localhost:5001/api/todoitems 」のように入力します。

  • [Body] タブを選択します。

  • [raw] ラジオ ボタンを選択します。

  • 型を [JSON (application/json)] に設定します。

  • 要求本文に、To Do アイテムの JSON を入力します。

    {
      "name":"walk dog",
      "isComplete":true
    }
    
  • [Send] を選択します。

    Postman での Create 要求

場所ヘッダー URI のテスト

場所ヘッダー URI は、ブラウザーでテストできます。 場所ヘッダー URI をコピーしてブラウザーに貼り付けます。

Postman でテストするには:

  • [Response] ウィンドウで、 [Headers] タブを選択します。

  • [Location] ヘッダー値をコピーします。

    Postman コンソールの [Headers] タブ

  • HTTP メソッドを GET に設定します。

  • URI を https://localhost:<port>/api/todoitems/1 に設定します。 たとえば、「 https://localhost:5001/api/todoitems/1 」のように入力します。

  • [Send] を選択します。

GET メソッドの確認

2 つの GET エンドポイントが実装されます。

  • GET /api/todoitems
  • GET /api/todoitems/{id}

ブラウザーまたは Postman から 2 つのエンドポイントを呼び出すことによって、アプリをテストします。 次に例を示します。

  • https://localhost:5001/api/todoitems
  • https://localhost:5001/api/todoitems/1

GetTodoItems への呼び出しによって、次のような応答が生成されます。

[
  {
    "id": 1,
    "name": "Item1",
    "isComplete": false
  }
]

Postman を使用して Get をテストする

  • 新しい要求を作成します。
  • HTTP メソッドを GET に設定します。
  • 要求 URI を https://localhost:<port>/api/todoitems に設定します。 たとえば、https://localhost:5001/api/todoitems のようにします。
  • Postman で [Two pane view] を設定します。
  • [Send] を選択します。

このアプリではメモリ内データベースが使用されます。 アプリが停止して開始された場合、上記の GET 要求はデータを返しません。 データが返されない場合は、アプリにデータを POST します。

ルーティングと URL パス

[HttpGet] 属性は、HTTP GET 要求に応答するメソッドを表します。 各メソッドの URL パスは次のように構成されます。

  • コントローラーの Route 属性でテンプレート文字列を使用します。

    [Route("api/[controller]")]
    [ApiController]
    public class TodoItemsController : ControllerBase
    {
        private readonly TodoContext _context;
    
        public TodoItemsController(TodoContext context)
        {
            _context = context;
        }
    
  • [controller] をコントローラーの名前 (慣例では "Controller" サフィックスを除くコントローラー クラス名) に置き換えます。 このサンプルでは、コントローラー クラス名は TodoItems Controller なので、コントローラー名は "TodoItems" です。 ASP.NET Core のルーティングでは、大文字と小文字が区別されません。

  • [HttpGet] 属性にルート テンプレート (たとえば、[HttpGet("products")]) がある場合は、それをパスに追加します。 このサンプルではテンプレートを使用しません。 詳細については、「Http[Verb] 属性を使用する属性ルーティング」を参照してください。

次の GetTodoItem メソッドで、"{id}" は To Do アイテムの一意識別子に使用するプレースホルダーの変数です。 GetTodoItem が呼び出されると、その id パラメーター内のメソッドに URL の "{id}" の値が指定されます。

// GET: api/TodoItems/5
[HttpGet("{id}")]
public async Task<ActionResult<TodoItem>> GetTodoItem(long id)
{
    var todoItem = await _context.TodoItems.FindAsync(id);

    if (todoItem == null)
    {
        return NotFound();
    }

    return todoItem;
}

戻り値

GetTodoItemsGetTodoItem メソッドの戻り値の型は、ActionResult<T> 型です。 ASP.NET Core は自動的にオブジェクトを JSON にシリアル化して、応答メッセージの本文に JSON を書き込みます。 ハンドルされない例外がないと仮定すると、この戻り値の型の応答コードは 200 OK です。 ハンドルされない例外は 5xx エラーに変換されます。

ActionResult 戻り値の型は、幅広い範囲の HTTP 状態コードを表すことができます。 たとえば、GetTodoItem は、次の 2 つの異なる状態値を返す可能性があります。

  • 要求された ID に一致するアイテムがない場合、このメソッドにより 404 ステータス NotFound エラー コードが返されます。
  • それ以外の場合、メソッドは JSON 応答本文で 200 を返します。 戻り値が item の場合、HTTP 200 応答が返されます。

PutTodoItem メソッド

PutTodoItem メソッドを検証します。

// PUT: api/TodoItems/5
[HttpPut("{id}")]
public async Task<IActionResult> PutTodoItem(long id, TodoItem todoItem)
{
    if (id != todoItem.Id)
    {
        return BadRequest();
    }

    _context.Entry(todoItem).State = EntityState.Modified;

    try
    {
        await _context.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!TodoItemExists(id))
        {
            return NotFound();
        }
        else
        {
            throw;
        }
    }

    return NoContent();
}

PutTodoItemPostTodoItem と似ていますが、HTTP PUT を使用します。 応答は 204 (No Content) となります。 HTTP 仕様に従って、PUT 要求では、変更だけでなく、更新されたエンティティ全体を送信するようクライアントに求めます。 部分的な更新をサポートするには、HTTP PATCH を使用します。

PutTodoItem 呼び出しでエラーが発生した場合、GET を呼び出してデータベース内にアイテムがあることを確認してください。

PutTodoItem メソッドのテスト

このサンプルでは、アプリを起動するたびに開始することが必要なメモリ内データベースが使われています。 PUT 呼び出しを実行する前に、データベース内にアイテムが存在している必要があります。 GET を呼び出して、PUT 呼び出しを実行する前にデータベース内にアイテムが確実に存在していることを確認します。

Id = 1 の To Do アイテムを更新し、その名前を "feed fish" に設定します。

  {
    "Id":1,
    "name":"feed fish",
    "isComplete":true
  }

次の図は、Postman の更新を示しています。

204 (No Content) の応答を示す Postman コンソール

DeleteTodoItem メソッド

DeleteTodoItem メソッドを検証します。

// DELETE: api/TodoItems/5
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteTodoItem(long id)
{
    var todoItem = await _context.TodoItems.FindAsync(id);
    if (todoItem == null)
    {
        return NotFound();
    }

    _context.TodoItems.Remove(todoItem);
    await _context.SaveChangesAsync();

    return NoContent();
}

DeleteTodoItem メソッドのテスト

Postman を使用して、To Do アイテムを削除します。

  • メソッドを DELETE に設定します。
  • 削除するオブジェクトの URI (たとえば、https://localhost:5001/api/todoitems/1) を設定します。
  • [Send] を選択します。

過剰な投稿を防止する

現在、サンプル アプリでは TodoItem オブジェクト全体が公開されています。 通常、運用環境のアプリでは、モデルのサブセットを使用して入力されるデータおよび返されるデータが制限されています。 その背景には複数の理由があり、セキュリティは主なものです。 モデルのサブセットは、通常、データ転送オブジェクト (DTO)、入力モデル、またはビュー モデルと呼ばれます。 この記事では DTO を使用しています。

DTO は次の目的で使用できます。

  • 過剰な投稿を防止する。
  • クライアントが表示しないことになっているプロパティを非表示にする。
  • ペイロード サイズを減らすために、いくつかのプロパティを省略する。
  • 入れ子になったオブジェクトを含むオブジェクト グラフをフラット化する。 フラット化されたオブジェクト グラフは、クライアントにとってより便利になる可能性があります。

DTO のアプローチを実演するために、TodoItem クラスを更新して、シークレット フィールドを含めます。

namespace TodoApi.Models
{
    public class TodoItem
    {
        public long Id { get; set; }
        public string Name { get; set; }
        public bool IsComplete { get; set; }
        public string Secret { get; set; }
    }
}

シークレット フィールドは、このアプリでは非表示にする必要がありますが、管理アプリの場合は公開することを選択できます。

シークレット フィールドを投稿および取得できることを確認します。

次のように DTO モデルを作成します。

public class TodoItemDTO
{
    public long Id { get; set; }
    public string Name { get; set; }
    public bool IsComplete { get; set; }
}

TodoItemDTO を使用するように TodoItemsController を更新します。

// GET: api/TodoItems
[HttpGet]
public async Task<ActionResult<IEnumerable<TodoItemDTO>>> GetTodoItems()
{
    return await _context.TodoItems
        .Select(x => ItemToDTO(x))
        .ToListAsync();
}

[HttpGet("{id}")]
public async Task<ActionResult<TodoItemDTO>> GetTodoItem(long id)
{
    var todoItem = await _context.TodoItems.FindAsync(id);

    if (todoItem == null)
    {
        return NotFound();
    }

    return ItemToDTO(todoItem);
}

[HttpPut("{id}")]
public async Task<IActionResult> UpdateTodoItem(long id, TodoItemDTO todoItemDTO)
{
    if (id != todoItemDTO.Id)
    {
        return BadRequest();
    }

    var todoItem = await _context.TodoItems.FindAsync(id);
    if (todoItem == null)
    {
        return NotFound();
    }

    todoItem.Name = todoItemDTO.Name;
    todoItem.IsComplete = todoItemDTO.IsComplete;

    try
    {
        await _context.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException) when (!TodoItemExists(id))
    {
        return NotFound();
    }

    return NoContent();
}

[HttpPost]
public async Task<ActionResult<TodoItemDTO>> CreateTodoItem(TodoItemDTO todoItemDTO)
{
    var todoItem = new TodoItem
    {
        IsComplete = todoItemDTO.IsComplete,
        Name = todoItemDTO.Name
    };

    _context.TodoItems.Add(todoItem);
    await _context.SaveChangesAsync();

    return CreatedAtAction(
        nameof(GetTodoItem),
        new { id = todoItem.Id },
        ItemToDTO(todoItem));
}

[HttpDelete("{id}")]
public async Task<IActionResult> DeleteTodoItem(long id)
{
    var todoItem = await _context.TodoItems.FindAsync(id);

    if (todoItem == null)
    {
        return NotFound();
    }

    _context.TodoItems.Remove(todoItem);
    await _context.SaveChangesAsync();

    return NoContent();
}

private bool TodoItemExists(long id) =>
     _context.TodoItems.Any(e => e.Id == id);

private static TodoItemDTO ItemToDTO(TodoItem todoItem) =>
    new TodoItemDTO
    {
        Id = todoItem.Id,
        Name = todoItem.Name,
        IsComplete = todoItem.IsComplete
    };

シークレット フィールドを投稿または取得できないことを確認します。

JavaScript を使用した Web API の呼び出し

チュートリアル:JavaScript を使用して ASP.NET Core Web API を呼び出す」を参照してください。

このチュートリアルでは、次の作業を行う方法について説明します。

  • Web API プロジェクトを作成する。
  • モデル クラスとデータベース コンテキストを追加する。
  • CRUD メソッドを使用してコントローラーのスキャフォールディング。
  • ルーティング、URL パス、戻り値を構成する。
  • Postman で Web API を呼び出す。

最後に、データベースに格納されている "To Do" アイテムを管理できる Web API が作成されます。

概要

このチュートリアルでは、次の API を作成します。

API 説明 要求本文 応答本文
GET /api/todoitems すべての To Do アイテムを取得します。 None To Do アイテムの配列
GET /api/todoitems/{id} ID でアイテムを取得します。 None To Do アイテム
POST /api/todoitems 新しいアイテムを追加します。 To Do アイテム To Do アイテム
PUT /api/todoitems/{id} 既存のアイテムを更新します。  To Do アイテム None
DELETE /api/todoitems/{id}     アイテムを削除します     None None

次の図は、アプリのデザインを示しています。

クライアントは、左側のボックスに表されます。 要求を送信し、アプリケーション (右側に描画されたボックス) から応答を受信します。 アプリケーション ボックス内の 3 つのボックスは、コントローラー、モデル、およびデータ アクセス レイヤーを表しています。 要求はアプリケーションのコントローラーに送られ、コントローラーとデータ アクセス レイヤー間で読み取り/書き込み操作が行われます。 モデルはシリアル化され、応答でクライアントに返されます。

必須コンポーネント

Web プロジェクトの作成

  • [ファイル] メニューで、 [新規作成] > [プロジェクト] の順に選択します。
  • [ASP.NET Core Web アプリケーション] テンプレートを選択して、 [次へ] をクリックします。
  • プロジェクトに「TodoApi」という名前を付け、 [作成] をクリックします。
  • [新しい ASP.NET Core Web アプリケーションを作成する] ダイアログで、 [.NET Core][ASP.NET Core 3.1] が選択されていることを確認します。 API テンプレートを選択し、 [作成] をクリックします。

VS の [新しいプロジェクト] ダイアログ

API のテスト

プロジェクト テンプレートによって WeatherForecast API が作成されます。 ブラウザーから Get メソッドを呼び出して、アプリをテストします。

Ctrl キーを押しながら F5 キーを押して、アプリを実行します。 Visual Studio でブラウザーが起動し、https://localhost:<port>/WeatherForecast にアクセスします。ここで、<port> はランダムに選択されたポート番号になります。

IIS Express 証明書を信頼するかどうかを確認するダイアログ ボックスが表示された場合は、 [はい] を選択します。 次に表示される [セキュリティ警告] ダイアログ ボックスで、 [はい] を選択します。

次のような JSON が返されます。

[
    {
        "date": "2019-07-16T19:04:05.7257911-06:00",
        "temperatureC": 52,
        "temperatureF": 125,
        "summary": "Mild"
    },
    {
        "date": "2019-07-17T19:04:05.7258461-06:00",
        "temperatureC": 36,
        "temperatureF": 96,
        "summary": "Warm"
    },
    {
        "date": "2019-07-18T19:04:05.7258467-06:00",
        "temperatureC": 39,
        "temperatureF": 102,
        "summary": "Cool"
    },
    {
        "date": "2019-07-19T19:04:05.7258471-06:00",
        "temperatureC": 10,
        "temperatureF": 49,
        "summary": "Bracing"
    },
    {
        "date": "2019-07-20T19:04:05.7258474-06:00",
        "temperatureC": -1,
        "temperatureF": 31,
        "summary": "Chilly"
    }
]

モデル クラスの追加

モデル は、アプリが管理するデータを表すクラスのセットです。 このアプリのモデルは、単一の TodoItem クラスです。

  • ソリューション エクスプローラー で、プロジェクトを右クリックします。 [追加] > [新しいフォルダー] の順に選択します。 フォルダーに「 Models 」という名前を付けます。

  • Models フォルダーを右クリックして、 [追加] > [クラス] の順に選択します。 クラスに「TodoItem」という名前を付け、 [追加] を選択します。

  • テンプレート コードを次のコードに置き換えます。

public class TodoItem
{
    public long Id { get; set; }
    public string Name { get; set; }
    public bool IsComplete { get; set; }
}

Id プロパティは、リレーショナル データベース内の一意のキーとして機能します。

モデル クラスはプロジェクト内のどこでも使用できますが、慣例により Models フォルダーが使用されます。

データベース コンテキストの追加

データベース コンテキスト は、データ モデルに対して Entity Framework 機能を調整するメイン クラスです。 このクラスは Microsoft.EntityFrameworkCore.DbContext クラスから派生させて作成します。

NuGet パッケージを追加する

  • [ツール] メニューで [NuGet パッケージ マネージャー]、[ソリューションの NuGet パッケージの管理] の順に選択します。
  • [参照] タブを選択し、検索ボックスに「Microsoft.EntityFrameworkCore.InMemory」と入力します。
  • 左側のウィンドウで、 [Microsoft.EntityFrameworkCore.InMemory] を選択します。
  • 右側のウィンドウで [プロジェクト] チェックボックスをオンにして、 [インストール] を選択します。

NuGet パッケージ マネージャー

TodoContext データベースコンテキストの追加

  • Models フォルダーを右クリックして、 [追加] > [クラス] の順に選択します。 クラスに「TodoContext」という名前を付け、 [追加] をクリックします。
  • 次のコードを入力します。

    using Microsoft.EntityFrameworkCore;
    
    namespace TodoApi.Models
    {
        public class TodoContext : DbContext
        {
            public TodoContext(DbContextOptions<TodoContext> options)
                : base(options)
            {
            }
    
            public DbSet<TodoItem> TodoItems { get; set; }
        }
    }
    

データベース コンテキストの登録

ASP.NET Core で、サービス (DB コンテキストなど) を依存関係の挿入 (DI)コンテナーに登録する必要があります。 コンテナーは、コントローラーにサービスを提供します。

次の強調表示されているコードを使用して、Startup.cs を更新します。

// Unused usings removed
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.EntityFrameworkCore;
using TodoApi.Models;

namespace TodoApi
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<TodoContext>(opt =>
               opt.UseInMemoryDatabase("TodoList"));
            services.AddControllers();
        }

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

            app.UseHttpsRedirection();

            app.UseRouting();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
}

上記のコードでは次の操作が行われます。

  • 不要な using 宣言を削除します。
  • DI コンテナーにデータベース コンテキストを追加します。
  • データベース コンテキストがメモリ内データベースを使用することを指定します。

コントローラーのスキャフォールディング

  • Controllers フォルダーを右クリックします。

  • [追加] > [スキャフォールディングされた新しい項目] を選択します。

  • [Entity Framework を使用したアクションがある API コントローラー] を選択してから、 [追加] を選択します。

  • [Entity Framework を使用したアクションがある API コントローラー] ダイアログで次を実行します。

    • モデル クラスTodoItem (TodoApi.Models) を選択します。
    • データ コンテキスト クラスTodoContext (TodoApi.Models) を選択します。
    • [追加] を選びます。

生成されたコードでは次の操作が行われます。

  • クラスを [ApiController] 属性でマークします。 この属性は、コントローラーが Web API 要求に応答することを示します。 属性によって有効化される特定の動作については、「ASP.NET Core を使って Web API を作成する」 を参照してください。
  • DI を使用して、データベース コンテキスト (TodoContext) をコントローラーに挿入します。 データベース コンテキストは、コントローラーの各 CRUD メソッドで使用されます。

ASP.NET Core テンプレートの対象は次のとおりです。

  • ビューを含むコントローラーには、ルート テンプレートの [action] が含まれます。
  • API コントローラーには、ルート テンプレートの [action] が含まれません。

[action] トークンがルート テンプレート内にない場合、アクション名はルートから除外されます。 つまり、アクションの関連付けられたメソッド名は一致するルートでは使用されません。

PostTodoItem 作成メソッドの確認

nameof 演算子を使用するために、PostTodoItem で return ステートメントを置き換えます。

// POST: api/TodoItems
[HttpPost]
public async Task<ActionResult<TodoItem>> PostTodoItem(TodoItem todoItem)
{
    _context.TodoItems.Add(todoItem);
    await _context.SaveChangesAsync();

    //return CreatedAtAction("GetTodoItem", new { id = todoItem.Id }, todoItem);
    return CreatedAtAction(nameof(GetTodoItem), new { id = todoItem.Id }, todoItem);
}

[HttpPost] 属性が示すように、上記のコードは HTTP POST メソッドです。 このメソッドは、HTTP 要求の本文から To Do アイテムの値を取得します。

詳細については、「Http[Verb] 属性を使用する属性ルーティング」を参照してください。

CreatedAtAction メソッド:

  • 成功すると、HTTP 201 状態コードが返されます。 HTTP 201 は、サーバーに新しいリソースを作成する HTTP POST メソッドに対する標準の応答です。
  • 応答に Location ヘッダーが追加されます。 Location ヘッダーでは、新しく作成された To Do アイテムの URI が指定されます。 詳細については、「10.2.2 201 Created」を参照してください。
  • GetTodoItem アクションを参照して Location ヘッダーの URI を作成します。 C# の nameof キーワードを使って、CreatedAtAction 呼び出しでアクション名をハードコーディングすることを回避しています。

Postman のインストール

このチュートリアルでは、Postman を使用して Web API をテストします。

  • Postman をインストールします。
  • Web アプリを起動します。
  • Postman を起動します。
  • [SSL 証明書の確認] を無効にします。
    • [ファイル] > [設定] ( [全般] タブ) で、 [SSL 証明書の確認] を無効にします。

      警告

      コントローラーをテストした後、SSL 証明書の検証を再度有効にします。

Postman を使用した PostTodoItem のテスト

  • 新しい要求を作成します。

  • HTTP メソッドを POST に設定します。

  • URI を https://localhost:<port>/api/todoitems に設定します。 たとえば、「 https://localhost:5001/api/todoitems 」のように入力します。

  • [Body] タブを選択します。

  • [raw] ラジオ ボタンを選択します。

  • 型を [JSON (application/json)] に設定します。

  • 要求本文に、To Do アイテムの JSON を入力します。

    {
      "name":"walk dog",
      "isComplete":true
    }
    
  • [Send] を選択します。

    Postman での Create 要求

Postman で Location ヘッダーの URI をテストする

  • [Response] ウィンドウで、 [Headers] タブを選択します。

  • [Location] ヘッダー値をコピーします。

    Postman コンソールの [Headers] タブ

  • HTTP メソッドを GET に設定します。

  • URI を https://localhost:<port>/api/todoitems/1 に設定します。 たとえば、「 https://localhost:5001/api/todoitems/1 」のように入力します。

  • [Send] を選択します。

GET メソッドの確認

これらのメソッドは、次の 2 つの GET エンドポイントを実装します。

  • GET /api/todoitems
  • GET /api/todoitems/{id}

ブラウザーまたは Postman から 2 つのエンドポイントを呼び出すことによって、アプリをテストします。 次に例を示します。

  • https://localhost:5001/api/todoitems
  • https://localhost:5001/api/todoitems/1

GetTodoItems への呼び出しによって、次のような応答が生成されます。

[
  {
    "id": 1,
    "name": "Item1",
    "isComplete": false
  }
]

Postman を使用して Get をテストする

  • 新しい要求を作成します。
  • HTTP メソッドを GET に設定します。
  • 要求 URI を https://localhost:<port>/api/todoitems に設定します。 たとえば、https://localhost:5001/api/todoitems のようにします。
  • Postman で [Two pane view] を設定します。
  • [Send] を選択します。

このアプリではメモリ内データベースが使用されます。 アプリが停止して開始された場合、上記の GET 要求はデータを返しません。 データが返されない場合は、アプリにデータを POST します。

ルーティングと URL パス

[HttpGet] 属性は、HTTP GET 要求に応答するメソッドを表します。 各メソッドの URL パスは次のように構成されます。

  • コントローラーの Route 属性でテンプレート文字列を使用します。

    [Route("api/[controller]")]
    [ApiController]
    public class TodoItemsController : ControllerBase
    {
        private readonly TodoContext _context;
    
        public TodoItemsController(TodoContext context)
        {
            _context = context;
        }
    
  • [controller] をコントローラーの名前 (慣例では "Controller" サフィックスを除くコントローラー クラス名) に置き換えます。 このサンプルでは、コントローラー クラス名は TodoItems Controller なので、コントローラー名は "TodoItems" です。 ASP.NET Core のルーティングでは、大文字と小文字が区別されません。

  • [HttpGet] 属性にルート テンプレート (たとえば、[HttpGet("products")]) がある場合は、それをパスに追加します。 このサンプルではテンプレートを使用しません。 詳細については、「Http[Verb] 属性を使用する属性ルーティング」を参照してください。

次の GetTodoItem メソッドで、"{id}" は To Do アイテムの一意識別子に使用するプレースホルダーの変数です。 GetTodoItem が呼び出されると、その id パラメーター内のメソッドに URL の "{id}" の値が指定されます。

// GET: api/TodoItems/5
[HttpGet("{id}")]
public async Task<ActionResult<TodoItem>> GetTodoItem(long id)
{
    var todoItem = await _context.TodoItems.FindAsync(id);

    if (todoItem == null)
    {
        return NotFound();
    }

    return todoItem;
}

戻り値

GetTodoItemsGetTodoItem メソッドの戻り値の型は、ActionResult<T> 型です。 ASP.NET Core は自動的にオブジェクトを JSON にシリアル化して、応答メッセージの本文に JSON を書き込みます。 この戻り値の型の応答コードは 200 で、ハンドルされない例外がないものと想定します。 ハンドルされない例外は 5xx エラーに変換されます。

ActionResult 戻り値の型は、幅広い範囲の HTTP 状態コードを表すことができます。 たとえば、GetTodoItem は、次の 2 つの異なる状態値を返す可能性があります。

  • 要求された ID と一致するアイテムがない場合、メソッドは 404 NotFound エラー コードを返します。
  • それ以外の場合、メソッドは JSON 応答本文で 200 を返します。 戻り値が item の場合、HTTP 200 応答が返されます。

PutTodoItem メソッド

PutTodoItem メソッドを検証します。

// PUT: api/TodoItems/5
[HttpPut("{id}")]
public async Task<IActionResult> PutTodoItem(long id, TodoItem todoItem)
{
    if (id != todoItem.Id)
    {
        return BadRequest();
    }

    _context.Entry(todoItem).State = EntityState.Modified;

    try
    {
        await _context.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!TodoItemExists(id))
        {
            return NotFound();
        }
        else
        {
            throw;
        }
    }

    return NoContent();
}

PutTodoItemPostTodoItem と似ていますが、HTTP PUT を使用します。 応答は 204 (No Content) となります。 HTTP 仕様に従って、PUT 要求では、変更だけでなく、更新されたエンティティ全体を送信するようクライアントに求めます。 部分的な更新をサポートするには、HTTP PATCH を使用します。

PutTodoItem 呼び出しでエラーが発生した場合、GET を呼び出してデータベース内にアイテムがあることを確認してください。

PutTodoItem メソッドのテスト

このサンプルでは、アプリを起動するたびに開始することが必要なメモリ内データベースが使われています。 PUT 呼び出しを実行する前に、データベース内にアイテムが存在している必要があります。 GET を呼び出して、PUT 呼び出しを実行する前にデータベース内にアイテムが確実に存在していることを確認します。

Id = 1 の To Do アイテムを更新し、その名前を "feed fish" に設定します。

  {
    "id":1,
    "name":"feed fish",
    "isComplete":true
  }

次の図は、Postman の更新を示しています。

204 (No Content) の応答を示す Postman コンソール

DeleteTodoItem メソッド

DeleteTodoItem メソッドを検証します。

// DELETE: api/TodoItems/5
[HttpDelete("{id}")]
public async Task<ActionResult<TodoItem>> DeleteTodoItem(long id)
{
    var todoItem = await _context.TodoItems.FindAsync(id);
    if (todoItem == null)
    {
        return NotFound();
    }

    _context.TodoItems.Remove(todoItem);
    await _context.SaveChangesAsync();

    return todoItem;
}

DeleteTodoItem メソッドのテスト

Postman を使用して、To Do アイテムを削除します。

  • メソッドを DELETE に設定します。
  • 削除するオブジェクトの URI (たとえば、https://localhost:5001/api/todoitems/1) を設定します。
  • [Send] を選択します。

過剰な投稿を防止する

現在、サンプル アプリでは TodoItem オブジェクト全体が公開されています。 通常、運用環境のアプリでは、モデルのサブセットを使用して入力されるデータおよび返されるデータが制限されています。 その背景には複数の理由があり、セキュリティは主なものです。 モデルのサブセットは、通常、データ転送オブジェクト (DTO)、入力モデル、またはビュー モデルと呼ばれます。 この記事では DTO を使用しています。

DTO は次の目的で使用できます。

  • 過剰な投稿を防止する。
  • クライアントが表示しないことになっているプロパティを非表示にする。
  • ペイロード サイズを減らすために、いくつかのプロパティを省略する。
  • 入れ子になったオブジェクトを含むオブジェクト グラフをフラット化する。 フラット化されたオブジェクト グラフは、クライアントにとってより便利になる可能性があります。

DTO のアプローチを実演するために、TodoItem クラスを更新して、シークレット フィールドを含めます。

public class TodoItem
{
    public long Id { get; set; }
    public string Name { get; set; }
    public bool IsComplete { get; set; }
    public string Secret { get; set; }
}

シークレット フィールドは、このアプリでは非表示にする必要がありますが、管理アプリの場合は公開することを選択できます。

シークレット フィールドを投稿および取得できることを確認します。

次のように DTO モデルを作成します。

public class TodoItemDTO
{
    public long Id { get; set; }
    public string Name { get; set; }
    public bool IsComplete { get; set; }
}

TodoItemDTO を使用するように TodoItemsController を更新します。

    [HttpGet]
    public async Task<ActionResult<IEnumerable<TodoItemDTO>>> GetTodoItems()
    {
        return await _context.TodoItems
            .Select(x => ItemToDTO(x))
            .ToListAsync();
    }

    [HttpGet("{id}")]
    public async Task<ActionResult<TodoItemDTO>> GetTodoItem(long id)
    {
        var todoItem = await _context.TodoItems.FindAsync(id);

        if (todoItem == null)
        {
            return NotFound();
        }

        return ItemToDTO(todoItem);
    }

    [HttpPut("{id}")]
    public async Task<IActionResult> UpdateTodoItem(long id, TodoItemDTO todoItemDTO)
    {
        if (id != todoItemDTO.Id)
        {
            return BadRequest();
        }

        var todoItem = await _context.TodoItems.FindAsync(id);
        if (todoItem == null)
        {
            return NotFound();
        }

        todoItem.Name = todoItemDTO.Name;
        todoItem.IsComplete = todoItemDTO.IsComplete;

        try
        {
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException) when (!TodoItemExists(id))
        {
            return NotFound();
        }

        return NoContent();
    }

    [HttpPost]
    public async Task<ActionResult<TodoItemDTO>> CreateTodoItem(TodoItemDTO todoItemDTO)
    {
        var todoItem = new TodoItem
        {
            IsComplete = todoItemDTO.IsComplete,
            Name = todoItemDTO.Name
        };

        _context.TodoItems.Add(todoItem);
        await _context.SaveChangesAsync();

        return CreatedAtAction(
            nameof(GetTodoItem),
            new { id = todoItem.Id },
            ItemToDTO(todoItem));
    }

    [HttpDelete("{id}")]
    public async Task<IActionResult> DeleteTodoItem(long id)
    {
        var todoItem = await _context.TodoItems.FindAsync(id);

        if (todoItem == null)
        {
            return NotFound();
        }

        _context.TodoItems.Remove(todoItem);
        await _context.SaveChangesAsync();

        return NoContent();
    }

    private bool TodoItemExists(long id) =>
         _context.TodoItems.Any(e => e.Id == id);

    private static TodoItemDTO ItemToDTO(TodoItem todoItem) =>
        new TodoItemDTO
        {
            Id = todoItem.Id,
            Name = todoItem.Name,
            IsComplete = todoItem.IsComplete
        };       
}

シークレット フィールドを投稿または取得できないことを確認します。

JavaScript を使用した Web API の呼び出し

チュートリアル:JavaScript を使用して ASP.NET Core Web API を呼び出す」を参照してください。

このチュートリアルでは、次の作業を行う方法について説明します。

  • Web API プロジェクトを作成する。
  • モデル クラスとデータベース コンテキストを追加する。
  • コントローラーを追加する。
  • CRUD メソッドを追加する。
  • ルーティングと URL パスを構成する。
  • 戻り値を指定する。
  • Postman で Web API を呼び出す。
  • JavaScript を使用した Web API の呼び出し。

最後に、リレーショナル データベースに格納されている "To Do" アイテムを管理できる Web API が作成されます。

概要 2.1

このチュートリアルでは、次の API を作成します。

API 説明 要求本文 応答本文
GET /api/todoitems すべての To Do アイテムを取得します。 None To Do アイテムの配列
GET /api/todoitems/{id} ID でアイテムを取得します。 None To Do アイテム
POST /api/todoitems 新しいアイテムを追加します。 To Do アイテム To Do アイテム
PUT /api/todoitems/{id} 既存のアイテムを更新します。  To Do アイテム None
DELETE /api/todoitems/{id}     アイテムを削除します     None None

次の図は、アプリのデザインを示しています。

クライアントは、左側のボックスに表されます。 要求を送信し、アプリケーション (右側に描画されたボックス) から応答を受信します。 アプリケーション ボックス内の 3 つのボックスは、コントローラー、モデル、およびデータ アクセス レイヤーを表しています。 要求はアプリケーションのコントローラーに送られ、コントローラーとデータ アクセス レイヤー間で読み取り/書き込み操作が行われます。 モデルはシリアル化され、応答でクライアントに返されます。

前提条件 2.1

警告

Visual Studio 2017 を使用している場合、Visual Studio で動作しない .NET Core SDK のバージョンについては、dotnet/sdk issue #3124 を参照してください。

Web プロジェクト 2.1 の作成

  • [ファイル] メニューで、 [新規作成] > [プロジェクト] の順に選択します。
  • [ASP.NET Core Web アプリケーション] テンプレートを選択して、 [次へ] をクリックします。
  • プロジェクトに「TodoApi」という名前を付け、 [作成] をクリックします。
  • [新しい ASP.NET Core Web アプリケーションを作成する] ダイアログで、 [.NET Core][ASP.NET Core 2.2] が選択されていることを確認します。 API テンプレートを選択し、 [作成] をクリックします。 [Enable Docker Support](Docker サポートを有効にする)選択しないで ください。

VS の [新しいプロジェクト] ダイアログ

API 2.1 のテスト

プロジェクト テンプレートによって values API が作成されます。 ブラウザーから Get メソッドを呼び出して、アプリをテストします。

Ctrl キーを押しながら F5 キーを押して、アプリを実行します。 Visual Studio でブラウザーが起動し、https://localhost:<port>/api/values にアクセスします。ここで、<port> はランダムに選択されたポート番号になります。

IIS Express 証明書を信頼するかどうかを確認するダイアログ ボックスが表示された場合は、 [はい] を選択します。 次に表示される [セキュリティ警告] ダイアログ ボックスで、 [はい] を選択します。

次の JSON が返されます。

["value1","value2"]

モデル クラス 2.1 の追加

モデル は、アプリが管理するデータを表すクラスのセットです。 このアプリのモデルは、単一の TodoItem クラスです。

  • ソリューション エクスプローラー で、プロジェクトを右クリックします。 [追加] > [新しいフォルダー] の順に選択します。 フォルダーに「 Models 」という名前を付けます。

  • Models フォルダーを右クリックして、 [追加] > [クラス] の順に選択します。 クラスに「TodoItem」という名前を付け、 [追加] を選択します。

  • テンプレート コードを次のコードに置き換えます。

namespace TodoApi.Models
{
    public class TodoItem
    {
        public long Id { get; set; }
        public string Name { get; set; }
        public bool IsComplete { get; set; }
    }
}

Id プロパティは、リレーショナル データベース内の一意のキーとして機能します。

モデル クラスはプロジェクト内のどこでも使用できますが、慣例により Models フォルダーが使用されます。

データベース コンテキスト 2.1 の追加

データベース コンテキスト は、データ モデルに対して Entity Framework 機能を調整するメイン クラスです。 このクラスは Microsoft.EntityFrameworkCore.DbContext クラスから派生させて作成します。

  • Models フォルダーを右クリックして、 [追加] > [クラス] の順に選択します。 クラスに「TodoContext」という名前を付け、 [追加] をクリックします。
  • テンプレート コードを次のコードに置き換えます。

    using Microsoft.EntityFrameworkCore;
    
    namespace TodoApi.Models
    {
        public class TodoContext : DbContext
        {
            public TodoContext(DbContextOptions<TodoContext> options)
                : base(options)
            {
            }
    
            public DbSet<TodoItem> TodoItems { get; set; }
        }
    }
    

データベース コンテキスト 2.1 の登録

ASP.NET Core で、サービス (DB コンテキストなど) を依存関係の挿入 (DI)コンテナーに登録する必要があります。 コンテナーは、コントローラーにサービスを提供します。

次の強調表示されているコードを使用して、Startup.cs を更新します。

// Unused usings removed
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using TodoApi.Models;

namespace TodoApi
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the 
        //container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<TodoContext>(opt =>
                opt.UseInMemoryDatabase("TodoList"));
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
        }

        // This method gets called by the runtime. Use this method to configure the HTTP 
        //request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                // The default HSTS value is 30 days. You may want to change this for 
                // production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseMvc();
        }
    }
}

上記のコードでは次の操作が行われます。

  • 不要な using 宣言を削除します。
  • DI コンテナーにデータベース コンテキストを追加します。
  • データベース コンテキストがメモリ内データベースを使用することを指定します。

コントローラー 2.1 の追加

  • Controllers フォルダーを右クリックします。

  • [追加] > [新しい項目] の順に選択します。

  • [新しい項目の追加] ダイアログで、 [API コントローラー クラス] テンプレートを選択します。

  • クラスに「TodoController」という名前を付け、 [追加] を選択します。

    [新しい項目の追加] ダイアログ。検索ボックスに「controller」と入力されています。Web API コントローラーが選択されています。

  • テンプレート コードを次のコードに置き換えます。

    using Microsoft.AspNetCore.Mvc;
    using Microsoft.EntityFrameworkCore;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    using TodoApi.Models;
    
    namespace TodoApi.Controllers
    {
        [Route("api/[controller]")]
        [ApiController]
        public class TodoController : ControllerBase
        {
            private readonly TodoContext _context;
    
            public TodoController(TodoContext context)
            {
                _context = context;
    
                if (_context.TodoItems.Count() == 0)
                {
                    // Create a new TodoItem if collection is empty,
                    // which means you can't delete all TodoItems.
                    _context.TodoItems.Add(new TodoItem { Name = "Item1" });
                    _context.SaveChanges();
                }
            }
        }
    }
    

上記のコードでは次の操作が行われます。

  • メソッドを使用せず、API コントローラー クラスを定義します。
  • クラスを [ApiController] 属性でマークします。 この属性は、コントローラーが Web API 要求に応答することを示します。 属性によって有効化される特定の動作については、「ASP.NET Core を使って Web API を作成する」 を参照してください。
  • DI を使用して、データベース コンテキスト (TodoContext) をコントローラーに挿入します。 データベース コンテキストは、コントローラーの各 CRUD メソッドで使用されます。
  • 追加データベースが空の場合、Item1 という名前のアイテムをデータベースにします。 このコードはコンストラクター内にあるので、新しい HTTP 要求が行われるたびに実行されます。 すべてのアイテムを削除した場合、コンストラクターは、次回に API メソッドが呼び出されたときに Item1 をもう一度作成します。 そのため、削除が実際には機能していても、機能しなかったように見える場合があります。

Get メソッド 2.1 の追加

To Do アイテムを取得する API を指定するには、TodoController クラスに次のメソッドを追加します。

// GET: api/Todo
[HttpGet]
public async Task<ActionResult<IEnumerable<TodoItem>>> GetTodoItems()
{
    return await _context.TodoItems.ToListAsync();
}

// GET: api/Todo/5
[HttpGet("{id}")]
public async Task<ActionResult<TodoItem>> GetTodoItem(long id)
{
    var todoItem = await _context.TodoItems.FindAsync(id);

    if (todoItem == null)
    {
        return NotFound();
    }

    return todoItem;
}

これらのメソッドは、次の 2 つの GET エンドポイントを実装します。

  • GET /api/todo
  • GET /api/todo/{id}

アプリがまだ実行中の場合は、停止します。 次に、それを再度実行して、最新の変更を含めます。

ブラウザーからこれらの 2 つのエンドポイントを呼び出すことによって、アプリをテストします。 次に例を示します。

  • https://localhost:<port>/api/todo
  • https://localhost:<port>/api/todo/1

GetTodoItems への呼び出しによって、次の HTTP 応答が生成されます。

[
  {
    "id": 1,
    "name": "Item1",
    "isComplete": false
  }
]

ルーティングと URL パス 2.1

[HttpGet] 属性は、HTTP GET 要求に応答するメソッドを表します。 各メソッドの URL パスは次のように構成されます。

  • コントローラーの Route 属性でテンプレート文字列を使用します。

    namespace TodoApi.Controllers
    {
        [Route("api/[controller]")]
        [ApiController]
        public class TodoController : ControllerBase
        {
            private readonly TodoContext _context;
    
  • [controller] をコントローラーの名前 (慣例では "Controller" サフィックスを除くコントローラー クラス名) に置き換えます。 このサンプルでは、コントローラー クラス名は Todo Controller なので、コントローラー名は "todo" です。 ASP.NET Core のルーティングでは、大文字と小文字が区別されません。

  • [HttpGet] 属性にルート テンプレート (たとえば、[HttpGet("products")]) がある場合は、それをパスに追加します。 このサンプルではテンプレートを使用しません。 詳細については、「Http[Verb] 属性を使用する属性ルーティング」を参照してください。

次の GetTodoItem メソッドで、"{id}" は To Do アイテムの一意識別子に使用するプレースホルダーの変数です。 GetTodoItem が呼び出されると、その id パラメーター内のメソッドに URL の "{id}" の値が指定されます。

// GET: api/Todo/5
[HttpGet("{id}")]
public async Task<ActionResult<TodoItem>> GetTodoItem(long id)
{
    var todoItem = await _context.TodoItems.FindAsync(id);

    if (todoItem == null)
    {
        return NotFound();
    }

    return todoItem;
}

戻り値 2.1

GetTodoItemsGetTodoItem メソッドの戻り値の型は、ActionResult<T> 型です。 ASP.NET Core は自動的にオブジェクトを JSON にシリアル化して、応答メッセージの本文に JSON を書き込みます。 この戻り値の型の応答コードは 200 で、ハンドルされない例外がないものと想定します。 ハンドルされない例外は 5xx エラーに変換されます。

ActionResult 戻り値の型は、幅広い範囲の HTTP 状態コードを表すことができます。 たとえば、GetTodoItem は、次の 2 つの異なる状態値を返す可能性があります。

  • 要求された ID と一致するアイテムがない場合、メソッドは 404 NotFound エラー コードを返します。
  • それ以外の場合、メソッドは JSON 応答本文で 200 を返します。 戻り値が item の場合、HTTP 200 応答が返されます。

GetTodoItems メソッド 2.1 のテスト

このチュートリアルでは、Postman を使用して Web API をテストします。

  • Postman をインストールします。
  • Web アプリを起動します。
  • Postman を起動します。
  • [SSL 証明書の確認] を無効にします。
  • [ファイル] > [設定] ( [全般] タブ) で、 [SSL 証明書の確認] を無効にします。

警告

コントローラーをテストした後、SSL 証明書の検証を再度有効にします。

  • 新しい要求を作成します。
    • HTTP メソッドを GET に設定します。
    • 要求 URI を https://localhost:<port>/api/todo に設定します。 たとえば、https://localhost:5001/api/todo のようにします。
  • Postman で [Two pane view] を設定します。
  • [Send] を選択します。

Postman での Get 要求

Create メソッド 2.1 の追加

Controllers/TodoController.cs 内に次の PostTodoItem メソッドを追加します。

// POST: api/Todo
[HttpPost]
public async Task<ActionResult<TodoItem>> PostTodoItem(TodoItem item)
{
    _context.TodoItems.Add(item);
    await _context.SaveChangesAsync();

    return CreatedAtAction(nameof(GetTodoItem), new { id = item.Id }, item);
}

[HttpPost] 属性が示すように、上記のコードは HTTP POST メソッドです。 このメソッドは、HTTP 要求の本文から To Do アイテムの値を取得します。

CreatedAtAction メソッド:

  • 成功すると、HTTP 201 状態コードが返されます。 HTTP 201 は、サーバーに新しいリソースを作成する HTTP POST メソッドに対する標準の応答です。

  • Location ヘッダーを応答に追加します。 Location ヘッダーでは、新しく作成された To Do アイテムの URI を指定します。 詳細については、「10.2.2 201 Created」を参照してください。

  • GetTodoItem アクションを参照して Location ヘッダーの URI を作成します。 C# の nameof キーワードを使って、CreatedAtAction 呼び出しでアクション名をハードコーディングすることを回避しています。

    // GET: api/Todo/5
    [HttpGet("{id}")]
    public async Task<ActionResult<TodoItem>> GetTodoItem(long id)
    {
        var todoItem = await _context.TodoItems.FindAsync(id);
    
        if (todoItem == null)
        {
            return NotFound();
        }
    
        return todoItem;
    }
    

PostTodoItem メソッド 2.1 のテスト

  • プロジェクトをビルドします。

  • Postman で、HTTP メソッド名を POST に設定します。

  • URI を https://localhost:<port>/api/Todo に設定します。 たとえば、「 https://localhost:5001/api/Todo 」のように入力します。

  • [Body] タブを選択します。

  • [raw] ラジオ ボタンを選択します。

  • 型を [JSON (application/json)] に設定します。

  • 要求本文に、To Do アイテムの JSON を入力します。

    {
      "name":"walk dog",
      "isComplete":true
    }
    
  • [Send] を選択します。

    Postman での Create 要求

    405 (Method Not Allowed) エラーが発生した場合は、PostTodoItem メソッドの追加後にプロジェクトをコンパイルしていないことが原因である可能性があります。

場所ヘッダー URI 2.1 のテスト

  • [Response] ウィンドウで、 [Headers] タブを選択します。

  • [Location] ヘッダー値をコピーします。

    Postman コンソールの [Headers] タブ

  • メソッドを GET に設定します。

  • URI を https://localhost:<port>/api/todoitems/2 に設定します。 たとえば、「 https://localhost:5001/api/todoitems/2 」のように入力します。

  • [Send] を選択します。

PutTodoItem メソッド 2.1 の追加

次の PutTodoItem メソッドを追加します。

// PUT: api/Todo/5
[HttpPut("{id}")]
public async Task<IActionResult> PutTodoItem(long id, TodoItem item)
{
    if (id != item.Id)
    {
        return BadRequest();
    }

    _context.Entry(item).State = EntityState.Modified;
    await _context.SaveChangesAsync();

    return NoContent();
}

PutTodoItemPostTodoItem と似ていますが、HTTP PUT を使用します。 応答は 204 (No Content) となります。 HTTP 仕様に従って、PUT 要求では、変更だけでなく、更新されたエンティティ全体を送信するようクライアントに求めます。 部分的な更新をサポートするには、HTTP PATCH を使用します。

PutTodoItem 呼び出しでエラーが発生した場合、GET を呼び出してデータベース内にアイテムがあることを確認してください。

PutTodoItem メソッド 2.1 のテスト

このサンプルでは、アプリを起動するたびに開始することが必要なメモリ内データベースが使われています。 PUT 呼び出しを実行する前に、データベース内にアイテムが存在している必要があります。 GET を呼び出して、PUT 呼び出しを実行する前にデータベース内にアイテムが確実に存在していることを確認します。

Id = 1 の To Do アイテムを更新し、その名前を "feed fish" に設定します。

  {
    "id":1,
    "name":"feed fish",
    "isComplete":true
  }

次の図は、Postman の更新を示しています。

204 (No Content) の応答を示す Postman コンソール

DeleteTodoItem メソッド 2.1 の追加

次の DeleteTodoItem メソッドを追加します。

// DELETE: api/Todo/5
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteTodoItem(long id)
{
    var todoItem = await _context.TodoItems.FindAsync(id);

    if (todoItem == null)
    {
        return NotFound();
    }

    _context.TodoItems.Remove(todoItem);
    await _context.SaveChangesAsync();

    return NoContent();
}

DeleteTodoItem の応答は 204 (No Content) となります。

DeleteTodoItem メソッド 2.1 のテスト

Postman を使用して、To Do アイテムを削除します。

  • メソッドを DELETE に設定します。
  • 削除するオブジェクトの URI (たとえば、https://localhost:5001/api/todo/1) を設定します。
  • [Send] を選択します。

サンプル アプリではすべてのアイテムを削除することができます。 ただし、最後のアイテムが削除されると、次回 API が呼び出されたときに、モデル クラス コンストラクターによって新しいアイテムが作成されます。

JavaScript 2.1 を使用した Web API の呼び出し

このセクションでは、JavaScript を使用して Web API を呼び出す HTML ページを追加します。 jQuery によって要求が開始されます。 JavaScript により、Web API の応答からの詳細を使ってページが更新されます。

Startup.cs を次の強調表示されたコードで更新して、静的ファイルを提供し、既定のファイル マッピングを有効にするためのアプリを構成します。

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        // The default HSTS value is 30 days. You may want to change this for 
        // production scenarios, see https://aka.ms/aspnetcore-hsts.
        app.UseHsts();
    }

    app.UseDefaultFiles();
    app.UseStaticFiles();
    app.UseHttpsRedirection();
    app.UseMvc();
}

プロジェクト ディレクトリで wwwroot フォルダーを作成します。

index.html という名前の HTML ファイルを wwwroot ディレクトリに追加します。 その内容を次のマークアップに置き換えます。

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>To-do CRUD</title>
    <style>
        input[type='submit'], button, [aria-label] {
            cursor: pointer;
        }

        #spoiler {
            display: none;
        }

        table {
            font-family: Arial, sans-serif;
            border: 1px solid;
            border-collapse: collapse;
        }

        th {
            background-color: #0066CC;
            color: white;
        }

        td {
            border: 1px solid;
            padding: 5px;
        }
    </style>
</head>
<body>
    <h1>To-do CRUD</h1>
    <h3>Add</h3>
    <form action="javascript:void(0);" method="POST" onsubmit="addItem()">
        <input type="text" id="add-name" placeholder="New to-do">
        <input type="submit" value="Add">
    </form>

    <div id="spoiler">
        <h3>Edit</h3>
        <form class="my-form">
            <input type="hidden" id="edit-id">
            <input type="checkbox" id="edit-isComplete">
            <input type="text" id="edit-name">
            <input type="submit" value="Save">
            <a onclick="closeInput()" aria-label="Close">&#10006;</a>
        </form>
    </div>

    <p id="counter"></p>

    <table>
        <tr>
            <th>Is Complete</th>
            <th>Name</th>
            <th></th>
            <th></th>
        </tr>
        <tbody id="todos"></tbody>
    </table>

    <script src="https://code.jquery.com/jquery-3.3.1.min.js"
            integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
            crossorigin="anonymous"></script>
    <script src="site.js"></script>
</body>
</html>

site.js という名前の JavaScript ファイルを wwwroot ディレクトリに追加します。 その内容を次のコードに置き換えます。

const uri = "api/todo";
let todos = null;
function getCount(data) {
  const el = $("#counter");
  let name = "to-do";
  if (data) {
    if (data > 1) {
      name = "to-dos";
    }
    el.text(data + " " + name);
  } else {
    el.text("No " + name);
  }
}

$(document).ready(function() {
  getData();
});

function getData() {
  $.ajax({
    type: "GET",
    url: uri,
    cache: false,
    success: function(data) {
      const tBody = $("#todos");

      $(tBody).empty();

      getCount(data.length);

      $.each(data, function(key, item) {
        const tr = $("<tr></tr>")
          .append(
            $("<td></td>").append(
              $("<input/>", {
                type: "checkbox",
                disabled: true,
                checked: item.isComplete
              })
            )
          )
          .append($("<td></td>").text(item.name))
          .append(
            $("<td></td>").append(
              $("<button>Edit</button>").on("click", function() {
                editItem(item.id);
              })
            )
          )
          .append(
            $("<td></td>").append(
              $("<button>Delete</button>").on("click", function() {
                deleteItem(item.id);
              })
            )
          );

        tr.appendTo(tBody);
      });

      todos = data;
    }
  });
}

function addItem() {
  const item = {
    name: $("#add-name").val(),
    isComplete: false
  };

  $.ajax({
    type: "POST",
    accepts: "application/json",
    url: uri,
    contentType: "application/json",
    data: JSON.stringify(item),
    error: function(jqXHR, textStatus, errorThrown) {
      alert("Something went wrong!");
    },
    success: function(result) {
      getData();
      $("#add-name").val("");
    }
  });
}

function deleteItem(id) {
  $.ajax({
    url: uri + "/" + id,
    type: "DELETE",
    success: function(result) {
      getData();
    }
  });
}

function editItem(id) {
  $.each(todos, function(key, item) {
    if (item.id === id) {
      $("#edit-name").val(item.name);
      $("#edit-id").val(item.id);
      $("#edit-isComplete")[0].checked = item.isComplete;
    }
  });
  $("#spoiler").css({ display: "block" });
}

$(".my-form").on("submit", function() {
  const item = {
    name: $("#edit-name").val(),
    isComplete: $("#edit-isComplete").is(":checked"),
    id: $("#edit-id").val()
  };

  $.ajax({
    url: uri + "/" + $("#edit-id").val(),
    type: "PUT",
    accepts: "application/json",
    contentType: "application/json",
    data: JSON.stringify(item),
    success: function(result) {
      getData();
    }
  });

  closeInput();
  return false;
});

function closeInput() {
  $("#spoiler").css({ display: "none" });
}

ローカルで HTML ページをテストするために、ASP.NET Core プロジェクトの起動設定への変更が要求される場合があります。

  • Properties\launchSettings.json を開きます。
  • アプリが強制的に index.html—プロジェクトの既定ファイルで開くようにするには、launchUrl プロパティを削除します。

このサンプルでは、Web API のすべての CRUD メソッドを呼び出します。 次に、API の呼び出しについて説明します。

To Do アイテム 2.1 のリストの取得

jQuery により HTTP GET 要求が Web API に送信され、API からは To Do アイテムの配列を表す JSON が返されます。 要求が成功した場合、success コールバック関数が呼び出されます。 コールバックでは、DOM は To Do 情報で更新されます。

$(document).ready(function() {
  getData();
});

function getData() {
  $.ajax({
    type: "GET",
    url: uri,
    cache: false,
    success: function(data) {
      const tBody = $("#todos");

      $(tBody).empty();

      getCount(data.length);

      $.each(data, function(key, item) {
        const tr = $("<tr></tr>")
          .append(
            $("<td></td>").append(
              $("<input/>", {
                type: "checkbox",
                disabled: true,
                checked: item.isComplete
              })
            )
          )
          .append($("<td></td>").text(item.name))
          .append(
            $("<td></td>").append(
              $("<button>Edit</button>").on("click", function() {
                editItem(item.id);
              })
            )
          )
          .append(
            $("<td></td>").append(
              $("<button>Delete</button>").on("click", function() {
                deleteItem(item.id);
              })
            )
          );

        tr.appendTo(tBody);
      });

      todos = data;
    }
  });
}

To Do アイテム 2.1 の追加

jQuery により、要求本文に To Do アイテムが含まれる HTTP POST 要求が送信されます。 accepts オプションと contentType オプションは application/json に設定されて、送受信されるメディアの種類を指定します。 To Do アイテムは、JSON.stringify を使用して JSON に変換されます。 API で正常な状況コードが返された場合、getData 関数が呼び出され、HTML テーブルを更新します。

function addItem() {
  const item = {
    name: $("#add-name").val(),
    isComplete: false
  };

  $.ajax({
    type: "POST",
    accepts: "application/json",
    url: uri,
    contentType: "application/json",
    data: JSON.stringify(item),
    error: function(jqXHR, textStatus, errorThrown) {
      alert("Something went wrong!");
    },
    success: function(result) {
      getData();
      $("#add-name").val("");
    }
  });
}

To Do アイテム 2.1 の更新

To Do アイテムの更新は、追加操作に似ています。 アイテムの一意の識別子を追加するように url が変更され、typePUT となります。

$.ajax({
  url: uri + "/" + $("#edit-id").val(),
  type: "PUT",
  accepts: "application/json",
  contentType: "application/json",
  data: JSON.stringify(item),
  success: function(result) {
    getData();
  }
});

To Do アイテム 2.1 の削除

To Do アイテムを削除するには、DELETE への AJAX 呼び出しで type を設定して、URL でアイテムの一意の識別子を指定します。

$.ajax({
  url: uri + "/" + id,
  type: "DELETE",
  success: function(result) {
    getData();
  }
});

Web API 2.1 への認証サポートの追加

ASP.NET Core Identity では、ASP.NET Core Web アプリにユーザー インターフェイス (UI) ログイン機能が追加されます。 Web API と SPA をセキュリティで保護するには、次のいずれかを使用します。

IdentityServer4 は、ASP.NET Core 用の OpenID Connect および OAuth 2.0 フレームワークです。 IdentityServer4 により、次のセキュリティ機能が有効になります。

  • サービスとしての認証 (AaaS)
  • 複数のアプリケーションの種類でのシングル サインオン/オフ (SSO)
  • API のアクセス制御
  • Federation Gateway

詳細については、「ようこそ! IdentityServer4」を参照してください。

その他のリソース 2.1

このチュートリアルのサンプル コードを表示またはダウンロードします。 ダウンロード方法に関するページを参照してください。

詳細については、次のリソースを参照してください。