之前寫過一篇文章《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》