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

相關文章
相關標籤/搜索