.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などのフレームワークとシームレスに統合されており、宣言的なバリデーションを実現します。独自の属性を実装する場合も、これらの設計パターンを参考にすることで、一貫性のある拡張が可能です。