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

作成者: Rick Anderson および Kirk Larkin

このチュートリアルでは、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 プロジェクトの作成

  • [ファイル] メニューで、 [新規作成] > [プロジェクト] の順に選択します。
  • 検索ボックスに「Web API」と入力します。
  • [ASP.NET Core Web API] テンプレートを選択し、 [次へ] を選択します。
  • [新しいプロジェクトの構成] ダイアログで、プロジェクトに TodoApi という名前を付けて、 [次へ] を選択します。
  • [追加情報] ダイアログで、 [フレームワーク][.NET 6.0 (プレビュー)] であることを確認し、 [作成] を選択します。

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

プロジェクト テンプレートにより、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 で既定のブラウザーが起動し、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 を選択します。
  • 右側のウィンドウで [プロジェクト] チェックボックスをオンにして、 [インストール] を選択します。

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

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

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

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

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

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

using Microsoft.EntityFrameworkCore;
using TodoApi.Models;


var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

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

var app = builder.Build();

// Configure the HTTP request pipeline.
if (builder.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
    //app.UseSwagger();
    //app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "TodoApi v1"));
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

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

  • 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 ステートメントを更新します。

[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 呼び出しでアクション名をハードコーディングすることを回避しています。

http-repl のインストール

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

  • コマンド プロンプトで次のコマンドを実行します。

    dotnet tool install -g Microsoft.dotnet-httprepl
    
  • .NET 5.0 SDK またはランタイムがインストールされていない場合は、.NET 5.0 ランタイムをインストールします。

PostTodoItem のテスト

  • Ctrl キーを押しながら F5 キーを押して、アプリを実行します。

  • 新しいターミナル ウィンドウを開き、次のコマンドを実行します。 アプリで別のポート番号を使用する場合は、httprepl コマンドの 5001 をお使いのポート番号に置き換えてください。

    httprepl https://localhost:5001/api/todoitems
    post -h Content-Type=application/json -c "{"name":"walk dog","isComplete":true}"
    

    このコマンドの出力例は次のとおりです。

    HTTP/1.1 201 Created
    Content-Type: application/json; charset=utf-8
    Date: Tue, 07 Sep 2021 20:39:47 GMT
    Location: https://localhost:5001/api/TodoItems/1
    Server: Kestrel
    Transfer-Encoding: chunked
    
    {
      "id": 1,
      "name": "walk dog",
      "isComplete": true
    }
    

場所ヘッダー URI のテスト

場所ヘッダーをテストするには、それをコピーして httprepl get コマンドに貼り付けます。

次の例では、httprepl セッション内であることを前提としています。 前の httprepl セッションを終了した場合は、次のコマンドで connecthttprepl に置き換えてください。

connect https://localhost:5001/api/todoitems/1
get

このコマンドの出力例は次のとおりです。

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Tue, 07 Sep 2021 20:48:10 GMT
Server: Kestrel
Transfer-Encoding: chunked

{
  "id": 1,
  "name": "walk dog",
  "isComplete": true
}

GET メソッドの確認

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

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

/api/todoitems/{id} ルートの例は既に確認しました。 /api/todoitems ルートをテストします。

connect https://localhost:5001/api/todoitems
get

このコマンドの出力例は次のとおりです。

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Tue, 07 Sep 2021 20:59:21 GMT
Server: Kestrel
Transfer-Encoding: chunked

[
  {
    "id": 1,
    "name": "walk dog",
    "isComplete": true
  }
]

今回返される JSON は 1 つのアイテムの配列です。

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

ルーティングと URL パス

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

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

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

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

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

[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 メソッドを検証します。

[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" に設定します。

connect https://localhost:5001/api/todoitems/1
put -h Content-Type=application/json -c "{"id":1,"name":"feed fish","isComplete":true}"

このコマンドの出力例は次のとおりです。

HTTP/1.1 204 No Content
Date: Tue, 07 Sep 2021 21:20:47 GMT
Server: Kestrel

DeleteTodoItem メソッド

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

[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 メソッドのテスト

Id = 1 の To Do アイテムを削除します。

connect https://localhost:5001/api/todoitems/1
delete

このコマンドの出力例は次のとおりです。

HTTP/1.1 204 No Content
Date: Tue, 07 Sep 2021 21:43:00 GMT
Server: Kestrel

過剰な投稿を防止する

現在、サンプル アプリでは 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 モデルを作成します。

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

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

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using TodoApi.Models;

namespace TodoApi.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class TodoItemsController : ControllerBase
    {
        private readonly TodoContext _context;

        public TodoItemsController(TodoContext context)
        {
            _context = context;
        }

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

        // GET: api/TodoItems/5
        [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);
        }
        // PUT: api/TodoItems/5
        // To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
        [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();
        }
        // POST: api/TodoItems
        // To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
        [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));
        }

        // 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();
        }

        private bool TodoItemExists(long id)
        {
            return _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 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 に認証サポートを追加

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」を参照してください。

その他の技術情報

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

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