0%

用 FluentValidation 驗證參數

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    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");
    }
    }

  1. 自己寫一個HasError的Extension```csharp
    ///
    /// FluentValidation 自訂驗證擴充方法.
    ///

    public static class FluentValidationExtensions
    {
    ///
    /// 驗證結果是否有 Error.
    ///

    ///
    ///
    public static bool HasError(this ValidationFailure validationFailure)
    {
    return validationFailure != null &&
    !string.IsNullOrWhiteSpace(validationFailure.ErrorMessage);
    }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20


3. 驗證的地方改成如下 ```csharp
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的方法 ```csharp
    ///


    /// 驗證是否為GUID
    ///

    ///
    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);
     }
    

    }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46


2. 接著在Extension的地方補上兩個擴充方法,分別是【應該是GUID】、【應該是GUID但允許其為空字串或Null】 ```csharp
/// <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<T, TProperty>.</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<T, TProperty>.</returns>
public static IRuleBuilderOptions<T, TProperty> IsGUIDAllowEmpty<T, TProperty>(
this IRuleBuilder<T, TProperty> ruleBuilder)
{
return ruleBuilder.SetValidator(new GUIDValidator(allowEmpty: true));
}
}

  1. 接著在原本驗證的地方補上
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    //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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    /// <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);
    }
    }

擴充方法```csharp
///


/// 是 DateTime 型別 or TimeStamp .
///

///
/// The type of the t property.
/// The rule builder.
/// IRuleBuilderOptions<T, TProperty>.
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));
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61


* **<span style="color: #073763;">驗證是否為GUID Array </span>**

```csharp
/// <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;
}
}

擴充方法```csharp
///


/// 是 Guid Array, 但允許空集合.
///

///
/// The type of the t property.
/// The rule builder.
/// IRuleBuilderOptions<T, TProperty>.
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));
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51


* **<span style="color: #073763;">驗證是否為數字 </span>**

```csharp
/// <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;
}
}

擴充方法```csharp
///


/// 是 Integer 型別.
///

///
/// The type of the t property.
/// The rule builder.
/// IRuleBuilderOptions<T, TProperty>.
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));
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78


* **<span style="color: #073763;">檢查數字是否在要求範圍內 </span>**

```csharp
/// <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;
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
/// <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);
}
}
}

擴充方法```csharp
///


/// 符合數字區間,但允許空值.
/// EX : (1 < x < 3)
///

///
/// The type of the property.
/// The type of the numeric.
/// The rule builder.
/// Up threshold.
/// Down threshold.
///
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(
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));
    }
1
2
3
4
5
6
7
8
9
使用方法 ```csharp
//OrderBy - 允許空值或(0、1)
this.RuleFor(x => x.OrderBy)
.NotNumericAllowEmptyOrBetweenOfAllowEquals<GetBuildingDealCaseParameter, string, int>(
"2",
"1")
.WithErrorCode("X400")
.WithMessage("OrderBy應該在1~2之間");

  • 驗證是否為數字Array

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    /// <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;
    }
    }

  • 擴充方法*```csharp
    ///

       /// 是數字 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));
       }