在三层模型中,我们通常会有一个Model层,用来和其它层传递数据。在给Model层赋值时,免不了要对数据进行某种规则的验证,比如需要验证某个字段是否允许为空,或者某个字段的值必须在一个范围内等等。当然我们可以在给字段赋值先验证,验证合格后才赋值给字段。这里我们主要讲怎么利用AOP实现Model层自已验证。
首先,我们需要使用Attribute特性来声明各种规则。如下:
[FieldTypeDecimal(0,100)]
[NonEmpty]
public decimal Price
{
get;
set;
}
上面的示例中,FieldTypeDecimal与NonEmpty就是两个自定义特性,NonEmpty用来标识Price不能为空,FieldTypeDecimal用来标识Price的赋值范围,[FieldTypeDecimal(0,100)]就表示FieldTypeDecimal的值只能大于等于0或小于等于1000。
定义分别如下:
FieldTypeDecimal /// <summary >
/// 验证属性的值是否大于或等于最小值,或者小于或等于最大值。
/// </summary >
public class FieldTypeDecimal : Attribute
{
/// <summary >
/// 是否验证最小值
/// </summary >
public bool IsValidMinValue
{
get;
set;
}
/// <summary >
/// 是否验证最大值
/// </summary >
public bool IsValidMaxValue
{
get;
set;
}
private int minValue;
public int MinValue
{
get { return minValue; }
set { minValue = value; }
}
private int maxValue;
public int MaxValue
{
get { return maxValue; }
set { maxValue = value; }
}
public FieldTypeDecimal(int minValue)
{
this.IsValidMinValue = true;
this.minValue = minValue;
}
public FieldTypeDecimal(int minValue, int maxValue)
{
this.IsValidMinValue = true;
this.IsValidMaxValue = true;
this.minValue = minValue;
this.maxValue = maxValue;
}
}
NonEmpty /// <summary >
/// 不可以为空
/// </summary >
public class NonEmpty : Attribute
{
public NonEmpty(){ }
}
注意:这两个类一定要继承Attribute。
然后将两个特性应用于某个Model类中,如下:
[ValidProxyAttribute]
public class Sale_QuotationSheetDetail : ContextBoundObject
{
[FieldTypeDecimal(0,100)]
[NonEmpty]
public decimal Price
{
get;
set;
}
}
注意上面Sale_QuotationSheetDetail 类一定要继承ContextBoundObject,这个非常重要。
注意看上面的ValidProxyAttribute类,我们也有应用了一个自定义特性ValidProxyAttribute,这个特性如下:
/// <summary >
/// 字段需要验证的标识
/// </summary >
public class ValidProxyAttribute : ProxyAttribute
{
public override MarshalByRefObject CreateInstance(Type serverType)
{
ValidProxy validProxy = new ValidProxy(serverType);
return validProxy.GetTransparentProxy() as MarshalByRefObject;
}
}
实现本文的功能最重要的部分来了,ValidProxy 类。
ValidProxy 类其实就是RealProxy的一个子类,我们可以重写Invoke方法,来拦截Price属性的赋值方法"Set_Price"(任何类的属性的set操作,其实都是一个Set_XXX的方法。),来验证值是否满足规则。
public class ValidProxy : RealProxy
{
public ValidProxy(Type serverType)
: base(serverType)
{ }
public override IMessage Invoke(IMessage msg)
{
if (msg is IConstructionCallMessage)
{
IConstructionCallMessage constructCallMsg = msg as IConstructionCallMessage;
IConstructionReturnMessage constructionReturnMessage = InitializeServerObject(constructCallMsg);
RealProxy.SetStubData(this, constructionReturnMessage.ReturnValue);
return constructionReturnMessage;
}
else if (msg is IMethodCallMessage)
{
IMethodCallMessage callMsg = msg as IMethodCallMessage;
object[] args = callMsg.Args;
IMessage message;
try
{
object baseObject = GetUnwrappedServer();
if (callMsg.MethodName.StartsWith("set_") && args.Length == 1)
{
string memberName = callMsg.MethodName.
substring(4);
MemberInfo info = baseObject.GetType().GetMember(memberName)[0];
string columnName = string.Empty;
if (info.IsDefined(typeof(IModel.FieldName), false))
{
IModel.FieldName fieldName = (IModel.FieldName)info.GetCustomAttributes(typeof(IModel.FieldName), false)[0];
columnName = fieldName.Name;
}
ValidObserver validObserver = new ValidObserver(columnName);
IModel.IMessage mes;
if (info.IsDefined(typeof(IModel.NonEmpty), false))
{
mes = (IModel.IMessage)info.GetCustomAttributes(typeof(IModel.NonEmpty), false)[0];
validObserver.AddMessage(mes, args[0]);
}
if (info.IsDefined(typeof(IModel.FieldTypeDecimal), false))
{
mes = (IModel.IMessage)info.GetCustomAttributes(typeof(IModel.FieldTypeDecimal), false)[0];
validObserver.AddMessage(mes, args[0]);
}
validObserver.Valid();
}
object o = callMsg.MethodBase.Invoke(baseObject, args);
message = new ReturnMessage(o, args, callMsg.InArgCount, callMsg.LogicalCallContext, callMsg);
}
catch (Exception e)
{
message = new ReturnMessage(e, callMsg);
}
return message;
}
else
return msg;
}
}
上面代码中的:
string memberName = callMsg.MethodName.substring(4);
MemberInfo info = baseObject.GetType().GetMember(memberName)[0];
string columnName = string.Empty;
if (info.IsDefined(typeof(IModel.FieldName), false))
{
IModel.FieldName fieldName = (IModel.FieldName)info.GetCustomAttributes(typeof(IModel.FieldName), false)[0];
columnName = fieldName.Name;
}
ValidObserver validObserver = new ValidObserver(columnName);
IModel.IMessage mes;
if (info.IsDefined(typeof(IModel.NonEmpty), false))
{
mes = (IModel.IMessage)info.GetCustomAttributes(typeof(IModel.NonEmpty), false)[0];
validObserver.AddMessage(mes, args[0]);
}
if (info.IsDefined(typeof(IModel.FieldTypeDecimal), false))
{
mes = (IModel.IMessage)info.GetCustomAttributes(typeof(IModel.FieldTypeDecimal), false)[0];
validObserver.AddMessage(mes, args[0]);
}
validObserver.Valid();
部分是我的规则验证部分,大家的验证应该都会不同,这里只是给大家一个参考。
解释一下上面几个关键方法或函数:
msg is IConstructionCallMessage 如果当前执行的是构造函数
msg is IMethodCallMessage 如果当前执行的自定义方法
object[] args = callMsg.Args; callMsg.Args表示属性的值
callMsg.MethodName 当前正在执行的方法的名称,(即set_XXXX方法)
GetUnwrappedServer() 返回当前方法所属的对象(即实现中的Sale_QuotationSheetDetail类)
info.GetCustomAttributes 为获取类的自定义特性,可以参考示例:
Asp.net中如何查找类属性的自定义Attribute这样,当我们在给Sale_QuotationSheetDetail的Price属性赋值时,都会自动执行ValidProxy 类的Invoke方法,大家可以调试下,以增加理解。