属性を使用したエンティティデータの検証

異なるビジネスタイプに対応するため、それぞれ新しいデータベーステーブルを追加する必要があるケースがあります。例えば、ビジネスタイプ「A」に対しては「Table_A」というテーブルがあり、そのフィールドの検証情報を格納するために「Table_VerifyA」という別のテーブルが存在します。

従来のコードでは、以下のような複雑で冗長なロジックが使われていました:


foreach (var pi in typeof(Table_A).GetProperties()) {
    if (...) { ... }
    else { ... }

    foreach (var p2 in typeof(Table_VerifyA).GetProperties()) {
        if (...) { ... }
        else { ... }
    }
}

このコードは非常に読みづらく、保守性も低いです。そこで、属性(Attribute)を使って検証ロジックを簡素化することにしました。

1. 検証インターフェースの定義

まず、すべての検証属性が実装すべきインターフェースを作成します。


/// <summary>
/// フィールド値に基づいて検証規則を初期化します。
/// </summary>
/// <param name="value">フィールドの値</param>
void InitializeRule(object value);

/// <summary>
/// 実際に検証を行います。
/// </summary>
/// <param name="input">検証対象の入力値</param>
/// <param name="errorMessage">エラーメッセージ</param>
/// <returns>検証結果</returns>
bool Validate(object input, out string errorMessage);

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

以下は最小値チェックを行う属性の例です。


public class MinimumValueValidation : Attribute, IValidation {
    private int _minimum;

    public void InitializeRule(object value) {
        if (value is int intValue && intValue >= 0) {
            _minimum = intValue;
        } else {
            throw new ArgumentException("不正な最小値が指定されました");
        }
    }

    public bool Validate(object input, out string errorMessage) {
        errorMessage = string.Empty;

        if (input is int inputValue) {
            if (inputValue < _minimum) {
                errorMessage = $"値は{_minimum}以上である必要があります。";
                return false;
            }
        } else {
            errorMessage = "数値以外の値が提供されました。";
            return false;
        }

        return true;
    }
}

3. エンティティクラスへの属性適用

次に、エンティティクラスに上記の属性を適用します。


[MinimumValueValidation]
public int SampleField { get; set; }

4. 検証ツールの作成

最後に、属性を使用して自動的に検証を行うユーティリティクラスを作成します。


public class ValidationManager {
    private Dictionary _rules = new Dictionary();

    public ValidationManager LoadRules<T>(T configuration) where T : class {
        var properties = typeof(T).GetProperties();
        foreach (var property in properties) {
            var attribute = property.GetCustomAttributes(typeof(IValidation), false).FirstOrDefault() as IValidation;
            if (attribute != null) {
                var value = property.GetValue(configuration);
                attribute.InitializeRule(value);
                _rules[property.Name] = attribute;
            }
        }
        return this;
    }

    public bool CheckValidity(object entity, out string failureMessage) {
        failureMessage = string.Empty;

        var entityProperties = entity.GetType().GetProperties();
        foreach (var property in entityProperties) {
            if (_rules.TryGetValue(property.Name, out var rule)) {
                var value = property.GetValue(entity);
                if (!rule.Validate(value, out var error)) {
                    failureMessage = $"{property.Name}: {error}";
                    return false;
                }
            }
        }

        return true;
    }
}

5. 実際の使用例

以下は、実際に検証を行う例です。


var validationManager = new ValidationManager();
validationManager.LoadRules(standards);

if (!validationManager.CheckValidity(targetEntity, out var message)) {
    throw new InvalidOperationException(message);
}

タグ: C# attribute Validation

8月28日 08:38 投稿