MiniAPI(二十)：エンティティの検証FluentValidation

API POSTで送信されるデータの有効性を検証するために、FluentValidation(詳細は公式ウェブサイト https://fluentvalidation.net をご覧ください)を導入することができます。asp.net mvcでは、モデルの検証を使用しており、エンティティクラス上に属性を追加することで検証効果を得ています。
FluentValidationの原理はAbstractValidator<T>の実装により、Tエンティティクラスの検証を行うものです。Tの属性を異なるルールを通じて検証します（より多くの検証ルールについては、公式ウェブサイトを参照してください）。以下の実装をご覧ください：
```csharp
public class Person{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Tel { get; set; }
    public string Email { get; set; }
    public DateTime Birthday { get; set; }
    public string IDCard { get; set; }
    public PersonAddress Address { get; set; }
}
public class PersonAddress{
    public string Country { get; set; }
    public string Province { get; set; }
    public string City { get; set; }
    public string County { get; set; }
    public string Address { get; set; }
    public string Postcode { get; set; }
}

/// <summary>
/// Person検証
/// </summary>
public class PersonValidator : AbstractValidator<Person>{
    public PersonValidator(IPersonService personService)
    {
        RuleFor(p => p.Name).NotNull().NotEmpty();
        RuleFor(p => p.Email).NotNull().EmailAddress();
        RuleFor(p => p.Birthday).NotNull();
        RuleFor(p => p.IDCard)
            .NotNull()
            .NotEmpty()
            .Length(18)
            .When(p => (DateTime.Now > p.Birthday.AddYears(1)))
            .WithMessage(p => $"出生日期为{p.Birthday}，现在时间为{DateTime.Now},大于一岁，CardID值必填！");
        RuleFor(p => p.Tel).NotNull().Matches(@"^(\d{3,4}-)?\d{6,8}$|^[1]+[3,4,5,8]+\d{9}$").WithMessage("电话格式为：0000-0000000或13000000000");
        RuleFor(p => p.Address).NotNull();
        RuleFor(p => p.Address).SetValidator(new PersonAddressValidator());
        //外部メソッドを呼び出して検証する
        RuleFor(p => p.Id).Must(id => personService.IsExist(id)).WithMessage(p => $"不存在id={p.Id}の用户");
    }
}
/// <summary>
/// Person Address検証
/// </summary>
public class PersonAddressValidator : AbstractValidator<PersonAddress>{
    public PersonAddressValidator()
    {
        RuleFor(a => a.Country).NotNull().NotEmpty();
        RuleFor(a => a.Province).NotNull().NotEmpty();
        RuleFor(a => a.City).NotNull().NotEmpty();
        RuleFor(a => a.County).NotNull().NotEmpty();
        RuleFor(a => a.Address).NotNull().NotEmpty();
        RuleFor(a => a.Postcode).NotNull().NotEmpty().Length(6);
    }
}
```
mini apiにFluentValidationを導入するのも簡単です。IValidator<T>の注入による実装が可能ですし、AddFluentValidationで注入し、IValidatorFactoryを用いてValidatorを取得し検証を行うこともできます。コードは以下の通りです：
```csharp
using FluentValidation;
using FluentValidation.AspNetCore;
using System.Text;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddFluentValidation();
builder.Services.AddScoped<IValidator<Person>, PersonValidator>();
builder.Services.AddScoped<IPersonService, PersonService>();
var app = builder.Build();

app.MapPost("/person", async (IValidator<Person> validator, Person person) => {
     var result = await validator.ValidateAsync(person);
     if (!result.IsValid)
     {
         var errors = new StringBuilder();
         foreach (var valid in result.Errors)
         {
             errors.AppendLine(valid.ErrorMessage);
         }
         return errors.ToString();
     }
     return "OK";
});
app.MapPost("/person1", async (IValidatorFactory validatorFactory, Person person) => {
    var result = await validatorFactory.GetValidator<Person>().ValidateAsync(person);
    if (!result.IsValid)
    {
        var errors = new StringBuilder();
        foreach (var valid in result.Errors)
        {
            errors.AppendLine(valid.ErrorMessage);
        }
        return errors.ToString();
    }
    return "OK";
});
app.MapPost("/person2", async (IValidatorFactory validatorFactory, Person person) => {
    var result = await validatorFactory.GetValidator(typeof(Person)).ValidateAsync(new ValidationContext<Person>(person));
    if (!result.IsValid)
    {
        var errors = new StringBuilder();
        foreach (var valid in result.Errors)
        {
            errors.AppendLine(valid.ErrorMessage);
        }
        return errors.ToString();
    }
    return "OK";
});
app.Run();

public interface IPersonService{
    public bool IsExist(int id);
}
public class PersonService : IPersonService{
    public bool IsExist(int id)
    {
        if (DateTime.Now.Second % 2 == 0)
        {
            return false;
        }
        else
        {
            return true;
        }
    }
}
```
(Translated by GPT)

元のリンク：https://mp.weixin.qq.com/s?__biz=MzA3NDM1MzIyMQ==&mid=2247484965&idx=1&sn=2e5889df62c416cb49a1ed33baed5fda&chksm=9f00590fa877d019a4cf2fa9f02f94d641943659fabf944bc2fdaeb9dff08c7140265636350f&token=541757603&lang=zh_CN#rd