【C#】Minimal API でのバリデーション実装方法

C#のMinimal APIでは、モデルのバリデーションを簡単に実装できます。本記事では、Minimal APIでバリデーションを行う 3 つの方法を紹介します。
- データアノテーションを使う
必須項目、文字数制限などのシンプルなバリデーションを実装できる - カスタムバリデーションを使う
if文などを使った複雑なバリデーションを実装できる - FluentValidation を利用する
より柔軟で拡張性のあるバリデーションを実装できる

それぞれの方法について、コード例を交えながら解説していきます。
なお、本記事で使用するコードは下記の記事で作成したものです。

データアノテーションを使う
もっともシンプルでよく使われる方法です。属性(アノテーション)をモデルのプロパティに直接定義できます。使用するには、C# の System.ComponentModel.DataAnnotations を利用する必要があります。
下記では、Model.csに定義したUserクラスに、System.ComponentModel.DataAnnotationsの注入と、[]を用いたバリデーションルールを適用しています。
using System.ComponentModel.DataAnnotations;
namespace Model
{
public class User
{
[Required(ErrorMessage = "名前は必須です")]
[StringLength(50, MinimumLength = 3, ErrorMessage = "名前は3文字以上50文字以下である必要があります")]
public string Name { get; set; } = string.Empty;
[Range(18, 100, ErrorMessage = "年齢は18歳以上100歳以下である必要があります")]
public int Age { get; set; }
}
}
- Required
必須チェック - StringLength({最大文字数},MinimumLength = {最小文字数})
文字数チェック。オプションとして最小文字数も指定できる - Range({最小値},{最大値})
数値の範囲チェック
各属性にはErrorMessageを設定できます。それぞれのチェックでエラーになったとき、どういったエラーメッセージを出力するかを決められます。
program.csでバリデーションが有効になるよう記載します。こちらでもSystem.ComponentModel.DataAnnotationsを追加します。
using System.ComponentModel.DataAnnotations;
using Model;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapPost("/users", (User user) =>
{
var validationResults = new List<ValidationResult>();
var context = new ValidationContext(user, null, null);
if (!Validator.TryValidateObject(user, context, validationResults, validateAllProperties: true))
{
return Results.BadRequest(validationResults.Select(v => v.ErrorMessage));
}
return Results.Ok($"Received user: {user.Name}, Age: {user.Age}");
});
app.Run();
- var validationResults = new List();
バリデーションの結果(エラーメッセージなど)を格納するための空のリストを用意 - var context = new ValidationContext(user, null, null);
「どのオブジェクトを検証するか」という情報を持つ ValidationContext を作成
ここでは user オブジェクト(POSTされたユーザー情報)を検証対象にしています。 - if (!Validator.TryValidateObject(user, context, validationResults, validateAllProperties: true))
Validator.TryValidateObject で、user オブジェクトのすべてのプロパティに対して、アノテーションのルールを使ってチェック
false が返ってきたら、何かしらのエラーあり
第1引数:検証するオブジェクト(user)
第2引数:検証用のコンテキスト(context)
第3引数:エラー結果を格納するリスト(validationResults)
第4引数:すべてのプロパティを検証するか(true で検証する)
データアノテーションを使ったバリデーションは、ちょっとしたチェックに向いています。
実行してみるとこうなります。
パラメータ

処理結果

カスタムバリデーション
データアノテーションでは対応できない、少し複雑な条件(例えば「名前に数字が含まれていないか」など)を実現したいときは、IValidatableObject を使ってモデルの中でバリデーションロジックを書く方法があります。
using System.ComponentModel.DataAnnotations;
namespace Model
{
public class User : IValidatableObject
{
public string Name { get; set; } = string.Empty;
public int Age { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (Name.Any(char.IsDigit))
{
yield return new ValidationResult("名前に数字を含めることはできません", new[] { nameof(Name) });
}
}
}
}
上記では、名前により詳細な条件を付与しています。カスタムバリデーションをする際のprogram.csはデータバリデーションを実施する場合と同じです。
実施してみるとこんな感じです。
パラメータ

処理結果

もちろん、データアノテーションと組み合わせることもできます。その場合、データアノテーションによるバリデーション→カスタムバリデーションの順で検査されます。
using System.ComponentModel.DataAnnotations;
namespace Model
{
public class User: IValidatableObject
{
[Required(ErrorMessage = "名前は必須です")]
[StringLength(50, MinimumLength = 3, ErrorMessage = "名前は3文字以上50文字以下である必要があります")]
public string Name { get; set; } = string.Empty;
[Range(18, 100, ErrorMessage = "年齢は18歳以上100歳以下である必要があります")]
public int Age { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (Name.Any(char.IsDigit))
{
yield return new ValidationResult("名前に数字を含めることはできません", new[] { nameof(Name) });
}
}
}
}
パラメータ

処理結果

カスタムバリデーションは柔軟ですが、ロジックがモデル内に埋め込まれるため、コードが少し見づらくなります。
FluentValidation
もっと柔軟で拡張しやすいバリデーションが必要な場合は、FluentValidation というライブラリを使う方法があります。ルールを外部のクラスで定義できるので、コードが整理しやすくなります。
FluentValidationを使用するには、まずパッケージを追加します。
dotnet add package FluentValidation.AspNetCore
モデルとバリデータを定義します。
using FluentValidation;
namespace Model
{
public class User
{
public string Name { get; set; } = string.Empty;
public int Age { get; set; }
}
public class UserValidator : AbstractValidator<User>
{
public UserValidator()
{
RuleFor(x => x.Name)
.NotEmpty().WithMessage("名前は必須です")
.Length(3, 50).WithMessage("名前は3文字以上50文字以下である必要があります");
RuleFor(x => x.Age)
.InclusiveBetween(18, 100).WithMessage("年齢は18歳以上100歳以下である必要があります");
}
}
}
program.csを下記のように書き換えます。
using FluentValidation;
using FluentValidation.AspNetCore;
using Model;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddValidatorsFromAssemblyContaining<UserValidator>();
var app = builder.Build();
app.MapPost("/users", (User user, IValidator<User> validator) =>
{
var validationResult = validator.Validate(user);
if (!validationResult.IsValid)
{
return Results.BadRequest(validationResult.Errors.Select(e => e.ErrorMessage));
}
return Results.Ok(new { Message = "ユーザー登録成功", User = user });
});
app.Run();
FluentValidationについては別の記事で詳細を解説しているので、参考になれば幸いです。

まとめ
Minimal APIにおけるバリデーションの実装についてまとめました。
方法 | 向いているケース | 特徴 |
---|---|---|
データアノテーション | 簡単なチェックだけで十分なとき | シンプルで手軽に使える |
カスタムバリデーション | ちょっと複雑なルールが必要なとき | 自由度は高いが、見通しはやや悪い |
FluentValidation | 柔軟で拡張しやすい構成にしたいとき | 別クラスで定義でき、保守性も高い |
開発するシステムの規模やチームのコーディング方針によって、最適なバリデーション方法は異なります。最初はシンプルに始めて、必要に応じて FluentValidation に移行するのも良いでしょう。

参考になればうれしいです。