【C#】条件付きや関連項目の入力チェックをスマートに書くテクニック

システム開発
スポンサーリンク
スポンサーリンク

C#のデータバリデーションを実装するとき、IValidatableObject インターフェースを活用すると、モデル単位で柔軟な検証ロジックを組み込めます。特に、DataAnnotations の属性だけでは実装できないカスタムバリデーション を簡単に追加できるのが強みです。

本記事では、IValidatableObject の基本的な使い方から、実践的な活用方法まで詳しく解説します。例えば、複数のプロパティの値を組み合わせたバリデーション や、データベースの値と照らし合わせた検証 など、実務で役立つ知識を習得できます。


IValidatableObjectとは?

ASP.NET MVCやASP.NET Coreでモデルバリデーションを柔軟に行いたい場合に使えるのが、IValidatableObjectインターフェイスです。標準の属性ベースのバリデーションでは難しい複雑な条件や、複数プロパティをまたいだ整合性チェックなどに活用できます。ここでは、IValidatableObjectの基本的な仕組みと、実装例を紹介します。

IValidatableObjectの概要と仕組み

IValidatableObjectは、ASP.NET MVCやASP.NET Coreのモデルクラスに追加することで、カスタムなバリデーションロジックを記述できるインターフェイスです。このインターフェイスは、System.ComponentModel.DataAnnotations名前空間に含まれており、Validateメソッドを通じて、任意のバリデーションエラーを返すことが可能です。

ポイント

  • 属性(アノテーション)では難しい、複数項目の整合性チェックが可能
  • モデル単位で柔軟なバリデーション処理が実装できる
  • サーバーサイドで実行されるため、セキュリティ的にも有効

ASP.NET Coreの例

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

public class ReservationModel : IValidatableObject
{
    public DateTime StartDate { get; set; }
    public DateTime EndDate { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (EndDate <= StartDate)
        {
            yield return new ValidationResult(
                "終了日は開始日より後である必要があります。",
                new[] { nameof(EndDate) }
            );
        }
    }
}

✅ 解説

  • Validateメソッドの中で、任意のロジックに基づいてバリデーションを実行します。
  • ValidationResultにエラーメッセージと対象プロパティ名を指定します。
  • yield return を使うことで複数のエラーを返すことも可能です。

✅ 使用時の注意点

  • クライアントサイドのバリデーションには反映されないため、JavaScriptと併用が必要なケースもあります。
  • ビジネスロジックと密結合しすぎないよう注意が必要です。

基本的な使い方

IValidatableObjectを使えば、単純なアノテーションだけでは表現できないバリデーションロジックを、モデルクラス内で柔軟に実装できます。ここでは、具体的な使い方とその実行タイミング、フォームバリデーションとの連携方法について解説します。

モデルにValidateメソッドを実装する

IValidatableObjectを使うには、対象のモデルクラスにこのインターフェイスを実装し、Validateメソッドを定義します。メソッド内ではValidationResultyield returnで返すことで、バリデーションエラーを通知します。

ASP.NET MVCの例

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

public class UserModel : IValidatableObject
{
    [Required]
    public string Password { get; set; }

    [Required]
    public string ConfirmPassword { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (Password != ConfirmPassword)
        {
            yield return new ValidationResult(
                "パスワードと確認用パスワードが一致しません。",
                new[] { nameof(ConfirmPassword) }
            );
        }
    }
}

✅ ポイント

  • 複数プロパティ(ここでは PasswordConfirmPassword)の整合性を検証できる
  • ValidationContext を通じてサービスにアクセスすることも可能(DI連携)
  • ModelState.IsValid により、コントローラーでバリデーション結果を確認できる

コントローラーでの確認例

[HttpPost]
public IActionResult Register(UserModel model)
{
    if (!ModelState.IsValid)
    {
        return View(model); // バリデーションエラー時は再表示
    }

    // 登録処理
    return RedirectToAction("Success");
}

✅ 補足

  • ModelState.IsValid は、Validate メソッドで返された ValidationResult を含む全てのバリデーション結果を評価します。
  • サーバーサイドでのチェックになるため、フロントエンドと重複させることで安全性を高められます。

IValidatableObject を活用した実践的なバリデーション

IValidatableObjectは単なる基本的なバリデーションだけでなく、実践的で現実的なユースケースに対しても柔軟に対応できます。特に、データベースとの整合性チェックやビジネスロジックに基づいた条件分岐、さらには依存関係の注入によるサービス連携などが挙げられます。

外部サービスとの連携による重複チェック

ユーザー登録時に、既に同じメールアドレスが存在していないかどうかをチェックしたい場合、リポジトリやサービスをValidationContext経由で取得して利用することができます。

ASP.NET Coreの例

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Microsoft.Extensions.DependencyInjection;

public class RegisterModel : IValidatableObject
{
    [Required]
    [EmailAddress]
    public string Email { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        var userService = validationContext.GetService<IUserService>();

        if (userService.IsEmailRegistered(Email))
        {
            yield return new ValidationResult(
                "このメールアドレスはすでに登録されています。",
                new[] { nameof(Email) }
            );
        }
    }
}

✅ ポイント

  • ValidationContext.GetService<T>() を使って、DIされたサービスにアクセス可能
  • DBとの重複チェックなど、現実の業務要件に即したバリデーションが実装できる
  • サーバー側のみで実行されるため、セキュリティリスクの低減にもつながる

✅ 注意点

  • Validate メソッド内でのサービス取得はDIが前提となるため、Startup.csなどでの適切なサービス登録が必須です。
  • 過度に複雑なロジックを実装すると、テスト性や保守性が低下するため、責務の分離を意識しましょう。

サービスインターフェースの例

public interface IUserService
{
    bool IsEmailRegistered(string email);
}

Startup.cs(ASP.NET Core)の例

services.AddScoped<IUserService, UserService>();

このように、IValidatableObjectはモデル単位でのバリデーションを超えて、より高度な業務要件に対応できる強力な仕組みです。実装次第で多彩なバリデーションを実現できます。


IValidatableObject のメリット・デメリット

IValidatableObjectは、ASP.NET系アプリケーションにおいて柔軟なバリデーションを実現する便利なインターフェイスです。ただし、万能というわけではなく、用途や設計方針によって向き不向きがあります。ここでは、IValidatableObjectの利点と注意すべき点について整理します。

メリット

柔軟なバリデーションロジックの実装が可能

単一プロパティではなく、複数プロパティにまたがる整合性チェックや条件付きチェックを行うことができます。例えば、「終了日が開始日より前でないか」や、「パスワードと確認用パスワードが一致するか」といったバリデーションは、アノテーションでは困難です。

依存関係を活用した高度なチェックも可能

ValidationContextからDIされたサービスにアクセスできるため、データベースとの重複チェックやビジネスルールの検証など、現実のアプリケーション要件に対応できます。

モデルごとの一貫したバリデーションができる

IValidatableObjectはモデルクラス自身にロジックを持たせる設計のため、関連するバリデーションが1か所にまとまるというメリットもあります。

デメリット

⚠️ クライアントサイドバリデーションに非対応

IValidatableObjectはサーバー側でのみ評価されるため、JavaScriptなどによる即時フィードバックが行えない点に注意が必要です。必要に応じて、クライアント側のバリデーションも別途実装しましょう。

⚠️ モデルとロジックの密結合化のリスク

モデルにバリデーションロジックを直接記述するため、関心の分離(Separation of Concerns)が弱くなりがちです。規模が大きくなると、テストや再利用性に影響することがあります。

⚠️ 複雑なロジックは保守が難しくなる可能性

Validateメソッドに複雑な分岐や外部依存が増えると、可読性や保守性が低下します。適度にサービスやヘルパーへ委譲する設計が望まれます。

このように、IValidatableObjectは非常に強力な機能を持ちつつも、設計とバランスを意識して使うことが重要です。特に業務システムなどでは、「どのバリデーションロジックをどこに書くか」という設計判断がシステムの質を左右します。


IValidatableObject を使うべきケース

IValidatableObjectは強力なバリデーション手段ですが、すべてのケースで使うべきというわけではありません。ここでは、アノテーションだけでは不十分な場面や、IValidatableObjectが真価を発揮する代表的なユースケースをご紹介します。

複数プロパティにまたがる整合性チェックが必要な場合

例:パスワードと確認用パスワードの一致チェック

個別のプロパティではなく、2つの入力値の関係を検証するケースに最適です。

例:開始日と終了日の前後関係

「終了日は開始日より後であるべき」というルールは、Compare属性では不十分なため、IValidatableObjectの活用が有効です。

ビジネスロジックや外部依存を含むバリデーションが必要な場合

例:メールアドレスの重複チェック(DB連携)

リポジトリやサービス経由でデータベースにアクセスし、すでに存在する値かどうかを検証するような処理は、IValidatableObjectでしか実現できません。

例:年齢制限(現在日時を基に動的に判定)

「18歳以上でなければ登録できない」などの動的な条件付きルールにも向いています。

高い柔軟性が求められる業務要件への対応が必要な場合

例:条件付きバリデーション

「チェックボックスがオンのときのみ、別のフィールドの入力が必須になる」など、状況に応じてルールが変わるケースでは、属性ベースよりも柔軟に対応できます。

例:複雑な業務ルールに基づいた検証

「営業日でなければならない」「在庫数と発注数の整合性」など、単純な比較ではなくビジネスルールに準拠した検証が必要な場合に有効です。

IValidatableObject を使う判断基準

  • 属性(アノテーション)だけでは表現が難しいか?
  • 複数のプロパティ間で整合性を確認する必要があるか?
  • 外部サービスやDBとの連携が必要か?
  • ロジックの柔軟性や拡張性が求められるか?

このような観点で判断し、IValidatableObjectを適切に活用することで、堅牢で保守性の高いバリデーション設計が実現できます。


まとめ

IValidatableObjectは、ASP.NET MVCやASP.NET Coreにおいて、柔軟で実践的なバリデーション処理を可能にするインターフェイスです。属性ベースでは難しい複雑な条件や、複数のフィールドにまたがる整合性チェック、さらには外部サービスとの連携による業務ロジックの検証まで幅広く対応できます。

✅ 本記事の振り返り

  • IValidatableObjectを使うことで、モデル単位でバリデーションを実装できる
  • Validateメソッド内では、ValidationResultを使って自由にエラーメッセージを返却可能
  • クライアントバリデーションには非対応なため、必要に応じてJSとの併用が望ましい
  • サービスやデータベースを活用した現実的なバリデーションに最適

ただし、すべてのケースでIValidatableObjectを使用する必要はなく、シンプルな検証にはアノテーションを優先し、複雑な要件時に限定して導入するのが効果的です。適切なユースケースを見極めることで、バリデーション設計の質が大きく向上します。

実際の開発現場でも、IValidatableObjectを上手に取り入れることで、堅牢でメンテナンス性の高いシステムが実現できるでしょう。

システム開発
スポンサーリンク
tobotoboをフォローする

コメント

タイトルとURLをコピーしました