Four Verification Programming Methods under ASP. NET MVC [Continued]

  • 2021-09-04 23:52:41
  • OfStack

In the article "Four Verification Programming Methods of ASP. NET MVC", we introduced four server-side verification programming methods supported by ASP. NET MVC ("Manual Verification", "Labeling ValidationAttribute Characteristics", "Making Data Types Realize IValidatableObject or IDataErrorInfo"), so how to provide support for these four different programming methods within ASP. NET MVC framework? Next, let's talk about the story behind this.

1. ModelValidator and ModelValidatorProvider

Although the way Model binds varies depending on the data type being validated, ASP. NET MVC always uses an object named ModelValidator to validate the bound data object. All ModelValidator types inherit from the abstract class ModelValidator with the following definition. Its GetClientValidationRules method returns a collection of element type ModelClientValidationRule, and ModelClientValidationRule is an encapsulation of client-side validation rules, which we'll cover in detail in the Client-side validation section.


 public abstract class ModelValidator
 {
 // Other members 
 public virtual IEnumerable<ModelClientValidationRule> GetClientValidationRules();
 public abstract IEnumerable<ModelValidationResult> Validate(object container);
 
 public virtual bool IsRequired { get; }
 }

Validation of the target data is accomplished by calling the Validate method, whose input parameter container represents the object being validated. It is precisely because it is always a complex type object that is validated, which is also called a "container" object with several data members, that the corresponding parameter is named container. The Validate method indicates that the return value of the validation result is not a simple Boolean value, but a collection of ModelValidationResult objects whose element type is defined as follows.


 public class ModelValidationResult
 { 
 public string MemberName { get; set; }
 public string Message { get; set; }
 }

ModelValidationResult has two string type attributes, MemberName, which represents the name of the validated data member, and Message, which represents an error message. 1 Generally, if an ModelValidationResult object comes from validation against the container object itself, its MemberName property is an empty string. For validation against a property of the container object, the property name is the MemberName property of the returned ModelValidationResult object.

The ModelValidationResult collection is returned only if validation fails. If the validated data object complies with all validation rules, the Validate method directly returns Null or an empty ModelValidationResult collection. It is worth mentioning that we sometimes use the static read-only field Success of ValidationResult to indicate the result of successful verification, which is actually Null.


 public class ValidationResult
 {
 // Other members 
 public static readonly ValidationResult Success;
 }

ModelValidator has a Boolean type read-only attribute IsRequired that indicates whether the ModelValidator performs "required" validation on the target data (that is, the validated data member must have a specific value), and this attribute returns False by default. We can define an attribute as a "required" data member by applying the RequiredAttribute feature.

We know that ASP. NET MVC mostly uses Provider mode to provide corresponding components. For example, ModelMetadata describing Model metadata is provided through corresponding ModelMetadataProvider, ModelBinder implementing Model binding can be provided through corresponding ModelBinderProvider, and ModelValidator for Model verification is no exception. Its corresponding provider is ModelValidatorProvider, and its corresponding type inherits from the abstract class ModelValidator Provider with the following definition.


 public abstract class ModelValidatorProvider
 {
 public abstract IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context);
 }

As shown in the above code snippet, the GetValidators method takes two parameters, one is the ModelMetadata object that describes the validated type or attribute Model metadata, and the other is the current ControllerContext. This method returns a collection of element type ModelValidator.

ASP. NET MVC registers the used ModelValidatorProvider with the static type ModelValidatorProviders. As shown in the following code snippet, ModelValidatorProviders has a static read-only attribute, Providers, corresponding to the type ModelValidatorProviderCollection, which represents a global collection of ModelValidatorProvider based on the entire Web application scope.


 public static class ModelValidatorProviders
 { 
 public static ModelValidatorProviderCollection Providers { get; }
 }
 public class ModelValidatorProviderCollection : Collection<ModelValidatorProvider>
 { 
 public ModelValidatorProviderCollection();
 public ModelValidatorProviderCollection(IList<ModelValidatorProvider> list);
 public IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context); 
 }

It is worth mentioning that the ModelMetadata type used to describe the Model metadata has the following GetValidators method, which returns an ModelValidator list created with ModelValidatorProvider registered on the ModelValidatorProviders static attribute Providers.


 public class ModelMetadata
 {
 // Other members 
 public virtual IEnumerable<ModelValidator> GetValidators(ControllerContext context);
 }

The UML shown in the figure at right lists the three core types that make up the Model authentication system. Specific Model validation is always done through a specific ModelValidator, and ModelValidatorProvider, as the provider of ModelValidator, is registered on top of static type ModelValidatorProviders.

2. DataAnnotationsModelValidator

We introduced three different programming methods of "automatic verification" in "Four Validation Programming Methods under ASP. NET MVC". ASP. NET MVC will use different ModelValidator to verify the bound parameters internally. A specific ModelValidator is usually provided by the corresponding ModelValidatorProvider, and the native ModelValidator provided by ASP. NET MVC and the corresponding ModelValidatorProvider will be introduced in detail in the following contents.

Of the three validation programming methods mentioned above, the first (defining validation rules using ValidationAttribute features applied to data types or their data members) is the most commonly used. This declarative authentication solution based on ValidationAttribute features is ultimately accomplished through DataAnnotationsModelValidator. An DataAnnotationsModelValidator object is actually an encapsulation of an ValidationAttribute feature, as can be seen from the definition shown below.


 public class DataAnnotationsModelValidator : ModelValidator
 { 
 public DataAnnotationsModelValidator(ModelMetadata metadata, ControllerContext context, ValidationAttribute attribute);
 public override IEnumerable<ModelClientValidationRule> GetClientValidationRules();
 public override IEnumerable<ModelValidationResult> Validate(object container);
 
 protected internal ValidationAttribute Attribute { get; }
 protected internal string   ErrorMessage { get; }
 public override bool   IsRequired { get; }
 }

The provider of DataAnnotationsModelValidator is DataAnnotationsModelValidatorProvider. For details of ValidationAttribute, DataAnnotationsModelValidator and DataAnnotationsModelValidatorProvider, please refer to the previous three articles.

ASP. NET MVC Model validation based on annotation properties: ValidationAttribute

ASP. NET MVC Verification of Model based on annotation properties: DataAnnotationsModelValidator

ASP. NET MVC Verification of Model Based on Label Properties: DataAnnotationsModelValidatorProvider

3. ValidatableObjectAdapter

If the data type being validated implements the IValidatable interface, ASP. NET MVC automatically invokes the implemented Validate method to validate it, and the ModelValidator created is an ValidatableObjectAdapter object. ValidatableObjectAdapter is defined as follows, and the implementation logic of its Validate method is simple: It directly calls the Validate method of the object being validated and converts the returned ValidationResult object to the ModelValidationResult type.


 public class ValidatableObjectAdapter : ModelValidator
 {
 public ValidatableObjectAdapter(ModelMetadata metadata, ControllerContext context);
 public override IEnumerable<ModelValidationResult> Validate(object container);
 }

Although ValidatableObjectAdapter is inherited from ModelValidator, ASP. NET MVC does not seem to regard it as an ModelValidator in the true sense, but as an "adapter (Adapter)". ASP. NET MVC also does not define a separate ModelValidatorProvider for ValidatableObjectAdapter, and its provider is actually DataAnnotationsModelValidatorProvider mentioned above.

4. DataErrorInfoModelValidator

If we have data types implementing the IDataErrorInfo interface, we can use the Error properties and indexes implemented to provide validation error messages for ourselves and the data members to which we belong. For such a data type, ASP. NET MVC eventually creates an DataErrorInfoModelValidator object to validate it, and DataErrorInfoClassModelValidator and DataErrorInfoPropertyModelValidator are two concrete DataErrorInfoModelValidator.

DataErrorInfoClassModelValidator and DataErrorInfoPropertyModelValidator are two internal types. The former performs validation against the container object itself, so it only needs to extract the error message from the implemented Error property and convert it into the returned ModelValidationResult object. The latter specifically validates a property of the container object and uses the property name in the implementation's Validate method to extract the corresponding error message from the implementation's index and convert it into the returned ModelValidationResult object.


 internal sealed class DataErrorInfoClassModelValidator : ModelValidator
 {
 public DataErrorInfoClassModelValidator(ModelMetadata metadata, ControllerContext controllerContext);
 public override IEnumerable<ModelValidationResult> Validate(object container);
 } 
 internal sealed class DataErrorInfoPropertyModelValidator : ModelValidator
 {
 public DataErrorInfoPropertyModelValidator(ModelMetadata metadata, ControllerContext controllerContext);
 public override IEnumerable<ModelValidationResult> Validate(object container);
 }

ASP. NET MVC ultimately provides both types of DataErrorInfoModelValidator using an DataErrorInfoModelValidatorProvider with the following definition. For the GetValidators method it implements, if the type of the object being validated implements the IDataErrorInfo interface, it creates an DataErrorInfoClassModelValidator object and adds it to the returned ModelValidator list. If a property value of the container type is validated and the container type implements the IDataErrorInfo interface, it creates an DataErrorInfoPropertyModelValidator object and adds it to the returned ModelValidator list.


 public class DataErrorInfoModelValidatorProvider : ModelValidatorProvider
 {
 public override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context);
 }

Related articles: