ASP.NET Core における MVC パターンを活用した Web API 構築

サーバーサイドレンダリングから Web API へ

これまでの解説では、Razor Pages を利用して HTML をブラウザに直接描画するサーバーサイドレンダリング型の ASP.NET Core アプリケーション構築について学びました。本章では、視点を変えて Web API の構築に焦点を当てます。これは、シングルページアプリケーション(SPA)やモバイルアプリのバックエンドとして機能するアプローチです。

Web API 開発においても、これまで習得した MVC デザインパターンの知識の多くを流用できます。ルーティング、モデルバインディング、バリデーションといった概念は共通しており、主な違いは「ビュー」の扱いにあります。HTML を返す代わりに、JSON や XML といったデータ形式で情報を返し、クライアント側がそれを用いて UI を制御または更新します。

ここでは、コントローラーとアクションの定義方法、クライアントアプリケーションが理解できる形式でのデータおよび HTTP ステータスコードの返し方について解説します。また、属性ルートを用いた URL 設計や、ASP.NET Core 2.1 以降で導入された [ApiController] 属性による convention の自動化、さらにコンテンツネゴシエーションを通じたレスポンスフォーマットの制御方法についても触れます。

Web API の役割と適用場面

従来の Web アプリケーションは、ユーザーに対して HTML を返すことで機能します。Razor Pages を使えば、サーバー側でテンプレートエンジンを用いて HTML を生成することが容易です。しかし、現代のアプリケーション開発では、図 9.1 に示すように多様なクライアントを想定する必要があります。

図 9.1 現代のアプリケーションは、ブラウザを利用する伝統的なユーザーに加え、SPA、モバイルアプリ、他のバックエンドサービスなど、多様な消費者を想定する必要があります。

Angular、React、Vue などのフレームワークを用いたクライアント側 SPA が普及しています。これらのアプリケーションは、ブラウザ上で動作する JavaScript によって HTML を生成します。初期ロード時にサーバーから JavaScript ファイルを取得し、その後は HTTP 通信を通じてデータ(主に JSON)のみを取得し、クライアント側で描画を更新します。このデータ提供用のサーバー側エンドポイントを Web API と呼びます。

図 9.2 Blazor WebAssembly などを例としたクライアント SPA の動作。初期リクエストでアプリファイルを読み込み、以降のリクエストでは Web API から JSON 形式のデータを取得します。

モバイルアプリも同様に、HTTP Web API を経由してサーバーと通信し、JSON などの汎用フォーマットでデータを受け取り UI を更新します。また、Web API は他のバックエンドサービス連携にも利用されます。例えば、メール送信機能を持つサービスを用意し、他アプリケーションから API を経由して利用させるようなケースです。

Web API を構築すべきかどうかは、アプリケーションの要件によります。クライアントフレームワークの利用予定がある場合や、モバイルアプリとの連携が必要な場合は Web API の導入を検討すべきです。一方で、ブラウザ外からのアクセスが必要なく、サーバーサイドで完結する UI で十分な場合は、Razor Pages のまま効率的に開発を続けられます。なお、後から Web API を追加することも容易であるため、最初はサーバーサイドレンダリングで始め、必要に応じて API を追加するアプローチも有効です。

Web API プロジェクトの作成と構造

ASP.NET Core では、Web API 用のプロジェクトテンプレートが用意されています。Visual Studio の新規プロジェクト作成画面、または CLI で dotnet new webapi コマンドを実行することで作成可能です。

図 9.5 新規プロジェクト作成時のテンプレート選択画面。ASP.NET Core Web API テンプレートを選択することで、API 専用の構成が施されたプロジェクトが生成されます。

生成されたプロジェクトの Startup.cs(または .NET 6 以降の Program.cs)を確認すると、Razor Pages プロジェクトとは異なる設定が見られます。リスト 9.1 は、Web API テンプレートにおける基本的な設定例です。

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        // API コントローラーに必要なサービス登録
        services.AddControllers();
        // Swagger/OpenAPI 文档生成サービスの追加
        services.AddSwaggerGen(c =>
        {
            c.SwaggerDoc("v1", new OpenApiInfo { Title = "MyApi", Version = "v1" });
        });
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
            // 開発環境での Swagger UI 有効化
            app.UseSwagger();
            app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "MyApi v1"));
        }
        app.UseHttpsRedirection();
        app.UseRouting();
        app.UseAuthorization();
        app.UseEndpoints(endpoints =>
        {
            // API コントローラーのエンドポイントマップ
            endpoints.MapControllers();
        });
    }
}

この設定により、アプリケーション内の API コントローラーが自動的に検出され、エンドポイントとして登録されます。また、開発時には Swagger UI を通じて API のテストや探索が可能になります。

コントローラーは、プロジェクト内の任意の場所に作成できますが、慣例として Controllers フォルダに配置されることが多いです。リスト 9.2 は、シンプルな Web API コントローラーの実装例です。

[ApiController]
public class ProductController : ControllerBase
{
    // 商品データの模擬リスト
    private readonly List<ProductItem> _inventory = new List<ProductItem>
    {
        new ProductItem { Id = 1, Name = "Notebook" },
        new ProductItem { Id = 2, Name = "Pen" },
        new ProductItem { Id = 3, Name = "Eraser" }
    };

    [HttpGet("products")]
    public IEnumerable<ProductItem> GetAll()
    {
        // 商品リストを返却
        return _inventory;
    }
}

public class ProductItem
{
    public int Id { get; set; }
    public string Name { get; set; }
}

Web API コントローラーは、通常 ControllerBase クラスを継承し、[ApiController] 属性を付与します。この属性は、Web API 開発における一般的な convention を自動的に適用します。アクションメソッド GetAll は商品リストを返却しており、クライアントには JSON 形式でシリアライズされたデータが 200 OK ステータスと共に返されます。

URL とアクションの紐付けはルーティングによって行われます。[HttpGet("products")] 属性により、このメソッドが HTTP GET 請求かつ URL が /products の場合に実行されることが定義されています。

また、状況に応じてデータだけでなく HTTP ステータスコードを明示的に返すことも重要です。例えば、存在しない ID が指定された場合に 404 Not Found を返すような処理は、リスト 9.3 のように実装できます。

[HttpGet("products/{id}")]
public ActionResult<ProductItem> GetById(int id)
{
    var item = _inventory.FirstOrDefault(x => x.Id == id);
    if (item == null)
    {
        // 該当商品がない場合、404 を返す
        return NotFound();
    }
    // 見つかった場合、データを返す(200 OK)
    return item;
}

戻り値の型に ActionResult<T> を使用することで、データそのもの(T)と、NotFoundResult などのステータスコードを表す結果の両方を返すことが可能になります。これは OpenAPI 文档との統合性も高める利点があります。

MVC パターンの Web API への適用

ASP.NET Core 以前では、Web API 框架と MVC 框架は別物でしたが、ASP.NET Core では統一されています。Web API コントローラーも、Razor Pages や MVC コントローラーと同じ基盤フレームワーク上で動作します。

図 9.9 従来の Razor Pages リクエスト処理フローと Web API エンドポイント呼び出しの比較。ミドルウェアパイプラインを通る点は共通していますが、レスポンス生成部分(ビュー vs 出力フォーマッター)が異なります。

リクエスト処理の流れは以下の通りです。

  1. RoutingMiddleware により URL が適切なコントローラーとアクションにマッチングされます。
  2. モデルバインディングにより、リクエストデータがアクション引数に結合されます。
  3. バリデーションが行われ、エラーがあれば ModelState に記録されます。
  4. アクションメソッドが実行され、ビジネスロジックが処理されます。
  5. 戻り値が出力フォーマッターによって JSON や XML などにシリアライズされ、レスポンスとして返されます。

Razor Pages では最終的に HTML が生成されるのに対し、Web API ではデータモデルがシリアライズされる点が主な差異です。ビジネスロジックはアプリケーションモデル(サービス層)に委譲し、コントローラーは入出力の処理に専念させる設計が推奨されます。

属性ルートによる URL 設計

Web API では、属性ルートを使用して URL とアクションを紐付けます。Razor Pages の @page ディレクティブと同様の概念ですが、C# 属性として記述します。

[Route("api/[controller]")]
public class VehicleController : ControllerBase
{
    // GET api/vehicle/start
    [HttpGet("start")]
    public IActionResult Ignition()
    {
        return Ok("Engine Started");
    }

    // GET api/vehicle/speed/100
    [HttpGet("speed/{speed:int}")]
    public IActionResult SetSpeed(int speed)
    {
        return Ok($"Speed set to {speed}");
    }
}

コントローラーレベルで [Route("api/[controller]")] を定義し、アクションレベルで相対パスを指定することで、重複を防ぎつつ整理された URL 構造を維持できます。[controller] トークンは、クラス名から "Controller" を除いた部分に置換されます。

また、HTTP メソッドごとにアクションを分けることも可能です。同じ URL パスでも、GET と POST で異なる処理を割り当てることができます。

[ApiController]
[Route("api/events")]
public class ScheduleController : ControllerBase
{
    // GET api/events - リスト取得
    [HttpGet]
    public IActionResult List() { /* ... */ }

    // POST api/events - 新規作成
    [HttpPost]
    public IActionResult Create([FromBody] EventDto dto) { /* ... */ }
}

このように HTTP 動詞をルーティングの一部として扱うことは、RESTful な API 設計において一般的です。定義されていない動詞でアクセスされた場合、405 Method Not Allowed が返されます。

[ApiController] 属性による convention の自動化

[ApiController] 属性を付与することで、以下の便利な convention が自動的に適用されます。

  • 属性ルートの必須化: 従来の convention ベースのルートは使用不可となります。
  • 自動的な 400 応答: モデルバリデーションに失敗した場合、自動的に 400 Bad Request が返されます。手動で ModelState.IsValid をチェックする必要がなくなります。
  • モデルバインディングソースの推論: 複雑な型は自動的にリクエストボディからバインドされます([FromBody] 属性の省略が可能)。
  • ProblemDetails への変換: エラーレスポンスが標準化された ProblemDetails 形式で返されます。

これにより、ボイラープレートコードを大幅に削減できます。ただし、これらの動作をカスタマイズしたい場合は、ConfigureApiBehaviorOptions を使用して設定を変更可能です。

services.AddControllers()
    .ConfigureApiBehaviorOptions(options =>
    {
        // 自動的な 400 応答を無効化
        options.SuppressModelStateInvalidFilter = true;
    });

レスポンスフォーマットとコンテンツネゴシエーション

ASP.NET Core の Web API は、デフォルトで JSON 形式でレスポンスを返します。ASP.NET Core 3.0 以降では、高性能な System.Text.Json が採用されています。必要に応じて、Newtonsoft.Json に切り替えることも可能です。

クライアントが要求するフォーマットに応じてレスポンス形式を変更する仕組みを、コンテンツネゴシエーションと呼びます。クライアントは Accept ヘッダーで希望するメディアタイプを通知し、サーバーはそれに対応できるフォーマッターを選択します。

デフォルトでは JSON のみがサポートされていますが、XML などをサポートするには出力フォーマッターを追加します。

services.AddControllers()
    .AddXmlSerializerFormatters();

これにより、Accept: application/xml ヘッダーを含むリクエストに対して XML 形式でレスポンスを返せるようになります。ただし、ブラウザからのリクエスト(Accept: */*)などは、デフォルトで JSON が優先される挙動に注意が必要です。この挙動を変更するには、以下のように設定します。

services.AddControllers(options =>
{
    // ブラウザの Accept ヘッダーを尊重する
    options.RespectBrowserAcceptHeader = true;
    // 文字列型の text/plain フォーマッターを削除
    options.OutputFormatters.RemoveType<StringOutputFormatter>();
});

これらの設定を適切に行うことで、多様なクライアントに対応可能な柔軟な Web API を構築できます。

タグ: aspnet-core web-api rest-api json-serialization mvc-pattern

Sun, 10 May 2026 08:43:06 +0900 投稿