カスタム属性とリフレクションを用いた.NETでのバリデーション実装

.NETでは、カスタム属性(Attribute)とリフレクションを組み合わせることで、柔軟なデータ検証(バリデーション)を実現できます。属性は、コードにメタデータを付与するための仕組みであり、実行時にリフレクションを通じてこれらの情報を取得・活用できます。

以下、カスタム属性を定義し、それを利用したバリデーションシステムの構築手順を解説します。

1. 基底となる検証属性の作成

まず、すべての検証属性の基底となる抽象クラス(またはインターフェース)を定義します。エラーメッセージを保持するプロパティを持たせます。

using System;

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public abstract class BaseValidationAttribute : Attribute
{
    public string ErrorMessage { get; set; }

    protected BaseValidationAttribute(string errorMessage)
    {
        ErrorMessage = errorMessage;
    }
}

2. 具体的な検証属性の実装

次に、個別の検証ルールを定義します。ここでは必須チェックと範囲チェックを例示します。

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class NonNullAttribute : BaseValidationAttribute
{
    public NonNullAttribute() : base("このフィールドは必須です。") { }
}

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class NumericRangeAttribute : BaseValidationAttribute
{
    public int LowerBound { get; set; }
    public int UpperBound { get; set; }

    public NumericRangeAttribute(int min, int max, string errorMessage = "値は {0} から {1} の範囲内である必要があります。")
        : base(errorMessage)
    {
        LowerBound = min;
        UpperBound = max;
    }
}

3. モデルクラスへの属性適用

作成した属性をデータモデルのプロパティに付与します。

public class UserProfile
{
    [NonNull]
    public string DisplayName { get; set; }

    [NumericRange(18, 99, ErrorMessage = "年齢は18歳から99歳の間でなければなりません。")]
    public int Age { get; set; }
}

4. 検証ロジックの実装(リフレクション利用)

リフレクションを用いて、オブジェクトのプロパティに付与された属性を読み取り、検証を実行する汎用的なバリデータークラスを作成します。

using System;
using System.Reflection;

public static class DataChecker
{
    public static bool IsValid<T>(T target, out string failureMessage)
    {
        failureMessage = null;
        Type type = typeof(T);
        PropertyInfo[] props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);

        foreach (var prop in props)
        {
            object[] attributes = prop.GetCustomAttributes(typeof(BaseValidationAttribute), true);
            if (attributes.Length == 0) continue;

            object value = prop.GetValue(target);

            foreach (var attr in attributes)
            {
                if (attr is NonNullAttribute nonNull)
                {
                    if (value == null || (value is string str && string.IsNullOrWhiteSpace(str)))
                    {
                        failureMessage = nonNull.ErrorMessage;
                        return false;
                    }
                }
                else if (attr is NumericRangeAttribute range)
                {
                    if (value is IComparable comparable)
                    {
                        if (comparable.CompareTo(range.LowerBound) < 0 || comparable.CompareTo(range.UpperBound) > 0)
                        {
                            failureMessage = string.Format(range.ErrorMessage, range.LowerBound, range.UpperBound);
                            return false;
                        }
                    }
                }
                // 必要に応じて他の属性の処理を追加
            }
        }
        return true;
    }
}

5. 検証の実行例

作成したバリデーターを使用して、モデルオブジェクトの妥当性をチェックします。

public class Program
{
    public static void Main(string[] args)
    {
        var user = new UserProfile { DisplayName = null, Age = 17 };
        string error;

        if (!DataChecker.IsValid(user, out error))
        {
            Console.WriteLine(error);  // 出力例: "このフィールドは必須です。"(最初のエラーで停止)
        }
    }
}

拡張:既存の検証属性の活用

.NET Frameworkや.NET Coreには、System.ComponentModel.DataAnnotations名前空間に標準の検証属性が多数用意されています。これらを直接利用することで、より手軽にバリデーションを実装できます。

代表的な属性を以下に示します:

属性目的
[Required]値がnullでないこと(文字列の場合は空白ではないこと)を検証
[StringLength]文字列の最大長を制限
[Range]数値が指定範囲内にあることを検証
[RegularExpression]正規表現によるパターンマッチング
[EmailAddress]メールアドレス形式の検証
[Url]URL形式の検証
[Phone]電話番号形式の検証
[CreditCard]クレジットカード番号の形式検証
[Compare]2つのプロパティ値の一致確認(例:パスワード確認)
[DataType]データ型の指定(検証は行わず、UIヒントとして利用)
[CustomValidation]カスタム検証ロジックの指定
[EnumDataType]値が指定列挙型の有効メンバーであることを検証

これらの標準属性は、ASP.NET CoreやEntity Frameworkなどのフレームワークとシームレスに統合されており、宣言的なバリデーションを実現します。独自の属性を実装する場合も、これらの設計パターンを参考にすることで、一貫性のある拡張が可能です。

タグ: C# DotNet Reflection custom-attributes data-validation

5月31日 08:03 投稿