處理 XML 時常反序列化為實體物件操作。傳統驗證寫法充滿 if-else 判斷,造成語法噪音。利用 C# Attribute 可建立聲明式驗證框架,大幅簡化程式碼。
[Flags]
public enum ValidateType
{
IsEmpty = 0x0001,
MinLength = 0x0002,
MaxLength = 0x0004,
IsNumber = 0x0008,
IsDateTime = 0x0010,
IsDecimal = 0x0020,
IsInCustomArray = 0x0040,
IsTelphone = 0x0080,
IsMobile = 0x0100
}
[AttributeUsage(AttributeTargets.All)]
public class ValidateAttribute : Attribute
{
public ValidateType ValidateType { get; private set; }
public int MinLength { get; set; }
public int MaxLength { get; set; }
public string[] CustomArray { get; set; }
public ValidateAttribute(ValidateType validateType)
{
this.ValidateType = validateType;
}
}
public class OrderRequest
{
[Validate(ValidateType.IsEmpty)]
public string OrderNo { get; set; }
[Validate(ValidateType.IsEmpty | ValidateType.MaxLength, MaxLength = 50)]
public string CommodityName { get; set; }
[Validate(ValidateType.IsEmpty | ValidateType.IsNumber)]
public string CommodityAmount { get; set; }
[Validate(ValidateType.IsEmpty | ValidateType.IsDecimal)]
public string CommodityWeight { get; set; }
[Validate(ValidateType.IsEmpty | ValidateType.IsDecimal)]
public string CommodityValue { get; set; }
[Validate(ValidateType.IsEmpty | ValidateType.IsDateTime)]
public string HopeArriveTime { get; set; }
[Validate(ValidateType.IsEmpty | ValidateType.IsInCustomArray, CustomArray = new[] { "現結", "到付", "月結" })]
public string PayMent { get; set; }
[Validate(ValidateType.MaxLength, MaxLength = 256)]
public string Remark { get; set; }
}
透過反射掃描屬性上的 ValidateAttribute,依 ValidateType 執行對應邏輯(正則表達式、長度檢查、自訂陣列比對等),回傳第一個錯誤訊息或 String.Empty 表示通過。
public static string GetValidateResult(object entityObject)
{
if (entityObject == null) throw new ArgumentNullException("entityObject");
Type type = entityObject.GetType();
PropertyInfo[] properties = type.GetProperties();
string validateResult = string.Empty;
foreach (PropertyInfo prop in properties)
{
object[] attrs = prop.GetCustomAttributes(typeof(ValidateAttribute), true);
if (attrs.Length == 0) continue;
object value = prop.GetValue(entityObject, null);
foreach (ValidateAttribute attr in attrs)
{
switch (attr.ValidateType)
{
case ValidateType.IsEmpty:
if (value == null || value.ToString().Length == 0)
validateResult = string.Format("元素 {0} 不能為空", prop.Name);
break;
case ValidateType.MaxLength:
if (value != null && value.ToString().Length > attr.MaxLength)
validateResult = string.Format("元素 {0} 長度不能大於 {1}", prop.Name, attr.MaxLength);
break;
case ValidateType.IsNumber:
if (value != null && !Regex.IsMatch(value.ToString(), @"^\d+$"))
validateResult = string.Format("元素 {0} 必須為數值", prop.Name);
break;
case ValidateType.IsDecimal:
if (value != null && !Regex.IsMatch(value.ToString(), @"^\d+[.]?\d+$"))
validateResult = string.Format("元素 {0} 必須為金額格式", prop.Name);
break;
case ValidateType.IsDateTime:
if (value != null && !Regex.IsMatch(value.ToString(), @"(\d{2,4})[-/]?([0]?[1-9]|[1][12])[-/]?([0][1-9]|[12]\d|[3][01])\s*([01]\d|[2][0-4])?[:]?([012345]?\d)?[:]?([012345]?\d)?"))
validateResult = string.Format("元素 {0} 日期格式錯誤", prop.Name);
break;
case ValidateType.IsInCustomArray:
if (attr.CustomArray == null || attr.CustomArray.Length == 0)
validateResult = string.Format("系統錯誤:元素 {0} 自訂資料來源為空", prop.Name);
else if (!Array.Exists(attr.CustomArray, s => s == value.ToString()))
validateResult = string.Format("元素 {0} 值必須為 {1} 之一", prop.Name, string.Join(",", attr.CustomArray));
break;
// 組合旗標處理略...
}
if (!string.IsNullOrEmpty(validateResult)) break;
}
if (!string.IsNullOrEmpty(validateResult)) break;
}
return validateResult;
}
OrderRequest order = XmlAntiSerialization.Deserialize(typeof(OrderRequest), xmlString) as OrderRequest;
string msg = AttributeHandle.GetValidateResult(order);
if (!string.IsNullOrEmpty(msg)) return msg;
// 繼續業務邏輯...
一行程式碼完成整個實體驗證,頁面清爽無比。