.NET 10におけるデータ検証の最小API
[[academy-video-youtube({"vid": "sW_AcN-nD0Y", "start_time": "0", "title": "Data Validation in .NET 10 Minimal APIs", "creator": "Tim Corey", "length": "~10m"})]]
ミニマルAPIは常にコントローラーを基にしたASP.NET Coreのスリムな代替手段でしたが、長らく注目すべきギャップがありました。それは、受信データの検証をするための組み込みサポートがないことです。 各ハンドラー内に手動チェックを組み込むか、サードパーティライブラリに依存していました。 .NET 10は、追加のパッケージなしで、クエリストリング、ヘッダー、リクエスト本文に対するファーストクラスデータアノテーションの検証により、そのギャップを埋めています。
Tim Coreyのウォークスルーに基づくこの.NET 10 Minimal APIsの詳細な説明では、検証サービスを登録し、モデルクラスに注釈を付け、セットアップを壊す可能性がある一般的なアクセス修飾子のピットフォールを避ける方法を説明します。
セットアップ: シンプルなミニマルAPI
[0:44 - 1:35] デモは、.NET 10で動作する簡素なASP.NET Core Minimal APIプロジェクトから始まります。表面積は意図的に小さく、各モデルを受け入れる2つのPOSTエンドポイントがあります。
app.MapPost("/person", (Person person) => Results.Ok(person));
app.MapPost("/login", (LoginModel login) => Results.Ok(login));app.MapPost("/person", (Person person) => Results.Ok(person));
app.MapPost("/login", (LoginModel login) => Results.Ok(login));Person モデルは基本的なアイデンティティフィールドを持ちます。 LoginModel は、メールアドレス、パスワード、およびパスワード確認フィールドの資格情報を処理します。 どちらもJSON本文として送信されます。 この時点では、入力チェックはまったくありません; APIは空の文字列や不正なメールアドレスを含むものすべてを受け入れます。
Scalar(.NET 10に付属する、最新のOpenAPI UI)が、よくテストされてリクエストをブラウザから直接送信します。このため、検証が結線される前後で、APIが確実に応答する内容を簡単に確認できます。
バリデーションサービスの登録
[2:36 - 3:06] 検証属性を有効にする前に、サービスレベルでオプトインする必要があります。 サービス登録ブロック内での単一の呼び出しが、すべてのMinimal APIエンドポイントでの強制を有効にします。
builder.Services.AddValidation();builder.Services.AddValidation();その1行が全体の設定ステップです。追加するミドルウェアも、フックするパイプラインステージもありません。 一度サービスが登録されると、フレームワークが自動的に引き継ぎます。 この呼び出しをスキップすると、データアノテーション属性がモデル上に存在しますが、評価されることはなく、リクエストが含むものに関わらず通過します。
最初のデバッグステップとしてこれを心に留めておく価値があります: 検証が黙って何もしない場合、AddValidation() が通常欠けている部分です。
クラスモデルへのバリデーションの追加
[3:00 - 3:55] サービス登録後、クラスベースのモデルに検証を追加するには、プロパティにデータアノテーション属性を装飾するだけです。
public class Person
{
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
}public class Person
{
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
}ファイルの先頭に System.ComponentModel.DataAnnotations using ディレクティブを追加した後、FirstName と LastName を [Required] としてマークすると、それらが検証されることを保証します。 Scalar を通じて空のボディで POST リクエストを送信すると、400 Bad Request が返され、構造化されたエラーレスポンスが生成されます:
{
"errors": {
"FirstName": ["The FirstName field is required."],
"LastName": ["The LastName field is required."]
}
}カスタムエラーハンドリングもフィルター属性もありません。 フレームワークが自動的にその応答を生成し、チェックはハンドラ本文の実行前に発生します。そのため、エンドポイントロジック内でnullをガードする必要はありません。
レコードへのバリデーションの適用
[4:29 - 5:30] 同じ属性はC#レコードにも適用されますが、レコードプロパティは通常、個別のメンバー宣言ではなく初期コンストラクタで定義されるため、構文がやや異なります。
public record LoginModel(
[Required] [EmailAddress] string Email,
[Required] string Password,
[Required] string ConfirmPassword
);public record LoginModel(
[Required] [EmailAddress] string Email,
[Required] string Password,
[Required] string ConfirmPassword
);コンストラクターパラメーターの属性は生成されたプロパティに適用されるため、[Required] と [EmailAddress] はクラス上での動作とまったく同じように動作します。 例えば "notanemail" のような不正なメールを指定してリクエストを送信すると、400 が返され Email フィールドが無効であることが示されます。
[Compare] 属性は、ConfirmPassword が Password に一致することを強制するためにも使用できます。 レコードでは、コンパイラにそれが生成されたメンバーでパラメータ自体ではないことを伝えるために、あからさまな動機付けが必要です。
[property: Compare(nameof(Password))]
string ConfirmPassword[property: Compare(nameof(Password))]
string ConfirmPassword[property:] ターゲットは、コンパイラーにパラメーターではなく生成されたメンバーに属性を付与するように指示します。 これがなければ、[Compare] はコンパイルされますが、チェック中に実行されることはありません。 このコンテキストでのレコードvsクラスの扱いで最も難解な部分です。クラスプロパティは自然に属性を受け入れますが、レコードパラメータはメンバーレベルで何かを操作するには明示的なターゲットが必要です。
Public Access Modifier Requirement
[7:51 - 9:00] よくある落とし穴は見逃しやすく、遭遇してもエラーメッセージを生成しません。 検証システムはリフレクションを使用してランタイムでモデルタイプを検査します。リフレクションがタイプのメンバーを見つけるためには、タイプ自体が public としてマークされている必要があります。
// Validation will NOT run; the class is internal by default
class Person { ... }
// Validation runs correctly
public class Person { ... }// Validation will NOT run; the class is internal by default
class Person { ... }
// Validation runs correctly
public class Person { ... }同じルールがレコードに適用されます。 モデルがアクセス修飾子なしで宣言された場合、C# はデフォルトで internal とし、サービスはそれを完全にスキップします。 エンドポイントはリクエストを受け取り、ハンドラーが実行され、エラーは返されません。 チェックは単に何も行わないままです。
これは ASP.NET Core の振る舞いであり、Minimal APIs に特有のものではありませんが、これらのプロジェクトはコンパクトである傾向があり、開発者が可視性を考えずにモデルをインラインまたは Program.cs の同じファイルに定義することがあるため、ここではより頻繁に表面化します。
プロジェクトを監査する簡単な方法: エンドポイントハンドラーに渡される任意のモデルタイプは public と明示的にマークされるべきです。 そうでなければ、どんなに多くの属性を加えてもフレームワークは発火しません。
You Can Validate
組み込みデータアノテーション属性は、追加の作業なしで最も一般的なシナリオをカバーします:
[Required]は null または空の値を拒否する[EmailAddress]はメール文字列の形式を検証する[Compare]は2つのプロパティが一致することを確認し、パスワード確認に役立ちます[Range]は数値または日付の境界を強制する[StringLength]はオプションの最小値を持つ文字列の長さを制限する[RegularExpression]はカスタムパターンに対して検証する
これらはすべて、クエリ文字列パラメータ、リクエストヘッダー、およびJSON本文に対して動作します。 同じモデルクラスまたはレコードは、異なるソースからバインドされてもその属性を一切変更することなく動作します。
結論
[7:46 - end] AddValidation() が登録されていてモデルが装飾されていると、Minimal APIs はクラスやレコード全体で入力制約を自動的に強制します。.NET 10 でこれを動作させるには、builder.Services.AddValidation() を一度だけ呼び出し、モデルプロパティを標準のデータ注釈属性で装飾し、すべてのモデルタイプが public としてマークされていることを確認してください。
レコード上の [property:] ターゲットと public 修飾子の要件が唯一の本当の落とし穴です。 それらは見逃しがちであり、コンパイルエラーではなく無音の失敗をもたらします。したがって、入力チェックが何もしないように見える場合は、常にチェックリストに載せておいてください。
完全なソースコードウォークスルーはTim CoreyのYouTube動画で視聴できます。

