使用Attribute校验对象属性数据是否合法
一、前言
说来惭愧,做了几年ASP.NET最近才有机会使用MVC开发生产项目。项目中新增、编辑表单提交存在大量服务端数据格式校验,各种if else显得代码过于繁琐,特别是表单数据比较多的时候尤为恶心,正好今天比较闲就写了一个Demo,统一验证Model层中的数据格式。在此说明一下,MVC自带数据检验功能同时还能主动通知到前端显示,个人感觉不太好用的样子(并没有深入研究),而且公司项目并没有使用MVC的辅助方法生成View,不知道MVC的数据校验功能能否起作用。
二、目标
通过调用对象的Validate方法,校验对象的属性是否全部合法,否则返回一条失败信息。
三、文件结构
四、实现
1、因为并不是所有的Model都应该有Validate方法,同时Validate方法的逻辑代码也是唯一的(解析对象属性上的特性,根据特性描述的规则校验数据是否合法),因此定义接口IValidate,并为IValidate绑定扩展方法Validate,待校验Model继承自IValidate接口(数据解析使用到的特性定义代码在后面)
public interface IValidate { }
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; } }
2、定义特性,首先我们应该需要一个描述属性名称的特性CaptionAttribute,用来描述该属性的名称,使得数据异常提示更为友好。
[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; } }
3、定义特性,校验规则特性应该都有一个Validate方法,校验当前规则是否可以同过,同时我们在解析规则的时候读取属性特性也应该只读取校验相关的特性,而不是所有的。因此定义特性父类ValidateAttribute。
[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); } }
4、定义特性,创建相关的业务规则特性,为了用户使用时可自定义提示信息,应重载构造方法(此处提供值范围规则及正则验证规则)。
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; } }
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()); } }
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})+$"; }
5、Validate方法返回的结果包含是否成功以及如果失败了则返回提示信息,因此定义一个校验结果类(差点就漏了这部分代码...)。
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; } }
五、使用
1、定义Model
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; } }
2、调用验证及输出结果