Note
これは、この記事の最新バージョンではありません。 現在のリリースについては、 この記事の .NET 10 バージョンを参照してください。
Warning
このバージョンの ASP.NET Core はサポートされなくなりました。 詳細については、 .NET および .NET Core サポート ポリシーを参照してください。 現在のリリースについては、 この記事の .NET 10 バージョンを参照してください。
Minimal API は、依存関係が最小限の HTTP API を作成するために設計されています。 ASP.NET Core での最小限のファイル、機能、依存関係のみを含むマイクロサービスやアプリに最適です。
このチュートリアルでは、ASP.NET Core を使用した最小限の API の構築の基本について説明します。 ASP.NET Core で API を作成するもう 1 つの方法は、コントローラーを使用することです。 最小限の API とコントローラー ベースの API の選択に関するヘルプについては、 API の概要を参照してください。 その他の機能を含む コントローラー に基づいて API プロジェクトを作成するチュートリアルについては、「 Web API の作成」を参照してください。
Overview
このチュートリアルでは、次の API を作成します。
| API | Description | リクエストの本文 | 応答本文 |
|---|---|---|---|
GET /todoitems |
すべての To Do アイテムを取得します。 | None | To Do アイテムの配列 |
GET /todoitems/complete |
完了した To Do 項目を取得します。 | None | To Do アイテムの配列 |
GET /todoitems/{id} |
ID でアイテムを取得します。 | None | To Do 項目 |
POST /todoitems |
新しいアイテムを追加します。 | To Do 項目 | To Do 項目 |
PUT /todoitems/{id} |
既存のアイテムを更新します。 | To Do 項目 | None |
DELETE /todoitems/{id} |
アイテムを削除します。 | None | None |
Prerequisites
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 パッケージの管理] を選択します。
- 「Browse」タブを選択します。
- [ プレリリースを含める] を選択します。
- 検索ボックスに 「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 エンドポイントは、アプリにデータを追加するために使われます。
表示>メニューを選択し、その他のウィンドウ>からEndpoints Explorerを選びます。
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 | Description | リクエストの本文 | 応答本文 |
|---|---|---|---|
GET /todoitems |
すべての To Do アイテムを取得します。 | None | To Do アイテムの配列 |
GET /todoitems/complete |
すべての完了した To Do 項目を取得します | None | To Do アイテムの配列 |
GET /todoitems/{id} |
ID でアイテムを取得します。 | None | 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 要求がアプリに送信され、応答ウィンドウに 応答 が表示されます。
応答本文は次の 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 の整理。
Learn more
最小限の API のクイック リファレンスを参照してください
Minimal API は、依存関係が最小限の HTTP API を作成するために設計されています。 ASP.NET Core での最小限のファイル、機能、依存関係のみを含むマイクロサービスやアプリに最適です。
このチュートリアルでは、ASP.NET Core を使用した最小限の API の構築の基本について説明します。 ASP.NET Core で API を作成するもう 1 つの方法は、コントローラーを使用することです。 最小限の API とコントローラー ベースの API の選択に関するヘルプについては、 API の概要を参照してください。 その他の機能を含む コントローラー に基づいて API プロジェクトを作成するチュートリアルについては、「 Web API の作成」を参照してください。
Overview
このチュートリアルでは、次の API を作成します。
| API | Description | リクエストの本文 | 応答本文 |
|---|---|---|---|
GET /todoitems |
すべての To Do アイテムを取得します。 | None | To Do アイテムの配列 |
GET /todoitems/complete |
完了した To Do 項目を取得します。 | None | To Do アイテムの配列 |
GET /todoitems/{id} |
ID でアイテムを取得します。 | None | To Do 項目 |
POST /todoitems |
新しいアイテムを追加します。 | To Do 項目 | To Do 項目 |
PUT /todoitems/{id} |
既存のアイテムを更新します。 | To Do 項目 | None |
DELETE /todoitems/{id} |
アイテムを削除します。 | None | None |
Prerequisites
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 パッケージの管理] を選択します。
- 「Browse」タブを選択します。
- 検索ボックスに 「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を指定せずに、「JSON for a to-do item」と入力します。{ "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 | Description | リクエストの本文 | 応答本文 |
|---|---|---|---|
GET /todoitems |
すべての To Do アイテムを取得します。 | None | To Do アイテムの配列 |
GET /todoitems/complete |
すべての完了した To Do 項目を取得します | None | To Do アイテムの配列 |
GET /todoitems/{id} |
ID でアイテムを取得します。 | None | 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 の整理。
Learn more
最小限の API のクイック リファレンスを参照してください
Minimal API は、依存関係が最小限の HTTP API を作成するために設計されています。 ASP.NET Core での最小限のファイル、機能、依存関係のみを含むマイクロサービスやアプリに最適です。
このチュートリアルでは、ASP.NET Core を使用した最小限の API の構築の基本について説明します。 ASP.NET Core で API を作成するもう 1 つの方法は、コントローラーを使用することです。 最小限の API とコントローラー ベースの API の選択に関するヘルプについては、 API の概要を参照してください。 その他の機能を含む コントローラー に基づいて API プロジェクトを作成するチュートリアルについては、「 Web API の作成」を参照してください。
Overview
このチュートリアルでは、次の API を作成します。
| API | Description | リクエストの本文 | 応答本文 |
|---|---|---|---|
GET /todoitems |
すべての To Do アイテムを取得します。 | None | To Do アイテムの配列 |
GET /todoitems/complete |
完了した To Do 項目を取得します。 | None | To Do アイテムの配列 |
GET /todoitems/{id} |
ID でアイテムを取得します。 | None | To Do 項目 |
POST /todoitems |
新しいアイテムを追加します。 | To Do 項目 | To Do 項目 |
PUT /todoitems/{id} |
既存のアイテムを更新します。 | To Do 項目 | None |
DELETE /todoitems/{id} |
アイテムを削除します。 | None | None |
Prerequisites
- 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 パッケージの管理] を選択します。
- 「Browse」タブを選択します。
- 検索ボックスに 「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を指定せずに、「JSON for a to-do item」と入力します。{ "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 | Description | リクエストの本文 | 応答本文 |
|---|---|---|---|
GET /todoitems |
すべての To Do アイテムを取得します。 | None | To Do アイテムの配列 |
GET /todoitems/complete |
すべての完了した To Do 項目を取得します | None | To Do アイテムの配列 |
GET /todoitems/{id} |
ID でアイテムを取得します。 | None | 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 の作成」を参照してください。
Overview
このチュートリアルでは、次の API を作成します。
| API | Description | リクエストの本文 | 応答本文 |
|---|---|---|---|
GET /todoitems |
すべての To Do アイテムを取得します。 | None | To Do アイテムの配列 |
GET /todoitems/complete |
完了した To Do 項目を取得します。 | None | To Do アイテムの配列 |
GET /todoitems/{id} |
ID でアイテムを取得します。 | None | To Do 項目 |
POST /todoitems |
新しいアイテムを追加します。 | To Do 項目 | To Do 項目 |
PUT /todoitems/{id} |
既存のアイテムを更新します。 | To Do 項目 | None |
DELETE /todoitems/{id} |
アイテムを削除します。 | None | None |
Prerequisites
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 パッケージの管理] を選択します。
- 「Browse」タブを選択します。
- 検索ボックスに 「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 エンドポイントは、アプリにデータを追加するために使われます。
表示>メニューを選択し、その他のウィンドウ>からEndpoints Explorerを選びます。
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 | Description | リクエストの本文 | 応答本文 |
|---|---|---|---|
GET /todoitems |
すべての To Do アイテムを取得します。 | None | To Do アイテムの配列 |
GET /todoitems/complete |
すべての完了した To Do 項目を取得します | None | To Do アイテムの配列 |
GET /todoitems/{id} |
ID でアイテムを取得します。 | None | 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 要求がアプリに送信され、応答ウィンドウに 応答 が表示されます。
応答本文は次の 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 の整理。
Learn more
最小限の API のクイック リファレンスを参照してください
ASP.NET Core