使用Attribute校驗對象屬性數據是否合法,attribute校驗
一、前言
說來慚愧,做了幾年ASP.NET最近才有機會使用MVC開發生產項目。項目中新增、編輯表單提交存在大量服務端數據格式校驗,各種if else顯得代碼過於繁瑣,特別是表單數據比較多的時候尤為惡心,正好今天比較閒就寫了一個Demo,統一驗證Model層中的數據格式。在此說明一下,MVC自帶數據檢驗功能同時還能主動通知到前端顯示,個人感覺不太好用的樣子(並沒有深入研究),而且公司項目並沒有使用MVC的輔助方法生成View,不知道MVC的數據校驗功能能否起作用。
二、目標
通過調用對象的Validate方法,校驗對象的屬性是否全部合法,否則返回一條失敗信息。
三、文件結構
![](https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017012017284433.png)
四、實現
1、因為並不是所有的Model都應該有Validate方法,同時Validate方法的邏輯代碼也是唯一的(解析對象屬性上的特性,根據特性描述的規則校驗數據是否合法),因此定義接口IValidate,並為IValidate綁定擴展方法Validate,待校驗Model繼承自IValidate接口(數據解析使用到的特性定義代碼在後面)
![](https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017012017284416.gif)
![]()
public interface IValidate
{
}
View Code
![](https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017012017284416.gif)
![]()
public static class ValidateExtension
{
/// <summary>
/// 校驗對象屬性值是否合法
/// </summary>
/// <param name="obj">待校驗對象</param>
/// <returns></returns>
public static ValidateResult Validate(this IValidate obj)
{
ValidateResult result = new ValidateResult();
PropertyInfo[] infos = obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (PropertyInfo p in infos)
{
//獲取數據校驗特性。
Attribute[] attrs = Attribute.GetCustomAttributes(p, typeof(ValidateAttribute), false);
if (attrs.Length <= 0)
{
continue;
}
//獲取名稱描述特性
CaptionAttribute caption = Attribute.GetCustomAttribute(p, typeof(CaptionAttribute), false) as CaptionAttribute;
object value = p.GetValue(obj);
foreach (Attribute attr in attrs)
{
ValidateAttribute validate = attr as ValidateAttribute;
if (validate == null)
{
continue;
}
result = Validate(validate, value, caption);
if (!result.IsSuccess)
{
return result;
}
}
}
return result;
}
/// <summary>
/// 校驗數據是否合法
/// </summary>
/// <param name="validate">校驗規則</param>
/// <param name="value">待校驗值</param>
/// <param name="caption">描述</param>
/// <returns></returns>
static ValidateResult Validate(ValidateAttribute validate, object value, CaptionAttribute caption)
{
ValidateResult result = new ValidateResult();
if (!validate.Validate(value))
{
result.IsSuccess = false;
if (caption == null)
{
result.ErrorMessage = validate.GetErrorMessage();
}
else
{
result.ErrorMessage = validate.GetErrorMessage(caption.Name);
}
}
return result;
}
}
View Code
2、定義特性,首先我們應該需要一個描述屬性名稱的特性CaptionAttribute,用來描述該屬性的名稱,使得數據異常提示更為友好。
![](https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017012017284416.gif)
![]()
[AttributeUsage(AttributeTargets.Property)]
public class CaptionAttribute : Attribute
{
/// <summary>
/// 構造方法
/// </summary>
/// <param name="name">屬性名稱</param>
public CaptionAttribute(string name)
{
this.Name = name;
}
/// <summary>
/// 屬性名稱
/// </summary>
public string Name { get; set; }
}
View Code
3、定義特性,校驗規則特性應該都有一個Validate方法,校驗當前規則是否可以同過,同時我們在解析規則的時候讀取屬性特性也應該只讀取校驗相關的特性,而不是所有的。因此定義特性父類ValidateAttribute。
![](https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017012017284416.gif)
![]()
[AttributeUsage(AttributeTargets.Property, Inherited = true)]
public abstract class ValidateAttribute : Attribute
{
/// <summary>
/// 校驗不通過提示信息
/// </summary>
protected string ErrorMessage { get; set; }
/// <summary>
/// 校驗數據是否合法
/// </summary>
/// <param name="value">待校驗的值</param>
/// <returns></returns>
public abstract bool Validate(object value);
/// <summary>
/// 獲取檢驗不通過提示信息
/// </summary>
/// <param name="name">字段名稱</param>
/// <returns></returns>
public string GetErrorMessage(string name = "")
{
if (string.IsNullOrEmpty(name))
{
name = "該字段";
}
return string.Format(this.ErrorMessage, name);
}
}
View Code
4、定義特性,創建相關的業務規則特性,為了用戶使用時可自定義提示信息,應重載構造方法(此處提供值范圍規則及正則驗證規則)。
![](https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017012017284416.gif)
![]()
public class RangeAttribute : ValidateAttribute
{
private int min = -1;
private int max = -1;
/// <summary>
/// 構造方法
/// </summary>
/// <param name="min">最小值</param>
/// <param name="max">最大值</param>
public RangeAttribute(int min, int max)
: this(min, max, string.Format("{0}應在{1}到{2}之間", "{0}", min, max))
{
}
/// <summary>
/// 構造方法
/// </summary>
/// <param name="min">最小值</param>
/// <param name="max">最大值</param>
/// <param name="errorMessage">校驗失敗提示信息</param>
public RangeAttribute(int min, int max, string errorMessage)
{
this.min = min;
this.max = max;
this.ErrorMessage = errorMessage;
}
/// <summary>
/// 校驗數據是否合法
/// </summary>
/// <param name="value">待校驗的值</param>
/// <returns></returns>
public override bool Validate(object value)
{
if (value == null)
{
return false;
}
decimal v;
if (!decimal.TryParse(value.ToString(), out v))
{
return false;
}
return v >= min && v <= max;
}
}
View Code
![](https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017012017284416.gif)
![]()
public class RegexAttribute : ValidateAttribute
{
private string regex = null;
/// <summary>
/// 構造方法
/// </summary>
/// <param name="regex">正則</param>
public RegexAttribute(string regex)
: this(regex, "{0}格式錯誤")
{
}
/// <summary>
/// 構造方法
/// </summary>
/// <param name="regex">正則</param>
/// <param name="errorMessage">校驗失敗提示信息</param>
public RegexAttribute(string regex, string errorMessage)
{
this.regex = regex;
this.ErrorMessage = errorMessage;
}
/// <summary>
/// 校驗數據是否合法
/// </summary>
/// <param name="value">待校驗的值</param>
/// <returns></returns>
public override bool Validate(object value)
{
if (value == null)
{
return false;
}
return new Regex(regex).IsMatch(value.ToString());
}
}
View Code
![](https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017012017284416.gif)
![]()
public class RegexExpression
{
/// <summary>
/// 手機號格式正則表達式(開放號段:13|4|5|7|8)
/// </summary>
public const string Mobile = "^1(3|4|5|7|8)\\d{9}$";
/// <summary>
/// 中文字符正則表達式(只允許輸入中文且不包含任何標點符號等)
/// </summary>
public const string Chinese = "^[\u4E00-\u9FFF]+$";
/// <summary>
/// 郵箱正則表達式
/// </summary>
public const string Email = @"^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$";
}
View Code
5、Validate方法返回的結果包含是否成功以及如果失敗了則返回提示信息,因此定義一個校驗結果類(差點就漏了這部分代碼...)。
![](https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017012017284416.gif)
![]()
public class ValidateResult
{
public ValidateResult()
{
this.IsSuccess = true;
}
/// <summary>
/// 構造方法
/// </summary>
/// <param name="isSuccess">是否校驗通過</param>
/// <param name="errorMessage">檢驗不通過提示信息</param>
public ValidateResult(bool isSuccess, string errorMessage)
{
this.IsSuccess = isSuccess;
this.ErrorMessage = errorMessage;
}
/// <summary>
/// 是否校驗通過
/// </summary>
public bool IsSuccess { get; set; }
/// <summary>
/// 檢驗不通過提示信息
/// </summary>
public string ErrorMessage { get; set; }
}
View Code
五、使用
1、定義Model
![](https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017012017284416.gif)
![]()
class User : IValidate
{
[Caption("手機號碼")]
[Regex(RegexExpression.Mobile)]
public string Mobile { get; set; }
[Caption("年齡")]
[Range(1, 120)]
public int Age { get; set; }
[Range(30, 280, "身高數據異常")]
public decimal Height { get; set; }
}
View Code
2、調用驗證及輸出結果
![](https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017012017284495.png)
![](https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017012017284412.png)
![](https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017012017284582.png)