2016年10月12日 星期三

用 FluentValidation 驗證參數


FluentValidation是個很不錯的套件,且擴充性也高,解決了一些以前常常要寫很多遍的驗證邏輯。


以前常常驗證參數時都會有很多的If..Else,搞得程式碼很長很醜之外,閱讀性不佳。自從公司同事推薦了這個套件後,用了兩三個專案發現程式碼變得簡潔易懂之外,寫了一些擴充方法也可以重複使用,不像以前常常重複造輪子


void Main()
{
 var Parameter = new APIInputParameter 
 {
  ID = "123",
  Name = "Toyo"
 };
 
 Guid _ID;
 //驗證邏輯
 if (string.IsNullOrWhiteSpace(Parameter.ID) ||
           !Guid.TryParse(Parameter.ID,out _ID) ||
     string.IsNullOrWhiteSpace(Parameter.Name) )
 {
  "參數錯誤".Dump();
 }
 else
 {
  "參數正確".Dump();
 }
  
  
}

public class APIInputParameter 
{
 //此參數應該為Guid,但為了能Log下來所以接的時候要先接成String
 //否則輸入端不是傳入GUID就記錄不到了
 public string ID { get; set; }
 public string Name {get;set;}
}


上面的範例是以前的寫法,常常欄位多,各個欄位又有不同的要求時,總是把驗證的邏輯寫得又臭又長。

加上公司要求所有輸入輸出的欄位都要被Log下來,所以基本上參數都要是String型別,否則如果ID寫成GUID,因為傳入的時候不是GUID會接不到,自然無法被Log到,但也因此衍生了驗證的複雜度提高的問題。

想想如果各個參數錯誤要回傳的訊息會不同時,又該寫的多複雜才做得到呢......



那讓來看看如何透FluentValidation 驗證參數,

  1. 首先先把驗證邏輯寫成一個Class
    public class APIInputParameterValidator : AbstractValidator<APIInputParameter> 
    {
     public APIInputParameterValidator()
     {
      //ID - 必填,應為GUID
      this.RuleFor(x => x.ID)
         .NotEmpty()
         .WithErrorCode("X400")
         .WithMessage("ID不得為空字串")
         .NotNull()
         .WithErrorCode("X400")
         .WithMessage("ID不得為Null");
    
      this.RuleFor(x => x.Name)
       .NotEmpty()
       .WithErrorCode("X401")
       .WithMessage("Name不得為空字串")
       .NotNull()
       .WithErrorCode("X401")
       .WithMessage("Name不得為Null");
     }
    }
    

  2. 自己寫一個HasError的Extension
    /// <summary>
    /// FluentValidation 自訂驗證擴充方法.
    /// </summary>
    public static class FluentValidationExtensions
    {
     /// <summary>
     /// 驗證結果是否有 Error.
     /// </summary>
     /// <param name="validationFailure"></param>
     /// <returns></returns>
     public static bool HasError(this ValidationFailure validationFailure)
     {
      return validationFailure != null &&
        !string.IsNullOrWhiteSpace(validationFailure.ErrorMessage);
     }
    }
    

  3. 驗證的地方改成如下
     
     var Parameter = new APIInputParameter 
     {
      ID = "123",
      Name = "Toyo"
     };
     
     // 檢查輸入參數
     var validator = new APIInputParameterValidator();
    
     var error = validator.Validate(Parameter).Errors.FirstOrDefault();
     if (error.HasError())
     {
       string.Format("{0}-{1}",error.ErrorCode,error.ErrorMessage).Dump();
     }
     else
     {
       "驗證成功".Dump();
     }
    



這樣只要有參數帶入錯誤,他就會依照你要求的帶回ErrorCode跟ErrorMessage,那可能各位會發現,阿驗證是否為GUID的地方怎麼不見了?? 因為套件並沒有提供,所以這邊要自己擴充


  1. 先寫一個驗證String是否為Guid的方法
    /// <summary>
        /// 驗證是否為GUID
        /// </summary>
        /// <seealso cref="FluentValidation.Validators.PropertyValidator" />
        public class GUIDValidator : PropertyValidator
        {
            /// <summary>
            /// 是否允許字串參數為空白.
            /// </summary>
            /// <value><c>true</c> if [allow empty]; otherwise, <c>false</c>.</value>
            private bool AllowEmpty { get; set; }
    
            /// <summary>
            /// Initializes a new instance of the <see cref="GUIDValidator"/> class.
            /// </summary>
            /// <param name="allowEmpty">if set to <c>true</c> [allow empty].</param>
            public GUIDValidator(
                bool allowEmpty = false) : base("傳入參數錯誤。")
            {
                this.AllowEmpty = allowEmpty;
            }
    
            /// <summary>
            /// Returns true if ... is valid.
            /// </summary>
            /// <param name="context">The context.</param>
            /// <returns>
            ///   <c>true</c> if the specified context is valid; otherwise, <c>false</c>.
            /// </returns>
            protected override bool IsValid(PropertyValidatorContext context)
            {
                var propertyValue = context.PropertyValue as string;
    
                if (AllowEmpty &&
                    string.IsNullOrWhiteSpace(propertyValue))
                {
                    return true;
                }
    
                Guid guid;
                return Guid.TryParse(propertyValue, out guid);
            }
        }
    

  2. 接著在Extension的地方補上兩個擴充方法,分別是【應該是GUID】、【應該是GUID但允許其為空字串或Null】
    /// <summary>
    /// FluentValidation 自訂驗證擴充方法.
    /// </summary>
    public static class FluentValidationExtensions
    {
     /// <summary>
     /// 驗證結果是否有 Error.
     /// </summary>
     /// <param name="validationFailure"></param>
     /// <returns></returns>
     public static bool HasError(this ValidationFailure validationFailure)
     {
      return validationFailure != null &&
        !string.IsNullOrWhiteSpace(validationFailure.ErrorMessage);
     }
    
     /// <summary>
     /// 應該是 GUID 型別.
     /// </summary>
     /// <typeparam name="T"></typeparam>
     /// <typeparam name="TProperty">The type of the t property.</typeparam>
     /// <param name="ruleBuilder">The rule builder.</param>
     /// <returns>IRuleBuilderOptions&lt;T, TProperty&gt;.</returns>
     public static IRuleBuilderOptions<T, TProperty> IsGUID<T, TProperty>(
      this IRuleBuilder<T, TProperty> ruleBuilder)
     {
      return ruleBuilder.SetValidator(new GUIDValidator(allowEmpty: false));
     }
    
     /// <summary>
     /// 應該是 GUID 型別, 但允許 String.Empty.
     /// </summary>
     /// <typeparam name="T"></typeparam>
     /// <typeparam name="TProperty">The type of the t property.</typeparam>
     /// <param name="ruleBuilder">The rule builder.</param>
     /// <returns>IRuleBuilderOptions&lt;T, TProperty&gt;.</returns>
     public static IRuleBuilderOptions<T, TProperty> IsGUIDAllowEmpty<T, TProperty>(
      this IRuleBuilder<T, TProperty> ruleBuilder)
     {
      return ruleBuilder.SetValidator(new GUIDValidator(allowEmpty: true));
     }
    }
    
  3. 接著在原本驗證的地方補上
            
    //ID - 必填,應為GUID
    this.RuleFor(x => x.ID)
      .NotEmpty()
      .WithErrorCode("X400")
      .WithMessage("ID不得為空字串")
      .NotNull()
      .WithErrorCode("X400")
      .WithMessage("ID不得為Null")
      .IsGUID()
      .WithErrorCode("X400")
      .WithMessage("ID應為GUID");
    

再執行原本的驗證就會得到錯誤訊息 X400-ID應為GUID
對我來說不止讓程式可讀性增加之外,也讓驗證的地方被分離出來,做到所謂的關注點分離

以下補上幾個我常常用到的驗證擴充出來的方法供各位參考,再強調一次,因為公司要求輸入輸出都要被Log下來,所以所有參數都是從String出發去驗證



  • 驗證是否為DateTime or TimeStamp

    /// <summary>
        /// 驗證是否為DateTime
        /// </summary>
        /// <seealso cref="FluentValidation.Validators.PropertyValidator" />
        public class DateTimeValidator : PropertyValidator
        {
            /// <summary>
            /// 是否允許參數為空白.
            /// </summary>
            /// <value><c>true</c> if [allow empty]; otherwise, <c>false</c>.</value>
            private bool AllowEmpty { get; set; }
    
            /// <summary>
            /// Initializes a new instance of the <see cref="DateTimeValidator"/> class.
            /// </summary>
            /// <param name="allowEmpty">if set to <c>true</c> [allow empty].</param>
            public DateTimeValidator(bool allowEmpty) : base("型別錯誤")
            {
                this.AllowEmpty = allowEmpty;
            }
    
            /// <summary>
            /// Returns true if ... is valid.
            /// </summary>
            /// <param name="context">The context.</param>
            /// <returns>
            ///   <c>true</c> if the specified context is valid; otherwise, <c>false</c>.
            /// </returns>
            protected override bool IsValid(PropertyValidatorContext context)
            {
                var propertyValue = context.PropertyValue as string;
    
                if (this.AllowEmpty &&
                    string.IsNullOrWhiteSpace(propertyValue))
                {
                    return true;
                }
    
                int value;
                bool result = int.TryParse(propertyValue, out value);
                //TimeStamp
                if (result && value > 0)
                {
                    return true;
                }
    
                DateTime dateTimeValue;
                return DateTime.TryParse(propertyValue, out dateTimeValue);
            }
        }
    

    擴充方法
            /// <summary>
            /// 是 DateTime 型別 or TimeStamp .
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <typeparam name="TProperty">The type of the t property.</typeparam>
            /// <param name="ruleBuilder">The rule builder.</param>
            /// <returns>IRuleBuilderOptions&lt;T, TProperty&gt;.</returns>
            public static IRuleBuilderOptions<T, TProperty> IsDateTimeOrTimeStamp<T, TProperty>(
                this IRuleBuilder<T, TProperty> ruleBuilder)
            {
                return ruleBuilder.SetValidator(new DateTimeValidator(allowEmpty: false));
            }
    
            /// <summary>
            /// 是 DateTime 型別 or TimeStamp, 但允許 String.Empty.
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <typeparam name="TProperty">The type of the t property.</typeparam>
            /// <param name="ruleBuilder">The rule builder.</param>
            /// <returns>IRuleBuilderOptions&lt;T, TProperty&gt;.</returns>
            public static IRuleBuilderOptions<T, TProperty> IsDateTimeOrTimeStampAllowEmpty<T, TProperty>(
                this IRuleBuilder<T, TProperty> ruleBuilder)
            {
                return ruleBuilder.SetValidator(new DateTimeValidator(allowEmpty: true));
            }
    


  • 驗證是否為GUID Array

    /// <summary>
        /// 驗證是否為GUID Array
        /// </summary>
        public class GUIDArrayValidator : PropertyValidator
        {
            /// <summary>
            /// 是否允許字串參數為空白.
            /// </summary>
            /// <value><c>true</c> if [allow empty]; otherwise, <c>false</c>.</value>
            private bool AllowEmpty { get; set; }
    
            /// <summary>
            /// Initializes a new instance of the <see cref="GUIDArrayValidator"/> class.
            /// </summary>
            /// <param name="allowEmpty">if set to <c>true</c> [allow empty].</param>
            public GUIDArrayValidator(
                bool allowEmpty = false) :base("傳入參數錯誤。")
            {
                this.AllowEmpty = allowEmpty;
            }
    
            /// <summary>
            /// Returns true if ... is valid.
            /// </summary>
            /// <param name="context">The context.</param>
            /// <returns>
            ///   <c>true</c> if the specified context is valid; otherwise, <c>false</c>.
            /// </returns>
            protected override bool IsValid(PropertyValidatorContext context)
            {
                var propertyValue = context.PropertyValue as List<string>;
                if (AllowEmpty &&
                    (propertyValue == null || propertyValue.Count == 0))
                {
                    return true;
                }
    
                if (!AllowEmpty &&
                    (propertyValue == null || propertyValue.Count == 0))
                {
                    return false;
                }
    
                Guid guid;
                foreach (var item in propertyValue)
                {
                    if (!Guid.TryParse(item, out guid))
                    {
                        return false;
                    }
                }
    
                return true;
            }
        }
    

    擴充方法
    /// <summary>
            /// 是 Guid Array, 但允許空集合.
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <typeparam name="TProperty">The type of the t property.</typeparam>
            /// <param name="ruleBuilder">The rule builder.</param>
            /// <returns>IRuleBuilderOptions&lt;T, TProperty&gt;.</returns>
            public static IRuleBuilderOptions<T, TProperty> IsGUIDArrayAllowEmpty<T, TProperty>(
                this IRuleBuilder<T, TProperty> ruleBuilder)
            {
                return ruleBuilder.SetValidator(new GUIDArrayValidator(allowEmpty: true));
            }
    
            /// <summary>
            /// 是 Guid Array.
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <typeparam name="TProperty">The type of the t property.</typeparam>
            /// <param name="ruleBuilder">The rule builder.</param>
            /// <returns>IRuleBuilderOptions&lt;T, TProperty&gt;.</returns>
            public static IRuleBuilderOptions<T, TProperty> IsGUIDArray<T, TProperty>(
                this IRuleBuilder<T, TProperty> ruleBuilder)
            {
                return ruleBuilder.SetValidator(new GUIDArrayValidator(allowEmpty: false));
            }
    
    

  • 驗證是否為數字

    /// <summary>
        /// 驗證是否為Integer
        /// </summary>
        /// <seealso cref="FluentValidation.Validators.PropertyValidator" />
        public class IntegerValidator : PropertyValidator
        {
            /// <summary>
            /// 是否允許字串參數為空白.
            /// </summary>
            /// <value><c>true</c> if [allow empty]; otherwise, <c>false</c>.</value>
            private bool AllowEmpty { get; set; }
    
            /// <summary>
            /// Initializes a new instance of the <see cref="IntegerValidator"/> class.
            /// </summary>
            /// <param name="allowEmpty">if set to <c>true</c> [allow empty].</param>
            public IntegerValidator(bool allowEmpty = false)
                : base("型別錯誤")
            {
                this.AllowEmpty = allowEmpty;
            }
    
            /// <summary>
            /// Returns true if ... is valid.
            /// </summary>
            /// <param name="context">The context.</param>
            /// <returns>
            ///   <c>true</c> if the specified context is valid; otherwise, <c>false</c>.
            /// </returns>
            /// <exception cref="NotImplementedException"></exception>
            protected override bool IsValid(PropertyValidatorContext context)
            {
                var propertyValue = context.PropertyValue as string;
    
                if (this.AllowEmpty &&
                    string.IsNullOrWhiteSpace(propertyValue))
                {
                    return true;
                }
    
                int value;
                bool result = int.TryParse(propertyValue, out value);
                return result;
            }
        }
    

    擴充方法
            /// <summary>
            /// 是 Integer 型別.
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <typeparam name="TProperty">The type of the t property.</typeparam>
            /// <param name="ruleBuilder">The rule builder.</param>
            /// <returns>IRuleBuilderOptions&lt;T, TProperty&gt;.</returns>
            public static IRuleBuilderOptions<T, TProperty> IsInteger<T, TProperty>(
                this IRuleBuilder<T, TProperty> ruleBuilder)
            {
                return ruleBuilder.SetValidator(new IntegerValidator(allowEmpty: false));
            }
    
            /// <summary>
            /// 是 Integer 型別, 但允許 String.Empty.
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <typeparam name="TProperty">The type of the t property.</typeparam>
            /// <param name="ruleBuilder">The rule builder.</param>
            /// <returns>IRuleBuilderOptions&lt;T, TProperty&gt;.</returns>
            public static IRuleBuilderOptions<T, TProperty> IsIntegerAllowEmpty<T, TProperty>(
                this IRuleBuilder<T, TProperty> ruleBuilder)
            {
                return ruleBuilder.SetValidator(new IntegerValidator(allowEmpty: true));
            }
    

  • 檢查數字是否在要求範圍內

    /// <summary>
        /// 驗證數字是否在範圍內
        /// </summary>
        /// <typeparam name="TNumeric">The type of the numeric.</typeparam>
        /// <seealso cref="FluentValidation.Validators.PropertyValidator" />
        public class NumericBetweenInValidator<TNumeric> : PropertyValidator
            where TNumeric : IComparable
        {
            private TNumeric compareValueUp;
    
            private TNumeric compareValueDown;
    
            /// <summary>
            /// 是否允許字串參數為空白.
            /// </summary>
            /// <value><c>true</c> if [allow empty]; otherwise, <c>false</c>.</value>
            private bool AllowEmpty { get; set; }
    
            /// <summary>
            /// 轉型是否成功
            /// </summary>
            private bool IsConvertable;
    
            /// <summary>
            /// 是否允許等於輸入的上下閥值值
            /// </summary>
            private bool AllowEquals;
    
            public NumericBetweenInValidator(
                string valueUp,
                string valueDown,
                bool allowEquals = false,
                bool allowEmpty = false) : base("傳入參數錯誤。")
            {
                this.compareValueUp = ConvertHelper.ToT<TNumeric>(valueUp, out IsConvertable);
    
                this.compareValueDown = ConvertHelper.ToT<TNumeric>(valueDown, out IsConvertable);
    
                this.AllowEquals = allowEquals;
                this.AllowEmpty = allowEmpty;
            }
    
            protected override bool IsValid(PropertyValidatorContext context)
            {
                var propertyValue = context.PropertyValue as string;
    
                if (this.AllowEmpty &&
                    string.IsNullOrWhiteSpace(propertyValue))
                {
                    return true;
                }
    
    
                var value = ConvertHelper.ToT<TNumeric>(propertyValue, out IsConvertable);
    
                if (!IsConvertable)
                {
                    return false;
                }
    
                // -1 value < compareValue
                // 0  value = compareValue
                // 1  value > compareValue
                if (AllowEquals)
                {
                    return value.CompareTo(compareValueDown) >= 0
                           && value.CompareTo(compareValueUp) <= 0;
                }
    
                return value.CompareTo(compareValueDown) > 0
                       && value.CompareTo(compareValueUp) < 0;
            }
        }
    
    /// <summary>
        /// Class ConvertHelper
        /// </summary>
        internal static class ConvertHelper
        {
            /// <summary>
            /// 轉型成 T.
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <param name="value">The value.</param>
            /// <returns></returns>
            /// <exception cref="System.ArgumentException">Convert fail.;Convert</exception>
            public static T ToT<T>(string value, out bool result) where T : IComparable
            {
                result = false;
    
                try
                {
                    switch (Type.GetTypeCode(typeof(T)))
                    {
                        case TypeCode.Double:
    
                            double doubleValue;
                            if (double.TryParse(value, out doubleValue))
                            {
                                result = true;
                            }
    
                            return (T)(object)Convert.ToDouble(value);
    
                        case TypeCode.Int16:
    
                            Int16 int16Value;
                            if (Int16.TryParse(value, out int16Value))
                            {
                                result = true;
                            }
    
                            return (T)(object)Convert.ToInt16(value);
    
                        case TypeCode.Int32:
    
                            Int32 int32Value;
                            if (Int32.TryParse(value, out int32Value))
                            {
                                result = true;
                            }
    
                            return (T)(object)Convert.ToInt32(value);
    
                        case TypeCode.Int64:
    
                            Int64 int64Value;
                            if (Int64.TryParse(value, out int64Value))
                            {
                                result = true;
                            }
    
                            return (T)(object)Convert.ToInt64(value);
    
                        case TypeCode.Decimal:
    
                            decimal decimalValue;
                            if (decimal.TryParse(value, out decimalValue))
                            {
                                result = true;
                            }
    
                            return (T)(object)Convert.ToDecimal(value);
    
                        default:
                            return default(T);
                    }
                }
                catch (Exception ex)
                {
                    return default(T);
                }
            }
        }
    
    擴充方法
    /// <summary>
            /// 符合數字區間,但允許空值.
            /// <para>EX : (1 &lt; x &lt; 3) </para>
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <typeparam name="TProperty">The type of the property.</typeparam>
            /// <typeparam name="TNumeric">The type of the numeric.</typeparam>
            /// <param name="ruleBuilder">The rule builder.</param>
            /// <param name="upThreshold">Up threshold.</param>
            /// <param name="downThreshold">Down threshold.</param>
            /// <returns></returns>
            public static IRuleBuilderOptions<T, TProperty> IsNumericAllowEmptyOrBetweenOf<T, TProperty, TNumeric>(
                this IRuleBuilder<T, TProperty> ruleBuilder,
                string upThreshold,
                string downThreshold)
                where TNumeric : IComparable
            {
                return ruleBuilder.SetValidator(
                    new NumericBetweenInValidator<TNumeric>(
                        upThreshold,
                        downThreshold,
                        allowEquals: false,
                        allowEmpty: true));
            }
    
            /// <summary>
            /// 符合數字區間且允許等於閥值,但允許空值.
            /// <para>EX : (1 &lt;= x &lt;= 3) </para>
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <typeparam name="TProperty">The type of the property.</typeparam>
            /// <typeparam name="TNumeric">The type of the numeric.</typeparam>
            /// <param name="ruleBuilder">The rule builder.</param>
            /// <param name="upThreshold">Up threshold.</param>
            /// <param name="downThreshold">Down threshold.</param>
            /// <returns></returns>
            public static IRuleBuilderOptions<T, TProperty> IsNumericAllowEmptyOrBetweenOfAllowEquals<T, TProperty, TNumeric>(
                this IRuleBuilder<T, TProperty> ruleBuilder,
                string upThreshold,
                string downThreshold)
                where TNumeric : IComparable
            {
                return ruleBuilder.SetValidator(
                    new NumericBetweenInValidator<TNumeric>(
                        upThreshold,
                        downThreshold,
                        allowEquals: true,
                        allowEmpty: true));
            }
    
            /// <summary>
            /// 符合數字區間.
            /// <para>EX : (1 &lt; x &lt; 3) </para>
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <typeparam name="TProperty">The type of the property.</typeparam>
            /// <typeparam name="TNumeric">The type of the numeric.</typeparam>
            /// <param name="ruleBuilder">The rule builder.</param>
            /// <param name="upThreshold">Up threshold.</param>
            /// <param name="downThreshold">Down threshold.</param>
            /// <returns></returns>
            public static IRuleBuilderOptions<T, TProperty> IsNumericBetweenOf<T, TProperty, TNumeric>(
                this IRuleBuilder<T, TProperty> ruleBuilder,
                string upThreshold,
                string downThreshold)
                where TNumeric : IComparable
            {
                return ruleBuilder.SetValidator(
                    new NumericBetweenInValidator<TNumeric>(
                        upThreshold,
                        downThreshold,
                        allowEquals: false,
                        allowEmpty: false));
            }
    
            /// <summary>
            /// 符合數字區間且允許等於閥值
            /// <para>EX : (1 &lt;= x &lt;= 3) </para>
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <typeparam name="TProperty">The type of the property.</typeparam>
            /// <typeparam name="TNumeric">The type of the numeric.</typeparam>
            /// <param name="ruleBuilder">The rule builder.</param>
            /// <param name="upThreshold">Up threshold.</param>
            /// <param name="downThreshold">Down threshold.</param>
            /// <returns></returns>
            public static IRuleBuilderOptions<T, TProperty> IsNumericBetweenOfAllowEquals<T, TProperty, TNumeric>(
                this IRuleBuilder<T, TProperty> ruleBuilder,
                string upThreshold,
                string downThreshold)
                where TNumeric : IComparable
            {
                return ruleBuilder.SetValidator(
                    new NumericBetweenInValidator<TNumeric>(
                        upThreshold,
                        downThreshold,
                        allowEquals: true,
                        allowEmpty: false));
            }
    
    使用方法
    //OrderBy - 允許空值或(0、1)
    this.RuleFor(x => x.OrderBy)
      .NotNumericAllowEmptyOrBetweenOfAllowEquals<GetBuildingDealCaseParameter, string, int>(
      "2",
      "1")
      .WithErrorCode("X400")
      .WithMessage("OrderBy應該在1~2之間");
    

  • 驗證是否為數字Array

    /// <summary>
        /// 驗證是否為數字 Array
        /// </summary>
        /// <seealso cref="FluentValidation.Validators.PropertyValidator" />
        public class NumericArrayValidator<TNumeric> : PropertyValidator
            where TNumeric : IComparable
        {
            /// <summary>
            /// 是否允許Array為Null或空集合.
            /// </summary>
            /// <value>
            ///   <c>true</c> if [allow empty]; otherwise, <c>false</c>.
            /// </value>
            private bool AllowEmpty { get; set; }
    
            /// <summary>
            /// Initializes a new instance of the <see cref="NumericArrayValidator{TNumeric}"/> class.
            /// </summary>
            /// <param name="allowEmpty">if set to <c>true</c> [allow empty].</param>
            public NumericArrayValidator(bool allowEmpty) : base("型別錯誤")
            {
                this.AllowEmpty = allowEmpty;
            }
    
            /// <summary>
            /// Returns true if ... is valid.
            /// </summary>
            /// <param name="context">The context.</param>
            /// <returns>
            ///   <c>true</c> if the specified context is valid; otherwise, <c>false</c>.
            /// </returns>
            protected override bool IsValid(PropertyValidatorContext context)
            {
                var propertyValue = context.PropertyValue as List<string>;
    
                if (this.AllowEmpty &&
                    (propertyValue == null || propertyValue.Count == 0))
                {
                    return true;
                }
    
                //不允許空集合或Null
                if (!this.AllowEmpty &&
                    (propertyValue == null || propertyValue.Count == 0))
                {
                    return false;
                }
    
    
                bool IsConvertable;
                foreach (var x in propertyValue)
                {
                    ConvertHelper.ToT<TNumeric>(x, out IsConvertable);
                    if (!IsConvertable)
                    {
                        return false;
                    }
                }
                return true;
            }
        }
    
    擴充方法
    /// <summary>
            /// 是數字 Array,但允許空陣列或Null.
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <typeparam name="TProperty">The type of the property.</typeparam>
            /// <typeparam name="TNumeric">The type of the numeric.</typeparam>
            /// <param name="ruleBuilder">The rule builder.</param>
            /// <returns></returns>
            public static IRuleBuilderOptions<T, TProperty> IsNumericArrayAllowEmpty<T, TProperty, TNumeric>(
                this IRuleBuilder<T, TProperty> ruleBuilder)
                where TNumeric : IComparable
            {
                return ruleBuilder.SetValidator(
                    new NumericArrayValidator<TNumeric>(allowEmpty: true));
            }
    
    
            /// <summary>
            /// 是數字 Array.
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <typeparam name="TProperty">The type of the property.</typeparam>
            /// <typeparam name="TNumeric">The type of the numeric.</typeparam>
            /// <param name="ruleBuilder">The rule builder.</param>
            /// <returns></returns>
            public static IRuleBuilderOptions<T, TProperty> IsNumericArray<T, TProperty, TNumeric>(
                this IRuleBuilder<T, TProperty> ruleBuilder)
                where TNumeric : IComparable
            {
                return ruleBuilder.SetValidator(
                    new NumericArrayValidator<TNumeric>(allowEmpty: false));
            }
    



0 意見:

張貼留言