所有驗證屬性繼承自 System.ComponentModel.DataAnnotations.ValidationAttribute,核心成員:
public abstract class ValidationAttribute : Attribute
{
public string ErrorMessage { get; set; }
public string ErrorMessageResourceName { get; set; }
public Type ErrorMessageResourceType { get; set; }
protected string ErrorMessageString { get; } // 最終錯誤訊息文字
public virtual string FormatErrorMessage(string name); // 格式化訊息,name 為顯示名稱
public virtual bool IsValid(object value); // 簡單驗證入口
protected virtual ValidationResult IsValid(object value, ValidationContext validationContext); // 完整驗證入口
public void Validate(object value, string name); // 拋出驗證例外
public ValidationResult GetValidationResult(object value, ValidationContext validationContext); // 統一入口
}
ErrorMessageResourceName + ErrorMessageResourceType,忽略 ErrorMessage。"{0}必須在{1}和{2}之間"。string.Format(CultureInfo.CurrentCulture, ErrorMessageString, name),name 為 DisplayName。預設僅支援 {0},需覆寫以支援多參數或具名預留位置。兩個 IsValid 互相呼叫:
// 簡單版呼叫完整版
public virtual bool IsValid(object value)
=> IsValid(value, null) == ValidationResult.Success;
// 完整版呼叫簡單版(預設)
protected virtual ValidationResult IsValid(object value, ValidationContext context)
=> IsValid(value) ? ValidationResult.Success : new ValidationResult(FormatErrorMessage(context?.DisplayName));
結果:若衍生類別兩者皆未覆寫,將陷入無限遞迴。框架內部偵測此情況並拋出 NotImplementedException:「此類尚未實現 IsValid(object value)。首選入口點是 GetValidationResult(),並且類應重寫 IsValid(object value, ValidationContext context)。」
ValidationContext 的單值驗證。ValidationContext(如跨屬性比較、資料庫查詢)。ValidationContext(若未提供)。
2. 呼叫 IsValid(value, context)。
3. 若回傳 ValidationResult.Success 直接回傳;否則以 ErrorMessage 或格式化訊息建立新 ValidationResult 回傳。
public void Validate(object value, string name)
{
var result = GetValidationResult(value, new ValidationContext(value) { DisplayName = name });
if (result != ValidationResult.Success)
throw new ValidationException(result.ErrorMessage);
}
適用於需立即中斷流程的場合(如手動驗證)。
public class AgeRangeAttribute : ValidationAttribute
{
public int MinAge { get; }
public int MaxAge { get; }
public AgeRangeAttribute(int min, int max) { MinAge = min; MaxAge = max; }
// 覆寫完整版,需計算年齡
protected override ValidationResult IsValid(object value, ValidationContext context)
{
if (value is DateTime birthDate)
{
int age = DateTime.Today.Year - birthDate.Year;
if (birthDate > DateTime.Today.AddYears(-age)) age--;
if (age >= MinAge && age <= MaxAge) return ValidationResult.Success;
}
return new ValidationResult(FormatErrorMessage(context.DisplayName));
}
// 覆寫格式化支援多參數
public override string FormatErrorMessage(string name)
=> string.Format(ErrorMessageString, name, MinAge, MaxAge);
}
ValidationAttribute 以雙 IsValid 入口提供彈性,框架透過 NotImplementedException 強制開發者實作至少一個。錯誤訊息支援資源檔與格式化模板,FormatErrorMessage 為客製化關鍵。掌握此機制可建構強健、可維護的自訂驗證屬性。