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

Note

これは、この記事の最新バージョンではありません。 現在のリリースについては、この記事の ASP.NET Core 8.0 バージョンを参照してください。

作成者: Rick Anderson および Kirk Larkin

このチュートリアルでは、データベースを使用するコントローラー ベースの Web API の構築の基本について説明します。 ASP.NET Core で API を作成するもう 1 つの方法は、"最小 API" を作成することです。 最小 API とコントローラー ベースの API の選択に関するヘルプについては、API の概要に関する記事をご覧ください。 最小 API の作成に関するチュートリアルについては、「チュートリアル: ASP.NET Core を使って最小 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

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

The client is represented by a box on the left. It submits a request and receives a response from the application, a box drawn on the right. Within the application box, three boxes represent the controller, the model, and the data access layer. The request comes into the application's controller, and read/write operations occur between the controller and the data access layer. The model is serialized and returned to the client in the response.

必須コンポーネント

Web プロジェクトの作成

  • [ファイル] メニューで [新規作成]>[プロジェクト] の順に選択します。
  • 検索ボックスに「Web API」と入力します。
  • [ASP.NET Core Web API] テンプレートを選択し、 [次へ] を選択します。
  • [新しいプロジェクトの構成] ダイアログで、プロジェクトに TodoApi という名前を付けて、[次へ] を選択します。
  • [追加情報] ダイアログで、次を行います。
    • [フレームワーク][.NET 8.0 (長期的なサポート)] になっていることを確認します。
    • [コントローラーを使用する (最小限の API を使用する場合はオフにします)] チェック ボックスがオンになっていることを確認します。
    • [Enable OpenAPI support] (OpenAPI サポートを有効にする) のチェックボックスがオンになっていることを確認します。
    • [作成] を選択します

NuGet パッケージの追加

このチュートリアルで使うデータベースをサポートするには、NuGet パッケージを追加する必要があります。

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

メモ

.NET アプリへのパッケージの追加に関するガイダンスについては、「パッケージ利用のワークフロー」 (NuGet ドキュメント) の "パッケージのインストールと管理" に関する記事を参照してください。 NuGet.org で正しいパッケージ バージョンを確認します。

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

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

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

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

This project is configured to use SSL. To avoid SSL warnings in the browser you can choose to trust the self-signed certificate that IIS Express has generated. Would you like to trust the IIS Express SSL certificate?

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

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

Security warning dialog

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

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 の有用なドキュメントやヘルプ ページを生成するために使用されます。 このチュートリアルでは、Swagger を使ってアプリをテストします。 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"
    }
]

モデル クラスの追加

モデルは、アプリが管理するデータを表すクラスのセットです。 このアプリのモデルは、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 クラスから派生させて作成します。

  • 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; } = null!;
    }
    

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

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

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

using Microsoft.EntityFrameworkCore;
using TodoApi.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddDbContext<TodoContext>(opt =>
    opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

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

  • 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 要求の本文から TodoItem の値を取得します。

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

PostTodoItem のテスト

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

  • Swagger ブラウザー ウィンドウで、[POST /api/TodoItems] を選択し、次に [試してみる] を選択します。

  • 要求本文の入力ウィンドウで、JSON を更新します。 たとえば、オブジェクトに適用された

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

    Swagger POST

場所ヘッダー URI のテスト

上記の POST では、Swagger UI の [応答ヘッダー]場所ヘッダーが表示されています。 たとえば、「 location: https://localhost:7260/api/TodoItems/1 」のように入力します。 場所ヘッダーには、作成されたリソースへの URI が表示されます。

場所ヘッダーをテストするには:

  • Swagger ブラウザー ウィンドウで、[GET /api/TodoItems/{id}] を選択し、次に [試してみる] を選択します。

  • id 入力ボックスに 1 を入力し、[実行] を選択します。

    Swagger GET

GET メソッドの確認

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

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

前のセクションでは、/api/todoitems/{id} ルートの例を示しました。

POST の指示に従って別の todo 項目を追加し、Swagger を使用して /api/todoitems ルートをテストします。

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

ルーティングと URL パス

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

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

    [Route("api/[controller]")]
    [ApiController]
    public class TodoItemsController : ControllerBase
    
  • [controller] をコントローラーの名前 (慣例では "Controller" サフィックスを除くコントローラー クラス名) に置き換えます。 このサンプルでは、コントローラー クラス名は TodoItemsController なので、コントローラー名は "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;
}

戻り値

GetTodoItems メソッドと GetTodoItem メソッドの戻り値の型は、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 メソッドのテスト

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

Swagger UI を使用して、PUT ボタンを使用して Id = 1 の TodoItem を更新し、その名前を "feed fish" に設定します。 応答は HTTP 204 No Content であることに注意してください。

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

Swagger UI を使用して、Id = 1 の TodoItem を削除します。 応答は HTTP 204 No Content であることに注意してください。

他のツールでテストする

Web API のテストには、他にも使用できるツールが多数あります。次に例を示します。

詳細については、次を参照してください。

過剰な投稿を防止する

現在、サンプル アプリでは 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
    // <snippet_GetByID>
    [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);
    }
    // </snippet_GetByID>

    // PUT: api/TodoItems/5
    // To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
    // <snippet_Update>
    [HttpPut("{id}")]
    public async Task<IActionResult> PutTodoItem(long id, TodoItemDTO todoDTO)
    {
        if (id != todoDTO.Id)
        {
            return BadRequest();
        }

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

        todoItem.Name = todoDTO.Name;
        todoItem.IsComplete = todoDTO.IsComplete;

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

        return NoContent();
    }
    // </snippet_Update>

    // POST: api/TodoItems
    // To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
    // <snippet_Create>
    [HttpPost]
    public async Task<ActionResult<TodoItemDTO>> PostTodoItem(TodoItemDTO todoDTO)
    {
        var todoItem = new TodoItem
        {
            IsComplete = todoDTO.IsComplete,
            Name = todoDTO.Name
        };

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

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

    // 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 ビデオ シリーズ

ビデオ: 「ビギナーズ シリーズ: Web API」を参照してください。

信頼性の高い Web アプリ パターン

.NET 用の信頼性の高い Web アプリ パターンYouTube のビデオ記事を参照して、ゼロからでも既存のアプリをリファクタリングしても、信頼性が高く、パフォーマンスが高く、テスト可能で、コスト効率が高く、スケーラブルな ASP.NET Core アプリを作成する方法に関するガイダンスを参照してください。

Web API に認証サポートを追加

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

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

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

重要

Duende Software により、Duende Identity Server を実稼働で使用することのライセンス料の支払いが求められる場合があります。 詳細については、「ASP.NET Core 5.0 から 6.0 への移行」を参照してください。

詳細については、Duende Identity Server に関するドキュメント (Duende ソフトウェアの Web サイト) を参照してください。

Azure に発行する

Azure へのデプロイについては、「クイックスタート: ASP.NET Web アプリをデプロイする」を参照してください。

その他の技術情報

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

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

このチュートリアルでは、データベースを使用するコントローラー ベースの Web API の構築の基本について説明します。 ASP.NET Core で API を作成するもう 1 つの方法は、"最小 API" を作成することです。 最小 API とコントローラー ベースの API の選択に関するヘルプについては、API の概要に関する記事をご覧ください。 最小 API の作成に関するチュートリアルについては、「チュートリアル: ASP.NET Core を使って最小 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

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

The client is represented by a box on the left. It submits a request and receives a response from the application, a box drawn on the right. Within the application box, three boxes represent the controller, the model, and the data access layer. The request comes into the application's controller, and read/write operations occur between the controller and the data access layer. The model is serialized and returned to the client in the response.

必須コンポーネント

Web プロジェクトの作成

  • [ファイル] メニューで [新規作成]>[プロジェクト] の順に選択します。
  • 検索ボックスに「Web API」と入力します。
  • [ASP.NET Core Web API] テンプレートを選択し、 [次へ] を選択します。
  • [新しいプロジェクトの構成] ダイアログで、プロジェクトに TodoApi という名前を付けて、[次へ] を選択します。
  • [追加情報] ダイアログで、次を行います。
    • [フレームワーク].NET 7.0 (またはそれ以降) であることを確認します。
    • [コントローラーを使用する (最小限の API を使用する場合はオフにします)] チェック ボックスがオンになっていることを確認します。
    • 作成 を選択します。

Note

.NET アプリへのパッケージの追加に関するガイダンスについては、「パッケージ利用のワークフロー」 (NuGet ドキュメント) の "パッケージのインストールと管理" に関する記事を参照してください。 NuGet.org で正しいパッケージ バージョンを確認します。

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

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

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

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

This project is configured to use SSL. To avoid SSL warnings in the browser you can choose to trust the self-signed certificate that IIS Express has generated. Would you like to trust the IIS Express SSL certificate?

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

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

Security warning dialog

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

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"
    }
]

モデル クラスの追加

モデルは、アプリが管理するデータを表すクラスのセットです。 このアプリのモデルは、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;
    
    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);

builder.Services.AddControllers();
builder.Services.AddDbContext<TodoContext>(opt =>
    opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

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

  • 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 要求の本文から TodoItem の値を取得します。

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

PostTodoItem のテスト

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

  • Swagger ブラウザー ウィンドウで、[POST /api/TodoItems] を選択し、次に [試してみる] を選択します。

  • 要求本文の入力ウィンドウで、JSON を更新します。 たとえば、オブジェクトに適用された

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

    Swagger POST

場所ヘッダー URI のテスト

上記の POST では、Swagger UI の [応答ヘッダー]場所ヘッダーが表示されています。 たとえば、「 location: https://localhost:7260/api/TodoItems/1 」のように入力します。 場所ヘッダーには、作成されたリソースへの URI が表示されます。

場所ヘッダーをテストするには:

  • Swagger ブラウザー ウィンドウで、[GET /api/TodoItems/{id}] を選択し、次に [試してみる] を選択します。

  • id 入力ボックスに 1 を入力し、[実行] を選択します。

    Swagger GET

GET メソッドの確認

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

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

前のセクションでは、/api/todoitems/{id} ルートの例を示しました。

POST の指示に従って別の todo 項目を追加し、Swagger を使用して /api/todoitems ルートをテストします。

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

ルーティングと URL パス

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

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

    [Route("api/[controller]")]
    [ApiController]
    public class TodoItemsController : ControllerBase
    
  • [controller] をコントローラーの名前 (慣例では "Controller" サフィックスを除くコントローラー クラス名) に置き換えます。 このサンプルでは、コントローラー クラス名は TodoItemsController なので、コントローラー名は "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;
}

戻り値

GetTodoItems メソッドと GetTodoItem メソッドの戻り値の型は、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 メソッドのテスト

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

Swagger UI を使用して、PUT ボタンを使用して Id = 1 の TodoItem を更新し、その名前を "feed fish" に設定します。 応答は HTTP 204 No Content であることに注意してください。

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

Swagger UI を使用して、Id = 1 の TodoItem を削除します。 応答は HTTP 204 No Content であることに注意してください。

http-repl、Postman、または curl を使用してテストする

http-replPostmancurl は、API のテストによく使用されます。 Swagger は curl を使用し、送信された curl コマンドを表示します。

これらのツールの手順については、次のリンクを参照してください。

http-repl の詳細については、「HttpRepl を使用して Web API をテストする」を参照してください。

過剰な投稿を防止する

現在、サンプル アプリでは 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
    // <snippet_GetByID>
    [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);
    }
    // </snippet_GetByID>

    // PUT: api/TodoItems/5
    // To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
    // <snippet_Update>
    [HttpPut("{id}")]
    public async Task<IActionResult> PutTodoItem(long id, TodoItemDTO todoDTO)
    {
        if (id != todoDTO.Id)
        {
            return BadRequest();
        }

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

        todoItem.Name = todoDTO.Name;
        todoItem.IsComplete = todoDTO.IsComplete;

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

        return NoContent();
    }
    // </snippet_Update>

    // POST: api/TodoItems
    // To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
    // <snippet_Create>
    [HttpPost]
    public async Task<ActionResult<TodoItemDTO>> PostTodoItem(TodoItemDTO todoDTO)
    {
        var todoItem = new TodoItem
        {
            IsComplete = todoDTO.IsComplete,
            Name = todoDTO.Name
        };

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

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

    // 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 ビデオ シリーズ

ビデオ: 「ビギナーズ シリーズ: Web API」を参照してください。

信頼性の高い Web アプリ パターン

.NET 用の信頼性の高い Web アプリ パターンYouTube のビデオ記事を参照して、ゼロからでも既存のアプリをリファクタリングしても、信頼性が高く、パフォーマンスが高く、テスト可能で、コスト効率が高く、スケーラブルな ASP.NET Core アプリを作成する方法に関するガイダンスを参照してください。

Web API に認証サポートを追加

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

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

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

重要

Duende Software により、Duende Identity Server を実稼働で使用することのライセンス料の支払いが求められる場合があります。 詳細については、「ASP.NET Core 5.0 から 6.0 への移行」を参照してください。

詳細については、Duende Identity Server に関するドキュメント (Duende ソフトウェアの Web サイト) を参照してください。

Azure に発行する

Azure へのデプロイについては、「クイックスタート: ASP.NET Web アプリをデプロイする」を参照してください。

その他の技術情報

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

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

このチュートリアルでは、データベースを使用するコントローラー ベースの Web API の構築の基本について説明します。 ASP.NET Core で API を作成するもう 1 つの方法は、"最小 API" を作成することです。 最小 API とコントローラー ベースの API の選択に関するヘルプについては、API の概要に関する記事をご覧ください。 最小 API の作成に関するチュートリアルについては、「チュートリアル: ASP.NET Core を使って最小 API を作成する」をご覧ください。

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

  • Web API プロジェクトを作成する。
  • モデル クラスとデータベース コンテキストを追加する。
  • CRUD メソッドを使用してコントローラーのスキャフォールディング。
  • ルーティング、URL パス、戻り値を構成する。
  • http-repl で 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

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

The client is represented by a box on the left. It submits a request and receives a response from the application, a box drawn on the right. Within the application box, three boxes represent the controller, the model, and the data access layer. The request comes into the application's controller, and read/write operations occur between the controller and the data access layer. The model is serialized and returned to the client in the response.

必須コンポーネント

Web プロジェクトの作成

  • [ファイル] メニューで [新規作成]>[プロジェクト] の順に選択します。
  • 検索ボックスに「Web API」と入力します。
  • [ASP.NET Core Web API] テンプレートを選択し、 [次へ] を選択します。
  • [新しいプロジェクトの構成] ダイアログで、プロジェクトに TodoApi という名前を付けて、[次へ] を選択します。
  • [追加情報] ダイアログで、次を行います。
    • [フレームワーク][.NET 6.0 (長期的なサポート)] になっていることを確認します。
    • [コントローラーを使用する (最小限の API を使用する場合はオフにします)] チェック ボックスがオンになっていることを確認します。
    • 作成 を選択します。

Note

.NET アプリへのパッケージの追加に関するガイダンスについては、「パッケージ利用のワークフロー」 (NuGet ドキュメント) の "パッケージのインストールと管理" に関する記事を参照してください。 NuGet.org で正しいパッケージ バージョンを確認します。

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

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

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

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

This project is configured to use SSL. To avoid SSL warnings in the browser you can choose to trust the self-signed certificate that IIS Express has generated. Would you like to trust the IIS Express SSL certificate?

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

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

Security warning dialog

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

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 バイナリのアーキテクチャは、現在実行中の OS アーキテクチャを表します。 別の OS アーキテクチャを指定するには、「dotnet tool install, --arch option」を参照してください。 詳細については、GitHub イシュー dotnet/AspNetCore.Docs #29262 を参照してください。

  • .NET 6.0 SDK またはランタイムがインストールされていない場合は、.NET 6.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" サフィックスを除くコントローラー クラス名) に置き換えます。 このサンプルでは、コントローラー クラス名は TodoItemsController なので、コントローラー名は "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;
}

戻り値

GetTodoItems メソッドと GetTodoItem メソッドの戻り値の型は、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 ビデオ シリーズ

ビデオ: 「ビギナーズ シリーズ: Web API」を参照してください。

Web API に認証サポートを追加

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

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

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

重要

Duende Software により、Duende Identity Server を実稼働で使用することのライセンス料の支払いが求められる場合があります。 詳細については、「ASP.NET Core 5.0 から 6.0 への移行」を参照してください。

詳細については、Duende Identity Server に関するドキュメント (Duende ソフトウェアの Web サイト) を参照してください。

Azure に発行する

Azure へのデプロイについては、「クイックスタート: ASP.NET Web アプリをデプロイする」を参照してください。

その他の技術情報

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

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

このチュートリアルでは、データベースを使用するコントローラー ベースの Web API の構築の基本について説明します。 ASP.NET Core で API を作成するもう 1 つの方法は、"最小 API" を作成することです。 最小 API とコントローラー ベースの API の選択に関するヘルプについては、API の概要に関する記事をご覧ください。 最小 API の作成に関するチュートリアルについては、「チュートリアル: ASP.NET Core を使って最小 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

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

The client is represented by a box on the left. It submits a request and receives a response from the application, a box drawn on the right. Within the application box, three boxes represent the controller, the model, and the data access layer. The request comes into the application's controller, and read/write operations occur between the controller and the data access layer. The model is serialized and returned to the client in the response.

必須コンポーネント

Web プロジェクトの作成

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

VS new project dialog

Note

.NET アプリへのパッケージの追加に関するガイダンスについては、「パッケージ利用のワークフロー」 (NuGet ドキュメント) の "パッケージのインストールと管理" に関する記事を参照してください。 NuGet.org で正しいパッケージ バージョンを確認します。

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

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

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

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

This project is configured to use SSL. To avoid SSL warnings in the browser you can choose to trust the self-signed certificate that IIS Express has generated. Would you like to trust the IIS Express SSL certificate?

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

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

Security warning dialog

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

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 Package Manager

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 が指定されます。 詳細については、「201 Created」を参照してください。
  • GetTodoItem アクションを参照して Location ヘッダーの URI を作成します。 C# の nameof キーワードを使って、CreatedAtAction 呼び出しでアクション名をハードコーディングすることを回避しています。

Postman のインストール

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

  • Postman をインストールします。
  • Web アプリを起動します。
  • Postman を起動します。
  • [SSL 証明書の確認] を無効にします。
    • Windows 用 Postman: [ファイル]>[設定] ([全般] タブ) の順に選択し、[SSL 証明書の確認] を無効にします。
    • macOS 用 Postman: [Postman]>[設定] ([全般] タブ) の順に選択し、[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 with create request

場所ヘッダー URI のテスト

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

Postman でテストするには:

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

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

    Headers tab of the Postman console

  • 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" サフィックスを除くコントローラー クラス名) に置き換えます。 このサンプルでは、コントローラー クラス名は TodoItemsController なので、コントローラー名は "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;
}

戻り値

GetTodoItems メソッドと GetTodoItem メソッドの戻り値の型は、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 の更新を示しています。

Postman console showing 204 (No Content) response

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 に認証サポートを追加

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

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

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

重要

Duende Software により、Duende Identity Server を実稼働で使用することのライセンス料の支払いが求められる場合があります。 詳細については、「ASP.NET Core 5.0 から 6.0 への移行」を参照してください。

詳細については、Duende Identity Server に関するドキュメント (Duende ソフトウェアの Web サイト) を参照してください。

Azure に発行する

Azure へのデプロイについては、「クイックスタート: ASP.NET Web アプリをデプロイする」を参照してください。

その他の技術情報

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

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

このチュートリアルでは、データベースを使用するコントローラー ベースの Web API の構築の基本について説明します。 ASP.NET Core で API を作成するもう 1 つの方法は、"最小 API" を作成することです。 最小 API とコントローラー ベースの API の選択に関するヘルプについては、API の概要に関する記事をご覧ください。 最小 API の作成に関するチュートリアルについては、「チュートリアル: ASP.NET Core を使って最小 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

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

The client is represented by a box on the left. It submits a request and receives a response from the application, a box drawn on the right. Within the application box, three boxes represent the controller, the model, and the data access layer. The request comes into the application's controller, and read/write operations occur between the controller and the data access layer. The model is serialized and returned to the client in the response.

必須コンポーネント

Web プロジェクトの作成

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

VS new project dialog

Note

.NET アプリへのパッケージの追加に関するガイダンスについては、「パッケージ利用のワークフロー」 (NuGet ドキュメント) の "パッケージのインストールと管理" に関する記事を参照してください。 NuGet.org で正しいパッケージ バージョンを確認します。

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 Package Manager

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 が指定されます。 詳細については、「201 Created」を参照してください。
  • GetTodoItem アクションを参照して Location ヘッダーの URI を作成します。 C# の nameof キーワードを使って、CreatedAtAction 呼び出しでアクション名をハードコーディングすることを回避しています。

Postman のインストール

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

  • Postman をインストールします。
  • Web アプリを起動します。
  • Postman を起動します。
  • [SSL 証明書の確認] を無効にします。
    • Windows 用 Postman: Windows 用 Postman [ファイル]>[設定] ([全般] タブ)、[SSL 証明書の確認] を無効にします。
    • Windows 用 macOS: Windows 用 Postman [Postman]>[設定] ([全般] タブ)、[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 with create request

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

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

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

    Headers tab of the Postman console

  • 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" サフィックスを除くコントローラー クラス名) に置き換えます。 このサンプルでは、コントローラー クラス名は TodoItemsController なので、コントローラー名は "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;
}

戻り値

GetTodoItems メソッドと GetTodoItem メソッドの戻り値の型は、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 の更新を示しています。

Postman console showing 204 (No Content) response

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 をセキュリティで保護するには、次のいずれかを使用します。

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

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

重要

Duende Software により、Duende Identity Server を実稼働で使用することのライセンス料の支払いが求められる場合があります。 詳細については、「ASP.NET Core 5.0 から 6.0 への移行」を参照してください。

詳細については、Duende Identity Server に関するドキュメント (Duende ソフトウェアの Web サイト) を参照してください。

Azure に発行する

Azure へのデプロイについては、「クイックスタート: ASP.NET Web アプリをデプロイする」を参照してください。

その他の技術情報

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

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