Util应用程序框架公共操做类(四):验证公共操做类

  为了可以验证领域实体,须要一个验证公共操做类来提供支持。因为我将使用企业库(Enterprise Library)的验证组件来完成这项任务,因此本文也将演示对第三方框架的封装要点。框架

  .Net提供了一个称为DataAnnotations的验证技术,即在对象的属性上添加一些Attribute,好比[Required]用来验证必填项。这是很是强大的特性,经过附加元数据的方式来提供验证,甚至在Mvc框架中还能自动生成Js客户端验证,从而能够很是方便的实现客户端和服务端的双重验证。ide

  可是遗憾的是,.Net没有直接提供验证DataAnnotations特性的功能。在Mvc中提供了一个ModelState.IsValid来进行验证,但使用这个方法有不少缺陷,我会在后面的领域实体验证一文中详细介绍这个问题。因此咱们如今须要本身来实现验证DataAnnotations的功能。性能

  先来考虑一下接口,如今须要一个方法来验证对象是否有效,因此只须要一个参数,参数类型为object便可。单元测试

  那么,返回什么结果呢?因为对象有多个属性,每一个属性上可能有多个DataAnnotations特性,这意味着可能有多个属性会验证失败。.Net中提供了一个ValidationResult来表示验证结果,它不只可以指示是否验证成功,并且包含验证失败的错误消息,这正是咱们须要的。能够直接返回ValidationResult的一个集合,好比List<ValidationResult>,不过用一个自定义的集合类包装一下更易用。测试

  验证结果集合类取名为ValidationResultCollection,代码以下。ui

using System.Collections; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; namespace Util.Validations { /// <summary>
    /// 验证结果集合 /// </summary>
    public class ValidationResultCollection : IEnumerable<ValidationResult> { /// <summary>
        /// 初始化验证结果集合 /// </summary>
        public ValidationResultCollection() { _results = new List<ValidationResult>(); } /// <summary>
        /// 验证结果 /// </summary>
        private readonly List<ValidationResult> _results; /// <summary>
        /// 是否有效 /// </summary>
        public bool IsValid { get { return _results.Count == 0; } } /// <summary>
        /// 验证结果个数 /// </summary>
        public int Count { get { return _results.Count; } } /// <summary>
        /// 添加验证结果 /// </summary>
        /// <param name="result">验证结果</param>
        public void Add( ValidationResult result ) { if ( result == null ) return; _results.Add( result ); } /// <summary>
        /// 添加验证结果集合 /// </summary>
        /// <param name="results">验证结果集合</param>
        public void AddResults( IEnumerable<ValidationResult> results ) { if ( results == null ) return; foreach( var result in results ) Add( result ); } /// <summary>
        /// 获取迭代器 /// </summary>
        IEnumerator<ValidationResult> IEnumerable<ValidationResult>.GetEnumerator() { return _results.GetEnumerator(); } /// <summary>
        /// 获取迭代器 /// </summary>
 IEnumerator IEnumerable.GetEnumerator() { return _results.GetEnumerator(); } } }
ValidationResultCollection

  验证接口取名为IValidation,代码以下。spa

namespace Util.Validations { /// <summary>
    /// 验证操做 /// </summary>
    public interface IValidation { /// <summary>
        /// 验证 /// </summary>
        /// <param name="target">验证目标</param>
        ValidationResultCollection Validate( object target ); } }

  下面准备来实现这个验证接口。日志

  一个办法是经过反射来查找全部属性上的ValidationAttribute特性,而后调用它的IsValid方法检查是否失败,代码以下。code

using System; using System.ComponentModel.DataAnnotations; using System.Reflection; namespace Util.Validations { /// <summary>
    /// 验证操做 /// </summary>
    public class Validation : IValidation { /// <summary>
        /// 初始化验证操做 /// </summary>
        public Validation() { _result = new ValidationResultCollection(); } /// <summary>
        /// 验证目标 /// </summary>
        private object _target; /// <summary>
        /// 结果 /// </summary>
        private readonly ValidationResultCollection _result; /// <summary>
        /// 验证 /// </summary>
        /// <param name="target">验证目标</param>
        public ValidationResultCollection Validate( object target ) { target.CheckNull( "target" ); _target = target; Type type = target.GetType(); var properties = type.GetProperties(); foreach( var property in properties ) ValidateProperty( property ); return _result; } /// <summary>
        /// 验证属性 /// </summary>
        private void ValidateProperty( PropertyInfo property ) { var attributes = property.GetCustomAttributes( typeof( ValidationAttribute ), true ); foreach( var attribute in attributes ) { var validationAttribute = attribute as ValidationAttribute; if ( validationAttribute == null ) continue; ValidateAttribute( property, validationAttribute ); } } /// <summary>
        /// 验证特性 /// </summary>
        private void ValidateAttribute( PropertyInfo property, ValidationAttribute attribute ) { bool isValid = attribute.IsValid( property.GetValue( _target ) ); if( isValid ) return; _result.Add( new ValidationResult( GetErrorMessage( attribute ) ) ); } /// <summary>
        /// 获取错误消息 /// </summary>
        private string GetErrorMessage( ValidationAttribute attribute ) { if( !string.IsNullOrEmpty( attribute.ErrorMessage ) ) return attribute.ErrorMessage; return Resource.GetString( attribute.ErrorMessageResourceType.FullName, attribute.ErrorMessageResourceName,attribute.ErrorMessageResourceType.Assembly ); } } }
Validation

  另外,在企业库中包含一个验证组件,它也能够完成这个任务。对象

  是选择本身实现,仍是选择第三方框架,哪一种更好呢?我考虑了如下几个问题。

  第一是性能,由于对实体进行验证是一个常规任务,换句话说,调用频率很高,而且每一个实体可能包含大量属性,因此提高性能就显得至关重要了。我简单测试了一下,在相同对象上执行100万次验证操做,发现企业库验证组件性能要高出10几倍。

  第二是健壮性和扩展性。咱们的代码可以考虑到的边界十分有限,在某些特定条件下就会出现Bug,另外,咱们只能完成目前想要的那点功能,当使用起来之后,有新的需求就须要持续维护。而第三方著名框架在全球范围使用,已经很是稳定和健壮,而且它能知足全球用户的需求,说明已经覆盖了咱们的大部分需求,因此对于某些特定功能,好比日志等,使用第三方框架远远优于本身开发。

  第三个问题是是否开源。若是我如今只拿到一个dll,我可能不会采用它,由于若是有Bug或者不知足个人需求,我却没法修改。对于只有一个dll的状况,通常建议不要用,除非它实现了你完不成的任务,这是走投无路的最后一招。对于企业库来讲,它彻底开源,并且无偿使用,因此没什么好顾虑的。

  第四个问题是引入程序集的数量。为了一个很简单的功能,引入一大堆程序集划算吗?对于Enterprise Library 5.0,为了实现这个验证功能,须要引入5个程序集,这常常让我生起干掉它的念头。不过到了Enterprise Library 6.0,只须要引入2个程序集就能够了,而这个数目在个人可接受范围内。

  经过上面的考虑,我决定使用企业库的验证组件来完成验证工做。

  咱们刚才定义了一个验证接口,这很是重要,除了能够清晰的代表咱们须要什么功能,还有一个做用就是隔离外部依赖。你不该该在项目上或应用程序框架内部直接调用企业库验证组件的API,由于之后你发现更好的验证组件时将动弹不得。定义了接口之后,在全部调用的地方使用这个接口,就能够为未来进行扩展奠基基础,只要接口不变,经过多态的方式切换实现,整个系统都不会受影响。这是使用第三方框架或外部接口最重要的一点。

  如今用企业库验证组件来实现咱们的验证接口。

  打开Util应用程序框架VS解决方案,考虑一下,咱们应该把实现验证接口的代码放到什么地方合适。最简单的办法是直接放进Util类库中,而后给Util类库引用企业库依赖程序集。但这会给Util类库形成高度耦合,若是下回你切换验证框架,就得修改Util类库,或者你其它地方在使用Util类库,但不须要进行验证,但仍是会把企业库依赖程序集带走。

  更好的办法是为有依赖的部分建立单独的程序集,这样你就能够按需所用,另外切换实现的时候也更加容易,添加一个新的程序集便可。这对于初学者会比较困难,由于初学者习惯于在少许程序集上工做,面对大量程序集会无所适从。不过随着经验的增长,你会慢慢熟悉,而且当一个VS解决方案中的程序集数量较多时,须要果断拆分红多个VS解决方案。还有一个问题是,初学者不喜欢根据依赖关系分类,若是他发现一个程序集中只有一个文件,他就会以为这个程序集没什么用,须要合并。这里主要忽略了依赖关系的存在,若是这个程序集引用了某些外部程序集,哪怕只有一个文件, 也是须要拆分的,由于没有它,将会把外部程序集引用到咱们更重要的程序集中,从而致使高度耦合。

  先建立一个名为Util.Validations.EntLib的类库项目,而后建立名为Util.Validations.EntLib.Tests的单元测试项目,并添加相关依赖引用。

  建立一个用来测试的样例对象Test,代码以下。

using System.ComponentModel.DataAnnotations; namespace Util.Validations.EntLib.Tests.Samples { /// <summary>
    /// 测试实体 /// </summary>
    public class Test{ /// <summary>
        /// 姓名 /// </summary>
        [Required( ErrorMessage = "姓名不能为空" )] public string Name { get; set; } /// <summary>
        /// 描述 /// </summary>
        [StringLength(5,ErrorMessage = "描述不能超过5位")] public string Description { get; set; } } }

  建立一个单元测试ValidationTest,代码以下。

using System.Linq; using Microsoft.VisualStudio.TestTools.UnitTesting; using Util.Validations.EntLib.Tests.Samples; namespace Util.Validations.EntLib.Tests { /// <summary>
    /// 验证测试 /// </summary>
 [TestClass] public class ValidationTest { /// <summary>
        /// 测试 /// </summary>
        private Test _test; /// <summary>
        /// 验证操做 /// </summary>
        private IValidation _validation; /// <summary>
        /// 测试初始化 /// </summary>
 [TestInitialize] public void TestInit() { _test = new Test(); _validation = new Validation(); } /// <summary>
        /// 验证姓名为必填项 /// </summary>
 [TestMethod] public void TestRequired() { var result = _validation.Validate( _test ); Assert.AreEqual( "姓名不能为空", result.First().ErrorMessage ); } /// <summary>
        /// 验证姓名为必填项及描述过长 /// </summary>
 [TestMethod] public void TestRequired_StringLength() { _test.Description = "123456"; var result = _validation.Validate( _test ); Assert.AreEqual( 2,result.Count ); Assert.AreEqual( "描述不能超过5位", result.Last().ErrorMessage ); } } }

  封装企业库验证组件的Validation类,代码以下。

using System.Collections.Generic; using Microsoft.Practices.EnterpriseLibrary.Validation; namespace Util.Validations.EntLib { /// <summary>
    /// 企业库验证操做 /// </summary>
    public class Validation : IValidation { /// <summary>
        /// 验证 /// </summary>
        /// <param name="target">验证目标</param>
        public ValidationResultCollection Validate( object target ) { var validator = ValidationFactory.CreateValidator( target.GetType() ); var results = validator.Validate( target ); return GetResult( results ); } /// <summary>
        /// 获取验证结果 /// </summary>
        private ValidationResultCollection GetResult( IEnumerable<ValidationResult> results ) { var result = new ValidationResultCollection(); foreach ( var each in results ) result.Add( new System.ComponentModel.DataAnnotations.ValidationResult( each.Message ) ); return result; } } }

  最后,补充一下,ValidationResultCollection和IValidation接口须要放在Util类库的Validations文件夹中,把接口与实现它的类分离到不一样的程序集,被称为分离接口模式,这让你在必要时能够经过新增程序集的方式扩展系统。

  本文为实体验证打下一个良好的基础,不过当实体验证失败时,须要进行处理,一个常规做法是抛出一个自定义异常,这是下一篇将要介绍的内容——异常公共操做类。

  .Net应用程序框架交流QQ群: 386092459,欢迎有兴趣的朋友加入讨论。

  谢谢你们的持续关注,个人博客地址:http://www.cnblogs.com/xiadao521/

  下载地址:http://files.cnblogs.com/xiadao521/Util.2014.11.18.1.rar

相关文章
相关标签/搜索