本文以一个项目中通用的验证类来举例说明如何使用自定义Attribute来扩展元数据。
在项目中,我们为了保证各个层次之间的松藕合,通常把在各个层次之间传递数据的封装在一个称为实体类的类中,比如ActionFrom
using System;
namespace AttributeTest
{
public class ActionForm
{
private string email = "";
private string password = "";
public string Email
{
get { return this.email; }
set { this.email = value; }
}
public string Password
{
get { return this.password; }
set { this.password = value; }
}
}
}
现在,在使用这些实体类中的数据之前,我们需要对其中的数据进行验证。通常我们会写个静态类,用来提供各种不同的验证方法。比如需要验证Email,验证Password,比如:
using System;
using System.Reflection;
using System.Text.RegularExpressions;
namespace AttributeTest
{
public class Validator
{
public static bool ValidateEmail(string email) {...}
public static bool ValidatePassword(string passwd) {...}
}
}
这样的硬编码混迹于各个层次之间,一旦实体类里某个属性发生变化,就不得不修改各个层次中的相关验证代码。于是,我们想到可以使用一个统一的验证方法用来验证所有的实体类中的属性。
public static bool Validate(string propertyName, string propertyValue, Validator.ValidateType t) {...}
这里,Validator.ValidateType 是Validator中提供的一个枚举。
public enum ValidateType
{
Email,
Password,
Number,
Id
}
这里这个验证方法,的第三个参数使得验证与实体类的耦合密度增加了。我们还是不得不在修改实体类的时候,修改验证方法的调用代码。
现在,我们需要自定义Attribute来扩展实体类的元数据。通过对实体类元数据的描述,我们可以去掉验证方法里的第三个参数。
using System;
namespace AttributeTest
{
[System.AttributeUsage(AttributeTargets.Property)]
public class ValidateAttribute : System.Attribute
{
public ValidateAttribute(ValidateType validateType)
{
this.validateType = validateType;
}
private ValidateType validateType;
public ValidateType ValidateType
{
get{ return this.validateType; }
set{ this.validateType = value; }
}
}
public enum ValidateType
{
Email,
Password,
Number,
Id
}
}
自定义Attribute(特性)必须继承于System.Attribute。还可以通过System.AttributeUsageAttribute特性,控制自定义特性的使用范围(构件),例如,字段、方法。[System.AttributeUsage(AttributeTargets.Property)]限制这个自定义特性只能使用在类的属性上。
现在,我们实现这个验证方法:
using System;
using System.Reflection;
using System.Text.RegularExpressions;
namespace AttributeTest
{
public class Validator
{
public static bool Validate(object validateObject, string validateProperty)
{
System.Type t = validateObject.GetType();
PropertyInfo pi = t.GetProperty(validateProperty);
string validateValue = pi.GetValue(validateObject, null) as string;
if( pi.IsDefined(typeof(ValidateAttribute), true) )
{
object[] atts = pi.GetCustomAttributes(true);
ValidateAttribute vatt = atts[0] as ValidateAttribute;
string strExpr = "";
switch(vatt.ValidateType)
{
case ValidateType.Email:
strExpr = @"^[/w-]+(/.[/w-]+)*@[/w-]+(/.[/w-]+)+$";
break;
case ValidateType.Password:
strExpr = @"/d{6}";
break;
case ValidateType.Number:
strExpr = @"^/d*$";
break;
case ValidateType.Id:
strExpr = @"^/w*$";
break;
default:
return true;
}
Regex validateRegex = new Regex(strExpr);
return validateRegex.IsMatch(validateValue);
}
return true;
}
}
}
该方法需要两个参数,一个是需要验证的实体类的实例,还有一个是需要验证的属性名。当然,我们还需要在实体类上加上我们自定义的特性:
using System;
namespace AttributeTest
{
public class ActionForm
{
private string email = "";
private string password = "";
[Validate(ValidateType.Email)]
public string Email
{
get {return this.email;}
set {this.email = value;}
}
[Validate(ValidateType.Password)]
public string Password
{
get {return this.password;}
set {this.password = value;}
}
}
}
我们通过自定义特性对实体类的元数据进行扩展,指定每个属性需要验证的类型。
现在我们可以这样使用这个验证类:
ActionForm form = new ActionForm();
form.Email = [email protected];
form.Password = "123456";
bool isValidEmail = Validator.Validate(form, "Email");
bool isValidPassword = Validator.Validate(form, "Password");
Console.WriteLine("Email is {0}.", isValidEmail?"valid":"invalid");
Console.WriteLine("Password is {0}.", isValidPassword?"valid":"invalid");
Console.ReadLine();
我们通过抛出自定义异常的方法,将验证扩大到实体类级别的验证:
public static void ValidateProperty(object validateObject, string validateProperty)
{
System.Type t = validateObject.GetType();
PropertyInfo pi = t.GetProperty(validateProperty);
string validateValue = pi.GetValue(validateObject, null) as string;
if( pi.IsDefined(typeof(ValidateAttribute), true) )
{
object[] atts = pi.GetCustomAttributes(true);
ValidateAttribute vatt = atts[0] as ValidateAttribute;
string strExpr = "";
switch(vatt.ValidateType)
{
case ValidateType.Email:
strExpr = @"^[/w-]+(/.[/w-]+)*@[/w-]+(/.[/w-]+)+$";
break;
case ValidateType.Password:
strExpr = @"/d{6}";
break;
case ValidateType.Number:
strExpr = @"^/d*$";
break;
case ValidateType.Id:
strExpr = @"^/w*$";
break;
default:
return;
}
Regex validateRegex = new Regex(strExpr);
if( !validateRegex.IsMatch(validateValue) )
{
throw new ApplicationException(validateProperty + " is invalid.");
}
}
}
public static void Validate(object validateObject)
{
System.Type t = validateObject.GetType();
PropertyInfo[] ps = t.GetProperties();
foreach(PropertyInfo pi in ps)
{
ValidateProperty(validateObject, pi.Name);
}
}
现在验证,只需要这样:
try
{
Validator.Validate(form);
}
catch(Exception ex)
{
Console.WriteLine(ex.Message);
}