注
これは、この記事の最新バージョンではありません。 現在のリリースについては、 この記事の .NET 9 バージョンを参照してください。
警告
このバージョンの ASP.NET Core はサポート対象から除外されました。 詳細については、 .NET および .NET Core サポート ポリシーを参照してください。 現在のリリースについては、 この記事の .NET 9 バージョンを参照してください。
重要
この情報はリリース前の製品に関する事項であり、正式版がリリースされるまでに大幅に変更される可能性があります。 Microsoft はここに示されている情報について、明示か黙示かを問わず、一切保証しません。
現在のリリースについては、 この記事の .NET 9 バージョンを参照してください。
Minimal API は、依存関係が最小限の HTTP API を作成するために設計されています。 ASP.NET Core での最小限のファイル、機能、依存関係のみを含むマイクロサービスやアプリに最適です。
このチュートリアルでは、ASP.NET Core で最小 API を構築するための基本について説明します。 ASP.NET Core で API を作成するもう 1 つの方法は、コントローラーを使用することです。 最小限の API とコントローラー ベースの API の選択に関するヘルプについては、 API の概要を参照してください。 その他の機能を含む コントローラー に基づいて API プロジェクトを作成するチュートリアルについては、「 Web API の作成」を参照してください。
概要
このチュートリアルでは、次の API を作成します。
API(アプリケーション・プログラミング・インターフェース) | 説明 | 要求本文 | 応答本文 |
---|---|---|---|
GET /todoitems |
すべての To Do アイテムを取得します。 | 無し | To Do アイテムの配列 |
GET /todoitems/complete |
完了した To Do 項目を取得します。 | 無し | To Do アイテムの配列 |
GET /todoitems/{id} |
ID でアイテムを取得します。 | 無し | To Do アイテム |
POST /todoitems |
新しいアイテムを追加します。 | To Do アイテム | To Do アイテム |
PUT /todoitems/{id} |
既存のアイテムを更新します。 | To Do アイテム | 無し |
DELETE /todoitems/{id} |
アイテムを削除します。 | 無し | 無し |
必須コンポーネント
ASP.NET および Web 開発ワークロードを含む Visual Studio 2022。
API プロジェクトを作成する
Visual Studio 2022 を起動し、[ 新しいプロジェクトの作成] を選択します。
[ 新しいプロジェクトの作成] ダイアログで、次の手順を 実行します。
-
Empty
ボックスに、「」と入力します。 - ASP.NET Core Empty テンプレートを選択し、[次へ] を選択します。
-
プロジェクトに TodoApi という名前を付け、[ 次へ] を選択します。
[追加情報] ダイアログで、次の 手順 を実行します。
- .NET 9.0 を選択する
- [最上位レベルのステートメントを使用しない] をオフにする
- [作成]を選択する
コードを確認する
Program.cs
ファイルには、次のコードが含まれています。
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
上記のコードでは次の操作が行われます。
- 事前に構成された既定値で WebApplicationBuilder と WebApplication を作成します。
-
/
を返す HTTP GET エンドポイントHello World!
を作成します。
アプリを実行する
Ctrl + F5 キーを押して、デバッガーなしで実行します。
Visual Studio に次のダイアログが表示されます。
IIS Express SSL 証明書を信頼する場合は、[ はい ] を選択します。
次のダイアログが表示されます。
開発証明書を信頼することに同意する場合は、[ はい ] を選択します。
Firefox ブラウザーを信頼する方法については、 Firefox SEC_ERROR_INADEQUATE_KEY_USAGE証明書エラーに関する記事を参照してください。
Visual Studio によって Kestrel Web サーバー が起動され、ブラウザー ウィンドウが開きます。
ブラウザーに Hello World!
が表示されます。
Program.cs
ファイルには、最小限の完成されたアプリが含まれています。
ブラウザー ウィンドウを閉じます。
NuGet パッケージを追加する
このチュートリアルで使用するデータベースと診断をサポートするには、NuGet パッケージを追加する必要があります。
- [ ツール ] メニューの [ NuGet パッケージ マネージャー] > [ソリューションの NuGet パッケージの管理] を選択します。
- 参照 タブを選択します。
- [ プレリリースを含める] を選択します。
- 検索ボックスに 「Microsoft.EntityFrameworkCore.InMemory 」と入力し、
Microsoft.EntityFrameworkCore.InMemory
を選択します。 - 右側のウィンドウで [ プロジェクト ] チェック ボックスをオンにし、[ インストール] を選択します。
- 上記の手順にしたがって、
Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
パッケージを追加します。
モデルおよびデータベース コンテキスト クラス
- project フォルダーで、次のコードを含む
Todo.cs
という名前のファイルを作成します。
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
}
上記のコードでは、このアプリのモデルを作成します。 "モデル" は、アプリが管理するデータを表すクラスです。
- 次のコードのファイルを、
TodoDb.cs
という名前で作成します。
using Microsoft.EntityFrameworkCore;
class TodoDb : DbContext
{
public TodoDb(DbContextOptions<TodoDb> options)
: base(options) { }
public DbSet<Todo> Todos => Set<Todo>();
}
上記のコードでは、データ モデルの Entity Framework 機能を調整するメイン クラスであるデータベース コンテキストを定義しています。 このクラスは Microsoft.EntityFrameworkCore.DbContext クラスから派生したクラスです。
API コードを追加する
-
Program.cs
ファイルの内容を次のコードに置き換えます。
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
app.MapGet("/todoitems", async (TodoDb db) =>
await db.Todos.ToListAsync());
app.MapGet("/todoitems/complete", async (TodoDb db) =>
await db.Todos.Where(t => t.IsComplete).ToListAsync());
app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound());
app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
});
app.MapPut("/todoitems/{id}", async (int id, Todo inputTodo, TodoDb db) =>
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return Results.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return Results.NoContent();
});
app.MapDelete("/todoitems/{id}", async (int id, TodoDb db) =>
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return Results.NoContent();
}
return Results.NotFound();
});
app.Run();
次の強調表示されたコードは、データベース コンテキストを 依存関係挿入 (DI) コンテナーに追加し、データベース関連の例外を表示できるようにします。
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
DI コンテナーは、データベース コンテキストやその他のサービスへのアクセスを提供します。
このチュートリアルでは 、エンドポイント エクスプローラーと .http ファイル を使用して API をテストします。
データの POST をテストする
次の Program.cs
のコードにより、データをメモリ内データベースに追加する HTTP POST のエンドポイント /todoitems
が作成されます。
app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
});
アプリを実行します。
/
エンドポイントが存在しなくなったため、ブラウザーに 404 エラーが表示されます。
POST エンドポイントは、アプリにデータを追加するために使われます。
表示>その他の Windows>Endpoints エクスプローラーを選択します。
POST エンドポイントを右クリックし、[要求の生成] を選択します。
次の例のような内容の
TodoApi.http
という新しいファイルがプロジェクト フォルダー内に作成されます。@TodoApi_HostAddress = https://localhost:7031 POST {{TodoApi_HostAddress}}/todoitems ###
- 最初の行では、すべてのエンドポイントに使われる変数を作成します。
- 次の行では、POST 要求を定義しています。
- トリプル ハッシュタグ (
###
) 行は要求の区切り記号であり、この後に続くのは別の要求向けのものです。
POST 要求にはヘッダーと本文が必要です。 要求のこれらの部分を定義するには、POST 要求行の直後に次の行を追加します。
Content-Type: application/json { "name":"walk dog", "isComplete":true }
先ほどのコードでは、Content-Type ヘッダーと JSON 要求の本文を追加しています。 TodoApi.http ファイルは次の例のようになりますが、実際のポート番号に置き換えてください。
@TodoApi_HostAddress = https://localhost:7057 POST {{TodoApi_HostAddress}}/todoitems Content-Type: application/json { "name":"walk dog", "isComplete":true } ###
アプリを実行します。
要求行の上にある
POST
リンクを選択します。POST 要求がアプリに送信され、応答ウィンドウに 応答 が表示されます。
GET エンドポイントを検証する
サンプル アプリでは、MapGet
の呼び出しにより、いくつかの GET エンドポイントを実装しています。
API(アプリケーション・プログラミング・インターフェース) | 説明 | 要求本文 | 応答本文 |
---|---|---|---|
GET /todoitems |
すべての To Do アイテムを取得します。 | 無し | To Do アイテムの配列 |
GET /todoitems/complete |
すべての完了した To Do 項目を取得します | 無し | To Do アイテムの配列 |
GET /todoitems/{id} |
ID でアイテムを取得します。 | 無し | To Do アイテム |
app.MapGet("/todoitems", async (TodoDb db) =>
await db.Todos.ToListAsync());
app.MapGet("/todoitems/complete", async (TodoDb db) =>
await db.Todos.Where(t => t.IsComplete).ToListAsync());
app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound());
GET エンドポイントをテストする
ブラウザーから GET
エンドポイントを呼び出すか、 エンドポイント エクスプローラーを使用して、アプリをテストします。 エンドポイント エクスプローラーの手順を次 に示します。
エンドポイント エクスプローラーで、最初の GET エンドポイントを右クリックし、[要求の生成] を選択します。
TodoApi.http
ファイルに以下の内容が追加されます。GET {{TodoApi_HostAddress}}/todoitems ###
新しい 要求行の上にある
GET
リンクを選択します。GET 要求がアプリに送信され、応答ウィンドウに 応答 が表示されます。
応答本文は次の JSON のようになります。
[ { "id": 1, "name": "walk dog", "isComplete": true } ]
エンドポイント エクスプローラーで、
/todoitems/{id}
GET エンドポイントを右クリックし、[要求の生成] を選択します。TodoApi.http
ファイルに以下の内容が追加されます。GET {{TodoApi_HostAddress}}/todoitems/{id} ###
{id}
を1
で置き換え新しい GET 要求行の上にある [要求の送信] リンクを選択します。
GET 要求がアプリに送信され、応答ウィンドウに 応答 が表示されます。
応答本文は次の JSON のようになります。
{ "id": 1, "name": "walk dog", "isComplete": true }
このアプリではメモリ内データベースが使用されます。 アプリを再起動すると、GET 要求はデータを返しません。 データが返されない場合は、アプリに データを POST し、GET 要求をもう一度試します。
戻り値
ASP.NET Core は、オブジェクトを JSON に自動的にシリアル化し、応答メッセージの本文に JSON を書き込みます。 この戻り値の型の応答コードは 200 OK です。ハンドルされない例外がないと仮定します。 ハンドルされない例外は 5xx エラーに変換されます。
戻り値の型は、さまざまな HTTP 状態コードを表すことができます。 たとえば、GET /todoitems/{id}
は、次の 2 つの異なる状態値を返す可能性があります。
- 要求された ID と一致する項目がない場合、メソッドは 404 状態NotFound エラー コードを返します。
- それ以外の場合、メソッドは JSON 応答本文で 200 を返します。 戻り値が
item
の場合、HTTP 200 応答が返されます。
PUT エンドポイントを検証する
サンプル アプリは、MapPut
を使用して単一の PUT エンドポイントを実装しています。
app.MapPut("/todoitems/{id}", async (int id, Todo inputTodo, TodoDb db) =>
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return Results.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return Results.NoContent();
});
このメソッドは、HTTP PUT を使用していることを除き、MapPost
メソッドに似ています。 応答が成功すると 、204 (コンテンツなし) が返されます。 HTTP 仕様に従って、PUT 要求では、変更だけでなく、更新されたエンティティ全体を送信するようクライアントに求めます。 部分的な更新をサポートするには、 HTTP PATCH を使用します。
PUT エンドポイントをテストする
このサンプルでは、アプリを起動するたびに開始することが必要なメモリ内データベースが使われています。 PUT 呼び出しを実行する前に、データベース内にアイテムが存在している必要があります。 GET を呼び出して、PUT 呼び出しを実行する前にデータベース内にアイテムが確実に存在していることを確認します。
Id = 1
の To Do 項目を更新し、その名前を "feed fish"
に設定します。
エンドポイント エクスプローラーでPUT エンドポイントを右クリックし、[要求の生成] を選択します。
TodoApi.http
ファイルに以下の内容が追加されます。PUT {{TodoApi_HostAddress}}/todoitems/{id} ###
PUT 要求行の
{id}
を1
に置き換えます。PUT 要求行の直後に以下の行を追加します。
Content-Type: application/json { "id": 1, "name": "feed fish", "isComplete": false }
先ほどのコードでは、Content-Type ヘッダーと JSON 要求の本文を追加しています。
新しい PUT 要求行の上にある [要求の 送信] リンクを選択します。
PUT 要求がアプリに送信され、応答ウィンドウに 応答 が表示されます。 応答本文は空であり、状態コードは 204 です。
DELETE エンドポイントの検証とテスト
サンプル アプリは、MapDelete
を使用して単一の DELETE エンドポイントを実装しています。
app.MapDelete("/todoitems/{id}", async (int id, TodoDb db) =>
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return Results.NoContent();
}
return Results.NotFound();
});
エンドポイント エクスプローラーで、DELETE エンドポイントを右クリックし、[要求の生成] を選択します。
DELETE 要求が
TodoApi.http
に追加されます。DELETE 要求行の
{id}
を1
に置き換えます。 DELETE 要求は次の例のようになります。DELETE {{TodoApi_HostAddress}}/todoitems/1 ###
DELETE リクエストの送信リンクを選択します。
DELETE 要求がアプリに送信され、応答ウィンドウに 応答 が表示されます。 応答本文は空であり、状態コードは 204 です。
MapGroup API を使う
サンプル アプリ コードでは、エンドポイントを設定するたびに todoitems
URL プレフィックスが繰り返されます。 API には共通の URL プレフィックスを持つエンドポイントのグループがあることが多く、MapGroup メソッドはそのようなグループの整理に役立ちます。 これにより、繰り返しのコードを減らし、RequireAuthorization や WithMetadata のようなメソッドを 1 回呼び出すだけで、エンドポイントのグループ全体をカスタマイズできます。
Program.cs
の内容を次のコードに置き換えます。
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
var todoItems = app.MapGroup("/todoitems");
todoItems.MapGet("/", async (TodoDb db) =>
await db.Todos.ToListAsync());
todoItems.MapGet("/complete", async (TodoDb db) =>
await db.Todos.Where(t => t.IsComplete).ToListAsync());
todoItems.MapGet("/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound());
todoItems.MapPost("/", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
});
todoItems.MapPut("/{id}", async (int id, Todo inputTodo, TodoDb db) =>
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return Results.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return Results.NoContent();
});
todoItems.MapDelete("/{id}", async (int id, TodoDb db) =>
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return Results.NoContent();
}
return Results.NotFound();
});
app.Run();
上記のコードには、次の変更が加えられています。
-
var todoItems = app.MapGroup("/todoitems");
を追加して、URL プレフィックス/todoitems
を使ってグループを設定します。 - すべての
app.Map<HttpVerb>
メソッドをtodoItems.Map<HttpVerb>
に変更します。 -
/todoitems
メソッド呼び出しから URL プレフィックスMap<HttpVerb>
を削除します。
エンドポイントをテストして、同じように動作することを確認します。
TypedResults API を使う
TypedResults ではなく Results を返すと、テストのしやすさや、エンドポイントを記述するための OpenAPI の応答型メタデータが自動的に返されるなど、いくつかの利点があります。 詳細については、「TypedResults と Results」を参照してください。
Map<HttpVerb>
メソッドは、ラムダ式を使う代わりにルート ハンドラー メソッドを呼び出すことができます。 例を表示するには、次のコード でProgram.cs を更新します。
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
var todoItems = app.MapGroup("/todoitems");
todoItems.MapGet("/", GetAllTodos);
todoItems.MapGet("/complete", GetCompleteTodos);
todoItems.MapGet("/{id}", GetTodo);
todoItems.MapPost("/", CreateTodo);
todoItems.MapPut("/{id}", UpdateTodo);
todoItems.MapDelete("/{id}", DeleteTodo);
app.Run();
static async Task<IResult> GetAllTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.ToArrayAsync());
}
static async Task<IResult> GetCompleteTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.Where(t => t.IsComplete).ToListAsync());
}
static async Task<IResult> GetTodo(int id, TodoDb db)
{
return await db.Todos.FindAsync(id)
is Todo todo
? TypedResults.Ok(todo)
: TypedResults.NotFound();
}
static async Task<IResult> CreateTodo(Todo todo, TodoDb db)
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return TypedResults.Created($"/todoitems/{todo.Id}", todo);
}
static async Task<IResult> UpdateTodo(int id, Todo inputTodo, TodoDb db)
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return TypedResults.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return TypedResults.NoContent();
}
static async Task<IResult> DeleteTodo(int id, TodoDb db)
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return TypedResults.NoContent();
}
return TypedResults.NotFound();
}
これで Map<HttpVerb>
のコードは、ラムダ式ではなくメソッドを呼び出すようになりました。
var todoItems = app.MapGroup("/todoitems");
todoItems.MapGet("/", GetAllTodos);
todoItems.MapGet("/complete", GetCompleteTodos);
todoItems.MapGet("/{id}", GetTodo);
todoItems.MapPost("/", CreateTodo);
todoItems.MapPut("/{id}", UpdateTodo);
todoItems.MapDelete("/{id}", DeleteTodo);
これらのメソッドは、IResult を実装し TypedResults で定義されるオブジェクトを返します。
static async Task<IResult> GetAllTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.ToArrayAsync());
}
static async Task<IResult> GetCompleteTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.Where(t => t.IsComplete).ToListAsync());
}
static async Task<IResult> GetTodo(int id, TodoDb db)
{
return await db.Todos.FindAsync(id)
is Todo todo
? TypedResults.Ok(todo)
: TypedResults.NotFound();
}
static async Task<IResult> CreateTodo(Todo todo, TodoDb db)
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return TypedResults.Created($"/todoitems/{todo.Id}", todo);
}
static async Task<IResult> UpdateTodo(int id, Todo inputTodo, TodoDb db)
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return TypedResults.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return TypedResults.NoContent();
}
static async Task<IResult> DeleteTodo(int id, TodoDb db)
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return TypedResults.NoContent();
}
return TypedResults.NotFound();
}
単体テストはこれらのメソッドを呼び出して、正しい型を返すかどうかをテストできます。 たとえば、メソッドが GetAllTodos
である場合:
static async Task<IResult> GetAllTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.ToArrayAsync());
}
単体テスト コードでは 、Ok<Todo[]> 型のオブジェクトがハンドラー メソッドから返されることを確認できます。 次に例を示します。
public async Task GetAllTodos_ReturnsOkOfTodosResult()
{
// Arrange
var db = CreateDbContext();
// Act
var result = await TodosApi.GetAllTodos(db);
// Assert: Check for the correct returned type
Assert.IsType<Ok<Todo[]>>(result);
}
過剰な投稿を防止する
現在、サンプル アプリでは Todo
オブジェクト全体が公開されています。 運用アプリケーションでは、多くの場合、モデルのサブセットを使用して、入力して返すことができるデータを制限します。 その背景には複数の理由があり、セキュリティは主なものです。 モデルのサブセットは、通常、データ転送オブジェクト (DTO)、入力モデル、またはビュー モデルと呼ばれます。 この記事では DTO を使用します。
DTO を使用すると、次のことができます。
- 過剰な投稿を防止する。
- クライアントが表示しないことになっているプロパティを非表示にする。
- ペイロード サイズを減らすには、いくつかのプロパティを省略します。
- 入れ子になったオブジェクトを含むオブジェクト グラフをフラット化する。 フラット化されたオブジェクト グラフは、クライアントにとってより便利になる可能性があります。
DTO のアプローチを実演するために、Todo
クラスを更新して、シークレット フィールドを含めます。
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public string? Secret { get; set; }
}
シークレット フィールドは、このアプリでは非表示にする必要がありますが、管理アプリの場合は公開することを選択できます。
シークレット フィールドを投稿および取得できることを確認します。
次のコードのファイルを、TodoItemDTO.cs
という名前で作成します。
public class TodoItemDTO
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public TodoItemDTO() { }
public TodoItemDTO(Todo todoItem) =>
(Id, Name, IsComplete) = (todoItem.Id, todoItem.Name, todoItem.IsComplete);
}
この DTO モデルを使うには、Program.cs
ファイルの内容を次のコードに置き換えます。
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
RouteGroupBuilder todoItems = app.MapGroup("/todoitems");
todoItems.MapGet("/", GetAllTodos);
todoItems.MapGet("/complete", GetCompleteTodos);
todoItems.MapGet("/{id}", GetTodo);
todoItems.MapPost("/", CreateTodo);
todoItems.MapPut("/{id}", UpdateTodo);
todoItems.MapDelete("/{id}", DeleteTodo);
app.Run();
static async Task<IResult> GetAllTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.Select(x => new TodoItemDTO(x)).ToArrayAsync());
}
static async Task<IResult> GetCompleteTodos(TodoDb db) {
return TypedResults.Ok(await db.Todos.Where(t => t.IsComplete).Select(x => new TodoItemDTO(x)).ToListAsync());
}
static async Task<IResult> GetTodo(int id, TodoDb db)
{
return await db.Todos.FindAsync(id)
is Todo todo
? TypedResults.Ok(new TodoItemDTO(todo))
: TypedResults.NotFound();
}
static async Task<IResult> CreateTodo(TodoItemDTO todoItemDTO, TodoDb db)
{
var todoItem = new Todo
{
IsComplete = todoItemDTO.IsComplete,
Name = todoItemDTO.Name
};
db.Todos.Add(todoItem);
await db.SaveChangesAsync();
todoItemDTO = new TodoItemDTO(todoItem);
return TypedResults.Created($"/todoitems/{todoItem.Id}", todoItemDTO);
}
static async Task<IResult> UpdateTodo(int id, TodoItemDTO todoItemDTO, TodoDb db)
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return TypedResults.NotFound();
todo.Name = todoItemDTO.Name;
todo.IsComplete = todoItemDTO.IsComplete;
await db.SaveChangesAsync();
return TypedResults.NoContent();
}
static async Task<IResult> DeleteTodo(int id, TodoDb db)
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return TypedResults.NoContent();
}
return TypedResults.NotFound();
}
シークレット フィールドを除くすべてのフィールドを投稿および取得できることを確認します。
完成したサンプルを使用したトラブルシューティング
解決できない問題が発生した場合は、コードを完成したプロジェクトと比較します。 完成したプロジェクトを表示またはダウンロード します (ダウンロード方法)。
次のステップ
- JSON シリアル化オプションを構成します。
- エラーと例外の処理: 開発環境では、最小限の API アプリに対して 開発者例外ページ が既定で有効になっています。 エラーと例外を処理する方法については、「 ASP.NET Core API でのエラーの処理」を参照してください。
- 最小限の API アプリをテストする例については、 この GitHub サンプルを参照してください。
- 最小限の API での OpenAPI サポート。
- クイック スタート: Azure に発行します。
- ASP.NET Core Minimal API の整理。
詳細情報
最小限の API のクイック リファレンスを参照してください
Minimal API は、依存関係が最小限の HTTP API を作成するために設計されています。 ASP.NET Core での最小限のファイル、機能、依存関係のみを含むマイクロサービスやアプリに最適です。
このチュートリアルでは、ASP.NET Core で最小 API を構築するための基本について説明します。 ASP.NET Core で API を作成するもう 1 つの方法は、コントローラーを使用することです。 最小限の API とコントローラー ベースの API の選択に関するヘルプについては、 API の概要を参照してください。 その他の機能を含む コントローラー に基づいて API プロジェクトを作成するチュートリアルについては、「 Web API の作成」を参照してください。
概要
このチュートリアルでは、次の API を作成します。
API(アプリケーション・プログラミング・インターフェース) | 説明 | 要求本文 | 応答本文 |
---|---|---|---|
GET /todoitems |
すべての To Do アイテムを取得します。 | 無し | To Do アイテムの配列 |
GET /todoitems/complete |
完了した To Do 項目を取得します。 | 無し | To Do アイテムの配列 |
GET /todoitems/{id} |
ID でアイテムを取得します。 | 無し | To Do アイテム |
POST /todoitems |
新しいアイテムを追加します。 | To Do アイテム | To Do アイテム |
PUT /todoitems/{id} |
既存のアイテムを更新します。 | To Do アイテム | 無し |
DELETE /todoitems/{id} |
アイテムを削除します。 | 無し | 無し |
必須コンポーネント
ASP.NET および Web 開発ワークロードを含む Visual Studio 2022。
API プロジェクトを作成する
Visual Studio 2022 を起動し、[ 新しいプロジェクトの作成] を選択します。
[ 新しいプロジェクトの作成] ダイアログで、次の手順を 実行します。
-
Empty
ボックスに、「」と入力します。 - ASP.NET Core Empty テンプレートを選択し、[次へ] を選択します。
-
プロジェクトに TodoApi という名前を付け、[ 次へ] を選択します。
[追加情報] ダイアログで、次の 手順 を実行します。
- .NET 7.0 を選択する
- [最上位レベルのステートメントを使用しない] をオフにする
- [作成]を選択する
コードを確認する
Program.cs
ファイルには、次のコードが含まれています。
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
上記のコードでは次の操作が行われます。
- 事前に構成された既定値で WebApplicationBuilder と WebApplication を作成します。
-
/
を返す HTTP GET エンドポイントHello World!
を作成します。
アプリを実行する
Ctrl + F5 キーを押して、デバッガーなしで実行します。
Visual Studio に次のダイアログが表示されます。
IIS Express SSL 証明書を信頼する場合は、[ はい ] を選択します。
次のダイアログが表示されます。
開発証明書を信頼することに同意する場合は、[ はい ] を選択します。
Firefox ブラウザーを信頼する方法については、 Firefox SEC_ERROR_INADEQUATE_KEY_USAGE証明書エラーに関する記事を参照してください。
Visual Studio によって Kestrel Web サーバー が起動され、ブラウザー ウィンドウが開きます。
ブラウザーに Hello World!
が表示されます。
Program.cs
ファイルには、最小限の完成されたアプリが含まれています。
NuGet パッケージを追加する
このチュートリアルで使用するデータベースと診断をサポートするには、NuGet パッケージを追加する必要があります。
- [ ツール ] メニューの [ NuGet パッケージ マネージャー] > [ソリューションの NuGet パッケージの管理] を選択します。
- 参照 タブを選択します。
- 検索ボックスに 「Microsoft.EntityFrameworkCore.InMemory 」と入力し、
Microsoft.EntityFrameworkCore.InMemory
を選択します。 - 右側のウィンドウで [ プロジェクト ] チェック ボックスをオンにします。
- [ バージョン ] ドロップダウンで、使用可能な最新バージョン 7 (
7.0.17
など) を選択し、[ インストール] を選択します。 - 上記の手順に従って、使用できる最新バージョン 7 を含む
Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
パッケージを追加します。
モデルおよびデータベース コンテキスト クラス
project フォルダーで、次のコードを含む Todo.cs
という名前のファイルを作成します。
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
}
上記のコードでは、このアプリのモデルを作成します。 "モデル" は、アプリが管理するデータを表すクラスです。
次のコードのファイルを、TodoDb.cs
という名前で作成します。
using Microsoft.EntityFrameworkCore;
class TodoDb : DbContext
{
public TodoDb(DbContextOptions<TodoDb> options)
: base(options) { }
public DbSet<Todo> Todos => Set<Todo>();
}
上記のコードでは、データ モデルの Entity Framework 機能を調整するメイン クラスであるデータベース コンテキストを定義しています。 このクラスは Microsoft.EntityFrameworkCore.DbContext クラスから派生したクラスです。
API コードを追加する
Program.cs
ファイルの内容を次のコードに置き換えます。
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
app.MapGet("/todoitems", async (TodoDb db) =>
await db.Todos.ToListAsync());
app.MapGet("/todoitems/complete", async (TodoDb db) =>
await db.Todos.Where(t => t.IsComplete).ToListAsync());
app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound());
app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
});
app.MapPut("/todoitems/{id}", async (int id, Todo inputTodo, TodoDb db) =>
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return Results.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return Results.NoContent();
});
app.MapDelete("/todoitems/{id}", async (int id, TodoDb db) =>
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return Results.NoContent();
}
return Results.NotFound();
});
app.Run();
次の強調表示されたコードは、データベース コンテキストを 依存関係挿入 (DI) コンテナーに追加し、データベース関連の例外を表示できるようにします。
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
DI コンテナーは、データベース コンテキストやその他のサービスへのアクセスを提供します。
Swagger を使って API テスト UI を作成する
選択できる Web API テスト ツールは多数あり、任意のツールを使ってこのチュートリアルの入門 API テスト手順を実行できます。
このチュートリアルでは、.NET パッケージ NSwag.AspNetCore を利用します。このパッケージには、OpenAPI 仕様に準拠したテスト UI を生成するための Swagger ツールが統合されています。
- NSwag: Swagger を ASP.NET Core アプリケーションに直接統合し、ミドルウェアと構成を提供する .NET ライブラリ。
- Swagger: OpenAPI 仕様に準拠した API テスト ページを生成する OpenAPIGenerator や SwaggerUI などのオープンソース ツールのセット。
- OpenAPI 仕様: コントローラーとモデル内の XML と属性の注釈に基づいて、API の機能を説明するドキュメント。
ASP.NET で OpenAPI と NSwag を使用する方法の詳細については、 Swagger/OpenAPI ASP.NET Core Web API のドキュメントを参照してください。
Swagger ツールをインストールする
次のコマンドを実行します。
dotnet add package NSwag.AspNetCore
前のコマンドは、Swagger ドキュメントと UI を生成するためのツールを含む NSwag.AspNetCore パッケージを追加します。
Swagger ミドルウェアを構成する
行
app
でvar app = builder.Build();
が定義される前に、次の強調表示されたコードを追加しますusing Microsoft.EntityFrameworkCore; var builder = WebApplication.CreateBuilder(args); builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList")); builder.Services.AddDatabaseDeveloperPageExceptionFilter(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddOpenApiDocument(config => { config.DocumentName = "TodoAPI"; config.Title = "TodoAPI v1"; config.Version = "v1"; }); var app = builder.Build();
上のコードでは、次のようになります。
builder.Services.AddEndpointsApiExplorer();
: API Explorer を有効にします。これは、HTTP API に関するメタデータを提供するサービスです。 API Explorer は、Swagger ドキュメントを生成するために Swagger によって使われます。builder.Services.AddOpenApiDocument(config => {...});
: Swagger OpenAPI ドキュメント ジェネレーターをアプリケーション サービスに追加し、タイトルやバージョンなど、API に関する詳細情報を提供するように構成します。 より堅牢な API の詳細を提供する方法については、「NSwag と ASP.NET Core の概要」を参照してください。行
app
でvar app = builder.Build();
が定義された後の次の行に、次の強調表示されたコードを追加しますvar app = builder.Build(); if (app.Environment.IsDevelopment()) { app.UseOpenApi(); app.UseSwaggerUi(config => { config.DocumentTitle = "TodoAPI"; config.Path = "/swagger"; config.DocumentPath = "/swagger/{documentName}/swagger.json"; config.DocExpansion = "list"; }); }
前のコードを使うと、生成された JSON ドキュメントと Swagger UI を Swagger ミドルウェアで提供できるようになります。 Swagger は開発環境でのみ有効です。 運用環境で Swagger を有効にすると、API の構造と実装に関する機密情報が漏えいする可能性があります。
データの POST をテストする
次の Program.cs
のコードにより、データをメモリ内データベースに追加する HTTP POST のエンドポイント /todoitems
が作成されます。
app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
});
アプリを実行します。
/
エンドポイントが存在しなくなったため、ブラウザーに 404 エラーが表示されます。
POST エンドポイントは、アプリにデータを追加するために使われます。
アプリがまだ実行されている状態で、ブラウザーで
https://localhost:<port>/swagger
に移動し、Swagger によって生成された API テスト ページを表示します。Swagger API テスト ページで、 Post /todoitems>Try it out を選択します。
要求本文フィールドには、API のパラメーターを反映する生成された形式の例が含まれていることに注意してください。
要求本文に、「オプションの
id
を指定せずに、To Do アイテムの JSON」と入力します。{ "name":"walk dog", "isComplete":true }
[ 実行] を選択します。
Swagger では、[実行] ボタンの下に [応答 ] ウィンドウが 表示 されます。
役に立つ詳細事項をいくつか紹介します。
- cURL: Swagger は Unix/Linux 構文の cURL コマンドの例を提供します。これは、Git for Windows の Git Bash を含め、Unix/Linux 構文を使用する任意の bash シェルを使用してコマンド ラインで実行できます。
- 要求 URL: API 呼び出しに対して Swagger UI の JavaScript コードによって作成される HTTP 要求の簡略化された表現。 実際の要求には、ヘッダー、クエリ パラメーター、要求本文などの詳細が含まれる場合があります。
- サーバーの応答: 応答の本文とヘッダーが含まれています。 応答本文には、
id
が1
に設定されたことが示されています。 - 応答コード: 201
HTTP
状態コードが返され、要求が正常に処理され、新しいリソースが作成されたことを示します。
GET エンドポイントを検証する
サンプル アプリでは、MapGet
の呼び出しにより、いくつかの GET エンドポイントを実装しています。
API(アプリケーション・プログラミング・インターフェース) | 説明 | 要求本文 | 応答本文 |
---|---|---|---|
GET /todoitems |
すべての To Do アイテムを取得します。 | 無し | To Do アイテムの配列 |
GET /todoitems/complete |
すべての完了した To Do 項目を取得します | 無し | To Do アイテムの配列 |
GET /todoitems/{id} |
ID でアイテムを取得します。 | 無し | To Do アイテム |
app.MapGet("/todoitems", async (TodoDb db) =>
await db.Todos.ToListAsync());
app.MapGet("/todoitems/complete", async (TodoDb db) =>
await db.Todos.Where(t => t.IsComplete).ToListAsync());
app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound());
GET エンドポイントをテストする
ブラウザーまたは Swagger からエンドポイントを呼び出してアプリをテストします。
Swagger で GET /todoitems>試してみる>実行します。
または、ブラウザーで URI を入力して
http://localhost:<port>/todoitems
を呼び出します。 たとえば、http://localhost:5001/todoitems
のように指定します。
GET /todoitems
への呼び出しにより、次のような応答が生成されます。
[
{
"id": 1,
"name": "walk dog",
"isComplete": true
}
]
Swagger で GET /todoitems/{id} を 呼び出して、特定の ID からデータを返します。
- GET /todoitems>を選択して試してください。
-
id フィールドを
1
に設定し、[実行] を選択します。
または、ブラウザーで URI を入力して
https://localhost:<port>/todoitems/1
を呼び出します。 たとえば、https://localhost:5001/todoitems/1
のように指定します。応答本文は次のようになります。
{ "id": 1, "name": "walk dog", "isComplete": true }
このアプリではメモリ内データベースが使用されます。 アプリを再起動すると、GET 要求はデータを返しません。 データが返されない場合は、アプリに データを POST し、GET 要求をもう一度試します。
戻り値
ASP.NET Core は、オブジェクトを JSON に自動的にシリアル化し、応答メッセージの本文に JSON を書き込みます。 この戻り値の型の応答コードは 200 OK です。ハンドルされない例外がないと仮定します。 ハンドルされない例外は 5xx エラーに変換されます。
戻り値の型は、さまざまな HTTP 状態コードを表すことができます。 たとえば、GET /todoitems/{id}
は、次の 2 つの異なる状態値を返す可能性があります。
- 要求された ID と一致する項目がない場合、メソッドは 404 状態NotFound エラー コードを返します。
- それ以外の場合、メソッドは JSON 応答本文で 200 を返します。 戻り値が
item
の場合、HTTP 200 応答が返されます。
PUT エンドポイントを検証する
サンプル アプリは、MapPut
を使用して単一の PUT エンドポイントを実装しています。
app.MapPut("/todoitems/{id}", async (int id, Todo inputTodo, TodoDb db) =>
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return Results.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return Results.NoContent();
});
このメソッドは、HTTP PUT を使用していることを除き、MapPost
メソッドに似ています。 応答が成功すると 、204 (コンテンツなし) が返されます。 HTTP 仕様に従って、PUT 要求では、変更だけでなく、更新されたエンティティ全体を送信するようクライアントに求めます。 部分的な更新をサポートするには、 HTTP PATCH を使用します。
PUT エンドポイントをテストする
このサンプルでは、アプリを起動するたびに開始することが必要なメモリ内データベースが使われています。 PUT 呼び出しを実行する前に、データベース内にアイテムが存在している必要があります。 GET を呼び出して、PUT 呼び出しを実行する前にデータベース内にアイテムが確実に存在していることを確認します。
Id = 1
の To Do 項目を更新し、その名前を "feed fish"
に設定します。
Swagger を使って PUT 要求を送信します。
「Put /todoitems/{id}」>の試してみるを選択します。
id フィールドを
1
に設定します。要求本文を次の JSON に設定します。
{ "name": "feed fish", "isComplete": false }
[ 実行] を選択します。
DELETE エンドポイントの検証とテスト
サンプル アプリは、MapDelete
を使用して単一の DELETE エンドポイントを実装しています。
app.MapDelete("/todoitems/{id}", async (int id, TodoDb db) =>
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return Results.NoContent();
}
return Results.NotFound();
});
Swagger を使って DELETE 要求を送信します。
DELETE /todoitems/{id}>試してみてください。
[ID] フィールドを [
1
に設定し、[実行] を選択します。DELETE 要求がアプリに送信され、[応答] ウィンドウに 応答 が表示されます。 応答本文が空で、 サーバーの応答 状態コードが 204 です。
MapGroup API を使う
サンプル アプリ コードでは、エンドポイントを設定するたびに todoitems
URL プレフィックスが繰り返されます。 API には共通の URL プレフィックスを持つエンドポイントのグループがあることが多く、MapGroup メソッドはそのようなグループの整理に役立ちます。 これにより、繰り返しのコードを減らし、RequireAuthorization や WithMetadata のようなメソッドを 1 回呼び出すだけで、エンドポイントのグループ全体をカスタマイズできます。
Program.cs
の内容を次のコードに置き換えます。
using NSwag.AspNetCore;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddOpenApiDocument(config =>
{
config.DocumentName = "TodoAPI";
config.Title = "TodoAPI v1";
config.Version = "v1";
});
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseOpenApi();
app.UseSwaggerUi(config =>
{
config.DocumentTitle = "TodoAPI";
config.Path = "/swagger";
config.DocumentPath = "/swagger/{documentName}/swagger.json";
config.DocExpansion = "list";
});
}
var todoItems = app.MapGroup("/todoitems");
todoItems.MapGet("/", async (TodoDb db) =>
await db.Todos.ToListAsync());
todoItems.MapGet("/complete", async (TodoDb db) =>
await db.Todos.Where(t => t.IsComplete).ToListAsync());
todoItems.MapGet("/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound());
todoItems.MapPost("/", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
});
todoItems.MapPut("/{id}", async (int id, Todo inputTodo, TodoDb db) =>
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return Results.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return Results.NoContent();
});
todoItems.MapDelete("/{id}", async (int id, TodoDb db) =>
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return Results.NoContent();
}
return Results.NotFound();
});
app.Run();
上記のコードには、次の変更が加えられています。
-
var todoItems = app.MapGroup("/todoitems");
を追加して、URL プレフィックス/todoitems
を使ってグループを設定します。 - すべての
app.Map<HttpVerb>
メソッドをtodoItems.Map<HttpVerb>
に変更します。 -
/todoitems
メソッド呼び出しから URL プレフィックスMap<HttpVerb>
を削除します。
エンドポイントをテストして、同じように動作することを確認します。
TypedResults API を使う
TypedResults ではなく Results を返すと、テストのしやすさや、エンドポイントを記述するための OpenAPI の応答型メタデータが自動的に返されるなど、いくつかの利点があります。 詳細については、「TypedResults と Results」を参照してください。
Map<HttpVerb>
メソッドは、ラムダ式を使う代わりにルート ハンドラー メソッドを呼び出すことができます。 例を表示するには、次のコード でProgram.cs を更新します。
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
var todoItems = app.MapGroup("/todoitems");
todoItems.MapGet("/", GetAllTodos);
todoItems.MapGet("/complete", GetCompleteTodos);
todoItems.MapGet("/{id}", GetTodo);
todoItems.MapPost("/", CreateTodo);
todoItems.MapPut("/{id}", UpdateTodo);
todoItems.MapDelete("/{id}", DeleteTodo);
app.Run();
static async Task<IResult> GetAllTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.ToArrayAsync());
}
static async Task<IResult> GetCompleteTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.Where(t => t.IsComplete).ToListAsync());
}
static async Task<IResult> GetTodo(int id, TodoDb db)
{
return await db.Todos.FindAsync(id)
is Todo todo
? TypedResults.Ok(todo)
: TypedResults.NotFound();
}
static async Task<IResult> CreateTodo(Todo todo, TodoDb db)
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return TypedResults.Created($"/todoitems/{todo.Id}", todo);
}
static async Task<IResult> UpdateTodo(int id, Todo inputTodo, TodoDb db)
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return TypedResults.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return TypedResults.NoContent();
}
static async Task<IResult> DeleteTodo(int id, TodoDb db)
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return TypedResults.NoContent();
}
return TypedResults.NotFound();
}
これで Map<HttpVerb>
のコードは、ラムダ式ではなくメソッドを呼び出すようになりました。
var todoItems = app.MapGroup("/todoitems");
todoItems.MapGet("/", GetAllTodos);
todoItems.MapGet("/complete", GetCompleteTodos);
todoItems.MapGet("/{id}", GetTodo);
todoItems.MapPost("/", CreateTodo);
todoItems.MapPut("/{id}", UpdateTodo);
todoItems.MapDelete("/{id}", DeleteTodo);
これらのメソッドは、IResult を実装し TypedResults で定義されるオブジェクトを返します。
static async Task<IResult> GetAllTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.ToArrayAsync());
}
static async Task<IResult> GetCompleteTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.Where(t => t.IsComplete).ToListAsync());
}
static async Task<IResult> GetTodo(int id, TodoDb db)
{
return await db.Todos.FindAsync(id)
is Todo todo
? TypedResults.Ok(todo)
: TypedResults.NotFound();
}
static async Task<IResult> CreateTodo(Todo todo, TodoDb db)
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return TypedResults.Created($"/todoitems/{todo.Id}", todo);
}
static async Task<IResult> UpdateTodo(int id, Todo inputTodo, TodoDb db)
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return TypedResults.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return TypedResults.NoContent();
}
static async Task<IResult> DeleteTodo(int id, TodoDb db)
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return TypedResults.NoContent();
}
return TypedResults.NotFound();
}
単体テストはこれらのメソッドを呼び出して、正しい型を返すかどうかをテストできます。 たとえば、メソッドが GetAllTodos
である場合:
static async Task<IResult> GetAllTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.ToArrayAsync());
}
単体テスト コードでは 、Ok<Todo[]> 型のオブジェクトがハンドラー メソッドから返されることを確認できます。 次に例を示します。
public async Task GetAllTodos_ReturnsOkOfTodosResult()
{
// Arrange
var db = CreateDbContext();
// Act
var result = await TodosApi.GetAllTodos(db);
// Assert: Check for the correct returned type
Assert.IsType<Ok<Todo[]>>(result);
}
過剰な投稿を防止する
現在、サンプル アプリでは Todo
オブジェクト全体が公開されています。 運用環境アプリケーション内の運用環境アプリでは、入力され、返されるデータを制限するためにモデルのサブセットがよく使われます。 その背景には複数の理由があり、セキュリティは主なものです。 モデルのサブセットは、通常、データ転送オブジェクト (DTO)、入力モデル、またはビュー モデルと呼ばれます。 この記事では DTO を使用します。
DTO を使用すると、次のことができます。
- 過剰な投稿を防止する。
- クライアントが表示しないことになっているプロパティを非表示にする。
- ペイロード サイズを減らすには、いくつかのプロパティを省略します。
- 入れ子になったオブジェクトを含むオブジェクト グラフをフラット化する。 フラット化されたオブジェクト グラフは、クライアントにとってより便利になる可能性があります。
DTO のアプローチを実演するために、Todo
クラスを更新して、シークレット フィールドを含めます。
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public string? Secret { get; set; }
}
シークレット フィールドは、このアプリでは非表示にする必要がありますが、管理アプリの場合は公開することを選択できます。
シークレット フィールドを投稿および取得できることを確認します。
次のコードのファイルを、TodoItemDTO.cs
という名前で作成します。
public class TodoItemDTO
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public TodoItemDTO() { }
public TodoItemDTO(Todo todoItem) =>
(Id, Name, IsComplete) = (todoItem.Id, todoItem.Name, todoItem.IsComplete);
}
この DTO モデルを使うには、Program.cs
ファイルの内容を次のコードに置き換えます。
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
var app = builder.Build();
app.MapGet("/todoitems", async (TodoDb db) =>
await db.Todos.Select(x => new TodoItemDTO(x)).ToListAsync());
app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(new TodoItemDTO(todo))
: Results.NotFound());
app.MapPost("/todoitems", async (TodoItemDTO todoItemDTO, TodoDb db) =>
{
var todoItem = new Todo
{
IsComplete = todoItemDTO.IsComplete,
Name = todoItemDTO.Name
};
db.Todos.Add(todoItem);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todoItem.Id}", new TodoItemDTO(todoItem));
});
app.MapPut("/todoitems/{id}", async (int id, TodoItemDTO todoItemDTO, TodoDb db) =>
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return Results.NotFound();
todo.Name = todoItemDTO.Name;
todo.IsComplete = todoItemDTO.IsComplete;
await db.SaveChangesAsync();
return Results.NoContent();
});
app.MapDelete("/todoitems/{id}", async (int id, TodoDb db) =>
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return Results.NoContent();
}
return Results.NotFound();
});
app.Run();
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public string? Secret { get; set; }
}
public class TodoItemDTO
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public TodoItemDTO() { }
public TodoItemDTO(Todo todoItem) =>
(Id, Name, IsComplete) = (todoItem.Id, todoItem.Name, todoItem.IsComplete);
}
class TodoDb : DbContext
{
public TodoDb(DbContextOptions<TodoDb> options)
: base(options) { }
public DbSet<Todo> Todos => Set<Todo>();
}
シークレット フィールドを除くすべてのフィールドを投稿および取得できることを確認します。
完成したサンプルを使用したトラブルシューティング
解決できない問題が発生した場合は、コードを完成したプロジェクトと比較します。 完成したプロジェクトを表示またはダウンロード します (ダウンロード方法)。
次のステップ
- JSON シリアル化オプションを構成します。
- エラーと例外の処理: 開発環境では、最小限の API アプリに対して 開発者例外ページ が既定で有効になっています。 エラーと例外を処理する方法については、「 ASP.NET Core API でのエラーの処理」を参照してください。
- 最小限の API アプリをテストする例については、 この GitHub サンプルを参照してください。
- 最小限の API での OpenAPI サポート。
- クイック スタート: Azure に発行します。
- ASP.NET Core Minimal API の整理。
詳細情報
最小限の API のクイック リファレンスを参照してください
Minimal API は、依存関係が最小限の HTTP API を作成するために設計されています。 ASP.NET Core での最小限のファイル、機能、依存関係のみを含むマイクロサービスやアプリに最適です。
このチュートリアルでは、ASP.NET Core で最小 API を構築するための基本について説明します。 ASP.NET Core で API を作成するもう 1 つの方法は、コントローラーを使用することです。 最小限の API とコントローラー ベースの API の選択に関するヘルプについては、 API の概要を参照してください。 その他の機能を含む コントローラー に基づいて API プロジェクトを作成するチュートリアルについては、「 Web API の作成」を参照してください。
概要
このチュートリアルでは、次の API を作成します。
API(アプリケーション・プログラミング・インターフェース) | 説明 | 要求本文 | 応答本文 |
---|---|---|---|
GET /todoitems |
すべての To Do アイテムを取得します。 | 無し | To Do アイテムの配列 |
GET /todoitems/complete |
完了した To Do 項目を取得します。 | 無し | To Do アイテムの配列 |
GET /todoitems/{id} |
ID でアイテムを取得します。 | 無し | To Do アイテム |
POST /todoitems |
新しいアイテムを追加します。 | To Do アイテム | To Do アイテム |
PUT /todoitems/{id} |
既存のアイテムを更新します。 | To Do アイテム | 無し |
DELETE /todoitems/{id} |
アイテムを削除します。 | 無し | 無し |
必須コンポーネント
- ASP.NET および Web 開発ワークロードを含む Visual Studio 2022。
- .NET 6 SDK
API プロジェクトを作成する
Visual Studio 2022 を起動し、[ 新しいプロジェクトの作成] を選択します。
[ 新しいプロジェクトの作成] ダイアログで、次の手順を 実行します。
-
Empty
ボックスに、「」と入力します。 - ASP.NET Core Empty テンプレートを選択し、[次へ] を選択します。
-
プロジェクトに TodoApi という名前を付け、[ 次へ] を選択します。
[追加情報] ダイアログで、次の 手順 を実行します。
- .NET 6.0 を選択する
- [最上位レベルのステートメントを使用しない] をオフにする
- [作成]を選択する
コードを確認する
Program.cs
ファイルには、次のコードが含まれています。
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
上記のコードでは次の操作が行われます。
- 事前に構成された既定値で WebApplicationBuilder と WebApplication を作成します。
-
/
を返す HTTP GET エンドポイントHello World!
を作成します。
アプリを実行する
Ctrl + F5 キーを押して、デバッガーなしで実行します。
Visual Studio に次のダイアログが表示されます。
IIS Express SSL 証明書を信頼する場合は、[ はい ] を選択します。
次のダイアログが表示されます。
開発証明書を信頼することに同意する場合は、[ はい ] を選択します。
Firefox ブラウザーを信頼する方法については、 Firefox SEC_ERROR_INADEQUATE_KEY_USAGE証明書エラーに関する記事を参照してください。
Visual Studio によって Kestrel Web サーバー が起動され、ブラウザー ウィンドウが開きます。
ブラウザーに Hello World!
が表示されます。
Program.cs
ファイルには、最小限の完成されたアプリが含まれています。
NuGet パッケージを追加する
このチュートリアルで使用するデータベースと診断をサポートするには、NuGet パッケージを追加する必要があります。
- [ ツール ] メニューの [ NuGet パッケージ マネージャー] > [ソリューションの NuGet パッケージの管理] を選択します。
- 参照 タブを選択します。
- 検索ボックスに 「Microsoft.EntityFrameworkCore.InMemory 」と入力し、
Microsoft.EntityFrameworkCore.InMemory
を選択します。 - 右側のウィンドウで [ プロジェクト ] チェック ボックスをオンにします。
- [ バージョン ] ドロップダウンで、使用可能な最新バージョン 7 (
6.0.28
など) を選択し、[ インストール] を選択します。 - 上記の手順に従って、使用できる最新バージョン 7 を含む
Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
パッケージを追加します。
モデルおよびデータベース コンテキスト クラス
project フォルダーで、次のコードを含む Todo.cs
という名前のファイルを作成します。
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
}
上記のコードでは、このアプリのモデルを作成します。 "モデル" は、アプリが管理するデータを表すクラスです。
次のコードのファイルを、TodoDb.cs
という名前で作成します。
using Microsoft.EntityFrameworkCore;
class TodoDb : DbContext
{
public TodoDb(DbContextOptions<TodoDb> options)
: base(options) { }
public DbSet<Todo> Todos => Set<Todo>();
}
上記のコードでは、データ モデルの Entity Framework 機能を調整するメイン クラスであるデータベース コンテキストを定義しています。 このクラスは Microsoft.EntityFrameworkCore.DbContext クラスから派生したクラスです。
API コードを追加する
Program.cs
ファイルの内容を次のコードに置き換えます。
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.MapGet("/todoitems", async (TodoDb db) =>
await db.Todos.ToListAsync());
app.MapGet("/todoitems/complete", async (TodoDb db) =>
await db.Todos.Where(t => t.IsComplete).ToListAsync());
app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound());
app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
});
app.MapPut("/todoitems/{id}", async (int id, Todo inputTodo, TodoDb db) =>
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return Results.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return Results.NoContent();
});
app.MapDelete("/todoitems/{id}", async (int id, TodoDb db) =>
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return Results.NoContent();
}
return Results.NotFound();
});
app.Run();
次の強調表示されたコードは、データベース コンテキストを 依存関係挿入 (DI) コンテナーに追加し、データベース関連の例外を表示できるようにします。
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
DI コンテナーは、データベース コンテキストやその他のサービスへのアクセスを提供します。
Swagger を使って API テスト UI を作成する
選択できる Web API テスト ツールは多数あり、任意のツールを使ってこのチュートリアルの入門 API テスト手順を実行できます。
このチュートリアルでは、.NET パッケージ NSwag.AspNetCore を利用します。このパッケージには、OpenAPI 仕様に準拠したテスト UI を生成するための Swagger ツールが統合されています。
- NSwag: Swagger を ASP.NET Core アプリケーションに直接統合し、ミドルウェアと構成を提供する .NET ライブラリ。
- Swagger: OpenAPI 仕様に準拠した API テスト ページを生成する OpenAPIGenerator や SwaggerUI などのオープンソース ツールのセット。
- OpenAPI 仕様: コントローラーとモデル内の XML と属性の注釈に基づいて、API の機能を説明するドキュメント。
ASP.NET で OpenAPI と NSwag を使用する方法の詳細については、 Swagger/OpenAPI ASP.NET Core Web API のドキュメントを参照してください。
Swagger ツールをインストールする
次のコマンドを実行します。
dotnet add package NSwag.AspNetCore
前のコマンドは、Swagger ドキュメントと UI を生成するためのツールを含む NSwag.AspNetCore パッケージを追加します。
Swagger ミドルウェアを構成する
Program.cs の先頭に次の
using
ステートメントを追加します。using NSwag.AspNetCore;
行
app
でvar app = builder.Build();
が定義される前に、次の強調表示されたコードを追加しますusing NSwag.AspNetCore; using Microsoft.EntityFrameworkCore; var builder = WebApplication.CreateBuilder(args); builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList")); builder.Services.AddDatabaseDeveloperPageExceptionFilter(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddOpenApiDocument(config => { config.DocumentName = "TodoAPI"; config.Title = "TodoAPI v1"; config.Version = "v1"; }); var app = builder.Build();
上のコードでは、次のようになります。
builder.Services.AddEndpointsApiExplorer();
: API Explorer を有効にします。これは、HTTP API に関するメタデータを提供するサービスです。 API Explorer は、Swagger ドキュメントを生成するために Swagger によって使われます。builder.Services.AddOpenApiDocument(config => {...});
: Swagger OpenAPI ドキュメント ジェネレーターをアプリケーション サービスに追加し、タイトルやバージョンなど、API に関する詳細情報を提供するように構成します。 より堅牢な API の詳細を提供する方法については、「NSwag と ASP.NET Core の概要」を参照してください。行
app
でvar app = builder.Build();
が定義された後の次の行に、次の強調表示されたコードを追加しますvar app = builder.Build(); if (app.Environment.IsDevelopment()) { app.UseOpenApi(); app.UseSwaggerUi(config => { config.DocumentTitle = "TodoAPI"; config.Path = "/swagger"; config.DocumentPath = "/swagger/{documentName}/swagger.json"; config.DocExpansion = "list"; }); }
前のコードを使うと、生成された JSON ドキュメントと Swagger UI を Swagger ミドルウェアで提供できるようになります。 Swagger は開発環境でのみ有効です。 運用環境で Swagger を有効にすると、API の構造と実装に関する機密情報が漏えいする可能性があります。
データの POST をテストする
次の Program.cs
のコードにより、データをメモリ内データベースに追加する HTTP POST のエンドポイント /todoitems
が作成されます。
app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
});
アプリを実行します。
/
エンドポイントが存在しなくなったため、ブラウザーに 404 エラーが表示されます。
POST エンドポイントは、アプリにデータを追加するために使われます。
アプリがまだ実行されている状態で、ブラウザーで
https://localhost:<port>/swagger
に移動し、Swagger によって生成された API テスト ページを表示します。Swagger API テスト ページで、 Post /todoitems>Try it out を選択します。
要求本文フィールドには、API のパラメーターを反映する生成された形式の例が含まれていることに注意してください。
要求本文に、「オプションの
id
を指定せずに、To Do アイテムの JSON」と入力します。{ "name":"walk dog", "isComplete":true }
[ 実行] を選択します。
Swagger では、[実行] ボタンの下に [応答 ] ウィンドウが 表示 されます。
役に立つ詳細事項をいくつか紹介します。
- cURL: Swagger は Unix/Linux 構文の cURL コマンドの例を提供します。これは、Git for Windows の Git Bash を含め、Unix/Linux 構文を使用する任意の bash シェルを使用してコマンド ラインで実行できます。
- 要求 URL: API 呼び出しに対して Swagger UI の JavaScript コードによって作成される HTTP 要求の簡略化された表現。 実際の要求には、ヘッダー、クエリ パラメーター、要求本文などの詳細が含まれる場合があります。
- サーバーの応答: 応答の本文とヘッダーが含まれています。 応答本文には、
id
が1
に設定されたことが示されています。 - 応答コード: 201
HTTP
状態コードが返され、要求が正常に処理され、新しいリソースが作成されたことを示します。
GET エンドポイントを検証する
サンプル アプリでは、MapGet
の呼び出しにより、いくつかの GET エンドポイントを実装しています。
API(アプリケーション・プログラミング・インターフェース) | 説明 | 要求本文 | 応答本文 |
---|---|---|---|
GET /todoitems |
すべての To Do アイテムを取得します。 | 無し | To Do アイテムの配列 |
GET /todoitems/complete |
すべての完了した To Do 項目を取得します | 無し | To Do アイテムの配列 |
GET /todoitems/{id} |
ID でアイテムを取得します。 | 無し | To Do アイテム |
app.MapGet("/", () => "Hello World!");
app.MapGet("/todoitems", async (TodoDb db) =>
await db.Todos.ToListAsync());
app.MapGet("/todoitems/complete", async (TodoDb db) =>
await db.Todos.Where(t => t.IsComplete).ToListAsync());
app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound());
GET エンドポイントをテストする
ブラウザーまたは Swagger からエンドポイントを呼び出してアプリをテストします。
Swagger で GET /todoitems>試してみる>実行します。
または、ブラウザーで URI を入力して
http://localhost:<port>/todoitems
を呼び出します。 たとえば、http://localhost:5001/todoitems
のように指定します。
GET /todoitems
への呼び出しにより、次のような応答が生成されます。
[
{
"id": 1,
"name": "walk dog",
"isComplete": true
}
]
Swagger で GET /todoitems/{id} を 呼び出して、特定の ID からデータを返します。
- GET /todoitems>を選択して試してください。
-
id フィールドを
1
に設定し、[実行] を選択します。
または、ブラウザーで URI を入力して
https://localhost:<port>/todoitems/1
を呼び出します。 例:https://localhost:5001/todoitems/1
応答本文は次のようになります。
{ "id": 1, "name": "walk dog", "isComplete": true }
このアプリではメモリ内データベースが使用されます。 アプリを再起動すると、GET 要求はデータを返しません。 データが返されない場合は、アプリに データを POST し、GET 要求をもう一度試します。
戻り値
ASP.NET Core は、オブジェクトを JSON に自動的にシリアル化し、応答メッセージの本文に JSON を書き込みます。 この戻り値の型の応答コードは 200 OK です。ハンドルされない例外がないと仮定します。 ハンドルされない例外は 5xx エラーに変換されます。
戻り値の型は、さまざまな HTTP 状態コードを表すことができます。 たとえば、GET /todoitems/{id}
は、次の 2 つの異なる状態値を返す可能性があります。
- 要求された ID と一致する項目がない場合、メソッドは 404 状態NotFound エラー コードを返します。
- それ以外の場合、メソッドは JSON 応答本文で 200 を返します。 戻り値が
item
の場合、HTTP 200 応答が返されます。
PUT エンドポイントを検証する
サンプル アプリは、MapPut
を使用して単一の PUT エンドポイントを実装しています。
app.MapPut("/todoitems/{id}", async (int id, Todo inputTodo, TodoDb db) =>
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return Results.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return Results.NoContent();
});
このメソッドは、HTTP PUT を使用していることを除き、MapPost
メソッドに似ています。 応答が成功すると 、204 (コンテンツなし) が返されます。 HTTP 仕様に従って、PUT 要求では、変更だけでなく、更新されたエンティティ全体を送信するようクライアントに求めます。 部分的な更新をサポートするには、 HTTP PATCH を使用します。
PUT エンドポイントをテストする
このサンプルでは、アプリを起動するたびに開始することが必要なメモリ内データベースが使われています。 PUT 呼び出しを実行する前に、データベース内にアイテムが存在している必要があります。 GET を呼び出して、PUT 呼び出しを実行する前にデータベース内にアイテムが確実に存在していることを確認します。
Id = 1
の To Do 項目を更新し、その名前を "feed fish"
に設定します。
Swagger を使って PUT 要求を送信します。
「Put /todoitems/{id}」>の試してみるを選択します。
id フィールドを
1
に設定します。要求本文を次の JSON に設定します。
{ "name": "feed fish", "isComplete": false }
[ 実行] を選択します。
DELETE エンドポイントの検証とテスト
サンプル アプリは、MapDelete
を使用して単一の DELETE エンドポイントを実装しています。
app.MapDelete("/todoitems/{id}", async (int id, TodoDb db) =>
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return Results.NoContent();
}
return Results.NotFound();
});
Swagger を使って DELETE 要求を送信します。
DELETE /todoitems/{id}>試してみてください。
[ID] フィールドを [
1
に設定し、[実行] を選択します。DELETE 要求がアプリに送信され、[応答] ウィンドウに 応答 が表示されます。 応答本文が空で、 サーバーの応答 状態コードが 204 です。
過剰な投稿を防止する
現在、サンプル アプリでは Todo
オブジェクト全体が公開されています。 運用環境アプリケーション内の運用環境アプリでは、入力され、返されるデータを制限するためにモデルのサブセットがよく使われます。 その背景には複数の理由があり、セキュリティは主なものです。 モデルのサブセットは、通常、データ転送オブジェクト (DTO)、入力モデル、またはビュー モデルと呼ばれます。 この記事では DTO を使用します。
DTO を使用すると、次のことができます。
- 過剰な投稿を防止する。
- クライアントが表示しないことになっているプロパティを非表示にする。
- ペイロード サイズを減らすには、いくつかのプロパティを省略します。
- 入れ子になったオブジェクトを含むオブジェクト グラフをフラット化する。 フラット化されたオブジェクト グラフは、クライアントにとってより便利になる可能性があります。
DTO のアプローチを実演するために、Todo
クラスを更新して、シークレット フィールドを含めます。
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public string? Secret { get; set; }
}
シークレット フィールドは、このアプリでは非表示にする必要がありますが、管理アプリの場合は公開することを選択できます。
シークレット フィールドを投稿および取得できることを確認します。
次のコードのファイルを、TodoItemDTO.cs
という名前で作成します。
public class TodoItemDTO
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public TodoItemDTO() { }
public TodoItemDTO(Todo todoItem) =>
(Id, Name, IsComplete) = (todoItem.Id, todoItem.Name, todoItem.IsComplete);
}
この DTO モデルを使うには、Program.cs
ファイルの内容を次のコードに置き換えます。
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
var app = builder.Build();
app.MapGet("/todoitems", async (TodoDb db) =>
await db.Todos.Select(x => new TodoItemDTO(x)).ToListAsync());
app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(new TodoItemDTO(todo))
: Results.NotFound());
app.MapPost("/todoitems", async (TodoItemDTO todoItemDTO, TodoDb db) =>
{
var todoItem = new Todo
{
IsComplete = todoItemDTO.IsComplete,
Name = todoItemDTO.Name
};
db.Todos.Add(todoItem);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todoItem.Id}", new TodoItemDTO(todoItem));
});
app.MapPut("/todoitems/{id}", async (int id, TodoItemDTO todoItemDTO, TodoDb db) =>
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return Results.NotFound();
todo.Name = todoItemDTO.Name;
todo.IsComplete = todoItemDTO.IsComplete;
await db.SaveChangesAsync();
return Results.NoContent();
});
app.MapDelete("/todoitems/{id}", async (int id, TodoDb db) =>
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return Results.NoContent();
}
return Results.NotFound();
});
app.Run();
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public string? Secret { get; set; }
}
public class TodoItemDTO
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public TodoItemDTO() { }
public TodoItemDTO(Todo todoItem) =>
(Id, Name, IsComplete) = (todoItem.Id, todoItem.Name, todoItem.IsComplete);
}
class TodoDb : DbContext
{
public TodoDb(DbContextOptions<TodoDb> options)
: base(options) { }
public DbSet<Todo> Todos => Set<Todo>();
}
シークレット フィールドを除くすべてのフィールドを投稿および取得できることを確認します。
Minimal API をテストする
最小限の API アプリをテストする例については、 この GitHub サンプルを参照してください。
Azure に発行する
Azure へのデプロイの詳細については、「 クイック スタート: ASP.NET Web アプリをデプロイする」を参照してください。
その他の技術情報
Minimal API は、依存関係が最小限の HTTP API を作成するために設計されています。 ASP.NET Core での最小限のファイル、機能、依存関係のみを含むマイクロサービスやアプリに最適です。
このチュートリアルでは、ASP.NET Core で最小 API を構築するための基本について説明します。 ASP.NET Core で API を作成するもう 1 つの方法は、コントローラーを使用することです。 最小限の API とコントローラー ベースの API の選択に関するヘルプについては、 API の概要を参照してください。 その他の機能を含む コントローラー に基づいて API プロジェクトを作成するチュートリアルについては、「 Web API の作成」を参照してください。
概要
このチュートリアルでは、次の API を作成します。
API(アプリケーション・プログラミング・インターフェース) | 説明 | 要求本文 | 応答本文 |
---|---|---|---|
GET /todoitems |
すべての To Do アイテムを取得します。 | 無し | To Do アイテムの配列 |
GET /todoitems/complete |
完了した To Do 項目を取得します。 | 無し | To Do アイテムの配列 |
GET /todoitems/{id} |
ID でアイテムを取得します。 | 無し | To Do アイテム |
POST /todoitems |
新しいアイテムを追加します。 | To Do アイテム | To Do アイテム |
PUT /todoitems/{id} |
既存のアイテムを更新します。 | To Do アイテム | 無し |
DELETE /todoitems/{id} |
アイテムを削除します。 | 無し | 無し |
必須コンポーネント
ASP.NET および Web 開発ワークロードを含む Visual Studio 2022。
API プロジェクトを作成する
Visual Studio 2022 を起動し、[ 新しいプロジェクトの作成] を選択します。
[ 新しいプロジェクトの作成] ダイアログで、次の手順を 実行します。
-
Empty
ボックスに、「」と入力します。 - ASP.NET Core Empty テンプレートを選択し、[次へ] を選択します。
-
プロジェクトに TodoApi という名前を付け、[ 次へ] を選択します。
[追加情報] ダイアログで、次の 手順 を実行します。
- .NET 8.0 (長期サポート) を選択する
- [最上位レベルのステートメントを使用しない] をオフにする
- [作成]を選択する
コードを確認する
Program.cs
ファイルには、次のコードが含まれています。
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
上記のコードでは次の操作が行われます。
- 事前に構成された既定値で WebApplicationBuilder と WebApplication を作成します。
-
/
を返す HTTP GET エンドポイントHello World!
を作成します。
アプリを実行する
Ctrl + F5 キーを押して、デバッガーなしで実行します。
Visual Studio に次のダイアログが表示されます。
IIS Express SSL 証明書を信頼する場合は、[ はい ] を選択します。
次のダイアログが表示されます。
開発証明書を信頼することに同意する場合は、[ はい ] を選択します。
Firefox ブラウザーを信頼する方法については、 Firefox SEC_ERROR_INADEQUATE_KEY_USAGE証明書エラーに関する記事を参照してください。
Visual Studio によって Kestrel Web サーバー が起動され、ブラウザー ウィンドウが開きます。
ブラウザーに Hello World!
が表示されます。
Program.cs
ファイルには、最小限の完成されたアプリが含まれています。
ブラウザー ウィンドウを閉じます。
NuGet パッケージを追加する
このチュートリアルで使用するデータベースと診断をサポートするには、NuGet パッケージを追加する必要があります。
- [ ツール ] メニューの [ NuGet パッケージ マネージャー] > [ソリューションの NuGet パッケージの管理] を選択します。
- 参照 タブを選択します。
- 検索ボックスに 「Microsoft.EntityFrameworkCore.InMemory 」と入力し、
Microsoft.EntityFrameworkCore.InMemory
を選択します。 - 右側のウィンドウで [ プロジェクト ] チェック ボックスをオンにし、[ インストール] を選択します。
- 上記の手順にしたがって、
Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
パッケージを追加します。
モデルおよびデータベース コンテキスト クラス
- project フォルダーで、次のコードを含む
Todo.cs
という名前のファイルを作成します。
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
}
上記のコードでは、このアプリのモデルを作成します。 "モデル" は、アプリが管理するデータを表すクラスです。
- 次のコードのファイルを、
TodoDb.cs
という名前で作成します。
using Microsoft.EntityFrameworkCore;
class TodoDb : DbContext
{
public TodoDb(DbContextOptions<TodoDb> options)
: base(options) { }
public DbSet<Todo> Todos => Set<Todo>();
}
上記のコードでは、データ モデルの Entity Framework 機能を調整するメイン クラスであるデータベース コンテキストを定義しています。 このクラスは Microsoft.EntityFrameworkCore.DbContext クラスから派生したクラスです。
API コードを追加する
-
Program.cs
ファイルの内容を次のコードに置き換えます。
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
app.MapGet("/todoitems", async (TodoDb db) =>
await db.Todos.ToListAsync());
app.MapGet("/todoitems/complete", async (TodoDb db) =>
await db.Todos.Where(t => t.IsComplete).ToListAsync());
app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound());
app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
});
app.MapPut("/todoitems/{id}", async (int id, Todo inputTodo, TodoDb db) =>
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return Results.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return Results.NoContent();
});
app.MapDelete("/todoitems/{id}", async (int id, TodoDb db) =>
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return Results.NoContent();
}
return Results.NotFound();
});
app.Run();
次の強調表示されたコードは、データベース コンテキストを 依存関係挿入 (DI) コンテナーに追加し、データベース関連の例外を表示できるようにします。
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
DI コンテナーは、データベース コンテキストやその他のサービスへのアクセスを提供します。
このチュートリアルでは 、エンドポイント エクスプローラーと .http ファイル を使用して API をテストします。
データの POST をテストする
次の Program.cs
のコードにより、データをメモリ内データベースに追加する HTTP POST のエンドポイント /todoitems
が作成されます。
app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
});
アプリを実行します。
/
エンドポイントが存在しなくなったため、ブラウザーに 404 エラーが表示されます。
POST エンドポイントは、アプリにデータを追加するために使われます。
表示>その他の Windows>Endpoints エクスプローラーを選択します。
POST エンドポイントを右クリックし、[要求の生成] を選択します。
次の例のような内容の
TodoApi.http
という新しいファイルがプロジェクト フォルダー内に作成されます。@TodoApi_HostAddress = https://localhost:7031 Post {{TodoApi_HostAddress}}/todoitems ###
- 最初の行では、すべてのエンドポイントに使われる変数を作成します。
- 次の行では、POST 要求を定義しています。
- トリプル ハッシュタグ (
###
) 行は要求の区切り記号であり、この後に続くのは別の要求向けのものです。
POST 要求にはヘッダーと本文が必要です。 要求のこれらの部分を定義するには、POST 要求行の直後に次の行を追加します。
Content-Type: application/json { "name":"walk dog", "isComplete":true }
先ほどのコードでは、Content-Type ヘッダーと JSON 要求の本文を追加しています。 TodoApi.http ファイルは次の例のようになりますが、実際のポート番号に置き換えてください。
@TodoApi_HostAddress = https://localhost:7057 Post {{TodoApi_HostAddress}}/todoitems Content-Type: application/json { "name":"walk dog", "isComplete":true } ###
アプリを実行します。
要求行の上にある
POST
リンクを選択します。POST 要求がアプリに送信され、応答ウィンドウに 応答 が表示されます。
GET エンドポイントを検証する
サンプル アプリでは、MapGet
の呼び出しにより、いくつかの GET エンドポイントを実装しています。
API(アプリケーション・プログラミング・インターフェース) | 説明 | 要求本文 | 応答本文 |
---|---|---|---|
GET /todoitems |
すべての To Do アイテムを取得します。 | 無し | To Do アイテムの配列 |
GET /todoitems/complete |
すべての完了した To Do 項目を取得します | 無し | To Do アイテムの配列 |
GET /todoitems/{id} |
ID でアイテムを取得します。 | 無し | To Do アイテム |
app.MapGet("/todoitems", async (TodoDb db) =>
await db.Todos.ToListAsync());
app.MapGet("/todoitems/complete", async (TodoDb db) =>
await db.Todos.Where(t => t.IsComplete).ToListAsync());
app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound());
GET エンドポイントをテストする
ブラウザーから GET
エンドポイントを呼び出すか、 エンドポイント エクスプローラーを使用して、アプリをテストします。 エンドポイント エクスプローラーの手順を次 に示します。
エンドポイント エクスプローラーで、最初の GET エンドポイントを右クリックし、[要求の生成] を選択します。
TodoApi.http
ファイルに以下の内容が追加されます。Get {{TodoApi_HostAddress}}/todoitems ###
新しい 要求行の上にある
GET
リンクを選択します。GET 要求がアプリに送信され、応答ウィンドウに 応答 が表示されます。
応答本文は次の JSON のようになります。
[ { "id": 1, "name": "walk dog", "isComplete": true } ]
エンドポイント エクスプローラーで、
/todoitems/{id}
GET エンドポイントを右クリックし、[要求の生成] を選択します。TodoApi.http
ファイルに以下の内容が追加されます。GET {{TodoApi_HostAddress}}/todoitems/{id} ###
{id}
を1
で置き換え新しい GET 要求行の上にある [要求の送信] リンクを選択します。
GET 要求がアプリに送信され、応答ウィンドウに 応答 が表示されます。
応答本文は次の JSON のようになります。
{ "id": 1, "name": "walk dog", "isComplete": true }
このアプリではメモリ内データベースが使用されます。 アプリを再起動すると、GET 要求はデータを返しません。 データが返されない場合は、アプリに データを POST し、GET 要求をもう一度試します。
戻り値
ASP.NET Core は、オブジェクトを JSON に自動的にシリアル化し、応答メッセージの本文に JSON を書き込みます。 この戻り値の型の応答コードは 200 OK です。ハンドルされない例外がないと仮定します。 ハンドルされない例外は 5xx エラーに変換されます。
戻り値の型は、さまざまな HTTP 状態コードを表すことができます。 たとえば、GET /todoitems/{id}
は、次の 2 つの異なる状態値を返す可能性があります。
- 要求された ID と一致する項目がない場合、メソッドは 404 状態NotFound エラー コードを返します。
- それ以外の場合、メソッドは JSON 応答本文で 200 を返します。 戻り値が
item
の場合、HTTP 200 応答が返されます。
PUT エンドポイントを検証する
サンプル アプリは、MapPut
を使用して単一の PUT エンドポイントを実装しています。
app.MapPut("/todoitems/{id}", async (int id, Todo inputTodo, TodoDb db) =>
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return Results.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return Results.NoContent();
});
このメソッドは、HTTP PUT を使用していることを除き、MapPost
メソッドに似ています。 応答が成功すると 、204 (コンテンツなし) が返されます。 HTTP 仕様に従って、PUT 要求では、変更だけでなく、更新されたエンティティ全体を送信するようクライアントに求めます。 部分的な更新をサポートするには、 HTTP PATCH を使用します。
PUT エンドポイントをテストする
このサンプルでは、アプリを起動するたびに開始することが必要なメモリ内データベースが使われています。 PUT 呼び出しを実行する前に、データベース内にアイテムが存在している必要があります。 GET を呼び出して、PUT 呼び出しを実行する前にデータベース内にアイテムが確実に存在していることを確認します。
Id = 1
の To Do 項目を更新し、その名前を "feed fish"
に設定します。
エンドポイント エクスプローラーでPUT エンドポイントを右クリックし、[要求の生成] を選択します。
TodoApi.http
ファイルに以下の内容が追加されます。Put {{TodoApi_HostAddress}}/todoitems/{id} ###
PUT 要求行の
{id}
を1
に置き換えます。PUT 要求行の直後に以下の行を追加します。
Content-Type: application/json { "name": "feed fish", "isComplete": false }
先ほどのコードでは、Content-Type ヘッダーと JSON 要求の本文を追加しています。
新しい PUT 要求行の上にある [要求の 送信] リンクを選択します。
PUT 要求がアプリに送信され、応答ウィンドウに 応答 が表示されます。 応答本文は空であり、状態コードは 204 です。
DELETE エンドポイントの検証とテスト
サンプル アプリは、MapDelete
を使用して単一の DELETE エンドポイントを実装しています。
app.MapDelete("/todoitems/{id}", async (int id, TodoDb db) =>
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return Results.NoContent();
}
return Results.NotFound();
});
エンドポイント エクスプローラーで、DELETE エンドポイントを右クリックし、[要求の生成] を選択します。
DELETE 要求が
TodoApi.http
に追加されます。DELETE 要求行の
{id}
を1
に置き換えます。 DELETE 要求は次の例のようになります。DELETE {{TodoApi_HostAddress}}/todoitems/1 ###
DELETE リクエストの送信リンクを選択します。
DELETE 要求がアプリに送信され、応答ウィンドウに 応答 が表示されます。 応答本文は空であり、状態コードは 204 です。
MapGroup API を使う
サンプル アプリ コードでは、エンドポイントを設定するたびに todoitems
URL プレフィックスが繰り返されます。 API には共通の URL プレフィックスを持つエンドポイントのグループがあることが多く、MapGroup メソッドはそのようなグループの整理に役立ちます。 これにより、繰り返しのコードを減らし、RequireAuthorization や WithMetadata のようなメソッドを 1 回呼び出すだけで、エンドポイントのグループ全体をカスタマイズできます。
Program.cs
の内容を次のコードに置き換えます。
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
var todoItems = app.MapGroup("/todoitems");
todoItems.MapGet("/", async (TodoDb db) =>
await db.Todos.ToListAsync());
todoItems.MapGet("/complete", async (TodoDb db) =>
await db.Todos.Where(t => t.IsComplete).ToListAsync());
todoItems.MapGet("/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound());
todoItems.MapPost("/", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
});
todoItems.MapPut("/{id}", async (int id, Todo inputTodo, TodoDb db) =>
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return Results.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return Results.NoContent();
});
todoItems.MapDelete("/{id}", async (int id, TodoDb db) =>
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return Results.NoContent();
}
return Results.NotFound();
});
app.Run();
上記のコードには、次の変更が加えられています。
-
var todoItems = app.MapGroup("/todoitems");
を追加して、URL プレフィックス/todoitems
を使ってグループを設定します。 - すべての
app.Map<HttpVerb>
メソッドをtodoItems.Map<HttpVerb>
に変更します。 -
/todoitems
メソッド呼び出しから URL プレフィックスMap<HttpVerb>
を削除します。
エンドポイントをテストして、同じように動作することを確認します。
TypedResults API を使う
TypedResults ではなく Results を返すと、テストのしやすさや、エンドポイントを記述するための OpenAPI の応答型メタデータが自動的に返されるなど、いくつかの利点があります。 詳細については、「TypedResults と Results」を参照してください。
Map<HttpVerb>
メソッドは、ラムダ式を使う代わりにルート ハンドラー メソッドを呼び出すことができます。 例を表示するには、次のコード でProgram.cs を更新します。
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
var todoItems = app.MapGroup("/todoitems");
todoItems.MapGet("/", GetAllTodos);
todoItems.MapGet("/complete", GetCompleteTodos);
todoItems.MapGet("/{id}", GetTodo);
todoItems.MapPost("/", CreateTodo);
todoItems.MapPut("/{id}", UpdateTodo);
todoItems.MapDelete("/{id}", DeleteTodo);
app.Run();
static async Task<IResult> GetAllTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.ToArrayAsync());
}
static async Task<IResult> GetCompleteTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.Where(t => t.IsComplete).ToListAsync());
}
static async Task<IResult> GetTodo(int id, TodoDb db)
{
return await db.Todos.FindAsync(id)
is Todo todo
? TypedResults.Ok(todo)
: TypedResults.NotFound();
}
static async Task<IResult> CreateTodo(Todo todo, TodoDb db)
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return TypedResults.Created($"/todoitems/{todo.Id}", todo);
}
static async Task<IResult> UpdateTodo(int id, Todo inputTodo, TodoDb db)
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return TypedResults.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return TypedResults.NoContent();
}
static async Task<IResult> DeleteTodo(int id, TodoDb db)
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return TypedResults.NoContent();
}
return TypedResults.NotFound();
}
これで Map<HttpVerb>
のコードは、ラムダ式ではなくメソッドを呼び出すようになりました。
var todoItems = app.MapGroup("/todoitems");
todoItems.MapGet("/", GetAllTodos);
todoItems.MapGet("/complete", GetCompleteTodos);
todoItems.MapGet("/{id}", GetTodo);
todoItems.MapPost("/", CreateTodo);
todoItems.MapPut("/{id}", UpdateTodo);
todoItems.MapDelete("/{id}", DeleteTodo);
これらのメソッドは、IResult を実装し TypedResults で定義されるオブジェクトを返します。
static async Task<IResult> GetAllTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.ToArrayAsync());
}
static async Task<IResult> GetCompleteTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.Where(t => t.IsComplete).ToListAsync());
}
static async Task<IResult> GetTodo(int id, TodoDb db)
{
return await db.Todos.FindAsync(id)
is Todo todo
? TypedResults.Ok(todo)
: TypedResults.NotFound();
}
static async Task<IResult> CreateTodo(Todo todo, TodoDb db)
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return TypedResults.Created($"/todoitems/{todo.Id}", todo);
}
static async Task<IResult> UpdateTodo(int id, Todo inputTodo, TodoDb db)
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return TypedResults.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return TypedResults.NoContent();
}
static async Task<IResult> DeleteTodo(int id, TodoDb db)
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return TypedResults.NoContent();
}
return TypedResults.NotFound();
}
単体テストはこれらのメソッドを呼び出して、正しい型を返すかどうかをテストできます。 たとえば、メソッドが GetAllTodos
である場合:
static async Task<IResult> GetAllTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.ToArrayAsync());
}
単体テスト コードでは 、Ok<Todo[]> 型のオブジェクトがハンドラー メソッドから返されることを確認できます。 次に例を示します。
public async Task GetAllTodos_ReturnsOkOfTodosResult()
{
// Arrange
var db = CreateDbContext();
// Act
var result = await TodosApi.GetAllTodos(db);
// Assert: Check for the correct returned type
Assert.IsType<Ok<Todo[]>>(result);
}
過剰な投稿を防止する
現在、サンプル アプリでは Todo
オブジェクト全体が公開されています。 運用環境アプリケーション内の運用環境アプリでは、入力され、返されるデータを制限するためにモデルのサブセットがよく使われます。 その背景には複数の理由があり、セキュリティは主なものです。 モデルのサブセットは、通常、データ転送オブジェクト (DTO)、入力モデル、またはビュー モデルと呼ばれます。 この記事では DTO を使用します。
DTO を使用すると、次のことができます。
- 過剰な投稿を防止する。
- クライアントが表示しないことになっているプロパティを非表示にする。
- ペイロード サイズを減らすには、いくつかのプロパティを省略します。
- 入れ子になったオブジェクトを含むオブジェクト グラフをフラット化する。 フラット化されたオブジェクト グラフは、クライアントにとってより便利になる可能性があります。
DTO のアプローチを実演するために、Todo
クラスを更新して、シークレット フィールドを含めます。
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public string? Secret { get; set; }
}
シークレット フィールドは、このアプリでは非表示にする必要がありますが、管理アプリの場合は公開することを選択できます。
シークレット フィールドを投稿および取得できることを確認します。
次のコードのファイルを、TodoItemDTO.cs
という名前で作成します。
public class TodoItemDTO
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public TodoItemDTO() { }
public TodoItemDTO(Todo todoItem) =>
(Id, Name, IsComplete) = (todoItem.Id, todoItem.Name, todoItem.IsComplete);
}
この DTO モデルを使うには、Program.cs
ファイルの内容を次のコードに置き換えます。
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
RouteGroupBuilder todoItems = app.MapGroup("/todoitems");
todoItems.MapGet("/", GetAllTodos);
todoItems.MapGet("/complete", GetCompleteTodos);
todoItems.MapGet("/{id}", GetTodo);
todoItems.MapPost("/", CreateTodo);
todoItems.MapPut("/{id}", UpdateTodo);
todoItems.MapDelete("/{id}", DeleteTodo);
app.Run();
static async Task<IResult> GetAllTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.Select(x => new TodoItemDTO(x)).ToArrayAsync());
}
static async Task<IResult> GetCompleteTodos(TodoDb db) {
return TypedResults.Ok(await db.Todos.Where(t => t.IsComplete).Select(x => new TodoItemDTO(x)).ToListAsync());
}
static async Task<IResult> GetTodo(int id, TodoDb db)
{
return await db.Todos.FindAsync(id)
is Todo todo
? TypedResults.Ok(new TodoItemDTO(todo))
: TypedResults.NotFound();
}
static async Task<IResult> CreateTodo(TodoItemDTO todoItemDTO, TodoDb db)
{
var todoItem = new Todo
{
IsComplete = todoItemDTO.IsComplete,
Name = todoItemDTO.Name
};
db.Todos.Add(todoItem);
await db.SaveChangesAsync();
todoItemDTO = new TodoItemDTO(todoItem);
return TypedResults.Created($"/todoitems/{todoItem.Id}", todoItemDTO);
}
static async Task<IResult> UpdateTodo(int id, TodoItemDTO todoItemDTO, TodoDb db)
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return TypedResults.NotFound();
todo.Name = todoItemDTO.Name;
todo.IsComplete = todoItemDTO.IsComplete;
await db.SaveChangesAsync();
return TypedResults.NoContent();
}
static async Task<IResult> DeleteTodo(int id, TodoDb db)
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return TypedResults.NoContent();
}
return TypedResults.NotFound();
}
シークレット フィールドを除くすべてのフィールドを投稿および取得できることを確認します。
完成したサンプルを使用したトラブルシューティング
解決できない問題が発生した場合は、コードを完成したプロジェクトと比較します。 完成したプロジェクトを表示またはダウンロード します (ダウンロード方法)。
次のステップ
- JSON シリアル化オプションを構成します。
- エラーと例外の処理: 開発環境では、最小限の API アプリに対して 開発者例外ページ が既定で有効になっています。 エラーと例外を処理する方法については、「 ASP.NET Core API でのエラーの処理」を参照してください。
- 最小限の API アプリをテストする例については、 この GitHub サンプルを参照してください。
- 最小限の API での OpenAPI サポート。
- クイック スタート: Azure に発行します。
- ASP.NET Core Minimal API の整理。
詳細情報
最小限の API のクイック リファレンスを参照してください
ASP.NET Core