programming4us
programming4us
ENTERPRISE

Microsoft Enterprise Library : Banishing Validation Complication - Diving in With Some Simple Examples (part 2)

- How To Install Windows Server 2012 On VirtualBox
- How To Bypass Torrent Connection Blocking By Your ISP
- How To Install Actual Facebook App On Kindle Fire
11/15/2013 6:55:01 PM

2. Using Validation Attributes

Having seen how you can use rule sets defined in configuration, and how you can display the results of a validation process, we can move on to explore the other ways you can define validation rules in your applications. The example application contains two classes that contain validation attributes and a self-validation method. The AttributedProduct class contains Validation block attributes, while the AnnotatedProduct class contains data annotation attributes.

Using the Validation Block Attributes

The example, Using Validation Attributes and Self-Validation, demonstrates use of the Validation block attributes. The AttributedProduct class has a range of different Validation block attributes applied to the properties of the class, applying the same rules as the MyRuleset rule set defined in configuration and used in the previous examples.

For example, the ID property carries attributes that add a Not Null validator, a String Length validator, and a Regular Expression validator. These validation rules are, by default, combined with an And operation, so all of the conditions must be satisfied if validation will succeed for the value of this property.

[NotNullValidator(MessageTemplate = "You must specify a product ID.")]
[StringLengthValidator(6, RangeBoundaryType.Inclusive,
6, RangeBoundaryType.Inclusive,
MessageTemplate = "Product ID must be {3} characters.")]
[RegexValidator("[A-Z]{2}[0-9]{4}",
MessageTemplate = "Product ID must be 2 letters and 4 numbers.")]
public string ID { get; set; }

Other validation attributes used within the AttributedProduct class include an Enum Conversion validator that ensures that the value of the ProductType property is a member of the ProductType enumeration, shown here. Note that the token {3} for the String Length validator used in the previous section of code is the lower bound value, while the token {3} for the Enum Conversion validator is the name of the enumeration it is comparing the validated value against.

[EnumConversionValidator(typeof(ProductType),
MessageTemplate = "Product type must be a value from the '{3}' enumeration.")]
public string ProductType { get; set; }

Combining Validation Attribute Operations

One other use of validation attributes worth a mention here is the application of a composite validator. By default, multiple validators defined for a member are combined using the And operation. If you want to combine multiple validation attributes using an Or operation, you must apply the ValidatorComposition attribute first and specify CompositionType.Or. The results of all validation operations defined in subsequent validation attributes are combined using the operation you specify for composition type.

The example class uses a ValidatorComposition attribute on the nullable DateDue property to combine a Not Null validator and a Relative DateTime validator. The top-level error message that the user will see for this property (when you do not recursively iterate through the contents of the ValidationResults) is the message from the ValidatorComposition attribute.

[ValidatorComposition(CompositionType.Or,
MessageTemplate = "Date due must be between today and six months time.")]
[NotNullValidator(Negated = true,
MessageTemplate = "Value can be NULL or a date.")]
[RelativeDateTimeValidator(0, DateTimeUnit.Day, 6, DateTimeUnit.Month,
MessageTemplate = "Value can be NULL or a date.")]
public DateTime? DateDue { get; set; }

If you want to allow null values for a member of a class, you can apply the IgnoreNulls attribute.

Applying Self-Validation

Some validation rules are too complex to apply using the validators provided with the Validation block or the .NET Data Annotation validation attributes. It may be that the values you need to perform validation come from different places, such as properties, fields, and internal variables, or involve complex calculations.

In this case, you can define self-validation rules as methods within your class (the method names are irrelevant). We've implemented a self-validation routine in the AttributedProduct class in the example application. The method simply checks that the combination of the values of the InStock, OnOrder, and DateDueAttributedProduct class to see the implementation. properties meets predefined rules. You can examine the code within the

Results of the Validation Operation

The example creates an invalid instance of the AttributedProduct class shown above, validates it, and then displays the results of the validation process. It creates the following output, though we have removed some of the repeated output here for clarity. You can run the example yourself to see the full results.

Created and populated a valid instance of the AttributedProduct class.
There were no validation errors.
Created and populated an invalid instance of the AttributedProduct class.
The following 7 validation errors were detected:
+ Target object: AttributedProduct, Member: ID
- Detected by: RegexValidator
- Validated value was: '12075'
- Message: 'Product ID must be 2 capital letters and 4 numbers.'
...
...
+ Target object: AttributedProduct, Member: ProductType
- Detected by: EnumConversionValidator
- Validated value was: 'FurryThings'
- Message: 'Product type must be a value from the 'ProductType' enumeration.'
...
...
+ Target object: AttributedProduct, Member: DateDue
- Detected by: OrCompositeValidator
- Validated value was: '19/08/2010 15:55:16'
- Message: 'Date due must be between today and six months time.'
+ Nested validators:
- Detected by: RelativeDateTimeValidator
- Validated value was: '18/11/2010 13:36:02'
- Message: 'Value can be NULL or a date.'
- Detected by: NotNullValidator
- Validated value was: '18/11/2010 13:36:02'
+ Target object: AttributedProduct, Member: ProductSelfValidation
- Detected by: [none]
- Tag value:
- Message: 'Total inventory (in stock and on order) cannot exceed 100 items.'

Notice that the output includes the name of the type and the name of the member (property) that was validated, as well as displaying type of validator that detected the error, the current value of the member, and the message. For the DateDue property, the output shows the two validators nested within the Or Composite validator. Finally, it shows the result from the self-validation method. The values you see for the self-validation are those the code in the self-validation method specifically added to the Validation Results instance.

Validating Subclass Types

While discussing validation through attributes, we should briefly touch on the factors involved when you validate a class that inherits from the type you specified when creating the validator you use to validate it. For example, if you have a class named SaleProduct that derives from Product, you can use a validator defined for the Product class to validate instances of the SaleProduct class. The Validate method will also apply any relevant rules defined in attributes in both the SaleProductProduct base class. class and the

If the derived class inherits a member from the base class and does not override it, the validators for that member defined in the base class apply to the derived class. If the derived class inherits a member but overrides it, the validators defined in the base class for that member do not apply to the derived class.

Validating Properties that are Objects

In many cases, you may have a property of your class defined as the type of another class. For example, your OrderLine class is likely to have a property that is a reference to an instance of the Product class. It's common for this property to be defined as a base type or interface type, allowing you to set it to an instance of any class that inherits or implements the type specified for the property.

You can validate such a property using an ObjectValidator attribute within the class. However, by default, the validator will validate the property using rules defined for the type of the property—in this example the type IProduct. If you want the validation to take place based on the actual type of the object that is currently set as the value of the property, you can add the ValidateActualType parameter to the ObjectValidator attribute, as shown here.

public class OrderLine {
[ObjectValidator(ValidateActualType=true)]
public IProduct OrderProduct { get; set; }
...
}

Using Data Annotation Attributes

The System.ComponentModel.DataAnnotations namespace in the .NET Framework contains a series of attributes that you can add to your classes and class members to signify metadata for these classes and members. They include a range of validation attributes that you can use to apply validation rules to your classes in much the same way as you can with the Validation block attributes. For example, the following shows how you can use the Range attribute to specify that the value of the property named OnOrder must be between 0 and 50.

[Range(0, 50, ErrorMessage = "Quantity on order must be between 0 and 50.")]
public int OnOrder { get; set; }

Compared to the validation attributes provided with the Validation block, there are some limitations when using the validation attributes from the DataAnnotations namespace:

  • The range of supported validation operations is less comprehensive, though there are some new validation types available in .NET Framework 4.0 that extend the range. However, some validation operations such as property value comparison, enumeration membership checking, and relative date and time comparison are not available when using data annotation validation attributes.

  • There is no capability to use Or composition, as there is with the Or Composite validator in the Validation block. The only composition available with data annotation validation attributes is the And operation.

  • You cannot specify rule sets names, and so all rules implemented with data annotation validation attributes belong to the default rule set.

  • There is no simple built-in support for self-validation, as there is in the Validation block.

You can, of course, include both data annotation and Validation block attributes in the same class if you wish, and implement self-validation using the Validation block mechanism in a class that contains data annotation validation attributes. The validation methods in the Validation block will process both types of attributes.

An Example of Using Data Annotations

The class named AnnotatedProduct contains data annotation attributes to implement the same rules as those applied by Validation block attributes in the Attributed Product class (which you saw in the previous example). However, due to the limitations with data annotations, the self-validation method within the class has to do more work to achieve the same validation rules.

For example, it has to check the minimum value of some properties as the data annotation attributes in version 3.5 of the .NET Framework only support validation of the maximum value (in version 4.0, they do support minimum value validation). It also has to check the value of the DateDue property to ensure it is not more than six months in the future, and that the value of the ProductType property is a member of the ProductType enumeration.

To perform the enumeration check, the self-validation method creates an instance of the Validation block Enum Conversion validator programmatically, and then calls its DoValidate method (which allows you to pass in all of the values required to perform the validation). The code passes to this method the value of the ProductType property, a reference to the current object, the name of the enumeration, and a reference to the ValidationResults instance being use to hold all of the validation errors.

var enumConverterValidator = new EnumConversionValidator(typeof(ProductType),
"Product type must be a value from the '{3}' enumeration.");
enumConverterValidator.DoValidate(ProductType, this, "ProductType", results);

The code that creates the object to validate, validates it, and then displays the results is the same as you saw in the previous example, with the exception that it creates an invalid instance of the AnnotatedProduct class, rather than the AttributedProduct class. The result when you run this example is also similar to that of the previous example, but with a few exceptions. We've listed some of the output here.

Created and populated an invalid instance of the AnnotatedProduct class.
The following 7 validation errors were detected:
+ Target object: AnnotatedProduct, Member: ID
- Detected by: [none]
- Tag value:
- Message: 'Product ID must be 6 characters.'
...
+ Target object: AnnotatedProduct, Member: ProductSelfValidation
- Detected by: [none]
- Tag value:
- Message: 'Total inventory (in stock and on order) cannot exceed 100 items.'
+ Target object: AnnotatedProduct, Member: ID
- Detected by: ValidationAttributeValidator
- Message: 'Product ID must be 2 capital letters and 4 numbers.'
+ Target object: AnnotatedProduct, Member: InStock
- Detected by: ValidationAttributeValidator
- Message: 'Quantity in stock cannot be less than 0.'

You can see that validation failures detected for data annotations contain less information than those detected for the Validation block attributes, and validation errors are shown as being detected by the ValidationAttributeValidator class—the base class for data annotation validation attributes. However, where we performed additional validation using the self-validation method, there is extra information available.

Defining Attributes in Metadata Classes

In some cases, you may want to locate your validation attributes (both Validation block attributes and .NET Data Annotation validation attributes) in a file separate from the one that defines the class that you will validate. This is a common scenario when you are using tools that generate the class files, and would therefore overwrite your validation attributes. To avoid this you can locate your validation attributes in a separate file that forms a partial class along with the main class file. This approach makes use of the Meta dataType attribute from the System.ComponentModel.DataAnnotations namespace.

You apply the MetadataType attribute to your main class file, specifying the type of the class that stores the validation attributes you want to apply to your main class members. You must define this as a partial class, as shown here.

[MetadataType(typeof(ProductMetadata))]
public partial class Product
{
... Existing members defined here, but without attributes or annotations ...
}

You then define the metadata type as a normal class, except that you declare simple properties for each of the members to which you want to apply validation attributes. The actual type of these properties is not important, and is ignored by the compiler. The accepted approach is to declare them all as type Object. As an example, if your Product class contains the ID and Description properties, you can define the metadata class for it, as shown here.

public class ProductMetadata
{
[Required(ErrorMessage = "ID is required.")]
[RegularExpression("[A-Z]{2}[0-9]{4}",
ErrorMessage = "Product ID must be 2 capital letters and 4 numbers.")]
public object ID;
[StringLength(100, ErrorMessage = "Description must be less than 100 chars.")]
public object Description;
}

Specifying the Location of Validation Rules

When you use a validator obtained from the ValidatorFactory, as we've done so far in the example, validation will take into account any applicable rule sets defined in configuration and in attributes and self-validation methods found within the target object. However, you can resolve different factory types if you want to perform validation using only rule sets defined in configuration, or using only attributes and self-validation. The specialized types of factory you can use are:

  • ConfigurationValidatorFactory . This factory creates validators that only apply rules defined in a configuration file, or in a configuration source you provide. By default it looks for configuration in the default configuration file (App. config or Web.config). However, you can create an instance of a class that implements the IConfigurationSource interface, populate it with configuration data from another file or configuration storage media, and use this when you create this validator factory.

  • AttributeValidatorFactory . This factory creates validators that only apply rules defined in Validation block attributes located in the target class, and rules defined through self-validation methods.

  • ValidationAttributeValidatorFactory . This factory creates validators that only apply rules defined in .NET Data Annotations validation attributes.

For example, to obtain a validator for the Product class that validates using only attributes and self-validation methods within the target instance, and validate an instance of this class, you resolve an instance of the AttributeValidatorFactory from the container, as shown here.

AttributeValidatorFactory attrFactory =
EnterpriseLibraryContainer.Current.GetInstance<AttributeValidatorFactory>();
Validator<Product> pValidator = attrFactory.CreateValidator<Product>();
ValidationResults valResults = pValidator.Validate(myProduct);
Other  
  •  Microsoft Enterprise Library : Banishing Validation Complication - How Do I Use The Validation Block?
  •  Microsoft Enterprise Library : Banishing Validation Complication - What Does the Validation Block Do? (part 2)
  •  Microsoft Enterprise Library : Banishing Validation Complication - What Does the Validation Block Do? (part 1)
  •  Microsoft Enterprise Library : A Cache Advance for Your Applications - How Do I Use the Caching Block (part 4) - Refreshing the Cache, Loading the Cache
  •  Microsoft Enterprise Library : A Cache Advance for Your Applications - How Do I Use the Caching Block (part 3) - Removing Items from and Flushing the Cache
  •  Microsoft Enterprise Library : A Cache Advance for Your Applications - How Do I Use the Caching Block (part 2)
  •  Microsoft Enterprise Library : A Cache Advance for Your Applications - How Do I Use the Caching Block (part 1) - Adding Items to and Retrieving Items from the Cache
  •  Microsoft Enterprise Library : A Cache Advance for Your Applications - How Do I Configure the Caching Block?
  •  Microsoft Visual Studio 2010 : Data Parallelism - Unrolling Sequential Loops into Parallel Tasks (part 4) - Handling Exceptions
  •  Microsoft Visual Studio 2010 : Data Parallelism - Unrolling Sequential Loops into Parallel Tasks (part 3) - Interrupting a Loop
  •  
    Top 10
    - Microsoft Visio 2013 : Adding Structure to Your Diagrams - Finding containers and lists in Visio (part 2) - Wireframes,Legends
    - Microsoft Visio 2013 : Adding Structure to Your Diagrams - Finding containers and lists in Visio (part 1) - Swimlanes
    - Microsoft Visio 2013 : Adding Structure to Your Diagrams - Formatting and sizing lists
    - Microsoft Visio 2013 : Adding Structure to Your Diagrams - Adding shapes to lists
    - Microsoft Visio 2013 : Adding Structure to Your Diagrams - Sizing containers
    - Microsoft Access 2010 : Control Properties and Why to Use Them (part 3) - The Other Properties of a Control
    - Microsoft Access 2010 : Control Properties and Why to Use Them (part 2) - The Data Properties of a Control
    - Microsoft Access 2010 : Control Properties and Why to Use Them (part 1) - The Format Properties of a Control
    - Microsoft Access 2010 : Form Properties and Why Should You Use Them - Working with the Properties Window
    - Microsoft Visio 2013 : Using the Organization Chart Wizard with new data
    REVIEW
    - First look: Apple Watch

    - 3 Tips for Maintaining Your Cell Phone Battery (part 1)

    - 3 Tips for Maintaining Your Cell Phone Battery (part 2)
    programming4us programming4us
    programming4us
     
     
    programming4us