Minimal APIs は .NET 6 で導入された興味深い機能で、API の作成方法を革新することを目的としています。
最小限のコードとゼロの定型コードで堅牢な API を構築する想像をしてみてください—もうコントローラー、ルーティング、ミドルウェアと格闘する必要はありません。それが Minimal APIs が可能にするものです。これらの API のアイデアは、開発プロセスを合理化し、非常に簡単で効率的にすることです。
この記事では、.NET 8 の Minimal APIs の世界に飛び込み、完全に機能する書店 API を作成する方法をガイドします。すべての本を取得する方法、ID で本を検索する方法、新しい本を追加する方法、そして本を削除する方法を学びます。始めましょう。
目次
前提条件
始める前に、以下の前提条件がお使いのマシンにインストールされていることを確認してください:
-
Visual Studio Code またはお好みのコードエディタ
-
C# Dev Kit for Visual Studio Code
代わりに、Visual Studio 2022を使用することもできます。これは.NET 8のサポートが組み込まれています。しかし、この記事ではVisual Studio Codeを使用します。軽量で使いやすく、クロスプラットフォームです。
APIのテストにはSwagger UIを使用します。Swagger UIは、ブラウザから直接APIと対話できる強力なツールです。APIエンドポイントをテストするためのユーザーフレンドリーなインターフェースを提供し、APIのテストとデバッグを容易にします。
新しいプロジェクトを作成すると、必要なパッケージが自動的にインストールされ、Swagger UIを使用するようにプロジェクトが設定されます。.NET 8にはデフォルトでSwagger UIが含まれているため、Visual Studioでアプリケーションを作成するか、.NETを使用するかに関わらず、Swagger UIが設定されます。
アプリケーションを実行すると、Swagger UIがブラウザで自動的に開きますが、VS Codeを使用しているため、ターミナルのポート番号をクリックする必要があります。
このプロジェクトのソースコードはGitHubで見つけることができます。
ミニマルAPI入門
多数のエンドポイントがあり、非常に大きく複雑なコードベースで作業していることを想像してみてください。伝統的に、ASP.NET CoreでAPIを構築するには、コントローラー、ルーティング、ミドルウェア、そして大量のボイラープレートコードを使用します。しかし、ASP.NET CoreでAPIを構築する方法には、伝統的な方法とミニマルな方法の2つがあります。
伝統的な方法はほとんどの開発者に馴染みがあり、コントローラーと広範なインフラストラクチャコードを含みます。ミニマルな方法は、.NET 6
で導入され、最小限のコードとゼロのボイラープレートでAPIを作成できるようにします。このアプローチは開発プロセスを簡素化し、インフラストラクチャコードに対応する代わりにビジネスロジックの記述に集中できるようにします。
ミニマルAPIは軽量で高速であり、小規模から中規模のAPI構築に最適です。プロトタイピング、マイクロサービスの構築、または複雑さをあまり必要としないシンプルなAPIの作成に理想的です。このハンドブックでは、.NET 6でのミニマルAPIの世界を探求し、ゼロから完全に機能する書店APIを作成する方法を学びます。
ミニマルAPIの作成方法
dotnet CLI
を使用してミニマルAPIを作成するのは簡単で、デフォルトのテンプレートはすでにミニマルAPIです。しかし、Visual Studioを使用する場合は、プロジェクトテンプレートに含まれるボイラープレートコードを削除する必要があります。
まずはdotnet CLI
を使用して最小限のAPIプロジェクトを作成しましょう。
dotnet new webapi -n BookStoreApi
dotnet new webapi
コマンドで新しい最小限のAPIプロジェクトBookStoreApi
が作成されます。このプロジェクトには、始めるために必要なファイルとフォルダが含まれています。
プロジェクトの構造を探ってみましょう:
-
Program.cs
:アプリケーションのエントリーポイントで、ホストの設定が行われます。 -
bookapi-minimal.sln
:プロジェクトを含むソリューションファイルです。 -
bookapi-minimal.http
:APIをテストするためのサンプルHTTPリクエストが含まれるファイルです。 -
bookapi-minimal.csproj
:プロジェクトの設定が含まれるプロジェクトファイルです。 -
appsettings.json
:アプリケーションの設定を保存する設定ファイルです。 -
appsettings.Development.json
:開発環境用の設定ファイルです。
プログラム.csファイルを開くと、コードが最小限であることに気づくでしょう。Program.cs
ファイルには以下のコードが含まれています:
var builder = WebApplication.CreateBuilder(args);
// コンテナーにサービスを追加します。
// Swagger/OpenAPIの設定について詳しくは、https://aka.ms/aspnetcore/swashbuckle を参照してください
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// HTTP要求パイプラインを構成します。
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
var summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
app.MapGet("/weatherforecast", () =>
{
var forecast = Enumerable.Range(1, 5).Select(index =>
new WeatherForecast
(
DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
Random.Shared.Next(-20, 55),
summaries[Random.Shared.Next(summaries.Length)]
))
.ToArray();
return forecast;
})
.WithName("GetWeatherForecast")
.WithOpenApi();
app.Run();
record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
{
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
まだコードを完全に理解していない場合でも心配しないでください—今後のセクションで詳しく説明します。重要なポイントは、最小限のAPIは非常に少ないコードで済むことです。これがその主要な利点の一つです。
デフォルトのコードは、セットアップをテストするために使用できるシンプルな天気予報APIを設定します。これは天気予報のリストを生成し、/weatherforecast
エンドポイントに対してGET
リクエストを行うとそれらを返します。また、コードにはAPIをテストするためのSwagger UIも含まれています。
app.MapGet
メソッドに特に注意してください。これはルートをハンドラ関数にマッピングします。この場合、/weatherforecast
ルートを天気予報のリストを返す関数にマッピングしています。次のセクションでは、同様のメソッドを使用して独自のエンドポイントを作成します。
プロジェクトのフォルダ構造を作成する前に、コントローラーベースとミニマルAPIの両方でのHTTPメソッドについて理解しましょう。
コントローラーベースとミニマルAPIのHTTPメソッド
コントローラーベースのアプローチは、Web APIを作成する従来の方法で、コントローラークラスを作成し、各HTTPメソッドに対してメソッドを定義する必要があります。例えば:
-
「GET」メソッドを作成するには、「[HttpGet]」属性を使用します。
-
「POST」メソッドを作成するには、「[HttpPost]」属性を使用します。
-
「PUT」メソッドを作成するには、「[HttpPut]」属性を使用します。
-
「DELETE」メソッドを作成するには、「[HttpDelete]」属性を使用します。
これがコントローラーベースのアプローチでエンドポイントが作成される方法です。
対照的に、ミニマルAPIは「app.MapGet」、「app.MapPost」、「app.MapPut」、「app.MapDelete」などのメソッドを使用してエンドポイントを作成します。これが 두つのアプローチの主な違いです:コントローラーベースのAPIは属性を使用してエンドポイントを定義するのに対し、ミニマルAPIはメソッドを使用します。
これでコントローラーベースとミニマルAPIの両方でHTTPリクエストを処理する方法を理解したので、プロジェクトのフォルダ構造を作成しましょう。
プロジェクトのフォルダ構造を作成する前に、まずは今持っているものを実行してみましょう。以前学んだように、Visual Studioまたは.NET CLIでプロジェクトを作成すると、デフォルトのWeatherForecastプロジェクトが付属しており、これを実行してUIで確認することができます。それを実行して、すべてが正常に動作することを確認してから、プロジェクトフォルダの作成に進みましょう。
次のコマンドを実行します:
dotnet run
以下の出力が表示されるはずです:
info: Microsoft.Hosting.Lifetime[14]
Now listening on: http://localhost:5228
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
Content root path: D:\Devolopemnt\Dotnet\bookapi-minimal
これは、アプリケーションが実行中であり、http://localhost:5228
でリスニングしていることを意味します。上述したように、dotnet CLI
とVisual Studio Codeを使用しているため、アプリケーションはブラウザを自動的に開いてくれません。これを手動で行う必要があります。
ブラウザを開き、http://localhost:5228/swagger/index.html
に移動して、APIからのデフォルトのレスポンスを確認します。
次のようなものが表示されるはずです:
次にやるべきことは、プロジェクトを整理し、必要なファイルとフォルダを作成して、スタートするための準備をすることです。
ミニマルAPIプロジェクトファイル
プロジェクトを整理するために、構造化されたフォルダ階層を作成します。これにより、コードをクリーンに保ち、保守しやすくなります。以下は使用するフォルダ構造です:
-
AppContext: データベースコンテキストおよび関連する設定を含みます。
-
設定: Entity Framework Coreの設定およびデータベースのシードデータを保持します。
-
契約: 私たちのアプリケーションで使用されるデータ転送オブジェクト(DTO)を含みます。
-
エンドポイント: ここで私たちのミニマルAPIエンドポイントを定義および設定します。
-
例外: プロジェクトで使用されるカスタム例外クラスを含みます。
-
拡張: プロジェクト全体で使用する拡張メソッドを保持します。
-
モデル: ビジネスロジックモデルを含みます。
-
サービス: ビジネスロジックを実装するサービスクラスを含みます。
-
インターフェース: 私たちのサービスをマッピングするためのインターフェース定義を保持します。
Visual Studio Codeでは、以下のようにこのフォルダ構造を作成できます:
- AppContext
- Configurations
- Contracts
- Endpoints
- Exceptions
- Extensions
- Models
- Services
- Interfaces
設定後、プロジェクトのフォルダ構造は以下のようになるはずです:
プロジェクト構造が設定されたので、コードの記述を始めることができます。まずはモデルの作成から始めましょう。
モデルの作成方法
このセクションでは、アプリケーションのモデルを作成します。モデルはアプリケーションの構成要素であり、アプリケーションが扱うデータを表します。例として、本のモデルを作成します。
始めるために、プロジェクトディレクトリにModels
という名前のフォルダを作成します。このフォルダ内に、BookModel.cs
という名前のファイルを作成し、以下のコードを追加します:
// Models/BookModel.cs
namespace bookapi_minimal.Models
{
public class BookModel
{
public Guid Id { get; set; }
public string Title { get; set; }
public string Author { get; set; }
public string Description { get; set; }
public string Category { get; set; }
public string Language { get; set; }
public int TotalPages { get; set; }
}
}
このBookModelクラス
は、本の詳細を表すプロパティを定義します。たとえば、title
、author
、description
、category
、language
、およびtotal pages
です。各プロパティは、本に関する特定の情報を保持するように設計されており、アプリケーション内で本のデータを管理し操作するのが容易になります。
モデルを作成したので、次はデータベースコンテキストを作成しましょう。
データベースコンテキストの作成方法
データベースコンテキストは、データベースとのセッションを表すクラスです。データベースとのインタラクションとデータベース操作の実行がその役割です。私たちのアプリケーションでは、Entity Framework Coreを使用してデータベースとやり取りします。
必要なパッケージのインストール
データベースコンテキストを作成する前に、以下のパッケージをインストールする必要があります:
-
Microsoft.EntityFrameworkCore
-
Microsoft.EntityFrameworkCore.SqlServer
-
FluentValidation.DependencyInjectionExtensions
これらのパッケージは、以下のコマンドを使用してインストールできます:
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet add package Microsoft.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Tools
dotnet add package FluentValidation.DependencyInjectionExtensions
パッケージのインストールを確認する
パッケージがインストールされていることを確認するには、プロジェクトのルートディレクトリにあるbookapi-minimal.csproj
ファイルを開いてください。インストールされたパッケージが以下のようにリストされているはずです:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<RootNamespace>bookapi_minimal</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="11.9.2" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.8">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.8">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
</ItemGroup>
</Project>
これでパッケージが正常にインストールされたことが確認できます。
それでは、データベースコンテキストを作成しましょう。
AppContextフォルダに、ApplicationContext.cs
という名前の新しいファイルを作成し、以下のコードを追加します:
// AppContext/ApplicationContext.cs
using bookapi_minimal.Models;
using Microsoft.EntityFrameworkCore;
namespace bookapi_minimal.AppContext
{
public class ApplicationContext(DbContextOptions<ApplicationContext> options) : DbContext(options)
{
// デフォルトのスキーマをデータベースコンテキストに設定
private const string DefaultSchema = "bookapi";
// データベース内の本のコレクションを表すDbSet
public DbSet<BookModel> Books { get; set; }
// データベースコンテキストを設定するコンストラクタ
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.HasDefaultSchema(DefaultSchema);
modelBuilder.ApplyConfigurationsFromAssembly(typeof(ApplicationContext).Assembly);
modelBuilder.ApplyConfigurationsFromAssembly(typeof(ApplicationContext).Assembly);
}
}
}
上記のコードを分解してみましょう:
-
ApplicationContext
という名前のクラスを定義し、DbContext
から継承します。DbContext
クラスはEntity Framework Coreの一部であり、データベースとのセッションを表します。 -
コンストラクタは
DbContextOptions<ApplicationContext>
のインスタンスを受け取ります。このコンストラクタはデータベースコンテキストオプションを設定するために使用されます。 -
Books
という名前のプロパティをDbSet<BookModel>
型で定義します。このプロパティはデータベース内の本のコレクションを表します。 -
データベーススキーマを設定し、アプリケーションで定義された設定を適用するために、
OnModelCreating
メソッドをオーバーライドします。
データベースコンテキストを作成したので、拡張メソッドを作成し、依存性注入コンテナにデータベースコンテキストを登録しましょう。
拡張メソッドを作成する
拡張メソッドを作成する前に、ASP.NET Coreの文脈で拡張メソッドが何であるかを理解しましょう。
拡張メソッドは、元のタイプを変更せずに既存のタイプに新しい機能を追加する静的メソッドです。ASP.NET Coreでは、拡張メソッドは通常、依存性注入コンテナにサービスを登録するために使用されるIServiceCollection
インターフェースの機能を拡張するために使用されます。
サービスは、データベースアクセス、ロギング、設定などの機能をアプリケーションに提供するコンポーネントです。IServiceCollection
インターフェース用の拡張メソッドを作成することで、依存性注入コンテナにサービスを登録するプロセスを簡略化できます。
すべてをProgram.cs
ファイルに入れる代わりに、依存性注入コンテナにサービスを登録するための拡張メソッドを作成します。これにより、コードをクリーンで整理された状態に保つことができます。
Extensions
フォルダ内に、ServiceExtensions.cs
という名前の新しいファイルを作成し、以下のコードを追加します:
using System.Reflection;
using bookapi_minimal.AppContext;
using FluentValidation;
using Microsoft.EntityFrameworkCore;
namespace bookapi_minimal.Extensions
{
public static class ServiceExtensions
{
public static void AddApplicationServices(this IHostApplicationBuilder builder)
{
if (builder == null) throw new ArgumentNullException(nameof(builder));
if (builder.Configuration == null) throw new ArgumentNullException(nameof(builder.Configuration));
// データベースコンテキストの追加
builder.Services.AddDbContext<ApplicationContext>(configure =>
{
configure.UseSqlServer(builder.Configuration.GetConnectionString("sqlConnection"));
});
// 現在のアセンブリからのバリデータの追加
builder.Services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly());
}
}
}
上記のコードを分解してみましょう:
-
staticクラス
ServiceExtensions
を定義し、その中にAddApplicationServices
という名前の拡張メソッドを含めます。このメソッドは、アプリケーションのリクエスト処理パイプラインを構成するために使用されるIHostApplicationBuilder
インターフェースを拡張します。 -
AddApplicationServices
メソッドは、IHostApplicationBuilder
のインスタンスをパラメータとして受け取ります。このパラメータは、アプリケーションの設定およびサービスにアクセスするために使用されます。 -
私たちは、依存性注入コンテナーに
ApplicationContext
を追加し、データベースプロバイダーとしてSQL Serverを使用するように設定します。接続文字列は、GetConnectionString
メソッドを使用してappsettings.json
ファイルから取得します。 -
私たちは、
AddValidatorsFromAssembly
メソッドを使用して現在のassembly
からvalidators
を追加します。このメソッドは、現在のアセンブリ内でIValidatorインターフェースを実装するクラスをスキャンし、依存性注入コンテナーに登録します。
次に、接続文字列をappsettings.json
ファイルに追加する必要があります。以下のコードをあなたのappsettings.json
ファイルに追加してください:
{
"ConnectionStrings": {
"sqlConnection": "Server=localhost\\SQLEXPRESS02;Database=BookAPIMinimalAPI;Integrated Security=true;TrustServerCertificate=true;"
}
}
your_password
を実際のSQL Serverのパスワードに置き換えてください。
あなたのappsettings.json
ファイルは次のようになるはずです:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"ConnectionStrings": {
"sqlConnection": "Server=localhost\\SQLEXPRESS02;Database=BookAPIMinimalAPI;Integrated Security=true;TrustServerCertificate=true;"
},
"AllowedHosts": "*"
}
おめでとうございます!データベースコンテキスト、拡張メソッド、およびアプリケーションの接続文字列の作成に成功しました。次のセクションでは、契約を ایجادします。
契約の作成方法
契約は、クライアントとサーバー間で交換されるデータの構造を定義するデータ転送オブジェクト(DTO)です。私たちのアプリケーションでは、APIエンドポイントによって送信および受信されるデータを表すために契約を作成します。
以下は、作成する契約です:
-
CreateBookRequest: 新しい本を作成する際に送信されるデータを表します。
-
UpdateBookRequest: 既存の本を更新する際に送信されるデータを表します。
-
BookResponse: 本を取得する際に返されるデータを表します。
-
ErrorResponse: 例外が発生した際に返されるエラーレスポンスを表します。
-
ApiResponse: APIによって返されるレスポンスを表します。
Contracts
フォルダーに、CreateBookRequest
という新しいファイルを作成し、以下のコードを追加します:
// Contracts/CreateBookRequest.cs
namespace bookapi_minimal.Contracts
{
public record CreateBookRequest
{
public string Title { get; init; }
public string Author { get; init; }
public string Description { get; init; }
public string Category { get; init; }
public string Language { get; init; }
public int TotalPages { get; init; }
}
}
Contracts
フォルダーに、UpdateBookRequest
という新しいファイルを作成し、以下のコードを追加します:
// Contracts/UpdateBookRequest.cs
namespace bookapi_minimal.Contracts
{
public record UpdateBookRequest
{
public string Title { get; set; }
public string Author { get; set; }
public string Description { get; set; }
public string Category { get; set; }
public string Language { get; set; }
public int TotalPages { get; set; }
}
}
Contracts
フォルダに、BookResponse
という名前の新しいファイルを作成し、以下のコードを追加します:
// Contracts/BookResponse.cs
namespace bookapi_minimal.Contracts
{
public record BookResponse
{
public Guid Id { get; set; }
public string Title { get; set; }
public string Author { get; set; }
public string Description { get; set; }
public string Category { get; set; }
public string Language { get; set; }
public int TotalPages { get; set; }
}
}
Contracts
フォルダに、ErrorResponse
という名前の新しいファイルを作成し、以下のコードを追加します:
// Contracts/ErrorResponse.cs
namespace bookapi_minimal.Contracts
{
public record ErrorResponse
{
public string Title { get; set; }
public int StatusCode { get; set; }
public string Message { get; set; }
}
}
Contracts
フォルダに、ApiResponse
という名前の新しいファイルを作成し、以下のコードを追加します:
// Contracts/ApiResponse.cs
namespace bookapi_minimal.Contracts
{
public class ApiResponse<T>
{
public T Data { get; set; }
public string Message { get; set; }
public ApiResponse(T data, string message)
{
Data = data;
Message = message;
}
}
}
これらの契約は、クライアントとサーバー間で交換されるデータの構造を定義するのに役立ち、アプリケーション内でデータを扱うことを容易にします。
次のセクションでは、アプリケーションのビジネスロジックを実装するためのサービスを作成します。
サービスの追加方法
サービスは、アプリケーションに機能を提供するコンポーネントです。私たちのアプリケーションでは、アプリケーションのビジネスロジックを実装するためのサービスを作成します。書籍のCRUD操作を処理し、書籍データを検証し、例外を処理するサービスを作成します。
ASP.NET Coreでは、サービスは依存関係注入コンテナーに登録され、コントローラーやエンドポイントなどの他のコンポーネントに注入できますが、これは最小限のAPIなので、サービスを直接エンドポイントに注入します。
サービスのインターフェースを作成しましょう。
// Interfaces/IBookService.cs
using bookapi_minimal.Contracts;
namespace bookapi_minimal.Interfaces
{
public interface IBookService
{
Task<BookResponse> AddBookAsync(CreateBookRequest createBookRequest);
Task<BookResponse> GetBookByIdAsync(Guid id);
Task<IEnumerable<BookResponse>> GetBooksAsync();
Task<BookResponse> UpdateBookAsync(Guid id, UpdateBookRequest updateBookRequest);
Task<bool> DeleteBookAsync(Guid id);
}
}
上記のコードを分解しましょう: 私たちは、本のCRUD操作を処理するメソッドを含むIBookService
という名前のインターフェースを定義しています。このインターフェースは以下のメソッドを定義しています:
-
AddBookAsync
: 新しい本をデータベースに追加します。 -
GetBookByIdAsync
: IDによって本を取得します。 -
GetBooksAsync
: データベースからすべての本を取得します。 -
UpdateBookAsync
: 既存の本を更新します。
私たちは earlier に作成したContracts
フォルダ内の契約を使用しています。IBookService
インターフェースは、サービスクラスによって実装されるメソッドの構造を定義しています。これにより、インターフェースと実装を分離し、コードのメンテナンスとテストが容易になります。
インターフェースを作成したので、次にインターフェースを実装するサービスクラスを作成しましょう。
Book Serviceの実装方法
このサービスはIBookService
インターフェースを実装し、アプリケーションのビジネスロジックを提供します。Services
フォルダ内に新しいファイルを作成し、BookService.cs
という名前を付けます。初期のファイルは以下のようになります:
// Services/BookService.cs
namespace bookapi_minimal.Services
{
public class BookService
{
}
}
まず行うべきことは、インターフェースをBookService
クラスに追加することです。BookService
クラスを更新して、IBookService
インターフェースを実装するようにします:
// Services/BookService.cs
using bookapi_minimal.Interfaces;
namespace bookapi_minimal.Services
{
public class BookService:IBookService
{
}
}
これを行うと、VS Codeにエラーが表示されることがあります。これは、インターフェースのメソッドを実装していないためです。では、BookService
クラスでメソッドを実装しましょう。
VS Codeでは、Ctrl + .
ショートカットを使用してインターフェースのメソッドを実装できます。すると、以下のコードが生成されます:
using bookapi_minimal.Contracts;
using bookapi_minimal.Interfaces;
namespace bookapi_minimal.Services
{
// 書籍を管理するサービスクラス
public class BookService : IBookService
{
// データベースに新しい書籍を追加するメソッド
public Task<BookResponse> AddBookAsync(CreateBookRequest createBookRequest)
{
throw new NotImplementedException();
}
// データベースから書籍を削除するメソッド
public Task<bool> DeleteBookAsync(Guid id)
{
throw new NotImplementedException();
}
// データベースからIDで書籍を取得するメソッド
public Task<BookResponse> GetBookByIdAsync(Guid id)
{
throw new NotImplementedException();
}
// データベースからすべての書籍を取得するメソッド
public Task<IEnumerable<BookResponse>> GetBooksAsync()
{
throw new NotImplementedException();
}
// データベース内の書籍を更新するメソッド
public Task<BookResponse> UpdateBookAsync(Guid id, UpdateBookRequest updateBookRequest)
{
throw new NotImplementedException();
}
}
}
今、インターフェースのメソッドがBookService
クラスで実装されていることがわかります。次のセクションで各メソッドのビジネスロジックを実装します。
それをする前に、BookService
クラスに必要な依存関係を追加しましょう。ApplicationContext
とILogger
の依存関係をBookService
クラスに注入する必要があります。ApplicationContext
はデータベースとやり取りするために使用され、ILogger
はログ記録に使用されます。
依存関係を注入するために、BookService
クラスを以下のように更新します:
// Services/BookService.cs
// ...
private readonly ApplicationContext _context; // データベースコンテキスト
private readonly ILogger<BookService> _logger; // 情報とエラーを記録するためのロガー
//..
依存関係を追加したので、BookService
コンストラクターを更新して依存関係を受け入れる必要があります。BookService
コンストラクターを以下のように更新します:
// Services/BookService.cs
// ...
// データベースコンテキストとロガーを初期化するコンストラクター
public BookService(ApplicationContext context, ILogger<BookService> logger)
{
_context = context;
_logger = logger;
}
// ...
依存関係を追加し、コンストラクターを更新したので、BookService
クラスの各メソッドのビジネスロジックを実装することができます。
「CREATE、READ、UPDATE、DELETE操作のロジックをBookService
クラスで作成しましょう。
AddBookAsync
メソッドの実装方法
前述したように、AddBookAsync
メソッドを使用して新しい本をデータベースに追加します。このメソッドでは、新しい本のエンティティを作成し、CreateBookRequest
オブジェクトからデータを本のエンティティにマッピングし、本のエンティティをデータベースに保存します。また、本のエンティティをBookResponse
オブジェクトとして返します。
BookService
クラスのAddBookAsync
メソッドを以下のように更新します:
// Services/BookService.cs
// ...
/// <summary>
/// 新しい本を追加
/// </summary>
/// <param name="createBookRequest">追加する本のリクエスト</param>
/// <returns>作成された本の詳細</returns>
public async Task<BookResponse> AddBookAsync(CreateBookRequest createBookRequest)
{
try
{
var book = new BookModel
{
Title = createBookRequest.Title,
Author = createBookRequest.Author,
Description = createBookRequest.Description,
Category = createBookRequest.Category,
Language = createBookRequest.Language,
TotalPages = createBookRequest.TotalPages
};
// 本をデータベースに追加
_context.Books.Add(book);
await _context.SaveChangesAsync();
_logger.LogInformation("Book added successfully.");
// 作成された本の詳細を返す
return new BookResponse
{
Id = book.Id,
Title = book.Title,
Author = book.Author,
Description = book.Description,
Category = book.Category,
Language = book.Language,
TotalPages = book.TotalPages
};
}
catch (Exception ex)
{
_logger.LogError($"Error adding book: {ex.Message}");
throw;
}
}
// ...」
このコードでは、CreateBookRequest
オブジェクトから新しい書籍エンティティを作成し、CreateBookRequest
オブジェクトから書籍エンティティへのデータマッピングを行い、書籍エンティティをデータベースに保存し、書籍エンティティをBookResponse
オブジェクトとして返しています。
また、ILogger
依存関係を使用して情報とエラーをログ記録しています。プロセス中に例外が発生した場合、エラーメッセージをログ記録し、例外を再スローします。
これでAddBookAsync
メソッドの実装が完了したので、次にGetBookByIdAsync
メソッドを実装しましょう。
GetBookByIdAsync
メソッドの実装方法
GetBookByIdAsync
メソッドは、データベースから指定されたIDを持つ書籍を取得するために使用されます。このメソッドでは、指定されたIDを持つ書籍をデータベースでクエリし、書籍エンティティをBookResponse
オブジェクトにマッピングし、BookResponse
オブジェクトを返します。
BookService
クラスのGetBookByIdAsync
メソッドを以下のように更新してください:
// Services/BookService.cs
//...
/// <summary>
/// IDで本を取得する
/// </summary>
/// <param name="id">本のID</param>
/// <returns>本の詳細</returns>
public async Task<BookResponse> GetBookByIdAsync(Guid id)
{
try
{
// IDで本を検索する
var book = await _context.Books.FindAsync(id);
if (book == null)
{
_logger.LogWarning($"Book with ID {id} not found.");
return null;
}
// 本の詳細を返す
return new BookResponse
{
Id = book.Id,
Title = book.Title,
Author = book.Author,
Description = book.Description,
Category = book.Category,
Language = book.Language,
TotalPages = book.TotalPages
};
}
catch (Exception ex)
{
_logger.LogError($"Error retrieving book: {ex.Message}");
throw;
}
}
//...
このコードでは、指定されたIDの本をデータベースでクエリし、本のエンティティをBookResponse
オブジェクトにマッピングし、BookResponse
オブジェクトを返しています。また、ILogger
依存関係を使用して情報とエラーをログ記録しています。
指定されたIDの本が見つからない場合、警告メッセージをログ記録し、nullを返します。プロセス中に例外が発生した場合、エラーメッセージをログ記録し、例外を再スローします。
これでGetBookByIdAsync
メソッドを実装したので、次にGetBooksAsync
メソッドを実装しましょう。
GetBooksAsync
メソッドの実装方法
GetBooksAsync
メソッドは、データベースからすべての本を取得するために使用されます。このメソッドでは、データベースにすべての本を問い合わせ、各本エンティティをBookResponse
オブジェクトにマッピングし、BookResponse
オブジェクトのリストを返します。
BookService
クラスのGetBooksAsync
メソッドを以下のように更新します:
// Services/BookService.cs
//...
/// <summary>
/// すべての本を取得
/// </summary>
/// <returns>すべての本のリスト</returns>
public async Task<IEnumerable<BookResponse>> GetBooksAsync()
{
try
{
// データベースからすべての本を取得
var books = await _context.Books.ToListAsync();
// すべての本の詳細を返す
return books.Select(book => new BookResponse
{
Id = book.Id,
Title = book.Title,
Author = book.Author,
Description = book.Description,
Category = book.Category,
Language = book.Language,
TotalPages = book.TotalPages
});
}
catch (Exception ex)
{
_logger.LogError($"Error retrieving books: {ex.Message}");
throw;
}
}
//...
ここでは、データベースにすべての本を問い合わせ、各本エンティティをBookResponse
オブジェクトにマッピングし、BookResponse
オブジェクトのリストを返しています。また、ILogger
依存関係を使用して情報とエラーをログ記録しています。プロセス中に例外が発生した場合、エラーメッセージをログ記録し、例外を再スローします。
GetBooksAsync
メソッドを実装したので、次にUpdateBookAsync
メソッドを実装しましょう。
UpdateBookAsync
メソッドの実装方法
UpdateBookAsync
メソッドは、データベース内の既存の本を更新するために使用されます。このメソッドでは、指定されたIDを持つ本をデータベースでクエリし、UpdateBookRequest
オブジェクトからのデータで本エンティティを更新し、更新された本エンティティをデータベースに保存し、更新された本エンティティをBookResponse
オブジェクトとして返します。
BookService
クラスのUpdateBookAsync
メソッドを以下のように更新してください:
// Services/BookService.cs
//...
/// <summary>
/// 既存の本を更新する
/// </summary>
/// <param name="id">更新する本のID</param>
/// <param name="book">更新された本のモデル</param>
/// <returns>更新された本の詳細</returns>
public async Task<BookResponse> UpdateBookAsync(Guid id, UpdateBookRequest book)
{
try
{
// 既存の本をIDで検索する
var existingBook = await _context.Books.FindAsync(id);
if (existingBook == null)
{
_logger.LogWarning($"Book with ID {id} not found.");
return null;
}
// 本の詳細を更新する
existingBook.Title = book.Title;
existingBook.Author = book.Author;
existingBook.Description = book.Description;
existingBook.Category = book.Category;
existingBook.Language = book.Language;
existingBook.TotalPages = book.TotalPages;
// 変更をデータベースに保存する
await _context.SaveChangesAsync();
_logger.LogInformation("Book updated successfully.");
// 更新された本の詳細を返す
return new BookResponse
{
Id = existingBook.Id,
Title = existingBook.Title,
Author = existingBook.Author,
Description = existingBook.Description,
Category = existingBook.Category,
Language = existingBook.Language,
TotalPages = existingBook.TotalPages
};
}
catch (Exception ex)
{
_logger.LogError($"Error updating book: {ex.Message}");
throw;
}
}
//...
ここでは、指定されたIDの本をデータベースでクエリし、UpdateBookRequest
オブジェクトからのデータで本エンティティを更新し、更新された本エンティティをデータベースに保存し、BookResponse
オブジェクトとして更新された本エンティティを返しています。また、ILogger
依存関係を使用して情報とエラーをログ記録しています。
指定されたIDの本が見つからない場合、警告メッセージをログ記録し、nullを返します。プロセス中に例外が発生した場合、エラーメッセージをログ記録し、例外を再スローします。
これでUpdateBookAsync
メソッドを実装したので、次にDeleteBookAsync
メソッドを実装しましょう。
DeleteBookAsync
メソッドの実装方法
DeleteBookAsync
メソッドは、データベースから既存の本を削除するために使用されます。このメソッドでは、指定されたIDの本をデータベースでクエリし、本エンティティをデータベースから削除し、本が正常に削除されたかどうかを示すブール値を返します。
BookService
クラスのDeleteBookAsync
メソッドを以下のように更新します:
// Services/BookService.cs
//...
/// <summary>
/// IDで指定された本を削除する
/// </summary>
/// <param name="id">削除する本のID</param>
/// <returns>本が削除された場合はtrue、それ以外はfalse</returns>
public async Task<bool> DeleteBookAsync(Guid id)
{
try
{
// IDで本を検索する
var book = await _context.Books.FindAsync(id);
if (book == null)
{
_logger.LogWarning($"Book with ID {id} not found.");
return false;
}
// データベースから本を削除する
_context.Books.Remove(book);
await _context.SaveChangesAsync();
_logger.LogInformation($"Book with ID {id} deleted successfully.");
return true;
}
catch (Exception ex)
{
_logger.LogError($"Error deleting book: {ex.Message}");
throw;
}
}
//...
このコードでは、指定されたIDを持つ本をデータベースで検索し、データベースから本のエンティティを削除し、本が正常に削除されたかどうかを示すブール値を返しています。また、ILogger
依存関係を使用して情報とエラーをログに記録しています。
指定されたIDの本が見つからない場合、警告メッセージをログに記録し、falseを返します。処理中に例外が発生した場合、エラーメッセージをログに記録し、例外を再スローします。
現在、BookService
クラスのAddBookAsync
、GetBookByIdAsync
、GetBooksAsync
、UpdateBookAsync
、およびDeleteBookAsync
メソッドに対する業務ロジックを正常に実装しました。これらのメソッドは、本のCRUD操作を行い、本のデータを検証し、例外を処理します。この時点で、BookService
クラスは以下のようになっているはずです:
using bookapi_minimal.AppContext;
using bookapi_minimal.Contracts;
using bookapi_minimal.Interfaces;
using bookapi_minimal.Models;
using Microsoft.EntityFrameworkCore;
namespace bookapi_minimal.Services
{
public class BookService : IBookService
{
private readonly ApplicationContext _context; // データベースコンテキスト
private readonly ILogger<BookService> _logger; // 情報とエラーを記録するためのロガー
// データベースコンテキストとロガーを初期化するコンストラクタ
public BookService(ApplicationContext context, ILogger<BookService> logger)
{
_context = context;
_logger = logger;
}
/// 新しい本を追加
/// </summary>
/// <param name="createBookRequest">追加する本のリクエスト</param>
/// <returns>作成された本の詳細</returns>
public async Task<BookResponse> AddBookAsync(CreateBookRequest createBookRequest)
{
try
{
var book = new BookModel
{
Title = createBookRequest.Title,
Author = createBookRequest.Author,
Description = createBookRequest.Description,
Category = createBookRequest.Category,
Language = createBookRequest.Language,
TotalPages = createBookRequest.TotalPages
};
// 本をデータベースに追加
_context.Books.Add(book);
await _context.SaveChangesAsync();
_logger.LogInformation("Book added successfully.");
// 作成された本の詳細を返す
return new BookResponse
{
Id = book.Id,
Title = book.Title,
Author = book.Author,
Description = book.Description,
Category = book.Category,
Language = book.Language,
TotalPages = book.TotalPages
};
}
catch (Exception ex)
{
_logger.LogError($"Error adding book: {ex.Message}");
throw;
}
}
/// <summary>
/// IDで本を取得
/// </summary>
/// <param name="id">本のID</param>
/// <returns>本の詳細</returns>
public async Task<BookResponse> GetBookByIdAsync(Guid id)
{
try
{
// IDで本を検索
var book = await _context.Books.FindAsync(id);
if (book == null)
{
_logger.LogWarning($"Book with ID {id} not found.");
return null;
}
// 本の詳細を返す
return new BookResponse
{
Id = book.Id,
Title = book.Title,
Author = book.Author,
Description = book.Description,
Category = book.Category,
Language = book.Language,
TotalPages = book.TotalPages
};
}
catch (Exception ex)
{
_logger.LogError($"Error retrieving book: {ex.Message}");
throw;
}
}
/// <summary>
/// すべての本を取得
/// </summary>
/// <returns>すべての本のリスト</returns>
public async Task<IEnumerable<BookResponse>> GetBooksAsync()
{
try
{
// データベースからすべての本を取得
var books = await _context.Books.ToListAsync();
// すべての本の詳細を返す
return books.Select(book => new BookResponse
{
Id = book.Id,
Title = book.Title,
Author = book.Author,
Description = book.Description,
Category = book.Category,
Language = book.Language,
TotalPages = book.TotalPages
});
}
catch (Exception ex)
{
_logger.LogError($"Error retrieving books: {ex.Message}");
throw;
}
}
/// <summary>
/// 既存の本を更新
/// </summary>
/// <param name="id">更新する本のID</param>
/// <param name="book">更新された本のモデル</param>
/// <returns>更新された本の詳細</returns>
public async Task<BookResponse> UpdateBookAsync(Guid id, UpdateBookRequest book)
{
try
{
// IDで既存の本を検索
var existingBook = await _context.Books.FindAsync(id);
if (existingBook == null)
{
_logger.LogWarning($"Book with ID {id} not found.");
return null;
}
// 本の詳細を更新
existingBook.Title = book.Title;
existingBook.Author = book.Author;
existingBook.Description = book.Description;
existingBook.Category = book.Category;
existingBook.Language = book.Language;
existingBook.TotalPages = book.TotalPages;
// データベースへの変更を保存
await _context.SaveChangesAsync();
_logger.LogInformation("Book updated successfully.");
// 更新された本の詳細を返す
return new BookResponse
{
Id = existingBook.Id,
Title = existingBook.Title,
Author = existingBook.Author,
Description = existingBook.Description,
Category = existingBook.Category,
Language = existingBook.Language,
TotalPages = existingBook.TotalPages
};
}
catch (Exception ex)
{
_logger.LogError($"Error updating book: {ex.Message}");
throw;
}
}
/// <summary>
/// IDで本を削除
/// </summary>
/// <param name="id">削除する本のID</param>
/// <returns>本が削除された場合はtrue、それ以外はfalse</returns>
public async Task<bool> DeleteBookAsync(Guid id)
{
try
{
// IDで本を検索
var book = await _context.Books.FindAsync(id);
if (book == null)
{
_logger.LogWarning($"Book with ID {id} not found.");
return false;
}
// 本をデータベースから削除
_context.Books.Remove(book);
await _context.SaveChangesAsync();
_logger.LogInformation($"Book with ID {id} deleted successfully.");
return true;
}
catch (Exception ex)
{
_logger.LogError($"Error deleting book: {ex.Message}");
throw;
}
}
}
}
おめでとうございます!AddBookAsync
、GetBookByIdAsync
、GetBooksAsync
、UpdateBookAsync
、およびDeleteBookAsync
メソッドのビジネスロジックをBookService
クラスに正常に実装しました。
やるべきことが1つあります:サービスを拡張メソッドに登録する必要があります。それを行いましょう。
ServiceExtensions.cs
ファイルに、次のコードを追加してください:
// Extensions/ServiceExtensions.cs
//..
builder.Services.AddScoped<IBookService, BookService>();
//...
これにより、BookService
クラスがスコープされたサービスとして登録されます。つまり、サービスはリクエストごとに1回作成され、リクエストが完了した後に破棄されます。
サービスが動作しているので、例外クラスの作成に進みましょう。
例外の作成方法
例外を適切に処理することは、アプリケーションの安定性と信頼性を確保するために重要です。ASP.NET Coreのコンテキストでは、主に2つの種類の例外があります:
-
システム例外:これらは.NETランタイムまたは基礎システムによってスローされる例外です。
-
アプリケーション例外:これらは、特定のエラーや条件を処理するためにアプリケーションコードによってスローされる例外です。
ASP.NET Core with .NET 8では、グローバル例外処理と呼ばれる新しい機能が導入されました。この機能により、アプリケーション全体で例外をグローバルに処理することができ、エラーの管理が容易になり、一貫したユーザーエクスペリエンスを提供することができます。
私たちのアプリケーションでは、特定のエラーや条件を処理するためのカスタム例外クラスを作成します。また、グローバル例外処理機能を活用して、アプリケーション全体で例外を管理し、エラーハンドリングの一貫したアプローチを保証します。
以下の例外クラスを作成する予定です:
-
NoBookFoundException
: 指定されたIDの書籍が見つからない場合に投げられます。 -
BookDoesNotExistException
: 指定されたIDの書籍が存在しない場合に投げられます。 -
GlobalExceptionHandler
: アプリケーション全体で例外を処理します。
Exceptions
フォルダ内に、NoBookFoundException.cs
という新しいファイルを作成し、以下のコードを追加します:
// Exceptions/NoBookFoundException.cs
namespace bookapi_minimal.Exceptions
{
public class NoBookFoundException : Exception
{
public NoBookFoundException() : base("No books found")
{}
}
}
このコードでは、NoBookFoundException
という名前のカスタム例外クラスを作成しており、これはException
クラスを継承しています。NoBookFoundException
クラスは、データベースに本が見つからないシナリオを処理するために使用されます。また、例外に対するカスタムエラーメッセージも提供しています。
Exceptions
フォルダ内に、BookDoesNotExistException.cs
という名前の新しいファイルを作成し、次のコードを追加します:
namespace bookapi_minimal.Exceptions
{
public class BookDoesNotExistException : Exception
{
private int id { get; set; }
public BookDoesNotExistException(int id) : base($"Book with id {id} does not exist")
{
this.id = id;
}
}
}
このコードでは、BookDoesNotExistException
という名前のカスタム例外クラスを作成しており、これはException
クラスを継承しています。BookDoesNotExistException
クラスは、指定されたIDを持つ本がデータベースに存在しないシナリオを処理するために使用されます。また、例外に対するカスタムエラーメッセージも提供しています。
Exceptions
フォルダ内に、GlobalExceptionHandler.cs
という名前の新しいファイルを作成し、次のコードを追加します:
// Exceptions/GlobalExceptionHandler.cs
using System.Net;
using bookapi_minimal.Contracts;
using Microsoft.AspNetCore.Diagnostics;
namespace bookapi_minimal.Exceptions
{
// グローバル例外処理クラスがIExceptionHandlerを実装
public class GlobalExceptionHandler : IExceptionHandler
{
private readonly ILogger<GlobalExceptionHandler> _logger;
// ロガーを初期化するコンストラクタ
public GlobalExceptionHandler(ILogger<GlobalExceptionHandler> logger)
{
_logger = logger;
}
// 非同期に例外を処理するメソッド
public async ValueTask<bool> TryHandleAsync(
HttpContext httpContext,
Exception exception,
CancellationToken cancellationToken)
{
// 例外の詳細をログに記録
_logger.LogError(exception, "An error occurred while processing your request");
var errorResponse = new ErrorResponse
{
Message = exception.Message,
Title = exception.GetType().Name
};
// 例外の種類に基づいてステータスコードを決定
switch (exception)
{
case BadHttpRequestException:
errorResponse.StatusCode = (int)HttpStatusCode.BadRequest;
break;
case NoBookFoundException:
case BookDoesNotExistException:
errorResponse.StatusCode = (int)HttpStatusCode.NotFound;
break;
default:
errorResponse.StatusCode = (int)HttpStatusCode.InternalServerError;
break;
}
// レスポンスのステータスコードを設定
httpContext.Response.StatusCode = errorResponse.StatusCode;
// エラーレスポンスをJSONとして書き込む
await httpContext.Response.WriteAsJsonAsync(errorResponse, cancellationToken);
// 例外が処理されたことを示すためにtrueを返す
return true;
}
}
}
上記のコードを分解してみましょう:
-
私たちは
GlobalExceptionHandler
という名前のクラスを定義し、IExceptionHandler
インターフェースを実装します。IExceptionHandler
インターフェースは、アプリケーション全体で例外をグローバルに処理するために使用されます。 -
GlobalExceptionHandler
クラスには、ILogger<GlobalExceptionHandler>
依存関係を初期化するコンストラクタが含まれています。ILogger
は、情報とエラーのログ記録に使用されます。 -
TryHandleAsync
メソッドは、例外を非同期的に処理するために使用されます。このメソッドは、HttpContext
、Exception
、およびCancellationToken
をパラメーターとして受け取ります。 -
私たちは、
ILogger
依存関係を使用して例外の詳細をログ記録します。 -
API によって返されるエラー応答を表すために、
ErrorResponse
オブジェクトを作成します。ErrorResponse
オブジェクトには、エラーメッセージ、タイトル、およびステータスコードが含まれています。 -
例外のタイプに基づいてステータスコードを決定します。例外が
BadHttpRequestException
の場合、ステータスコードをBadRequest
に設定します。例外がNoBookFoundException
またはBookDoesNotExistException
の場合、ステータスコードをNotFound
に設定します。それ以外の場合は、ステータスコードをInternalServerError
に設定します。 -
応答ステータスコードは、
httpContext.Response.StatusCode
プロパティを使用して設定します。 -
エラーレスポンスは、
httpContext.Response.WriteAsJsonAsync
メソッドを使用してJSONとして書き込みます。 -
例外が正常に処理されたことを示すために、
true
を返します。
例外クラスを作成したので、依存関係注入コンテナーにGlobalExceptionHandler
を登録しましょう。依存関係注入コンテナーにサービスを登録するための拡張メソッドを作成したので、GlobalExceptionHandler
をServiceExtensions
クラスに追加します。
Extensions
フォルダ内のServiceExtensions
クラスを以下のように更新します:
// Extensions/ServiceExtensions.cs
//...
builder.Services.AddExceptionHandler<GlobalExceptionHandler>();
builder.Services.AddProblemDetails();
//...
AddExceptionHandler
メソッドは、依存関係注入コンテナーにGlobalExceptionHandler
を登録します。AddProblemDetails
メソッドは、依存関係注入コンテナーにProblemDetails
クラスを登録します。
依存関係注入コンテナーにGlobalExceptionHandler
を登録したので、アプリケーション全体で例外をグローバルに処理するために使用できます。次のセクションでは、書籍データとやり取りするAPIエンドポイントを作成します。
APIエンドポイントの作成方法
ASP.NET CoreのミニマルAPIの文脈では、エンドポイントを設定する方法はいくつかあります。
それらを直接Program.cs
ファイルで定義することもできます。しかし、プロジェクトが成長し、より多くのエンドポイントや機能を追加する必要がある場合は、コードをよりよく整理することが役立ちます。これを実現する一つの方法は、エンドポイントをすべて処理する別のクラスを作成することです。
上述したように、ミニマルAPIは従来のASP.NET Coreアプリケーションのようにコントローラーやビューを使用しません。代わりに、MapGet
、MapPost
、MapPut
、MapDelete
などのメソッドを使用して、APIエンドポイントのHTTPメソッドとルートを定義します。
始めるには、Endpoints
フォルダーに移動し、BookEndpoints.cs
という新しいファイルを作成します。次のコードをファイルに追加します:
// Endpoints/BookEndpoints.cs
namespace bookapi_minimal.Endpoints
{
public static class BookEndPoint
{
public static IEndpointRouteBuilder MapBookEndPoint(this IEndpointRouteBuilder app)
{
return app;
}
}
}
BookEndpoints
クラスには、IEndpointRouteBuilder
オブジェクトを返す MapBookEndPoint
メソッドが含まれています。IEndpointRouteBuilder
オブジェクトは、API エンドポイントのHTTPメソッドとルートを定義するために使用されます。次のセクションでは、作成
、読み取り
、更新
、および 削除
のためのAPIエンドポイントを定義します。
AddBookAsync
エンドポイントの作成方法
このセクションでは、AddBookAsync
エンドポイントを作成します。このエンドポイントは、JSONペイロードとしてBook
オブジェクトを受け取り、データベースに追加します。このエンドポイントのHTTPメソッドとルートを定義するために、MapPost
メソッドを使用します。
BookEndpoints
クラスに以下のコードを追加します:
// Endpoints/BookEndpoints.cs
//...
// 新しい本を追加するエンドポイント
app.MapPost("/books", async (CreateBookRequest createBookRequest, IBookService bookService) =>
{
var result = await bookService.AddBookAsync(createBookRequest);
return Results.Created($"/books/{result.Id}", result);
});
//...
-
ルート定義: MapPostメソッドは、エンドポイントのルートを
/books
として定義します。 -
リクエストモデル: エンドポイントは、JSONペイロードとして
CreateBookRequest
オブジェクトを受け取ります。CreateBookRequest
オブジェクトには、新しい本を作成するために必要なデータが含まれています。 -
レスポンスモデル: エンドポイントは、JSONペイロードとして
Book
オブジェクトを返します。Book
オブジェクトには、新しく作成された本のデータが含まれています。 -
戻り値: エンドポイントは、
Created
結果を返します。Created
結果には、新しく作成された本の場所とBook
オブジェクトが含まれています。
GetBookAsync
本エンドポイントの作成方法
このセクションでは、GetBookAsync
エンドポイントを作成します。このエンドポイントは、クエリパラメータとして本のIDを受け取り、指定されたIDの本を返します。このエンドポイントのHTTPメソッドとルートを定義するために、MapGet
メソッドを使用します。
BookEndpoints
クラスに以下のコードを追加します:
// Endpoints/BookEndpoints.cs
// ...
// 全ての本を取得するエンドポイント
app.MapGet("/books", async (IBookService bookService) =>
{
var result = await bookService.GetBooksAsync();
return Results.Ok(result);
});
//...
-
ルート定義: MapGetメソッドは、エンドポイントのルートを
/books
として定義します。 -
リクエストモデル: エンドポイントは、JSONペイロードとして
Book
オブジェクトを受け取ります。Book
オブジェクトには、新しい本を作成するために必要なデータが含まれています。 -
レスポンスモデル: エンドポイントは、JSONペイロードとして
Book
オブジェクトを返します。Book
オブジェクトには、新しく作成された本のデータが含まれています。 -
戻り値: エンドポイントは、
Ok
結果を返します。Ok
結果には、Book
オブジェクトが含まれています。
GetBookByIdAsync
本エンドポイントの作成方法
このセクションでは、GetBookByIdAsync
エンドポイントを作成します。このエンドポイントは、ルートパラメータとして書籍のIDを受け入れ、指定されたIDの書籍を返します。このエンドポイントのHTTPメソッドとルートを定義するために MapGet
メソッドを使用します。
// Endpoints/BookEndpoints.cs
//...
// IDで書籍を取得するエンドポイント
app.MapGet("/books/{id:guid}", async (Guid id, IBookService bookService) =>
{
var result = await bookService.GetBookByIdAsync(id);
return result != null ? Results.Ok(result) : Results.NotFound();
});
//...
-
ルートの定義: MapGet メソッドは、以下のようにエンドポイントのルートを定義します
/books/{id:guid}
。パラメータ{id:guid}
は、id
パラメータが GUID であることを指定します。 -
リクエストモデル: エンドポイントは
Book
オブジェクトを JSON ペイロードとして受け入れます。Book
オブジェクトには、新しい書籍を作成するために必要なデータが含まれています。 - レスポンスモデル: エンドポイントは
Book
オブジェクトを JSON ペイロードとして返します。Book
オブジェクトには、新しく作成された書籍のデータが含まれています。 -
戻り値: エンドポイントは、本が見つかった場合に
Ok
結果を返します。本が見つからない場合はNotFound
結果が返されます。
UpdateBookAsync
エンドポイントの作成方法
このセクションでは、UpdateBookAsync
エンドポイントを作成します。このエンドポイントは、ルートパラメータとして本のIDを受け取り、JSONペイロードとしてBook
オブジェクトを受け取り、指定されたIDの本を更新します。このエンドポイントのHTTPメソッドとルートを定義するために、MapPut
メソッドを使用します。
BookEndpoints
クラスに以下のコードを追加します:
// Endpoints/BookEndpoints.cs
//...
// 本をIDで更新するエンドポイント
app.MapPut("/books/{id:guid}", async (Guid id, UpdateBookRequest updateBookRequest, IBookService bookService) =>
{
var result = await bookService.UpdateBookAsync(id, updateBookRequest);
return result != null ? Results.Ok(result) : Results.NotFound();
});
//...
-
ルート定義: MapPutメソッドは、エンドポイントのルートを
/books/{id:guid}
として定義します。{id:guid}
パラメータは、id
パラメータがGUIDであることを指定します。 -
リクエストモデル: エンドポイントは、JSONペイロードとして
Book
オブジェクトを受け入れます。Book
オブジェクトには、新しい本を作成するために必要なデータが含まれています。 -
レスポンスモデル: エンドポイントは、JSONペイロードとして
Book
オブジェクトを返します。Book
オブジェクトには、新しく作成された本のデータが含まれています。 -
戻り値: エンドポイントは、本が見つかった場合に
Ok
結果を返します。本が見つからない場合は、NotFound
結果が返されます。
DeleteBookAsync
本エンドポイントの作成方法
このセクションでは、DeleteBookAsync
エンドポイントを作成します。このエンドポイントは、ルートパラメータとして書籍IDを受け入れ、指定されたIDの書籍を削除します。このエンドポイントのHTTPメソッドとルートを定義するためにMapDelete
メソッドを使用します。
BookEndpoints
クラスに以下のコードを追加してください:
// Endpoints/BookEndpoints.cs
//...
// IDで書籍を削除するエンドポイント
app.MapDelete("/books/{id:guid}", async (Guid id, IBookService bookService) =>
{
var result = await bookService.DeleteBookAsync(id);
return result ? Results.NoContent() : Results.NotFound();
});
//...
-
ルートの定義: MapDeleteメソッドは、エンドポイントのルートを
/books/{id:guid}
と定義します。{id:guid}
パラメータはid
パラメータがGUIDであることを指定します。 -
リクエストモデル: このエンドポイントは、JSONペイロードとして
Book
オブジェクトを受け入れます。Book
オブジェクトには新しい書籍を作成するために必要なデータが含まれています。 - レスポンスモデル: このエンドポイントは、JSONペイロードとして
Book
オブジェクトを返します。Book
オブジェクトには新しく作成された書籍のデータが含まれています。 -
戻り値: エンドポイントは、本が正常に削除された場合に
NoContent
結果を返します。本が見つからない場合はNotFound
結果が返されます。
これで本のエンドポイントのためのすべてのメソッドを定義しました。したがって、あなたのエンドポイントクラスは次のようになるはずです:
// Endpoints/BookEndpoints.cs
using bookapi_minimal.Contracts;
using bookapi_minimal.Interfaces;
namespace bookapi_minimal.Endpoints
{
public static class BookEndPoint
{
public static IEndpointRouteBuilder MapBookEndPoint(this IEndpointRouteBuilder app)
{
// エンドポイントの定義
// 新しい本を追加するエンドポイント
app.MapPost("/books", async (CreateBookRequest createBookRequest, IBookService bookService) =>
{
var result = await bookService.AddBookAsync(createBookRequest);
return Results.Created($"/books/{result.Id}", result);
});
// すべての本を取得するエンドポイント
app.MapGet("/books", async (IBookService bookService) =>
{
var result = await bookService.GetBooksAsync();
return Results.Ok(result);
});
// IDで本を取得するエンドポイント
app.MapGet("/books/{id:guid}", async (Guid id, IBookService bookService) =>
{
var result = await bookService.GetBookByIdAsync(id);
return result != null ? Results.Ok(result) : Results.NotFound();
});
// IDで本を更新するエンドポイント
app.MapPut("/books/{id:guid}", async (Guid id, UpdateBookRequest updateBookRequest, IBookService bookService) =>
{
var result = await bookService.UpdateBookAsync(id, updateBookRequest);
return result != null ? Results.Ok(result) : Results.NotFound();
});
// IDで本を削除するエンドポイント
app.MapDelete("/books/{id:guid}", async (Guid id, IBookService bookService) =>
{
var result = await bookService.DeleteBookAsync(id);
return result ? Results.NoContent() : Results.NotFound();
});
return app;
}
}
}
おめでとうございます!本のAPIのためのすべてのエンドポイントを作成しました。これらのエンドポイントは、本のCRUD操作を処理し、リクエストとデータに基づいて適切なレスポンスを返します。
エンドポイントの登録方法
本のAPIのエンドポイントを定義した後、次のステップはこれらのエンドポイントをProgram.cs
ファイルに登録することです。本のエンドポイントを登録するためにMapBookEndpoints
メソッドを使用します。
また、Program.cs
クラスを整理して、組織化され、保守可能な状態を保つべきです。
// Program.cs
using System.Reflection;
using bookapi_minimal.Endpoints;
using bookapi_minimal.Services;
using Microsoft.OpenApi.Models;
var builder = WebApplication.CreateBuilder(args);
builder.AddApplicationServices();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c=>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "Mimal API", Version = "v1", Description = "Showing how you can build minimal " +
"api with .net" });
// Swagger JSONおよびUIのコメントパスを設定します。
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
c.IncludeXmlComments(xmlPath);
});
var app = builder.Build();
// HTTPリクエストパイプラインを構成します。
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseExceptionHandler();
app.MapGroup("/api/v1/")
.WithTags(" Book endpoints")
.MapBookEndPoint();
app.Run();
Program.cs
ファイルの主要なコンポーネントを分解しましょう:
-
AddApplicationServices:このメソッドはAPIに必要なサービスを登録します。これは、依存関係注入コンテナーにサービスを追加するために以前に作成した拡張メソッドです。
-
AddSwaggerGen:このメソッドはSwaggerジェネレーターを登録し、APIのSwaggerドキュメントを作成するために使用されます。Swaggerドキュメント内でAPIのタイトル、バージョン、および説明を指定します。
-
MapGroup: このメソッドはエンドポイントをグループ化します。パスをパラメータとして受け取り、
IEndpointRouteBuilder
オブジェクトを返します。エンドポイントにタグを追加するためにWithTags
メソッドを使用し、書籍エンドポイントを登録するためにMapBookEndpoints
メソッドを使用します。 -
Run: このメソッドはアプリケーションを起動します。
Swaggerドキュメントを有効にするには、GenerateDocumentationFile
プロパティを.csproj
ファイルに追加する必要があります。この例では、ファイル名はbookapi-minimal.csproj
ですが、プロジェクトによって名前が異なる場合があります。
.csproj
ファイルに次の行を追加します:
<PropertyGroup>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
最終的には、bookapi-minimal.csprojは以下のようになります:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<RootNamespace>bookapi_minimal</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="11.9.2" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.8">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.8">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
</ItemGroup>
</Project>
これでProgram.cs
ファイルに書籍エンドポイントを登録したので、アプリケーションを実行し、Swaggerを使用してAPIエンドポイントをテストできます。
アプリケーションを実行すると、以下のURLでSwaggerドキュメントが表示されるはずです:https://localhost:5001/swagger/index.html
。SwaggerドキュメントにはAPIエンドポイント、リクエストとレスポンスのモデルに関する情報が提供されており、ブラウザから直接エンドポイントをテストすることができます。次のような表示がされるはずです:
おめでとうございます!書籍サービスのビジネスロジックを実装し、カスタム例外を作成し、APIエンドポイントを定義し、Program.cs
ファイルでエンドポイントを登録しました。また、Swaggerドキュメントを有効にしてAPIエンドポイントをテストできるようにしました。
データベースにシードデータを追加する方法
もう一つの重要なステップは、アプリケーション起動時にデータベースに初期データをシードすることです。このシードデータにより、データベースが初期化され、手動でデータを追加せずにAPIエンドポイントをテストできるようになります。
マイグレーションを実行し、APIエンドポイントをテストする前に、いくつかのシードデータを追加しましょう。
これを実現するために、Configurationフォルダに新しいクラスを作成し、BookTypeConfigurations
という名前を付け、以下のコードを追加します:
using bookapi_minimal.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace bookapi_minimal.Configurations
{
public class BookTypeConfigurations : IEntityTypeConfiguration<BookModel>
{
public void Configure(EntityTypeBuilder<BookModel> builder)
{
// テーブル名の設定
builder.ToTable("Books");
// 主キーの設定
builder.HasKey(x => x.Id);
// プロパティの設定
builder.Property(x => x.Id).ValueGeneratedOnAdd();
builder.Property(x => x.Title).IsRequired().HasMaxLength(100);
builder.Property(x => x.Author).IsRequired().HasMaxLength(100);
builder.Property(x => x.Description).IsRequired().HasMaxLength(500);
builder.Property(x => x.Category).IsRequired().HasMaxLength(100);
builder.Property(x => x.Language).IsRequired().HasMaxLength(50);
builder.Property(x => x.TotalPages).IsRequired();
// シードデータ
builder.HasData(
new BookModel
{
Id = Guid.NewGuid(),
Title = "The Alchemist",
Author = "Paulo Coelho",
Description = "The Alchemist follows the journey of an Andalusian shepherd",
Category = "Fiction",
Language = "English",
TotalPages = 208
},
new BookModel
{
Id = Guid.NewGuid(),
Title = "To Kill a Mockingbird",
Author = "Harper Lee",
Description = "A novel about the serious issues of rape and racial inequality.",
Category = "Fiction",
Language = "English",
TotalPages = 281
},
new BookModel
{
Id = Guid.NewGuid(),
Title = "1984",
Author = "George Orwell",
Description = "A dystopian social science fiction novel and cautionary tale about the dangers of totalitarianism. ",
Category = "Fiction",
Language = "English",
TotalPages = 328
}
);
}
}
}
上記のコードを詳しく見ていきましょう:
Entity Framework Coreでは、IEntityTypeConfiguration
インターフェースを使用してエンティティタイプとデータベースのシードデータを設定できます。BookTypeConfigurations
クラスはIEntityTypeConfiguration<BookModel>
インターフェースを実装し、BookModel
エンティティの設定を提供します。
-
Configureメソッド: このメソッドは
BookModel
エンティティタイプを設定するために使用されます。テーブル名、主キー、およびBookModel
エンティティのプロパティを定義します。-
テーブル名:
ToTable
メソッドはデータベースに作成されるテーブルの名前を指定します。この場合、テーブル名は「Books」と設定されています。 -
主キー:
HasKey
メソッドはBookModel
エンティティの主キーを指定します。主キーはId
プロパティに設定されています。 -
プロパティ:
Property
メソッドはBookModel
エンティティのプロパティを設定します。各プロパティのデータ型、長さ、および制約を指定します。
-
-
初期データ:
HasData
メソッドは、データベースに初期データを提供します。APIエンドポイントのテスト用に、3つのBookModel
オブジェクトをサンプルデータで作成します。
これでBookTypeConfigurations
クラスを作成したので、この設定をApplicationContext
クラスに登録する必要があります。これにより、データベースの作成または移行時にこの設定が適用されることが保証されます。
ようやくAPIのテストがほぼ準備できています。しかし、それをする前に、データベースを作成し、シードデータを適用するためのマイグレーションを実行する必要があります。
appsettings.json
ファイルにデータベース接続文字列を追加したことを覚えていますか?今、マイグレーションを実行し、後にデータベースを更新してマイグレーションが有効になるようにしましょう。
マイグレーションの実行方法
マイグレーションを使用すると、モデルクラスに加えられた変更に基づいてデータベーススキーマを更新できます。Entity Framework Coreでは、dotnet ef migrations add
コマンドを使用して、これらの変更を反映した新しいマイグレーションを作成できます。
マイグレーションを実行するには、ターミナルで次のコマンドを実行します:
dotnet ef migrations add InitialCreate
コマンドが成功すると、次のような出力が表示されます:
Build started...
Build succeeded.
Done. To undo this action, use 'ef migrations remove'
プロジェクト内にMigrations
という新しいフォルダが表示されます。このフォルダには、モデルクラスに加えられた変更に基づいて作成されたマイグレーションファイルが含まれています。これらのマイグレーションファイルには、データベーススキーマを更新するためのSQLコマンドが含まれています。
データベースの更新方法
マイグレーションを作成した後、データベーススキーマを更新するためにマイグレーションを適用する必要があります。dotnet ef database update
コマンドを使用してマイグレーションを適用し、データベースを更新できます。SQL Serverが実行されていることを確認してください。
ターミナルで次のコマンドを実行します:
dotnet ef database update
これにより、モデルクラスに加えられた変更に基づいてデータベーススキーマが更新されます。データベース接続文字列にエラーがないことを確認してください。
APIエンドポイントのテスト方法
今すぐにSwaggerを使用してエンドポイントをテストすることができます。これを行うには、ターミナルで以下のコマンドを実行してアプリケーションを実行します:
dotnet run
これでアプリケーションが実行されます。ブラウザを開き、https://localhost:5001/swagger/index.html
にアクセスしてSwaggerドキュメントを表示できます。APIエンドポイントの一覧、リクエストとレスポンスのモデル、およびブラウザから直接エンドポイントをテストする機能が表示されるはずです。
ポート番号が5001
と異なる場合でも心配しないでください – それでも動作します。使用しているマシンの種類によってポートが変更されることがありますが、同じ結果が得られます。
Get All Books
エンドポイントのテスト方法
Get All Books
エンドポイントをテストするには、以下の手順に従ってください:
-
Swaggerドキュメントで、
GET /api/v1/books
エンドポイントをクリックします。 -
Try it out
ボタンをクリックします。 -
Execute
ボタンをクリックします。
これにより、データベース内のすべての本を取得するためのリクエストがAPIに送信されます。
APIからのレスポンスが表示され、データベースにシードされた本の一覧が含まれるはずです。
以下の画像はAPIからのレスポンスを示しています:
「IDで書籍を取得する」エンドポイントのテスト方法
「IDで書籍を取得する」エンドポイントをテストするには、以下の手順に従ってください:
-
Swaggerドキュメントで、
GET /api/v1/books/{id}
エンドポイントをクリックします。 -
id
フィールドに書籍のIDを入力します。データベースにシードされた書籍IDのいずれかを使用できます。 -
試してみる
ボタンをクリックします。
これにより、指定されたIDの書籍を取得するためのリクエストがAPIに送信されます。APIからのレスポンスが表示され、指定されたIDの書籍が含まれるはずです。
以下の画像はAPIからのレスポンスを示しています:
「書籍を追加する」エンドポイントのテスト方法
「書籍を追加する」エンドポイントをテストするには、以下の手順に従ってください:
-
Swaggerドキュメントで、
POST /api/v1/books
エンドポイントをクリックします。 -
試してみる
ボタンをクリックします。 -
リクエストボディに書籍の詳細を入力します。
-
「
実行
」ボタンをクリックします。
これにより、新しい本をデータベースに追加するためのリクエストがAPIに送信されます。
APIからのレスポンスが表示され、新しく作成された本が含まれるはずです。
以下の画像は、APIからのレスポンスを示しています:
「本を更新
」エンドポイントのテスト方法
「本を更新
」エンドポイントをテストするには、以下の手順に従ってください:
-
Swaggerドキュメントで、
PUT /api/v1/books/{id}
エンドポイントをクリックします。 -
id
フィールドに本のIDを入力します。今さっき追加した本のIDを使用できます。 -
「
試してみる
」ボタンをクリックします。
これにより、指定されたIDの本を更新するためのリクエストがAPIに送信されます。
APIからのレスポンスが表示され、更新された本が含まれるはずです。
以下の画像は、APIからのレスポンスを示しています:
「本を削除
」エンドポイントのテスト方法
「本を削除
」エンドポイントをテストするには、以下の手順に従ってください:
-
Swaggerドキュメントで、
DELETE /api/v1/books/{id}
エンドポイントをクリックします。 -
id
フィールドに本のIDを入力します。今追加した本のIDまたはシードデータのいずれかを使用できます。 -
Try it out
ボタンをクリックします。
これにより、指定されたIDの本を削除するためのリクエストがAPIに送信されます。
以下の画像は、APIからのレスポンスを示しています:
おめでとうございます!本のCRUD操作をすべて実装し、Swaggerを使用してAPIエンドポイントをテストし、期待通りに動作することを確認しました。この基盤を活かして、APIにさらに機能を追加することができます。
結論
このハンドブックでは、ASP.NET Coreで.NET 8を使用して最小限のAPIを作成する方法を探りました。CRUD操作をサポートする包括的な本APIを構築し、カスタム例外を実装し、APIエンドポイントを定義および登録し、Swaggerドキュメントを有効にして簡単にテストできるようにしました。
このチュートリアルに従うことで、ASP.NET Coreで最小限のAPIを構築するための solid な基盤を得ました。この知識を活かして、さまざまな分野や業界のための堅牢なAPIを作成することができます。
このチュートリアルが役立つものであり、有益であったことを願っています。読んでいただきありがとうございます!
ソーシャルメディアで気軽にご連絡ください:
Source:
https://www.freecodecamp.org/news/create-a-minimal-api-in-net-core-handbook/