本節介紹Util應用框架如何進行驗證.
概述
驗證是業務健壯性的基礎.
.Net 提供了一套稱爲 DataAnnotations 數據註解的方法,可以對屬性進行一些基本驗證,比如必填項驗證,長度驗證等.
Util應用框架使用標準的數據註解作爲基礎驗證,並對自定義驗證進行擴展.
基礎用法
引用Nuget包
Nuget包名: Util.Validation.
通常不需要手工引用它.
數據註解
數據註解是一種.Net 特性 Attribute,可以在屬性上應用它們.
常用數據註解
下面列出一些常用數據註解,如果還不能滿足需求,可以創建自定義的數據註解.
-
RequiredAttribute 必填項驗證
[Required] 驗證屬性不能是空值.
範例:
public class Test { [Required] public string Name { get; set; } }
[Required] 支持一些參數,可以設置驗證失敗的提示消息.
public class Test { [Required(ErrorMessage = "名稱不能爲空")] public string Name { get; set; } }
-
StringLengthAttribute 字符串長度驗證
[StringLength] 可以對字符串長度進行驗證.
下面的例子驗證 Name 屬性的字符串最大長度爲 5.
public class Test { [StringLength(5)] public string Name { get; set; } }
還可以同時設置最小長度.
下面驗證 Name 屬性字符串最小長度爲1,最大長度爲 5.
public class Test { [StringLength(5,MinimumLength = 1)] public string Name { get; set; } }
-
MaxLengthAttribute 字符串最大長度驗證
[MaxLength] 也可以用來驗證字符串最大長度.
驗證 Name 屬性的字符串最大長度爲 5.
public class Test { [MaxLength(5)] public string Name { get; set; } }
-
MinLengthAttribute 字符串最小長度驗證
[MinLength] 也可以用來驗證字符串最小長度.
驗證 Name 屬性的字符串最小長度爲 1.
public class Test { [MinLength(1)] public string Name { get; set; } }
-
RangeAttribute 數值範圍驗證
[Range] 用於驗證數值範圍.
下面驗證 Money 屬性的值必須在 1 到 5 之間的範圍.
public class Test { [Range( 1, 5 )] public int Money { get; set; } }
-
EmailAddressAttribute 電子郵件驗證
[EmailAddress] 用於驗證電子郵件的格式.
public class Test { [EmailAddress] public int Email { get; set; } }
-
PhoneAttribute 手機號驗證
[Phone] 用於驗證手機號的格式.
public class Test { [Phone] public int Tel { get; set; } }
-
IdCardAttribute 身份證驗證
[IdCard] 用於驗證身份證的格式.
它是一個Util應用框架自定義的數據註解.
public class Test { [IdCard] public int IdCard { get; set; } }
-
UrlAttribute Url驗證
[Url] 用於驗證網址格式.
public class Test { [Url] public int Url { get; set; } }
-
RegularExpressionAttribute 正則表達式驗證
[RegularExpression] 可以使用正則表達式進行驗證.
由於正則表達式比較複雜,對於經常使用的場景,應封裝成自定義數據註解.
下面使用正則表達式驗證身份證,可以封裝到 [IdCard] 數據註解,從而避免正則表達式的複雜性.
public class Test { [RegularExpression( @"(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)" )] public string IdCard { get; set; } }
驗證數據註解
雖然在對象屬性上添加了數據註解,但它們並不會自動觸發驗證.
你可以使用 Asp.Net Core 提供的方法驗證對象上的數據註解.
Util 提供了一個輔助方法 Util.Validation.DataAnnotationValidation.Validate 用來驗證數據註解.
DataAnnotationValidation.Validate 方法接收一個對象參數,只需將要驗證的對象實例傳入即可.
返回類型爲驗證結果集合,包含所有驗證失敗的消息.
public class Test {
[Required]
public string Name { get; set; }
public ValidationResultCollection Validate() {
return DataAnnotationValidation.Validate( this );
}
}
大部分情況下,你並不需要調用 DataAnnotationValidation.Validate 方法驗證數據註解.
實體,值對象,DTO等對象已經內置了 Validate 方法,它們會自動驗證數據註解.
Util Angular UI 數據註解驗證支持
Util Angular UI支持 Razor TagHelper服務端標籤語法.
可以在表單組件使用 Lambda表達式綁定 DTO 對象屬性.
TestDto參數對象 Name 屬性使用 [Required] 設置必填項驗證.
public class TestDto : DtoBase {
[Required]
[Display(Name = "name")]
public string Name { get; set; }
}
Razor 頁面聲明 TestDto 模型, 定義輸入框 util-input,使用 for 屬性綁定到 TestDto 參數對象的 Name 屬性.
@page
@model TestDto
<util-form>
<util-input id="input_Name" for="Name" />
</util-form>
Razor頁面最終會生成html,表單標籤 nz-form-label 添加了 nzRequired 必填項屬性, 輸入框 input 添加了 required 必填項屬性.
<form nz-form>
<nz-form-item>
<nz-form-label [nzRequired]="true">name</nz-form-label>
<nz-form-control [nzErrorTip]="vt_input_Name">
<input #input_Name="" #v_input_Name="xValidationExtend" name="name" nz-input="" x-validation-extend="" [(ngModel)]="model.name" [required]="true" />
<ng-template #vt_input_Name="">{{v_input_Name.getErrorMessage()}}</ng-template>
</nz-form-control>
</nz-form-item>
</form>
通過將DTO數據註解轉換成標籤的驗證屬性,可以讓 Web Api 和 UI 的驗證同步.
自定義驗證
數據註解可以解決一些常見的驗證場景.
但業務上可能需要編寫自定義代碼以更靈活的方式驗證.
Util應用框架定義了一個驗證接口 Util.Validation.IValidation.
IValidation 接口定義了 Validate 方法,執行該方法返回驗證結果集合.
/// <summary>
/// 驗證操作
/// </summary>
public interface IValidation {
/// <summary>
/// 驗證
/// </summary>
ValidationResultCollection Validate();
}
實體,值對象,DTO等對象類型實現了 IValidation 接口,意味着這些對象可以通過標準的 Validate 方法進行驗證.
var entity = new TestEntity();
entity.Validate();
不論對象內部多麼複雜,要驗證它只需調用 Validate 方法即可.
驗證邏輯被完全封裝到對象內部.
DTO自定義驗證
DTO參數對象 Validate 方法默認僅驗證數據註解,如果有錯誤將拋出 Warning 異常.
Warning 異常代表業務錯誤,它的錯誤消息會返回給客戶端.
Validate 是一個虛方法,可以進行重寫.
public class TestDto : DtoBase {
[Required]
public string Name { get; set; }
public override ValidationResultCollection Validate() {
base.Validate();
if ( Name.Contains( "test" ) )
throw new Warning( "名稱不能包含test" );
return ValidationResultCollection.Success;
}
}
TestDto 重寫了 Validate 方法.
首先調用 base.Validate(); ,保證數據註解得到驗證.
如果數據註解驗證通過, 判斷 Name 屬性是否包含 test 字符串,如果包含則拋出 Warning 異常.
由於DTO參數僅用來傳遞數據,不應包含複雜的驗證邏輯,通過重寫 Validate 方法添加簡單自定義驗證邏輯應能滿足需求.
另外, DTO參數驗證失敗,可直接拋出 Warning 異常,讓全局異常處理器進行處理.
領域對象自定義驗證
領域對象包含實體和值對象等.
對於較複雜的業務場景,與DTO不同的是,領域對象可用於業務處理,而不是傳遞數據.
需要爲領域對象提供更多的驗證支持.
領域對象有多種方式進行自定義驗證.
-
重寫 Validate 方法
領域對象最簡單的自定義驗證方式是重寫 Validate 方法,並提供額外的驗證邏輯.
public class TestEntity : AggregateRoot<TestEntity> { public TestEntity() : this( Guid.Empty ) { } public TestEntity( Guid id ) : base( id ) { } [Required] public string Name { get; set; } public override ValidationResultCollection Validate() { base.Validate(); if( Name.Contains( "test" ) ) throw new Warning( "名稱不能包含test" ); return ValidationResultCollection.Success; } }
不過重寫 Validate 驗證方式也存在一些問題.
-
Validate 方法逐漸變得臃腫,代碼穩定性在降低.
-
代碼的清晰度很低,重要的驗證條件屬於業務規則,卻被一堆雜亂的 if else 判斷淹沒了.
-
-
驗證規則
驗證規則 Util.Validation.IValidationRule 代表一個驗證條件,接口定義如下.
/// <summary> /// 驗證規則 /// </summary> public interface IValidationRule { /// <summary> /// 驗證 /// </summary> ValidationResult Validate(); }
可以爲較複雜和重要的驗證條件創建驗證規則對象,把複雜的驗證邏輯封裝起來,並從領域對象中分離出來.
-
創建驗證規則對象
約定: 驗證規則對象需要取一個符合業務驗證規則的名稱, 並以 ValidationRule 結尾,文件放到 ValidationRules 目錄中.
ValidationRule 結尾可能導致名稱過長.
這裏演示就隨便起一個 SampleValidationRule.
驗證規則依賴一些對象才能進行驗證,如何才能獲取依賴?
通過驗證規則對象的構造方法傳入需要的依賴對象.
驗證規則不通過Ioc容器管理,在需要的地方通過 new 創建驗證規則實例.
SampleValidationRule 示例構造方法只接收一個參數,但可以根據需要接收更多依賴項.
實現驗證規則的 Validate 方法.
如果驗證成功返回 ValidationResult.Success.
如果驗證失敗返回驗證結果對象 ValidationResult, 並設置驗證失敗消息.
public class SampleValidationRule : IValidationRule { private readonly TestEntity _entity; public SampleValidationRule( TestEntity entity ) { _entity = entity; } public ValidationResult Validate() { if( _entity.Name.Contains( "test" ) ) return new ValidationResult( "名稱不能包含test" ); return ValidationResult.Success; } }
-
將驗證規則添加到領域對象
領域對象基類定義了 AddValidationRule 方法,用於添加驗證規則對象.
從領域對象外部調用 AddValidationRule 傳入驗證規則.
var entity = new TestEntity(); entity.AddValidationRule( new SampleValidationRule( entity ) );
可以通過工廠方法封裝驗證規則.
public class TestEntity : AggregateRoot<TestEntity> { public TestEntity() : this( Guid.Empty ) { } public TestEntity( Guid id ) : base( id ) { } [Required] public string Name { get; set; } public static TestEntity Create() { var entity = new TestEntity(); entity.AddValidationRule( new SampleValidationRule( entity ) ); return entity; } } var entity = TestEntity.Create(); entity.Validate();
對於比較固定且只依賴領域對象本身的驗證規則,可以在構造方法添加.
public class TestEntity : AggregateRoot<TestEntity> { public TestEntity() : this( Guid.Empty ) { } public TestEntity( Guid id ) : base( id ) { AddValidationRule( new SampleValidationRule( this ) ); } [Required] public string Name { get; set; } }
-
設置驗證處理器
驗證規則僅返回驗證結果,驗證失敗如何處理由驗證處理器決定.
/// <summary> /// 驗證處理器 /// </summary> public interface IValidationHandler { /// <summary> /// 處理驗證錯誤 /// </summary> /// <param name="results">驗證結果集合</param> void Handle( ValidationResultCollection results ); }
領域對象默認的驗證處理器在驗證失敗時拋出 Warning 異常.
你可以設置自己的驗證處理器來替換默認的.
下面定義的 NothingHandler 在驗證失敗時什麼也不做.
/// <summary> /// 驗證失敗,不做任何處理 /// </summary> public class NothingHandler : IValidationHandler { /// <summary> /// 處理驗證錯誤 /// </summary> /// <param name="results">驗證結果集合</param> public void Handle( ValidationResultCollection results ) { } }
調用 SetValidationHandler 方法設置驗證處理器.
var entity = new TestEntity(); entity.AddValidationRule( new SampleValidationRule( entity ) ); entity.SetValidationHandler( new NothingHandler() );
-
驗證攔截器
Util應用框架定義了幾個用於驗證的參數攔截器.
-
NotNullAttribute
-
驗證是否爲 null,如果爲 null 拋出 ArgumentNullException 異常.
-
使用範例:
public interface ITestService : ISingletonDependency { void Test( [NotNull] string value ); }
-
-
NotEmptyAttribute
-
使用 string.IsNullOrWhiteSpace 驗證是否爲空字符串,如果爲空則拋出 ArgumentNullException 異常.
-
使用範例:
public interface ITestService : ISingletonDependency { void Test( [NotEmpty] string value ); }
-
-
ValidAttribute
-
如果對象實現了 IValidation 驗證接口,則自動調用對象的 Validate 方法進行驗證.
-
使用範例:
驗證單個對象.
public interface ITestService : ISingletonDependency { void Test( [Valid] CustomerDto dto ); }
驗證對象集合.
public interface ITestService : ISingletonDependency { void Test( [Valid] List<CustomerDto> dto ); }
-
源碼解析
DataAnnotationValidation 數據註解驗證操作
可以調用 DataAnnotationValidation 的 Validate 方法驗證數據註解.
/// <summary>
/// 數據註解驗證操作
/// </summary>
public static class DataAnnotationValidation {
/// <summary>
/// 驗證
/// </summary>
/// <param name="target">驗證目標</param>
public static ValidationResultCollection Validate( object target ) {
if( target == null )
throw new ArgumentNullException( nameof( target ) );
var result = new ValidationResultCollection();
var validationResults = new List<ValidationResult>();
var context = new ValidationContext( target, null, null );
var isValid = Validator.TryValidateObject( target, context, validationResults, true );
if ( !isValid )
result.AddList( validationResults );
return result;
}
}
ValidationResultCollection 驗證結果集合
ValidationResultCollection 用於收集驗證結果消息.
/// <summary>
/// 驗證結果集合
/// </summary>
public class ValidationResultCollection : List<ValidationResult> {
/// <summary>
/// 初始化驗證結果集合
/// </summary>
public ValidationResultCollection() : this( "" ) {
}
/// <summary>
/// 初始化驗證結果集合
/// </summary>
/// <param name="result">驗證結果</param>
public ValidationResultCollection( string result ) {
if( string.IsNullOrWhiteSpace( result ) )
return;
Add( new ValidationResult( result ) );
}
/// <summary>
/// 成功驗證結果集合
/// </summary>
public static readonly ValidationResultCollection Success = new();
/// <summary>
/// 是否有效
/// </summary>
public bool IsValid => Count == 0;
/// <summary>
/// 添加驗證結果集合
/// </summary>
/// <param name="results">驗證結果集合</param>
public void AddList( IEnumerable<ValidationResult> results ) {
if( results == null )
return;
foreach( var result in results )
Add( result );
}
/// <summary>
/// 輸出驗證消息
/// </summary>
public override string ToString() {
if( IsValid )
return string.Empty;
return this.First().ErrorMessage;
}
}
ThrowHandler 驗證處理器
ThrowHandler 是默認的驗證處理器,在驗證失敗時拋出 Warning 異常.
/// <summary>
/// 驗證失敗,拋出異常
/// </summary>
public class ThrowHandler : IValidationHandler{
/// <summary>
/// 處理驗證錯誤
/// </summary>
/// <param name="results">驗證結果集合</param>
public void Handle( ValidationResultCollection results ) {
if ( results.IsValid )
return;
throw new Warning( results.First().ErrorMessage );
}
}
ValidAttribute 驗證攔截器
ValidAttribute 是一個 Aop 參數攔截器,可以對實現了 IValidation 接口的單個對象或對象集合進行驗證.
/// <summary>
/// 驗證攔截器
/// </summary>
public class ValidAttribute : ParameterInterceptorBase {
/// <summary>
/// 執行
/// </summary>
public override async Task Invoke( ParameterAspectContext context, ParameterAspectDelegate next ) {
Validate( context.Parameter );
await next( context );
}
/// <summary>
/// 驗證
/// </summary>
private void Validate( Parameter parameter ) {
if ( Reflection.IsGenericCollection( parameter.RawType ) ) {
ValidateCollection( parameter );
return;
}
IValidation validation = parameter.Value as IValidation;
validation?.Validate();
}
/// <summary>
/// 驗證集合
/// </summary>
private void ValidateCollection( Parameter parameter ) {
if ( !( parameter.Value is IEnumerable<IValidation> validations ) )
return;
foreach ( var validation in validations )
validation.Validate();
}
}