之前写过一篇文章《ASP.NET MVC中的验证》,唯一的遗憾就是在使用Data Annotation Validators方式验证的时候,如果数据库是Entityframework等自动生成的文件,就没有办法使用扩展属性标记进行标记。现在已经开始有了一些其它的Asp.net MVC 验证框架,使用上跟Data Annotation Validators差不太多,但是普遍有这样的问题,如果数据库是Entityframework生成的edm文件,没有办法进行扩展属性标记。
今天在网上发现了另外一个 Asp.net MVC 验证框架---xVal框架,使用上跟Data Annotation Validators非常接近,也有类似的问题。
简单介绍下,xVal是一个开源的asp.net mvc验证框架,有关它的介绍,可以参考:《xVal - a validation framework for ASP.NET MVC》
xVal使用了MS-PL的开源协议 ,也就是说,它允许用户看、修改和分发源代码,而不论出自商业用途还是非商业用途,类似BSD许可证。
xVal可以通过IRulesProvider接口,通过这个接口可以进行扩展,很明显,它只扩展了Castle框架跟NHibernate框架,通过如下两个程序集就可以看出来:
xVal.RulesProviders.CastleValidator.dll
xVal.RulesProviders.NHibernateValidator.dll
基本上可以得出结论:xVal没有提供对Entityframework框架的扩展,还需要我们做扩展。
最终,网上的一片文章给了我提示,问题得到了解决,解决的思路就是建立一个伙伴类,这个伙伴类跟原来的类的结构定义是一样的,在进行验证的时候,不对edm文件中的类进行验证,而是对伙伴类进行验证。
这里就以xVal框架为例进行Demo演示吧。
首先我们建立一个类模拟Entityframework生成的edm文件中的类,类的定义代码如下:
模拟EF中的User类
public
partial
class
User
{
public
string
UserName {
get
;
set
; }
public
string
Password {
get
;
set
; }
public
string
Address {
get
;
set
; }
public
string
Telephone {
get
;
set
; }
public
int
Age {
get
;
set
; }
public
string
Email {
get
;
set
;}
}
接下来我们建立一个伙伴类
伙伴类的代码
public
class
UserMetadata
{
[Required]
[StringLength(
10
)]
public
string
UserName {
get
;
set
; }
[Required]
[StringLength(
18
)]
[DataType(DataType.Password)]
public
string
Password {
get
;
set
; }
[Required]
[StringLength(
100
)]
public
string
Address {
get
;
set
; }
[Required]
[DataType(DataType.PhoneNumber)]
public
string
Telephone {
get
;
set
; }
[Required]
[Range(
1
,
100
)]
public
int
Age {
get
;
set
; }
[Required]
[DataType(DataType.EmailAddress)]
public
string
Email {
get
;
set
; }
}
再接下来,我们使用partial关键字为User类进行扩展,扩展类的定义如下:
扩展类的定义
[MetadataType(
typeof
(UserMetadata))]
public
partial
class
User
{
}
注意这段代码:[MetadataType(typeof(UserMetadata))]
为了方便大家阅读,我把整体代码贴出来,整体代码如下:
整体代码
using
System.ComponentModel.DataAnnotations;
namespace
MVCValidate.Models
{
public
partial
class
User
{
public
string
UserName {
get
;
set
; }
public
string
Password {
get
;
set
; }
public
string
Address {
get
;
set
; }
public
string
Telephone {
get
;
set
; }
public
int
Age {
get
;
set
; }
public
string
Email {
get
;
set
;}
}
[MetadataType(
typeof
(UserMetadata))]
public
partial
class
User
{
}
public
class
UserMetadata
{
[Required]
[StringLength(
10
)]
public
string
UserName {
get
;
set
; }
[Required]
[StringLength(
18
)]
[DataType(DataType.Password)]
public
string
Password {
get
;
set
; }
[Required]
[StringLength(
100
)]
public
string
Address {
get
;
set
; }
[Required]
[DataType(DataType.PhoneNumber)]
public
string
Telephone {
get
;
set
; }
[Required]
[Range(
1
,
100
)]
public
int
Age {
get
;
set
; }
[Required]
[DataType(DataType.EmailAddress)]
public
string
Email {
get
;
set
; }
}
}
接下来,我们要实现伙伴类跟原类的替换方法了,代码如下所示:
DataAnnotationsValidationRunner类的代码
using
System.Collections.Generic;
using
System.ComponentModel;
using
System.ComponentModel.DataAnnotations;
using
System.Linq;
using
xVal.ServerSide;
namespace
MVCValidate.Models
{
internal
static
class
DataAnnotationsValidationRunner
{
//
TODO: DOES NOT SUPPORT METADATA TYPE
///
// Warning: For some reason, DataTypeAttribute.IsValid() always returns "true", regardless of whether
///
// it is actually valid. Need to improve this test runner to fix that.
//
public static IEnumerable<ErrorInfo> GetErrors(object instance)
//
{
//
return from prop in TypeDescriptor.GetProperties(instance).Cast<PropertyDescriptor>()
//
from attribute in prop.Attributes.OfType<ValidationAttribute>()
//
where !attribute.IsValid(prop.GetValue(instance))
//
select new ErrorInfo(prop.Name, attribute.FormatErrorMessage(string.Empty), instance);
//
}
///
<summary>
///
Get any errors associated with the model also investigating any rules dictated by attached Metadata buddy classes.
///
</summary>
///
<param name="instance"></param>
///
<returns></returns>
public
static
IEnumerable
<
ErrorInfo
>
GetErrors(
object
instance)
{
var metadataAttrib
=
instance.GetType().GetCustomAttributes(
typeof
(MetadataTypeAttribute),
true
).OfType
<
MetadataTypeAttribute
>
().FirstOrDefault();
var buddyClassOrModelClass
=
metadataAttrib
!=
null
?
metadataAttrib.MetadataClassType : instance.GetType();
var buddyClassProperties
=
TypeDescriptor.GetProperties(buddyClassOrModelClass).Cast
<
PropertyDescriptor
>
();
var modelClassProperties
=
TypeDescriptor.GetProperties(instance.GetType()).Cast
<
PropertyDescriptor
>
();
return
from buddyProp
in
buddyClassProperties
join modelProp
in
modelClassProperties on buddyProp.Name equals modelProp.Name
from attribute
in
buddyProp.Attributes.OfType
<
ValidationAttribute
>
()
where
!
attribute.IsValid(modelProp.GetValue(instance))
select
new
ErrorInfo(buddyProp.Name, attribute.FormatErrorMessage(
string
.Empty), instance);
}
}
}
完成以上的代码以后,大部分工作就完成了,接下来,我们在Controller中编写一个create方法,来模拟Create操作,代码如下所示:
Controller层的代码
using
System;
using
System.Collections.Generic;
using
System.Linq;
using
System.Web;
using
System.Web.Mvc;
using
System.Web.Mvc.Ajax;
using
MVCValidate.Models;
using
xVal.ServerSide;
namespace
MVCValidate.Controllers
{
public
class
UserController : Controller
{
[AcceptVerbs(HttpVerbs.Post)]
public
ActionResult Create(User user)
{
var errors
=
DataAnnotationsValidationRunner.GetErrors(user);
if
(errors.Any())
{
new
RulesException(errors).AddModelStateErrors(ModelState,
"
user
"
);
}
return
View();
}
}
}
接下来,编写View层的代码,比较简单,我就直接贴出来了,代码如下:
View层的代码
<%
@ Page Language
=
"
C#
"
Inherits
=
"
System.Web.Mvc.ViewPage<MVCValidate.Models.User>
"
%>
<!
DOCTYPE html PUBLIC
"
-//W3C//DTD XHTML 1.0 Transitional//EN
"
"
http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd
"
>
<
html xmlns
=
"
http://www.w3.org/1999/xhtml
"
>
<
head runat
=
"
server
"
>
<
title
>
Create
</
title
>
</
head
>
<
body
>
<%=
Html.ValidationSummary(
"
Create was unsuccessful. Please correct the errors and try again.
"
)
%>
<%
using
(Html.BeginForm()) {
%>
<
fieldset
>
<
legend
>
Fields
</
legend
>
<
p
>
<
label
for
=
"
UserName
"
>
UserName:
</
label
>
<%=
Html.TextBox(
"
user.UserName
"
)
%>
<%=
Html.ValidationMessage(
"
user.UserName
"
)
%>
</
p
>
<
p
>
<
label
for
=
"
Password
"
>
Password:
</
label
>
<%=
Html.TextBox(
"
user.Password
"
)
%>
<%=
Html.ValidationMessage(
"
user.Password
"
)
%>
</
p
>
<
p
>
<
label
for
=
"
Address
"
>
Address:
</
label
>
<%=
Html.TextBox(
"
user.Address
"
)
%>
<%=
Html.ValidationMessage(
"
user.Address
"
)
%>
</
p
>
<
p
>
<
label
for
=
"
Telephone
"
>
Telephone:
</
label
>
<%=
Html.TextBox(
"
user.Telephone
"
)
%>
<%=
Html.ValidationMessage(
"
user.Telephone
"
)
%>
</
p
>
<
p
>
<
label
for
=
"
Age
"
>
Age:
</
label
>
<%=
Html.TextBox(
"
user.Age
"
)
%>
<%=
Html.ValidationMessage(
"
user.Age
"
)
%>
</
p
>
<
p
>
<
label
for
=
"
Email
"
>
Email:
</
label
>
<%=
Html.TextBox(
"
user.Email
"
)
%>
<%=
Html.ValidationMessage(
"
user.Email
"
)
%>
</
p
>
<
p
>
<
input type
=
"
submit
"
value
=
"
Create
"
/>
</
p
>
</
fieldset
>
<%
}
%>
<
div
>
<%=
Html.ActionLink(
"
Back to List
"
,
"
Index
"
)
%>
</
div
>
</
body
>
</
html
>
最终的效果如下图所示:
Asp.net mvc开源验证框架非常的多,只是有相似问题的更多,有了这个通用的方法,就可以很容易对其他验证框架进行扩展了。
最后,为了方便大家学习,代码我进行了打包,下载地址在这里:
代码下载
【参考文章】:
《Using MetadataType attribute with ASP.NET MVC xVal Validation Framework》