10
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【.NET 10】FastAPIユーザーが Minimal API を試してみた〜本当に代替になるのか検証

10
Posted at

【.NET 10】FastAPIユーザーが Minimal API を試してみた〜本当に代替になるのか検証

はじめに

普段は Python の FastAPI でAPIサーバーを書いているのですが、
「.NET 10 の Minimal API がかなり進化しているらしい」という噂を聞いて、実際に試してみました。

この記事は、FastAPI の主要機能に一つずつ対応させる形で、.NET 10 Minimal API で同じことができるか検証した話になります。
結論から言うと、想像以上に「FastAPIっぽく」書けて驚きました。

対象読者

  • FastAPI の経験があり、C# / .NET にも興味がある方
  • .NET 10 の Minimal API でどこまでできるか知りたい方
  • API フレームワークの選定で悩んでいる方

環境

  • .NET SDK: 10.0.103
  • OS: Ubuntu 24.04 (WSL2)
  • NuGet パッケージ:
    • Microsoft.AspNetCore.OpenApi 10.0.3
    • Scalar.AspNetCore 2.12.46

まず、対応表を作ってみた

FastAPI が支持される理由を整理して、.NET 10 でどう対応できるか表にしてみました。

FastAPI の特徴 FastAPI (Python) .NET 10 Minimal API (C#)
簡潔なルーティング @app.get("/") デコレータ app.MapGet("/", ...) メソッドチェーン
データモデルと型安全性 Pydantic BaseModel record 型 + Data Annotations
自動バリデーション Pydantic が自動で検証 ネイティブサポート(AddValidation
自動 API ドキュメント Swagger UI / ReDoc(標準) OpenAPI + Scalar(モダン UI)
非同期処理 async def async Task<IResult>
依存性の注入 (DI) Depends() ビルトイン DI コンテナ
JSON シリアライズ 自動(json/orjson System.Text.Json(自動・高速)

これだけ見ると、かなり対抗できそうです。
では、一つずつ実際のコードで見ていきます。

検証1: ルーティングの書き心地

FastAPI

FastAPI では、デコレータで直感的にエンドポイントを定義できます。

main.py
from fastapi import FastAPI

app = FastAPI()

@app.get("/users/{user_id}")
async def get_user(user_id: int):
    return {"id": user_id, "name": "たろう"}

.NET 10 Minimal API

.NET 10 では app.MapGet などのメソッドチェーンで同じことを書きます。
ファイル1つで完結する点も FastAPI と同じです。

Program.cs
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/users/{id}", (int id) =>
    new User(id, "たろう"));

app.Run();

record User(int Id, string Name);

record 型を使うと、Pydantic の BaseModel と同じ感覚でデータモデルを1行で定義できます。
これは思った以上に書き心地が良いです。

検証2: 自動バリデーション

FastAPI 最大の武器と言っても過言ではない自動バリデーション
型にアノテーションをつけるだけで、不正なリクエストを勝手に弾いてくれるアレです。

FastAPI

main.py
from pydantic import BaseModel, Field

class User(BaseModel):
    id: int = Field(gt=0)
    name: str = Field(min_length=2, max_length=50)
    age: int = Field(ge=18, le=120)

@app.post("/users")
async def create_user(user: User):
    return user

不正なデータを送ると 422 Unprocessable Entity が自動で返ります。
これが本当に楽なんですよね。

.NET 10 Minimal API

実は、これまでの .NET Minimal API には標準の自動バリデーションがありませんでした
外部ライブラリ(FluentValidationMiniValidation)を入れてフィルターを書く必要があり、これが FastAPI と比べた時の最大の弱点でした。

しかし、.NET 10 でついにネイティブの自動バリデーションが入りました!

この機能は Request Delegate Generator (RDG) という仕組みの強化として導入されています。
RDG がコンパイル時にバリデーションコードを自動生成するため、実行時のオーバーヘッドがほぼゼロという点も FastAPI(実行時チェック)に対する強みです。

仕組みを図にすると、こんなイメージです。

設定は2ステップだけです。

ステップ1: .csproj にジェネレーターの設定を追加

<PropertyGroup>
  <!-- バリデーション用インターセプターの生成を有効化 -->
  <InterceptorsNamespaces>$(InterceptorsNamespaces);Microsoft.AspNetCore.Http.Validation.Generated</InterceptorsNamespaces>
</PropertyGroup>

ステップ2: Program.csAddValidation() を呼ぶ

Program.cs
var builder = WebApplication.CreateBuilder(args);

// ★これだけで自動バリデーションが有効になる
builder.Services.AddValidation();

var app = builder.Build();

app.MapPost("/users", (User user) =>
{
    // ここに到達した時点で user は検証済み
    return Results.Created($"/users/{user.Id}", user);
})
.WithName("CreateUser");

モデル側は Data Annotations を付けるだけ。

Program.cs
public record User(
    [Required]
    [Range(1, int.MaxValue)]
    int Id,

    [Required]
    [StringLength(50, MinimumLength = 2)]
    string Name,

    [Range(18, 120)]
    int Age
);

不正なデータを送ると、FastAPI と同じように自動で弾かれます。

{
  "title": "One or more validation errors occurred.",
  "status": 400,
  "errors": {
    "Id": ["The field Id must be between 1 and 2147483647."],
    "Name": ["The field Name must be a string with a minimum length of 2 and a maximum length of 50."],
    "Age": ["The field Age must be between 18 and 120."]
  }
}

ステータスコードの違いに注意
FastAPI は 422 Unprocessable Entity を返しますが、.NET は標準で 400 Bad Request を返します。
FastAPI から移行する場合、クライアント側のエラーハンドリングでステータスコードを判定している箇所があれば修正が必要です。

これはかなり大きなアップデートだと思います。
FastAPI との差が一気に縮まりました。

「A が空なら B を必須にする」といった相関チェックが必要な場合は、FluentValidation を併用するのも有効です。
ただ、単純なチェックなら標準機能で完全に事足ります。

検証3: API ドキュメント自動生成

FastAPI の Swagger UI と ReDoc は、コードを書くだけで美しいドキュメントが出てくる、あの体験です。

.NET 10 Minimal API

.NET 10 では、Scalar というモダンな API ドキュメント UI を使います。

Program.cs
using Scalar.AspNetCore;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenApi();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();             // OpenAPI 定義を生成(/openapi/v1.json)
    app.MapScalarApiReference();  // Scalar UI を提供(/scalar)
}

たった2行追加するだけで、こんな画面が手に入ります。

scalar.jpeg

正直、FastAPI の ReDoc より見た目が良いです。
ダークモード標準対応で、JavaScript / Python / C# など複数言語のコードサンプルも自動表示されます。

.NET 9 以降、従来の Swashbuckle(Swagger UI)は標準から外れました。
今後は Scalar が .NET エコシステムのスタンダードになりつつあります。

検証4: 依存性の注入(DI)

FastAPI では Depends() でDB接続やサービスを注入します。

main.py
def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

@app.get("/users")
async def get_users(db: Session = Depends(get_db)):
    return db.query(User).all()

.NET 10

.NET にはフレームワークレベルで DI コンテナが組み込まれています。
追加ライブラリは一切不要です。

Program.cs
builder.Services.AddScoped<IUserService, UserService>();

app.MapGet("/users", (IUserService service) =>
    service.GetAllUsers());

引数にインターフェースを書くだけで、フレームワークが自動的にインスタンスを注入してくれます。
FastAPI の Depends() よりもさらにシンプルだと感じました。

完成したコード

最終的に出来上がった Program.cs がこちらです。
このファイル1つで API サーバーが完成します。

Program.cs
using System.ComponentModel.DataAnnotations;
using Scalar.AspNetCore;

var builder = WebApplication.CreateBuilder(args);

// API ドキュメント生成と自動バリデーションを登録
builder.Services.AddOpenApi();
builder.Services.AddValidation();

var app = builder.Build();

// 開発環境でのみ API ドキュメントを有効化
if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();
    app.MapScalarApiReference();
}

app.UseHttpsRedirection();

var users = new Dictionary<int, User>();

// POST /users - ユーザー作成(Data Annotations により自動バリデーション実行)
app.MapPost("/users", (User user) =>
{
    users[user.Id] = user;
    return Results.Created($"/users/{user.Id}", user);
})
.WithName("CreateUser");

// GET /users/{id} - ユーザー取得
app.MapGet("/users/{id}", (int id) =>
{
    return users.TryGetValue(id, out var user)
        ? Results.Ok(user)
        : Results.NotFound(new { Message = "User not found" });
})
.WithName("GetUser");

app.Run();

// Pydantic 風のデータモデル
public record User(
    [Required]
    [Range(1, int.MaxValue)]
    int Id,

    [Required]
    [StringLength(50, MinimumLength = 2)]
    string Name,

    [Range(18, 120)]
    int Age
);

試してみたい方へ

手元で動かす場合は、以下のコマンドを実行してください。

# プロジェクトの作成
dotnet new webapi -minimal -o minimal_api
cd minimal_api

# NuGet パッケージの追加
dotnet add package Scalar.AspNetCore

# 実行
dotnet run

.csprojInterceptorsNamespaces の設定を忘れずに追加してください。

起動後、以下の URL にアクセスできます:

  • API ドキュメント(Scalar): http://localhost:<port>/scalar/v1
  • OpenAPI 定義: http://localhost:<port>/openapi/v1.json

※ポート番号は dotnet run 実行時のコンソール出力(Now listening on: ...)を確認してください。

おまけ: Native AOT でさらに差をつける

FastAPI(Python)に対する .NET の隠し球が Native AOT(Ahead-of-Time コンパイル)です。
通常の JIT コンパイルでも十分高速ですが、Native AOT を使うとランタイム不要の単一バイナリにコンパイルできます。

dotnet publish -c Release -r linux-x64 /p:PublishAot=true

これにより:

  • 🚀 起動時間が数ミリ秒(JIT のウォームアップがゼロ)
  • 📦 メモリ消費が極小(ランタイムを含まない)
  • 🐳 コンテナイメージが超軽量

AWS Lambda や Cloud Run など、コールドスタートが気になるサーバーレス / コンテナ環境では特に効果が大きいです。
Python ベースの FastAPI では原理的に達成できない領域なので、ここは .NET の圧倒的な強みだと感じました。

まとめ

FastAPI の強み .NET 10 での実現方法 評価
簡潔なルーティング app.MapGet / app.MapPost ✅ 同等
Pydantic バリデーション Data Annotations + AddValidation ✅ 同等
Swagger UI / ReDoc Scalar(よりモダン) ✅ 同等以上
DI(Depends ビルトイン DI コンテナ ✅ 同等以上
非同期処理 async/await ✅ 同等
実行速度 JIT/AOT コンパイル 🚀 .NET が圧倒的に速い
デプロイサイズ Native AOT 🚀 サーバーレスに最適

正直、ここまでFastAPIに近い書き心地になっているとは思いませんでした。
特に .NET 10 で標準のバリデーションが入ったのは大きくて、**「外部ライブラリなしでFastAPIと同等のことができる」**と言える状態になっています。

コンパイル言語ゆえの圧倒的なパフォーマンスNative AOT によるサーバーレス適性、そして静的型付けによるリファクタリングの安全性を考えると、
Python エコシステム(機械学習ライブラリ等)への依存がなければ、十分に代替候補になり得ると感じました。

参考記事


※この記事の文章は、Antigravityで作成したベースをもとに、筆者が構成・加筆したものです。

10
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?