次の方法で共有


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

Note

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

警告

このバージョンの ASP.NET Core はサポート対象から除外されました。 詳細については、「.NET および .NET Core サポート ポリシー」を参照してください。 現在のリリースについては、この記事の .NET 8 バージョンを参照してください。

重要

この情報はリリース前の製品に関する事項であり、正式版がリリースされるまでに大幅に変更される可能性があります。 Microsoft はここに示されている情報について、明示か黙示かを問わず、一切保証しません。

現在のリリースについては、この記事の .NET 8 バージョンを参照してください。

作成者: 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

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

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

前提条件

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 を選択します。
  • 右側のウィンドウで [プロジェクト] チェックボックスをオンにして、 [インストール] を選択します。

Note

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

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

プロジェクト テンプレートにより、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 の有用なドキュメントやヘルプ ページを生成するために使用されます。 このチュートリアルでは、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 フォルダーを右クリックしします。

  • [追加]>New Scaffolded Item を選択します。

  • [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 アプリ パターン

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

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

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

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

前提条件

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 に次のダイアログが表示されます。

このプロジェクトは 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 の有用なドキュメントやヘルプ ページを生成するために使用されます。 このチュートリアルでは、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 フォルダーを右クリックしします。

  • [追加]>New Scaffolded Item を選択します。

  • [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 アプリ パターン

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

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

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

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

前提条件

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 を選択します。
  • 右側のウィンドウで [プロジェクト] チェックボックスをオンにして、 [インストール] を選択します。

Note

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

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

プロジェクト テンプレートにより、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 の有用なドキュメントやヘルプ ページを生成するために使用されます。 このチュートリアルでは、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 フォルダーを右クリックしします。

  • [追加]>New Scaffolded Item を選択します。

  • [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 アプリ パターン

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

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 アプリをデプロイする」を参照してください。

その他の技術情報

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

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