ASP.NET MVC验证框架中关于属性标记的通用扩展方法
2017-04-25 15:37
609 查看
之前写过一篇文章《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开源验证框架非常的多,只是有相似问题的更多,有了这个通用的方法,就可以很容易对其他验证框架进行扩展了。
最后,为了方便大家学习,代码我进行了打包,下载地址在这里:
代码下载
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开源验证框架非常的多,只是有相似问题的更多,有了这个通用的方法,就可以很容易对其他验证框架进行扩展了。
最后,为了方便大家学习,代码我进行了打包,下载地址在这里:
代码下载
相关文章推荐
- 一起谈.NET技术,ASP.NET MVC验证框架中关于属性标记的通用扩展方法
- 《ASP.NET MVC验证框架中关于属性标记的通用扩展方法》之继续扩展
- ASP.NET MVC验证框架中关于属性标记的通用扩展方法
- 关于ASP.NET MVC框架的代码提示汉化实现方法
- 扩展ASP.NET MVC三层框架并使用StructureMap实现依赖注入1-Model层的实现
- 关于linux asp.net MVC网站中 httpHandlers配置无效的处理方法
- ASP.NET全栈开发教程之在MVC中使用服务端验证的方法
- ASP.NET MVC中对Model进行分步验证的解决方法
- 扩展ASP.NET MVC三层框架且使用StructureMap实现依赖注入1-Model层
- 关于ASP.NET MVC的业务逻辑验证(validation)
- ASP.NET MVC中对Model进行分步验证的解决方法
- 通过扩展改善ASP.NET MVC的验证机制[使用篇]
- 关于ASP.NET MVC中Response.Redirect和RedirectToAction的BUG (跳转后继续执行后面代码而不结束进程)以及处理方法
- 扩展Html Helper类,ASP.NET MVC框架提供了一个帮助我们构造Html元素的类:TagBuilder
- 支持ASP.NET MVC、WebFroM的表单验证框架ValidationSuar使用介绍
- ASP.NET MVC 5 - 验证编辑方法(Edit method)和编辑视图(Edit view)
- 一起谈.NET技术,ASP.NET MVC中对Model进行分步验证的解决方法
- 使用StructureMap扩展ASP.NET MVC三层结构框架系列文章总结篇(附源码下载)
- ASP.NET MVC学前篇之扩展方法、链式编程
- 【ASP.NET MVC 学习笔记】- 14 HtmlHlper的扩展方法