🚫 Ad Blocker Detected

Please disable your AD blocker to continue using this site. Ads help us keep the content free! please press keyboard F5 to refresh page after disabled AD blocker

請關閉廣告攔截器以繼續使用本網站。廣告有助於我們保證內容免費。謝謝! 關閉後請按 F5 刷新頁面

0%

探討Model驗證標籤(ValidationAttribute) (第21天)

Agenda

前言

CachedDataAnnotationsMetadataAttributes這個類別攔截某些標籤可被攔截驗證.

本篇會介紹另一個可以客製化驗證ValidationAttribute,常用驗證標籤並講述是如何參數屬性是如何取得這個標籤和使用過程.

我有做一個可以針對於Asp.net MVC Debugger的專案,只要下中斷點就可輕易進入Asp.net MVC原始碼.

ValidationAttribute

ValidationAttribute類別在System.ComponentModel.DataAnnotations命名空間下.

我們可以建立一個類別繼承ValidationAttributebool IsValid(object value)重載方法來製做我們客制化驗證機制.

IsValid方法有一個Bool回傳值回傳true代表驗證通過false反之

1
2
3
4
5
6
7
8
public abstract class ValidationAttribute : Attribute{
//...
public virtual bool IsValid(object value)
{

}
//....
}
  • RegularExpressionAttribute
  • StringLengthAttribute
  • RangeAttribute

如果查看上面幾個標籤原始碼可發現這幾個標籤都是繼承於一個ValidationAttribute類別(這也是為什麼我們可以透過繼承ValidationAttribute來擴充自己驗證方式).

ModelValidatorProviders

ModelValidatorProviders提供

  • DataAnnotationsModelValidatorProvider
  • DataErrorInfoModelValidatorProvider
  • ClientDataTypeModelValidatorProvider

ModelValidatorProviderCollection是一個ModelValidatorProvider集合,可對於此集合加入ModelValidatorProvider物件.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static class ModelValidatorProviders
{
private static readonly ModelValidatorProviderCollection _providers = new ModelValidatorProviderCollection()
{
new DataAnnotationsModelValidatorProvider(),
new DataErrorInfoModelValidatorProvider(),
new ClientDataTypeModelValidatorProvider()
};

public static ModelValidatorProviderCollection Providers
{
get { return _providers; }
}
}
public class ModelValidatorProviderCollection : Collection<ModelValidatorProvider>
{
//...
}

如果我們需要加入一個客製化ModelValidatorProvider,可以直接將相應物件新增到ModelValidatorProvidersProviders集合中。

ModelValidator

所有的參數驗證都繼承自抽像類型ModelValidator,這個抽象類別有幾個重要成員.

  • IsRequired:表示該ModelValidator是否是對目標屬性進行必要性驗證,默認是False
  • GetClientValidationRules方法:ModelClientValidationRule是對客戶端驗證規則的封裝,我們會在進行客戶端驗證時對其進行詳細介紹。
  • Validate方法:對於屬性實施驗證,驗證完後回傳一個ModelValidationResult的集合物件.
1
2
3
4
5
6
7
8
9
public abstract class ModelValidator
{
///....
public abstract IEnumerable<ModelValidationResult> Validate(object container);

public virtual IEnumerable<ModelClientValidationRule> GetClientValidationRules();

public virtual bool IsRequired { get; }
}

CompositeModelValidator

從類別名稱可看出CompositeModelValidator,實並不是一個真正對Model物件實施驗證ModelValidator,它是一系列ModelValidator組合,根據基於Model本身類型及其屬性的Model元數據動態的取得ModelValidator(通過調用ModelMetadata.GetValidators方法)對Model參數實施驗證。

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
private class CompositeModelValidator : ModelValidator
{
//...

public override IEnumerable<ModelValidationResult> Validate(object container)
{
bool propertiesValid = true;

ModelMetadata[] properties = Metadata.PropertiesAsArray;

for (int propertyIndex = 0; propertyIndex < properties.Length; propertyIndex++)
{
ModelMetadata propertyMetadata = properties[propertyIndex];
foreach (ModelValidator propertyValidator in propertyMetadata.GetValidators(ControllerContext))
{
foreach (ModelValidationResult propertyResult in propertyValidator.Validate(Metadata.Model))
{
propertiesValid = false;
yield return CreateSubPropertyResult(propertyMetadata, propertyResult);
}
}
}

if (propertiesValid)
{
foreach (ModelValidator typeValidator in Metadata.GetValidators(ControllerContext))
{
foreach (ModelValidationResult typeResult in typeValidator.Validate(container))
{
yield return typeResult;
}
}
}
}
}

下圖是ModelValidator、ModelValidatorProvider、ModelValidatorProvidersUML關係圖

UML_Model

DataAnnotationsModelValidator

ModelValidator物件是用於進行Model參數驗證的模組類別中的ValidationAttribute透過建構子設定檢驗的標籤.

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
public class DataAnnotationsModelValidator : ModelValidator
{
public DataAnnotationsModelValidator(ModelMetadata metadata, ControllerContext context, ValidationAttribute attribute);
protected internal ValidationAttribute Attribute { get; private set; }

public override bool IsRequired
{
get { return Attribute is RequiredAttribute; }
}

internal static ModelValidator Create(ModelMetadata metadata, ControllerContext context, ValidationAttribute attribute)
{
return new DataAnnotationsModelValidator(metadata, context, attribute);
}

public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
{
//....
}

public override IEnumerable<ModelValidationResult> Validate(object container)
{
//....
}
}

DataAnnotationsModelValidator

DataAnnotationsModelValidator<TAttribute>這個泛型類別有一個合約TAttribute必須為ValidationAttribute

DataAnnotationsModelValidator<TAttribute>的子類。當我們將這些ValidationAttribute應用到Model型別時,真正用於Model參數驗證是ModelValidator的轉接器類別

在這裡使用轉接器模式把每個繼承ValidationAttribute標籤適配給一個ModelValidator物件.

例如下面程式碼每個ModelValidator都有自己的轉接器類別.

  • RangeAttributeAdapter = RangeAttribute
  • RequiredAttributeAdapter = RequiredAttribute
  • StringLengthAttributeAdapter = StringLengthAttribute
  • RegularExpressionAttributeAdapter = RegularExpressionAttribute
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
public class
DataAnnotationsModelValidator<TAttribute> : DataAnnotationsModelValidator
where TAttribute : ValidationAttribute
{
//....
}

public class RequiredAttributeAdapter : DataAnnotationsModelValidator<RequiredAttribute>
{
//....
}

public class RangeAttributeAdapter : DataAnnotationsModelValidator<RangeAttribute>
{
//....
}

public class RegularExpressionAttributeAdapter : DataAnnotationsModelValidator< RegularExpressionAttribute>
{
//....
}

public class StringLengthAttributeAdapter : DataAnnotationsModelValidator< StringLengthAttribute>
{
//....
}

DataAnnotationsModelValidatorProvider

有一個委派DataAnnotationsModelValidationFactory主要可以存放一個執行動作且回傳一個ModelValidator

1
2
3
public delegate ModelValidator DataAnnotationsModelValidationFactory(ModelMetadata metadata, ControllerContext context, ValidationAttribute attribute);

public delegate ModelValidator DataAnnotationsValidatableObjectAdapterFactory(ModelMetadata metadata, ControllerContext context);
  • AttributeFactories:一個字典集合從屬性載入預設擁有ValidationAttribute標籤(上面介紹的轉接器RegularExpressionAttributeAdapter….)
  • DefaultAttributeFactory:如果從AttributeFactories這個字典無法取得繼承ValidationAttribute標籤(自己客製化)就藉由DataAnnotationsModelValidator取得.
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
internal static DataAnnotationsModelValidationFactory DefaultAttributeFactory =
(metadata, context, attribute) => new DataAnnotationsModelValidator(metadata, context, attribute);

internal static DataAnnotationsValidatableObjectAdapterFactory DefaultValidatableFactory =
(metadata, context) => new ValidatableObjectAdapter(metadata, context);

internal static Dictionary<Type, DataAnnotationsModelValidationFactory> AttributeFactories = BuildAttributeFactoriesDictionary();

protected override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes)
{
//..
foreach (ValidationAttribute attribute in attributes.OfType<ValidationAttribute>())
{
DataAnnotationsModelValidationFactory factory;
if (!AttributeFactories.TryGetValue(attribute.GetType(), out factory))
{
factory = DefaultAttributeFactory;
}
results.Add(factory(metadata, context, attribute));
}

// Produce a validator if the type supports IValidatableObject
if (typeof(IValidatableObject).IsAssignableFrom(metadata.ModelType))
{
DataAnnotationsValidatableObjectAdapterFactory factory;
if (!ValidatableFactories.TryGetValue(metadata.ModelType, out factory))
{
factory = DefaultValidatableFactory;
}
results.Add(factory(metadata, context));
}
//....
}

最後從Model元數據中載入所有ValidationAttribute驗證標籤後就會在DefaultModelBinder.BindProperties呼叫方法時被觸發驗證

小結:

在整個Model-Binding流程中,算是蠻複雜的石頭希望可以跟大家簡述一些綁定概念和做法.

ValidationAttribute是舊有的類別,MVC利用一系列手法將她很好融入系統中.

原本ValidationAttribute被多個標籤繼承,透過DataAnnotationsModelValidator<TAttribute>設計(讓我驚豔),變成一個1對1關係(每個ValidationAttribute都有自己的轉接器物件),之後就可以在BuildAttributeFactoriesDictionary()更方便使用.

__此文作者__:Daniel Shih(石頭)
__此文地址__: https://isdaniel.github.io/ithelp-day21/
__版權聲明__:本博客所有文章除特別聲明外,均採用 CC BY-NC-SA 3.0 TW 許可協議。轉載請註明出處!

如果本文對您幫助很大,可街口支付斗內鼓勵石頭^^