ASP.NET MVC验证框架中关于属性标记的通用扩展方法

时间:2022-11-04 14:34:18

之前写过一篇文章《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许可证。

ASP.NET MVC验证框架中关于属性标记的通用扩展方法 

xVal可以通过IRulesProvider接口,通过这个接口可以进行扩展,很明显,它只扩展了Castle框架跟NHibernate框架,通过如下两个程序集就可以看出来:

  1. xVal.RulesProviders.CastleValidator.dll
  2. xVal.RulesProviders.NHibernateValidator.dll
基本上可以得出结论:xVal没有提供对Entityframework框架的扩展,还需要我们做扩展。 

 最终,网上的一片文章给了我提示,问题得到了解决,解决的思路就是建立一个伙伴类,这个伙伴类跟原来的类的结构定义是一样的,在进行验证的时候,不对edm文件中的类进行验证,而是对伙伴类进行验证。

 这里就以xVal框架为例进行Demo演示吧。

首先我们建立一个类模拟Entityframework生成的edm文件中的类,类的定义代码如下:

ASP.NET MVC验证框架中关于属性标记的通用扩展方法模拟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 ;}
    }

 接下来我们建立一个伙伴类

ASP.NET MVC验证框架中关于属性标记的通用扩展方法伙伴类的代码
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类进行扩展,扩展类的定义如下:

ASP.NET MVC验证框架中关于属性标记的通用扩展方法
ASP.NET MVC验证框架中关于属性标记的通用扩展方法扩展类的定义
    [MetadataType( typeof (UserMetadata))]
    
public   partial   class  User
    { 
        
    }
ASP.NET MVC验证框架中关于属性标记的通用扩展方法

注意这段代码:[MetadataType(typeof(UserMetadata))]  

为了方便大家阅读,我把整体代码贴出来,整体代码如下:

ASP.NET MVC验证框架中关于属性标记的通用扩展方法整体代码
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 ; }
    }

}

接下来,我们要实现伙伴类跟原类的替换方法了,代码如下所示:

ASP.NET MVC验证框架中关于属性标记的通用扩展方法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操作,代码如下所示:

ASP.NET MVC验证框架中关于属性标记的通用扩展方法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层的代码,比较简单,我就直接贴出来了,代码如下:

ASP.NET MVC验证框架中关于属性标记的通用扩展方法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验证框架中关于属性标记的通用扩展方法

 Asp.net mvc开源验证框架非常的多,只是有相似问题的更多,有了这个通用的方法,就可以很容易对其他验证框架进行扩展了。

 最后,为了方便大家学习,代码我进行了打包,下载地址在这里:

 代码下载