Alternative Architecture DOJO

オルターブースのクラウドネイティブ特化型ブログです。

【.NET】FluentValidationを使用してネストされた値を検証する

こんにちは!エンジニアのはっしーです!

今回はFluentValidationを使用して、ネストされた値を検証する方法を紹介したいと思います。

FluentValidationとは

FluentValidationは値の検証を行うための.NETライブラリです。

FluentValidation — FluentValidation documentation

プロジェクトの作成

  • Visual Studioを開きます。
  • 新しいプロジェクトの作成を選択します。
  • テンプレートからASP.NET Core Web APIを選択します。
  • 任意のプロジェクト名とソリューション名を入力します。 (今回のサンプルではデフォルトの「WebApplication1」をそのまま使用します。)

モデルの作成

  • モデルを作成します。
  • プロジェクト配下にModelsフォルダーを作成します。
  • Modelsフォルダーの中に以下のモデルクラスを作成します。
  • OrderクラスがProductクラスを持っている構造です。
namespace WebApplication1.Models;

/// <summary>
/// 注文情報
/// </summary>
public class Order
{
    public string CustomerName { get; set; }
    public string CustomerEmail { get; set; }
    public Product[] Product { get; set; }
}
  • Productクラス
namespace WebApplication1.Models;

/// <summary>
/// 製品情報
/// </summary>
public class Product
{
    public string Name { get; set; }
    public int Price { get; set; }
}

バリデーターの作成

  • パッケージマネージャーコンソールを開きます。
    「表示」=> 「その他のウィンドウ」=> 「パッケージマネージャーコンソール」
  • パッケージマネージャーコンソールで以下のコマンドを入力します。
    install-package FluentValidation
  • プロジェクト配下にValidatorsフォルダーを作成します。
  • Validatorsフォルダーにバリデータークラスを作成します。
  • まずはProductクラスを検証するProductValidatorを作成します。
using FluentValidation;
using WebApplication1.Models;

namespace WebApplication1.Validators;

/// <summary>
/// 製品クラス用のバリデーター
/// </summary>
public class ProductValidator : AbstractValidator<Product>
{
    public ProductValidator()
    {
        RuleFor(model => model.Name)
            .NotNull()
            .NotEmpty();

        RuleFor(model => model.Price)
            .NotNull()
            .NotEmpty();
    }
}
  • 次にOrderクラスを検証するOrderValidatorを作成します。
using FluentValidation;
using WebApplication1.Models;

namespace WebApplication1.Validators;

/// <summary>
/// 注文クラス用のバリデーター
/// </summary>
public class OrderValidator : AbstractValidator<Order>
{
    public OrderValidator()
    {
        RuleFor(model => model.CustomerName)
            .NotNull()
            .NotEmpty();

        RuleFor(model => model.CustomerEmail)
            .NotNull()
            .NotEmpty()
            .EmailAddress();

        // ネストされた配列データに対するバリデーション
        RuleForEach(model => model.Product)
            .NotNull()
            .NotEmpty()
            .SetValidator(new ProductValidator());
    }
}

.SetValidator()メソッドに、先に用意したProductValidatorを渡すことでOrderモデルにネストされているProductモデルに対して値の検証を行うことができます。
また、RuleForEach()を使用することで配列対して検証を行うことができます。

APIの作成

  • 検証用のMinimal APIを作成します.
  • Program.csに以下の記述を追加します。
// 追加
using WebApplication1.Models;
using FluentValidation;
using FluentValidation.Results;
using WebApplication1.Validators;
// 追加
// Orderのバリデーターを登録
builder.Services.AddScoped<IValidator<Order>, OrderValidator>();
// 追加
// 検証用APIを定義
app.MapPost("/api/orders", async (IValidator<Order> validator, Order order) =>
{
    if (order == null) return Results.BadRequest();

    // リクエストのバリデーション
    ValidationResult validationResult = await validator.ValidateAsync(order);
    if (!validationResult.IsValid)
    {
        return Results.BadRequest(validationResult);
    }

    return Results.Ok(validationResult);
});
  • Program.cs全体は以下のようになります。
// 追加
using WebApplication1.Models;
using FluentValidation;
using FluentValidation.Results;
using WebApplication1.Validators;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

// 追加
// Orderのバリデーターを登録
builder.Services.AddScoped<IValidator<Order>, OrderValidator>();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();

// 追加
// 検証用APIを定義
app.MapPost("/api/orders", async (IValidator<Order> validator, Order order) =>
{
    if (order == null) return Results.BadRequest();

    // リクエストのバリデーション
    ValidationResult validationResult = await validator.ValidateAsync(order);
    if (!validationResult.IsValid)
    {
        return Results.BadRequest(validationResult);
    }

    return Results.Ok(validationResult);
});

app.Run();

動かしてみる

今回はAPIクライアントにPostmanを使用します。
次の内容でリクエストを送信します。 - Methd: POST - URL: https://localhost:7022/api/orders - リクエストボディ

{
  "customerName": "c_name",
  "customerEmail": "test@customer.com",
  "product": [
    {
      "name": "p_name1",
      "price": 1000
    },
    {
      "name": "p_name2",
      "price": 2000
    }
  ]
}


レスポンスは以下の通りです。
検証を通過し、リクエストが処理されていることがわかります。

{
    "isValid": true,
    "errors": [],
    "ruleSetsExecuted": [
        "default"
    ]
}


では、ネストされているProductの値を空にしてリクエストを送ってみます。

{
  "customerName": "c_name",
  "customerEmail": "test@customer.com",
  "product": [
    {
      "name": "",
      "price": 1000
    },
    {
      "name": "p_name2",
      "price": 2000
    }
  ]
}


レスポンスは以下の通りです。

{
    "isValid": false,
    "errors": [
        {
            "propertyName": "Product[0].Name",
            "errorMessage": "'Name' は空であってはなりません。",
            "attemptedValue": "",
            "customState": null,
            "severity": 0,
            "errorCode": "NotEmptyValidator",
            "formattedMessagePlaceholderValues": {
                "PropertyName": "Name",
                "PropertyValue": "",
                "CollectionIndex": 0
            }
        }
    ],
    "ruleSetsExecuted": [
        "default"
    ]
}

リクエスト値の検証ができていることが確認できます。

まとめ

今回はFluentValidationを使用してネストされている値に対して検証を行う方法を紹介しました。 紹介した検証以外にもFluentValidationには様々な組み込みの検証メソッドがあります。また独自にカスタムした検証を行うこともできます。 ASP.NET標準のDataAnnotationでは検証できない場合などにご活用ください。

最後まで読んでいただきありがとうございました。

サンプル github.com

参考

FluentValidation — FluentValidation documentation

.NETで自前で入力チェックを行う際に便利なFluentValidationの使い方 - Qiita

www.alterbooth.com

cloudpointer.tech

www.alterbooth.com