FluentValidation是個很不錯的套件,且擴充性也高,解決了一些以前常常要寫很多遍的驗證邏輯。
以前常常驗證參數時都會有很多的If..Else,搞得程式碼很長很醜之外,閱讀性不佳。自從公司同事推薦了這個套件後,用了兩三個專案發現程式碼變得簡潔易懂之外,寫了一些擴充方法也可以重複使用,不像以前常常重複造輪子
1 | void Main() |
上面的範例是以前的寫法,常常欄位多,各個欄位又有不同的要求時,總是把驗證的邏輯寫得又臭又長。
加上公司要求所有輸入輸出的欄位都要被Log下來,所以基本上參數都要是String型別,否則如果ID寫成GUID,因為傳入的時候不是GUID會接不到,自然無法被Log到,但也因此衍生了驗證的複雜度提高的問題。
想想如果各個參數錯誤要回傳的訊息會不同時,又該寫的多複雜才做得到呢……
那讓來看看如何透FluentValidation 驗證參數,
- 首先先把驗證邏輯寫成一個Class
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22public 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");
}
}
- 自己寫一個HasError的Extension```csharp
///
/// FluentValidation 自訂驗證擴充方法.
///
public static class FluentValidationExtensions
{
///
/// 驗證結果是否有 Error.
///
///
///
public static bool HasError(this ValidationFailure validationFailure)
{
return validationFailure != null &&
!string.IsNullOrWhiteSpace(validationFailure.ErrorMessage);
}
}
1 |
|
這樣只要有參數帶入錯誤,他就會依照你要求的帶回ErrorCode跟ErrorMessage,那可能各位會發現,阿驗證是否為GUID的地方怎麼不見了?? 因為套件並沒有提供,所以這邊要自己擴充
先寫一個驗證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 |
|
- 接著在原本驗證的地方補上
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 rule builder.
///
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<T, TProperty>.</returns>
public static IRuleBuilderOptions<T, TProperty> IsDateTimeOrTimeStampAllowEmpty<T, TProperty>(
this IRuleBuilder<T, TProperty> ruleBuilder)
{
return ruleBuilder.SetValidator(new DateTimeValidator(allowEmpty: true));
}
1 |
|
擴充方法```csharp
///
/// 是 Guid Array, 但允許空集合.
///
///
///
/// The rule builder.
///
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<T, TProperty>.</returns>
public static IRuleBuilderOptions<T, TProperty> IsGUIDArray<T, TProperty>(
this IRuleBuilder<T, TProperty> ruleBuilder)
{
return ruleBuilder.SetValidator(new GUIDArrayValidator(allowEmpty: false));
}
1 |
|
擴充方法```csharp
///
/// 是 Integer 型別.
///
///
///
/// The rule builder.
///
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<T, TProperty>.</returns>
public static IRuleBuilderOptions<T, TProperty> IsIntegerAllowEmpty<T, TProperty>(
this IRuleBuilder<T, TProperty> ruleBuilder)
{
return ruleBuilder.SetValidator(new IntegerValidator(allowEmpty: true));
}
1 |
|
1 | /// <summary> |
擴充方法```csharp
///
/// 符合數字區間,但允許空值.
///
///
///
///
///
/// 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 <= x <= 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 < x < 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 <= x <= 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 | 使用方法 ```csharp |
驗證是否為數字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)); }