Web 開發中表單驗證不可或缺。ASP.NET MVC 早期無內建驗證屬性 (DataAnnotations 尚未普及),本文從手動 if (int.TryParse...) 起步,逐步引入 Attribute、反射、ModelBinder 概念,打造輕量驗證框架雛形。
if (!int.TryParse(Request.Form["age"], out int age)) { /* 錯誤 */ }
if (age < 0 || age > 150) { /* 錯誤 */ }
// 每個欄位重複、易漏、難維護
[AttributeUsage(AttributeTargets.Property)]
public class RangeAttribute : Attribute
{
public int Min { get; set; }
public int Max { get; set; }
public string Message { get; set; }
public RangeAttribute(int min, int max, string message)
{
Min = min; Max = max; Message = message;
}
}
[AttributeUsage(AttributeTargets.Property)]
public class RequiredAttribute : Attribute
{
public string Message { get; set; }
public RequiredAttribute(string message) { Message = message; }
}
public class Student
{
[Range(0, 100, "分數需在 0-100")]
public int Score { get; set; }
[Required("姓名必填")]
public string Name { get; set; }
}
public class ValidatableBase
{
public bool Validate(out List errors)
{
errors = new List();
foreach (var prop in GetType().GetProperties())
{
var val = prop.GetValue(this);
// Range
var rangeAttr = prop.GetCustomAttributes(typeof(RangeAttribute), true).FirstOrDefault() as RangeAttribute;
if (rangeAttr != null && val is int iv)
{
if (iv < rangeAttr.Min || iv > rangeAttr.Max)
errors.Add(rangeAttr.Message);
}
// Required
var reqAttr = prop.GetCustomAttributes(typeof(RequiredAttribute), true).FirstOrDefault() as RequiredAttribute;
if (reqAttr != null)
{
if (val == null || (val is string s && string.IsNullOrWhiteSpace(s)))
errors.Add(reqAttr.Message);
}
}
return errors.Count == 0;
}
}
public class Student : ValidatableBase { /* 屬性定義 */ }
ASP.NET MVC DefaultModelBinder 綁定完成後,可呼叫 model.Validate(out errors),將錯誤加入 ModelState,View 自動顯示 ValidationMessageFor。
[TestMethod]
public void Student_Validate_Fail()
{
var s = new Student { Score = -1, Name = "" };
var ok = s.Validate(out var errors);
Assert.IsFalse(ok);
Assert.AreEqual(2, errors.Count);
}
下篇將解決 Validate 方法膨脹、驗證邏輯硬編碼問題:引入 IValidator 介面,讓屬性自我驗證,實現開放封閉原則。