Delen via


Chapter 6 - Banishing Validation Complication

Introduction

If you happen to live in the U.S. and I told you that the original release date of version 2.0 of Enterprise Library was 13/01/2006, you'd wonder if I'd invented some new kind of calendar. Perhaps they added a new month to the calendar without you noticing (which I'd like to call Plutember in honor of the now-downgraded ninth planet). Of course, in many other countries around the world, 13/01/2006 is a perfectly valid date in January. This validation issue is well known and the solution probably seems obvious, but I once worked with an application that used dates formatted in the U.S. mm/dd/yyyy pattern for the filenames of reports it generated, and defaulted to opening the report for the previous day when you started the program. Needless to say, on my machines set up to use U.K. date formats, it never did manage to find the previous day's report.

Even better, when I tried to submit a technical support question on their Web site, it asked me for the date I purchased the software. The JavaScript validation code in the Web page running on my machine checked the format of my answer (27/04/2008) and accepted it. But the server refused to believe that there are twenty seven months in a year, and blocked my submission. I had to lie and say I purchased it on May 1 instead.

The problem is that validation can be an onerous task, especially when you need to do it in so many places in your applications, and for a lot of different kinds of values. It's extremely easy to end up with repeated code scattered throughout your classes, and yet still leave holes where unexpected input can creep into your application and possibly cause havoc.

Robust validation can help to protect your application against malicious users and dangerous input (including SQL injection attacks), ensure that it processes only valid data and enforces business rules, and improve responsiveness by detecting invalid data before performing expensive processing tasks.

So, how do you implement comprehensive and centralized validation in your applications? One easy solution is to take advantage of the Enterprise Library Validation block. The Validation block is a highly flexible solution that allows you to specify validation rules in configuration, with attributes, or in code, and have that validation applied to objects, method parameters, fields, and properties. It even includes features that integrate with Windows® Forms, Windows Presentation Foundation (WPF), ASP.NET, and Windows Communication Foundation (WCF) applications to present validation errors within the user interface or have them handled automatically by the service.

Techniques for Validation

Before we explore the Validation block, it's worth briefly reviewing some validation good practices. In general, there are three factors you should consider: where you are going to perform validation, what data should you validate, and how you will perform this validation.

Where Should I Validate?

Validation should, of course, protect your entire application. However, it is often the case that you need to apply validation in more than one location. If your application consists of layers, distributed services, or discrete components, you probably need to validate at each boundary. This is especially the case where individual parts of the application could be called from more than one place (for example, a business layer that is used by several user interfaces and other services).

It is also a really good idea to validate at trust boundaries, even if the components on each side of the boundary are not physically separated. For example, your business layer may run under a different trust level or account context than your data layer (even if they reside on the same machine). Validation at this boundary can prevent code that is running in low trust and which may have been compromised, from submitting invalid data to code that runs in higher trust mode.

Finally, a common scenario: validation in the user interface. Validating data on the client can improve application responsiveness, especially if the UI is remote from the server. Users do not have to wait for the server to respond when they enter or submit invalid data, and the server does not need to attempt to process data that it will later reject. However, remember that even if you do validate data on the client or in the UI you must always revalidate on the server or in the receiving service. This protects against malicious users who may circumvent client-side validation and submit invalid data.

What Should I Validate?

To put it simply, everything. Or, at least any input values you will use in your application that may cause an error, involve a security risk, or could result in incorrect processing. Remember that Web page and service requests may contain data that the user did not enter directly, but could be used in your application. This can include cookies, header information, credentials, and context information that the server may use in various ways. Treat all input data as suspicious until you have validated it.

How Should I Validate?

For maximum security, your validation process should be designed to accept only data that you can directly determine to be valid. This approach is known as positive validation and generally uses an allow list that specifies data that satisfies defined criteria, and rejects all other data. Examples are rules that check if a number is between two predefined limits, or if the submitted value is within a list of valid values. Use this approach whenever possible.

The alternative and less-secure approach is to use a block list containing values that are not valid. This is called negative validation, and generally involves accepting only data that does not meet specific criteria. For example, as long as a string does not contain any of the specified invalid characters, it would be accepted. You should use this approach cautiously and as a secondary line of defense, because it is very difficult to create a complete list of criteria for all known invalid input—which may allow malicious data to enter your system.

Finally, consider sanitizing data. While this is not strictly a validation task, you can as an extra precaution attempt to eliminate or translate characters in an effort to make the input safe. However, do not rely on this technique alone because, as with negative validation, it can be difficult to create a complete list of criteria for all known invalid input unless there is a limited range of invalid values.

What Does the Validation Block Do?

The Validation block consists of a broad range of validators, plus a mechanism that executes these validators and collects and correlates the results to provide an overall validation result (true/valid or false/invalid). The Validation block can use individual attributes applied to classes and class members that the application uses (both the validation attributes provided with the Validation block and data annotation attributes from the System.ComponentModel.DataAnnotations namespace), in addition to rule sets defined in the configuration of the block, which specify the validation rules to apply.

The typical scenario when using the Validation block is to define rule sets through configuration or attributes applied to your classes. Each rule set specifies the set of individual validators and combinations of these validators that implement the validation rules you wish to apply to that class. Then you use a ValidatorFactory (or one of the equivalent implementations of this factory) to create a type validator for the class, optionally specifying the rule set it should use. If you don’t specify a rule set, it uses the default rules. Then you can call the Validate method of the type validator. This method returns an instance of the ValidationResults class that contains details of all the validation errors detected. Figure 1 illustrates this process.

Figure 1

An overview of the validation process

Ff953182.d97b6338-593f-4321-b9e7-2e3f6ca2a431-thumb(en-us,PandP.50).png

When you use a rule set to validate an instance of a specific type or object, the block can apply the rules to:

  • The type itself
  • The values of public readable properties
  • The values of public fields
  • The return values of public methods that take no parameters

Note

Notice that you can validate the values of method parameters and the return type of methods that take parameters when that method is invoked, only by using the validation call handler (which is part of the Validation block) in conjunction with the Unity dependency injection and interception mechanism. The validation call handler will validate the parameter values based on the rules for each parameter type and any validation attributes applied to the parameters. We don’t cover the use of the validation call handler in this guide, as it requires you to be familiar with Unity interception techniques. For more information about interception and the validation call handler, see the Unity interception documentation installed with Enterprise Library or available online at https://go.microsoft.com/fwlink/?LinkId=188875.

Alternatively, you can create individual validators programmatically to validate specific values, such as strings or numeric values. However, this is not the main focus of the block—though we do include samples in this chapter that show how you can use individual validators.

In addition, the Validation block contains features that integrate with Windows® Forms, Windows Presentation Foundation (WPF), ASP.NET, and Windows Communication Foundation (WCF) applications. These features use a range of different techniques to connect to the UI, such as a proxy validator class based on the standard ASP.NET Validator control that you can add to a Web page, a ValidationProvider class that you can specify in the properties of Windows Forms controls, a ValidatorRule class that you can specify in the definition of WPF controls, and a behavior extension that you can specify in the <system.ServiceModel> section of your WCF configuration. You'll see more details of these features later in this chapter.

The Range of Validators

Validators implement functionality for validating Microsoft® .NET Framework data types. The validators included with the Validation block fall into three broad categories: value validators, composite validators, and type (object) validators. The value validators allow you to perform specific validation tests such as verifying:

  • The length of a string, or the occurrence of a specified set of characters within it.
  • Whether a value lies within a specified range, including tests for dates and times relative to a specified date/time.
  • Whether a value is one of a specified set of values, or can be converted to a specific data type or enumeration value.
  • Whether a value is null, or is the same as the value of a specific property of an object.
  • Whether the value matches a specified regular expression.

The composite validators are used to combine other validators when you need to apply more complex validation rules. The Validation block includes an AND validator and an OR validator, each of which acts as a container for other validators. By nesting these composite validators in any combination and populating them with other validators, you can create very comprehensive and very specific validation rules.

Table 1 describes the complete set of validators provided with the Validation block.

Table 1

The validators provided with the Validation block

Validator type

Validator name

Description

Value Validators

Contains Characters Validator

Checks that an arbitrary string, such as a string entered by a user in a Web form, contains any or all of the specified characters.

Date Time Range Validator

Checks that a DateTime object falls within a specified range.

Domain Validator

Checks that a value is one of the specified values in a specified set.

Enum Conversion Validator

Checks that a string can be converted to a value in a specified enumeration type.

Not Null Validator

Checks that the value is not null.

Property Comparison Validator

Compares the value to be checked with the value of a specified property.

Range Validator

Checks that a value falls within a specified range.

Regular Expression Validator

Checks that the value matches the pattern specified by a regular expression.

Relative Date Time Validator

Checks that the DateTime value falls within a specified range using relative times and dates.

String Length Validator

Checks that the length of the string is within the specified range.

Type Conversion Validator

Checks that a string can be converted to a specific type.

Type

Validators

Object Validator

Causes validation to occur on an object reference. All validators defined for the object's type will be invoked.

Object Collection Validator

Checks that the object is a collection of the specified type and then invokes validation on each element of the collection.

Composite Validators

And Composite Validator

Requires all validators that make up the composite validator to be true.

Or Composite Validator

Requires at least one of the validators that make up the composite validator be true.

Single Member Validators

Field Value Validator

Validates a field of a type.

Method Return Value Validator

Validates the return value of a method of a type.

Property Value Validator

Validates the value of a property of a type.

For more details on each validator, see the documentation installed with Enterprise Library or available online at https://go.microsoft.com/fwlink/?LinkId=188874. You will see examples that use many of these validators throughout this chapter.

Validating with Attributes

If you have full access to the source code of your application, you can use attributes within your classes to define your validation rules. You can apply validation attributes in the following ways:

  • To a field. The Validation block will check that the field value satisfies all validation rules defined in validators applied to the field.
  • To a property. The Validation block will check that the value of the get property satisfies all validation rules defined in validators applied to the property.
  • To a method that takes no parameters. The Validation block will check that the return value of the method satisfies all validation rules defined in validators applied to the method.
  • To an entire class, using only the NotNullValidator, ObjectCollectionValidator, AndCompositeValidator, and OrCompositeValidator). The Validation block can check if the object is null, that it is a member of the specified collection, and that any validation rules defined within it are satisfied.
  • To a parameter in a WCF Service Contract. The Validation block will check that the parameter value satisfies all validation rules defined in validators applied to the parameter.
  • To parameters of methods that are intercepted, by using the validation call handler in conjunction with the Policy Injection application block. For more information on using interception, see Appendix C, "Policy Injection in Enterprise Library."

Each of the validators described in the previous section has a related attribute that you apply in your code, specifying the values for validation (such as the range or comparison value) as parameters to the attribute. For example, you can validate a property that must have a value between 0 and 10 inclusive by applying the following attribute to the property definition, as seen in the following code.

[RangeValidator(0, RangeBoundaryType.Inclusive, 10, RangeBoundaryType.Inclusive)] 

DataAnnotations Attributes

In addition to using the built-in validation attributes, the Validation block will perform validation defined in the vast majority of the validation attributes in the System.ComponentModel.DataAnnotations namespace. These attributes are typically used by frameworks and object/relational mapping (O/RM) solutions that auto-generate classes that represent data items. They are also generated by the ASP.NET validation controls that perform both client-side and server-side validation. While the set of validation attributes provided by the Validation block does not map exactly to those in the DataAnnotations namespace, the most common types of validation are supported. A typical use of data annotations is shown here.

[System.ComponentModel.DataAnnotations.Required(
        ErrorMessage = "You must specify a value for the product ID.")]
[System.ComponentModel.DataAnnotations.StringLength(6, 
        ErrorMessage = "Product ID must be 6 characters.")]
[System.ComponentModel.DataAnnotations.RegularExpression("[A-Z]{2}[0-9]{4}",
        ErrorMessage = "Product ID must be 2 capital letters and 4 numbers.")]
public string ID { get; set; }

In reality, the Validation block validation attributes are data annotation attributes, and can be used (with some limitations) whenever you can use data annotations attributes—for example, with ASP.NET Dynamic Data applications. The main difference is that the Validation block attribute validation occurs only on the server, and not on the client.

Also keep in mind that, while DataAnnotations supports most of the Validation block attributes, not all of the validation attributes provided with the Validation block are supported by the built-in .NET validation mechanism. For more information, see the documentation installed with Enterprise Library, and the topic "System.ComponentModel.DataAnnotations Namespace" at https://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.aspx.

Self-Validation

Self-validation might sound as though you should be congratulating yourself on your attractiveness and wisdom, and your status as fine and upstanding citizen. However, in Enterprise Library terms, self-validation is concerned with the use of classes that contain their own validation logic.

For example, a class that stores spare parts for aircraft might contain a function that checks if the part ID matches a specific format containing letters and numbers. You add the HasSelfValidation attribute to the class, add the SelfValidation attribute to any validation functions it contains, and optionally add attributes for the built-in Validation block validators to any relevant properties. Then you can validate an instance of the class using the Validation block. The block will execute the self-validation method.

Note

Self-validation cannot be used with the UI validation integration features for Windows Forms, WPF, or ASP.NET.

Self-validation is typically used where the validation rule you want to apply involves values from different parts of your class or values that are not publicly exposed by the class, or when the validation scenario requires complex rules that even a combination of composed validators cannot achieve. For example, you may want to check if the sum of the number of products on order and the number already in stock is less than a certain value before allowing a user to order more. The following extract from one of the examples you'll see later in this chapter shows how self-validation can be used in this case.

[HasSelfValidation]
public class AnnotatedProduct : IProduct
  ...
  ... code to implement constructor and properties goes here
  ...

  [SelfValidation]
  public void Validate(ValidationResults results)
  {
    string msg = string.Empty;
    if (InStock + OnOrder > 100)
    {
      msg = "Total inventory (in stock and on order) cannot exceed 100 items.";
      results.AddResult(new ValidationResult(msg, this, "ProductSelfValidation",
                        "", null));
  }
}

The Validation block calls the self-validation method when you validate this class instance, passing to it a reference to the collection of ValidationResults that it is populating with any validation errors found. The code above simply adds one or more new ValidationResult instances to the collection if the self-validation method detects an invalid condition. The parameters of the ValidationResult constructor are:

  • The validation error message to display to the user or write to a log. The ValidationResult class exposes this as the Message property.
  • A reference to the class instance where the validation error was discovered (usually the current instance). The ValidationResult class exposes this as the Target property.
  • A string value that describes the location of the error (usually the name of the class member, or some other value that helps locate the error). The ValidationResult class exposes this as the Key property.
  • An optional string tag value that can be used to categorize or filter the results. The ValidationResult class exposes this as the Tag property.
  • A reference to the validator that performed the validation. This is not used in self-validation, though it will be populated by other validators that validate individual members of the type. The ValidationResult class exposes this as the Validator property.

Validation Rule Sets

A validation rule set is a combination of all the rules with the same name, which may be located in a configuration file or other configuration source, in attributes defined within the target type, and implemented through self-validation. In other words, a rule set includes any type of validation rule that has a specified name.

Note

Rule set names are case-sensitive. The two rule sets named MyRuleset and MyRuleSet are different!

How do you apply a name to a validation rule? And what happens if you don't specify a name? In fact, the way it works is relatively simple, even though it may appear complicated when you look at configuration and attributes, and take into account how these are actually processed.

To start with, every validation rule is a member of some rule set. If you do not specify a name, that rule is a member of the default rule set; effectively, this is the rule set whose name is an empty string. When you do specify a name for a rule, it becomes part of the rule set with that name.

Assigning Validation Rules to Rule Sets

You specify rule set names in a variety of ways, depending on the location and type of the rule:

  • In configuration. You define a type that you want to apply rules to, and then define one or more rule sets for that type. To each rule set you add the required combination of validators, each one representing a validation rule within that rule set. You can specify one rule set for each type as the default rule set for that type. The rules within this rule set are then treated as members of the default (unnamed) rule set, as well as that named rule set.

  • In Validation block validator attributesapplied to classes and their members. Every validation attribute will accept a rule set name as a parameter. For example, you specify that a NotNullValidator is a member of a rule set named MyRuleset, like this.

    [NotNullValidator(MessageTemplate = "Cannot be null", 
                      Ruleset = "MyRulesetName")]
    
  • In SelfValidation attributeswithin a class. You add the Ruleset parameter to the attribute to indicate which rule set this self-validation rule belongs to. You can define multiple self-validation methods in a class, and add them to different rule sets if required.

    [SelfValidation(Ruleset = "MyRulesetName")]
    

Configuring Validation Block Rule Sets

The Enterprise Library configuration console makes it easy to define rule sets for specific types that you will validate. Each rule set specifies a type to which you will apply the rule set, and allows you to specify a set of validation rules. You can then apply these rules as a complete set to an instance of an object of the defined type.

Figure 2

The configuration for the examples

Ff953182.7a738e24-b5d9-4193-aa38-94d3d4cb304a-thumb(en-us,PandP.50).png

Performing Validation and Displaying Validation Errors

To initiate validation, you call the Validate method of your validator. There are two overloads of this method: one that creates and returns a populated ValidationResults instance, and one that accepts an existing ValidationResults instance as a parameter. The second overload allows you to perform several validation operations, and collect all of the errors in one ValidationResults instance.

You can check if validation succeeded, or if any validation errors were detected, by examining the IsValid property of a ValidationResults instance, and displaying details of any validation errors that occurred. The following code shows a simple example of how you can display the most relevant details of each validation error. See the section on self-validation earlier in this chapter for a description of the properties of each individual ValidationResult within the ValidationResults.

// Check if the ValidationResults detected any validation errors.
if (results.IsValid)
{
  Console.WriteLine("There were no validation errors.");
}
else
{
  Console.WriteLine("The following {0} validation errors were detected:",
                     results.Count);  
  // Iterate through the collection of validation results.
  foreach (ValidationResult item in results)
  {
    // Show the target member name and current value.
    Console.WriteLine("Target:'{0}' Key:'{1}' Tag:'{2}' Message:'{3}'", 
                      item.Target, item.Key, item.Tag, item.Message);
  }
}

Alternatively, you can extract more information about the validation result for each individual validator where an error occurred. The example application we provide demonstrates how you can do this, and you'll see more details later in this chapter.

Understanding Message Template Tokens

One specific and very useful feature of the individual validators you define in your configuration or attributes is the capability to include tokens in the message to automatically insert values of the validator's properties. This applies no matter how you create your validator—in rule sets defined in configuration, as validation attributes, or when you create validators programmatically.

The Message property of a validator is actually a template, not just a simple text string that is displayable. When the block adds an individual ValidationResult to the ValidationResults instance for each validation error it detects, it parses the value of the Message property looking for tokens that it will replace with the value of specific properties of the validator that detected the error.

The value injected into the placeholder tokens, and the number of tokens used, depends on the type of validator—although there are three tokens that are common to all validators. The token {0} will be replaced by the value of the object being validated (ensure that you escape this value before you display or use it in order to guard against injection attacks). The token {1} will contain the name of the member that was being validated, if available, and is equivalent to the Key property of the validator. The token {2) will contain the value of the Tag property of the validator.

The remaining tokens depend the on the individual validator type. For example, in the case of the Contains Characters validator, the tokens {3} and {4} will contain the characters to check for and the ContainsCharacters value (All or Any). In the case of a range validator, such as the String Length validator, the tokens {3} to {6} will contain the values and bound types (Inclusive, Exclusive, or Ignore) for the lower and upper bounds you specify for the validator. For example, you may define a String Length validator like this:

[StringLengthValidator(5, RangeBoundaryType.Inclusive, 20, 
       RangeBoundaryType.Inclusive,
       MessageTemplate = "{1} must be between {3} and {5} characters.")]

If this validator is attached to a property named Description, and the value of this property is invalid, the ValidationResults instance will contain the error message Description must be between 5 and 20 characters.

Other validators use tokens that are appropriate for the type of validation they perform. The documentation installed with Enterprise Library lists the tokens for each of the Validation block validators. You will also see the range of tokens used in the examples that follow.

Diving in With Some Simple Examples

The remainder of this chapter shows how you can use the Validation block in a variety of situations within your applications. We provide a simple console-based example application implemented as the Microsoft Visual Studio® solution named Validation. You can open it in Visual Studio to view the code, or run the application directly from the bin\Debug folder.

The application uses three versions of a class that stores product information. All of these implement an interface named IProduct, as illustrated in Figure 3. Each has a string property that is designed to be set to a value from an enumeration called ProductType that defines the valid set of product type names.

Figure 3

The product classes used in the examples

Ff953182.96521794-5895-4af7-9d0d-335ddc788b2f(en-us,PandP.50).png

Creating a Type Validator using the ValidatorFactory

You can resolve a ValidatorFactory instance through the Enterprise Library container and use it to create a validator for a specific target type. This validator will validate objects using a rule set, and/or any attributes and self-validation methods the target object contains. To obtain an instance of the ValidatorFactory class, you can use the following code.

ValidatorFactory valFactory
    = EnterpriseLibraryContainer.Current.GetInstance<ValidatorFactory>();

You can then create a validator for any type you want to validate. For example, this code creates a validator for the Product class and then validates an instance of that class named myProduct.

Validator<Product> pValidator = valFactory.CreateValidator<Product>();
ValidationResults valResults = pValidator.Validate(myProduct);

By default, the validator will use the default rule set defined for the target type (you can define multiple rule sets for a type, and specify one of these as the default for this type). If you want the validator to use a specific rule set, you specify this as the single parameter to the CreateValidator method, as shown here.

Validator<Product> productValidator 
    = valFactory.CreateValidator<Product>("RuleSetName");
ValidationResults valResults = productValidator.Validate(myProduct);

The example named Using a Validation Rule Set to Validate an Object creates an instance of the Product class that contains invalid values for all of the properties, and then uses the code shown above to create a type validator for this type and validate it. It then displays details of the validation errors contained in the returned ValidationResults instance. However, rather than using the simple technique of iterating over the ValidationResults instance displaying the top-level errors, it uses code to dive deeper into the results to show more information about each validation error, as you will see in the next section.

Delving Deeper into ValidationResults

You can check if validation succeeded, or if any validation error were detected, by examining the IsValid property of a ValidationResults instance and displaying details of any validation errors that occurred. However, when you simply iterate over a ValidationResults instance (as we demonstrated in the section "Performing Validation and Displaying Validation Errors" earlier in this chapter), we displayed just the top-level errors. In many cases, this is all you will require. If the validation error occurs due to a validation failure in a composite (And or Or) validator, the error this approach will display is the message and details of the composite validator.

However, sometimes you may wish to delve deeper into the contents of a ValidationResults instance to learn more about the errors that occurred. This is especially the case when you use nested validators inside a composite validator. The code we use in the example provides richer information about the errors. When you run the example, it displays the following results (we've removed some repeated content for clarity).

The following 6 validation errors were detected:
+ Target object: Product, Member: DateDue
  - Detected by: OrCompositeValidator
  - Tag value: Date Due
  - Validated value was: '23/11/2010 13:45:41'
  - Message: 'Date Due must be between today and six months time.'
  + Nested validators:
    - Detected by: NotNullValidator
    - Validated value was: '23/11/2010 13:45:41'
    - Message: 'Value can be NULL or a date.'
    - Detected by: RelativeDateTimeValidator
    - Validated value was: '23/11/2010 13:45:41'
    - Message: 'Value can be NULL or a date.'
+ Target object: Product, Member: Description
  - Detected by: OrCompositeValidator
  - Validated value was: '-'
  - Message: 'Description can be NULL or a string value.'
  + Nested validators:
    - Detected by: StringLengthValidator
    - Validated value was: '-'
    - Message: 'Description must be between 5 and 100 characters.'
    - Detected by: NotNullValidator
    - Validated value was: '-'
    - Message: 'Value can be NULL.'
...
...
+ Target object: Product, Member: ProductType
  - Detected by: EnumConversionValidator
  - Tag value: Product Type
  - Validated value was: 'FurryThings'
  - Message: 'Product Type must be a value from the 'ProductType' enumeration.'

You can see that this shows the target object type and the name of the member of the target object that was being validated. It also shows the type of the validator that performed the operation, the Tag property values, and the validation error message. Notice also that the output includes the validation results from the validators nested within the two OrCompositeValidator validators. To achieve this, you must iterate recursively through the ValidationResults instance because it contains nested entries for the composite validators.

The code we used also contains a somewhat contrived feature: to be able to show the value being validated, some examples that use this routine include the validated value at the start of the message using the {0} token in the form: [{0}] validation error message. The example code parses the Message property to extract the value and the message when it detects that this message string contains such a value. It also encodes this value for display in case it contains malicious content.

While this may not represent a requirement in real-world application scenarios, it is useful here as it allows the example to display the invalid values that caused the validation errors and help you understand how each of the validators works. We haven’t listed the code here, but you can examine it in the example application to see how it works, and adapt it to meet your own requirements. You'll find it in the ShowValidationResults, ShowValidatorDetails, and GetTypeNameOnly routines located in the region named Auxiliary routines at the end of the main program file.

Using the Object Validator

An alternative approach to validating objects is to programmatically create an Object Validator by calling its constructor. You specify the type that it will validate and, optionally, a rule set to use when performing validation. If you do not specify a rule set name, the validator will use the default rule set. When you call the Validate method of the Object validator, it creates a type-specific validator for the target type you specify, and you can use this to validate the object, as shown here.

Validator pValidator = new ObjectValidator(typeof(Product), "RuleSetName");
ValidationResults valResults = pValidator.Validate(myProduct);

Alternatively, you can call the default constructor of the Object validator. In this case, it will create a type-specific validator for the type of the target instance you pass to the Validate method. If you do not specify a rule set name in the constructor, the validation will use the default rule set defined for the type it is validating.

Validator pValidator = new ObjectValidator("RuleSetName");
ValidationResults valResults = pValidator.Validate(myProduct);

The validation will take into account any applicable rule sets, and any attributes and self-validation methods found within the target object.

Differences Between the Object Validator and the Factory-Created Type Validators

While the two approaches you've just seen to creating or obtaining a validator for an object achieve the same result, there are some differences in their behavior:

  • If you do not specify a target type when you create an Object Validator programmatically, you can use it to validate any type. When you call the Validate method, you specify the target instance, and the Object validator creates a type-specific validator for the type of the target instance. In contrast, the validator you obtain from a factory can only be used to validate instances of the type you specify when you obtain the validator. However, it can also be used to validate subclasses of the specified type, but it will use the rules defined for the specified target type.
  • The Object Validator will always use rules in configuration for the type of the target object, and attributes and self-validation methods within the target instance. In contrast, you can use a specific factory class type to obtain validators that only validate the target instance using one type of rule source (in other words, just configuration rule sets, or just one type of attributes).
  • The Object Validator will acquire a type-specific validator of the appropriate type each time you call the Validate method, even if you use the same instance of the Object validator every time. In contrast, a validator obtained from one of the factory classes does not need to do this, and will offer improved performance.

As you can see from the flexibility and performance advantages listed above, you should generally consider using the ValidatorFactory approach for creating validators to validate objects rather than creating individual Object Validator instances.

Validating Collections of Objects

Before we leave the topic of validation of objects, it is worth looking at how you can validate collections of objects. The Object Collection validator can be used to check that every object in a collection is of the specified type, and to perform validation on every member of the collection. You can apply the Object Collection validator to a property of a class that is a collection of objects using a Validation block attribute if you wish, as shown in this example that ensures that the ProductList property is a collection of Product instances, and that every instance in the collection contains valid values.

[ObjectCollectionValidator(typeof(Product))]
public Product[] ProductList { get; }

You can also create an Object Collection validator programmatically, and use it to validate a collection held in a variable. The example named Validating a Collection of Objects demonstrates this approach. It creates a List named productList that contains two instances of the Product class, one of which contains all valid values, and one that contains invalid values for some of its properties. Next, the code creates an Object Collection validator for the Product type and then calls the Validate method.

// Create an Object Collection Validator for the collection type. 
Validator collValidator 
          = new ObjectCollectionValidator(typeof(Product));

// Validate all of the objects in the collection.
ValidationResults results = collValidator.Validate(productList);

Finally, the code displays the validation errors using the same routine as in earlier examples. As the invalid Product instance contains the same values as the previous example, the result is the same. You can run the example and view the code to verify that this is the case.

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), as described earlier in this chapter in the section "Self-Validation." 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 DateDue properties meets predefined rules. You can examine the code within the AttributedProduct class to see the implementation.

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 ValidationResults 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 SaleProduct class and the Product base class.

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.

For more information about data annotations, see https://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.aspx (.NET Framework 3.5) and https://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations(VS.100).aspx (.NET Framework 4.0).

An Example of Using Data Annotations

The examples we provide for this chapter include one named Using Data Annotation Attributes and Self-Validation. This uses only the range of data annotation attributes in version 3.5 of the .NET Framework, so you can run it on machines that do not have Visual Studio 2010 or version 4.0 of the .NET Framework installed.

The class named AnnotatedProduct contains data annotation attributes to implement the same rules as those applied by Validation block attributes in the AttributedProduct 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 MetadataType 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. The only change to the content of this class compared to the attributed versions you saw in the previous sections of this chapter is that it contains no validation attributes.

[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);

Creating and Using Individual Validators

You can create an instance of any of the validators included in the Validation block directly in your code, and then call its Validate method to validate an object or value. For example, you can create a new Date Time Range validator and set the properties, such as the upper and lower bounds, the message, and the Tag property. Then you call the Validate method of the validator, specifying the object or value you want to validate. The example, Creating and Using Validators Directly, demonstrates the creation and use of some of the individual and composite validators provided with the Validation block.

Validating Strings for Contained Characters

The example code first creates a ContainsCharactersValidator that specifies that the validated value must contain the characters c, a, and t, and that it must contain all of these characters (you can, if you wish, specify that it must only contain Any of the characters). The code also sets the Tag property to a user-defined string that helps to identify the validator in the list of errors. The overload of the Validate method used here returns a new ValidationResults instance containing a ValidationResult instance for each validation error that occurred.

// Create a Contains Characters Validator and use it to validate a string.
Validator charsValidator = new ContainsCharactersValidator("cat", 
                         ContainsCharacters.All, 
                         " Value must contain {4} of the characters '{3}'.");
charsValidator.Tag = "Validating the String value 'disconnected'";
ValidationResults valResults = charsValidator.Validate("disconnected");

Validating Integers within a Domain

Next, the example code creates a new DomainValidator for integer values, specifying an error message and an array of acceptable values. Then it can be used to validate an integer, with a reference to the existing ValidationResults instance passed to the Validate method this time.

// Create a Domain Validator and use it to validate an Integer value.
Validator integerValidator = new DomainValidator<int>(
                                 "Value must be in the list 1, 3, 7, 11, 13.", 
                                 new int[] {1, 3, 7, 11, 13});
integerValidator.Tag = "Validating the Integer value '42'";
integerValidator.Validate(42, valResults);

Validating with a Composite Validator

To show how you can create composite validators, the next section of the example creates an array containing two validators: a NotNullValidator and a StringLengthValidator. The first parameter of the NotNullValidator sets the Negated property. In this example, we set it to true so that the validator will allow null values. The StringLengthValidator specifies that the string it validates must be exactly five characters long. Notice that range validators such as the StringLengthValidator have properties that specify not only the upper and lower bound values, but also whether these values are included in the valid result set (RangeBoundaryType.Inclusive) or excluded (RangeBoundaryType.Exclusive). If you do not want to specify a value for the upper or lower bound of a range validator, you must set the corresponding property to RangeBoundaryType.Ignore.

Validator[] valArray = new Validator[] 
{
  new NotNullValidator(true, "Value can be NULL."),
  new StringLengthValidator(5, RangeBoundaryType.Inclusive, 
                            5, RangeBoundaryType.Inclusive, 
                            "Must be between {3} ({4}) and {5} ({6}) chars.")
};

Having created an array of validators, we can now use this to create a composite validator. There are two composite validators, the AndCompositeValidator and the OrCompositeValidator. You can combine these as well to create any nested hierarchy of validators you require, with each combination returning a valid result if all (with the AndCompositeValidator) or any (with the OrCompositeValidator) of the validators it contains are valid. The example creates an OrCompositeValidator, which will return true (valid) if the validated string is either null or contains exactly five characters. Then it validates a null value and an invalid string, passing into the Validate method the existing ValidationResults instance.

Validator orValidator = new OrCompositeValidator(
                            "Value can be NULL or a string of 5 characters.",
                            valArray);

// Validate two values with the Or Composite Validator.
orValidator.Validate(null, valResults);
orValidator.Validate("MoreThan5Chars", valResults);

Validating Single Members of an Object

The Validation block contains three validators you can use to validate individual members of a class directly, instead of validating the entire type using attributes or rule sets. Although you may not use this approach very often, you might find it to be useful in some scenarios. The Field Value validator can be used to validate the value of a field of a type. The Method Return Value validator can be used to validate the return value of a method of a type. Finally, the Property Value validator can be used to validate the value of a property of a type.

The example shows how you can use a Property Value validator. The code creates an instance of the Product class that has an invalid value for the ID property, and then creates an instance of the PropertyValueValidator class, specifying the type to validate and the name of the target property. This second parameter of the constructor is the validator to use to validate the property value—in this example a Regular Expression validator. Then the code can initiate validation by calling the Validate method, passing in the existing ValidationResults instance, as shown here.

IProduct productWithID = new Product();
PopulateInvalidProduct(productWithID);
Validator propValidator = new PropertyValueValidator<Product>("ID",
   new RegexValidator("[A-Z]{2}[0-9]{4}", 
                      "Product ID must be 2 capital letters and 4 numbers.")
);
propValidator.Validate(productWithID, valResults);

If required, you can create a composite validator containing a combination of validators, and specify this composite validator in the second parameter. A similar technique can be used with the Field Value validator and Method Return Value validator.

After performing all of the validation operations, the example displays the results by iterating through the ValidationResults instance that contains the results for all of the preceding validation operations. It uses the same ShowValidationResults routine we described earlier in this chapter. This is the result:

The following 4 validation errors were detected:
+ Target object: disconnected, Member:
  - Detected by: ContainsCharactersValidator
  - Tag value: Validating the String value 'disconnected'
  - Message: 'Value must contain All of the characters 'cat'.'
+ Target object: 42, Member:
  - Detected by: DomainValidator`1[System.Int32]
  - Tag value: Validating the Integer value '42'
  - Message: 'Value must be in the list 1, 3, 7, 11, 13.'
+ Target object: MoreThan5Chars, Member:
  - Detected by: OrCompositeValidator
  - Message: 'Value can be NULL or a string of 5 characters.'
  + Nested validators:
    - Detected by: NotNullValidator
    - Message: 'Value can be NULL.'
    - Detected by: StringLengthValidator
    - Message: 'Value must be between 5 (Inclusive) and 5 (Inclusive) chars.'
+ Target object: Product, Member: ID
  - Detected by: RegexValidator
  - Message: 'Product ID must be 2 capital letters and 4 numbers.'

You can see how the message template tokens create the content of the messages that are displayed, and the results of the nested validators we defined for the Or Composite validator. If you want to experiment with individual validators, you can modify and extend this example routine to use other validators and combinations of validators.

WCF Service Validation Integration

This section of the chapter demonstrates how you can integrate your validation requirements for WCF services with the Validation block. The Validation block allows you to add validation attributes to the parameters of methods defined in your WCF service contract, and have the values of these automatically validated each time the method is invoked by a client.

To use WCF integration, you edit your service contract, edit the WCF configuration to add the Validation block and behaviors, and then handle errors that arise due to validation failures. In addition to the other assemblies required by Enterprise Library and the Validation block, you must add the assembly named Microsoft.Practices.EnterpriseLibrary.Validation.Integration.WCF to your application and reference them all in your service project.

The example, Validating Parameters in a WCF Service, demonstrates validation in a simple WCF service. It uses a service named ProductService (defined in the ExampleService project of the solution). This service contains a method named AddNewProduct that accepts a set of values for a product, and adds this product to its internal list of products.

Defining Validation in the Service Contract

The service contract, shown below, carries the ValidationBehavior attribute, and each service method defines a fault contract of type ValidationFault.

[ServiceContract]
[ValidationBehavior]
public interface IProductService
{
  [OperationContract]
  [FaultContract(typeof(ValidationFault))]
  bool AddNewProduct(
    [NotNullValidator(MessageTemplate = "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.")]
    string id,
    ...
    [IgnoreNulls(MessageTemplate = "Description can be NULL or a string value.")]
    [StringLengthValidator(5, RangeBoundaryType.Inclusive, 
        100, RangeBoundaryType.Inclusive,
        MessageTemplate = "Description must be between {3} and {5} characters.")]
    string description,
    [EnumConversionValidator(typeof(ProductType),
        MessageTemplate = "Must be a value from the '{3}' enumeration.")]
    string prodType,
    ...
    [ValidatorComposition(CompositionType.Or,
        MessageTemplate = "Date 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.")]
    DateTime? dateDue);
}

You can see that the service contract defines a method named AddNewProduct that takes as parameters the value for each property of the Product class we've used throughout the examples. Although the previous listing omits some attributes to limit duplication and make it easier to see the structure of the contract, the rules applied in the example service we provide are the same as you saw in earlier examples of validating a Product instance. The method implementation within the WCF service is simple—it just uses the values provided to create a new Product and adds it to a generic List.

Editing the Service Configuration

After you define the service and its validation rules, you must edit the service configuration to force validation to occur. The first step is to specify the Validation block as a behavior extension. You will need to provide the appropriate version information for the assembly, which you can obtain from the configuration file generated by the configuration tool for the client application, or from the source code of the example, depending on whether you are using the assemblies provided with Enterprise Library or assemblies you have compiled yourself.

<extensions>
  <behaviorExtensions>
    <add name="validation"
             type="Microsoft.Practices...WCF.ValidationElement,
                   Microsoft.Practices...WCF" />
  </behaviorExtensions>

  ... other existing behavior extensions here ...

</extensions>

Next, you edit the <behaviors> section of the configuration to define the validation behavior you want to apply. As well as turning on validation here, you can specify a rule set name (as shown) if you want to perform validation using only a subset of the rules defined in the service. Validation will then only include rules defined in validation attributes that contain the appropriate Ruleset parameter (the configuration for the example application does not specify a rule set name here).

<behaviors>
  <endpointBehaviors>
    <behavior name="ValidationBehavior">
      <validation enabled="true" ruleset="MyRuleset" />
    </behavior>
  </endpointBehaviors>     

  ... other existing behaviors here ...

</behaviors>

Note

Note that you cannot use a configuration rule set with a WCF service—all validation rules must be in attributes.

Finally, you edit the <services> section of the configuration to link the ValidationBehavior defined above to the service your WCF application exposes. You do this by adding the behaviorConfiguration attribute to the service element for your service, as shown here.

<services>
  <service behaviorConfiguration="ExampleService.ProductServiceBehavior"
           name="ExampleService.ProductService">
    <endpoint address="" behaviorConfiguration="ValidationBehavior"
              binding="wsHttpBinding" contract="ExampleService.IProductService">
      <identity>
        <dns value="localhost" />
      </identity>
    </endpoint>
  <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
  </service>
  ...
</services>

Using the Product Service and Detecting Validation Errors

At last you can use the WCF service you have created. The example uses a service reference added to the main project, and initializes the service using the service reference in the usual way. It then creates a new instance of a Product class, populates it with valid values, and calls the AddNewProduct method of the WCF service. Then it repeats the process, but this time by populating the product instance with invalid values. You can examine the code in the example to see this if you wish.

However, one important issue is the way that service exceptions are handled. The example code specifically catches exceptions of type FaultException<ValidationFault>. This is the exception generated by the service, and ValidationFault is the type of the fault contract we specified in the service contract.

Validation errors detected in the WCF service are returned in the Details property of the exception as a collection. You can simply iterate this collection to see the validation errors. However, if you want to combine them into a ValidationResults instance for display, especially if this is part of a multi-step process that may cause other validation errors, you must convert the collection of validation errors returned in the exception.

The example application does this using a method named ConvertToValidationResults, as shown here. Notice that the validation errors returned in the ValidationFault do not contain information about the validator that generated the error, and so we must use a null value for this when creating each ValidationResult instance.

// Convert the validation details in the exception to individual
// ValidationResult instances and add them to the collection.
ValidationResults adaptedResults = new ValidationResults();
foreach (ValidationDetail result in results)
{
  adaptedResults.AddResult(new ValidationResult(result.Message, target,
                                                result.Key, result.Tag, null));
}
return adaptedResults;

When you execute this example, you will see a message indicating the service being started—this may take a while the first time, and may even time out so that you need to try again. Then the output shows the result of validating the valid Product instance (which succeeds) and the result of validating the invalid instance (which produces the now familiar list of validation errors shown here).

The following 6 validation errors were detected:
+ Target object: Product, Member:
  - Detected by: [none]
  - Tag value: id
  - Message: 'Product ID must be two capital letters and four numbers.'
...
+ Target object: Product, Member:
  - Detected by: [none]
  - Tag value: description
  - Message: 'Description can be NULL or a string value.'
+ Target object: Product, Member:
  - Detected by: [none]
  - Tag value: prodType
  - Message: 'Product type must be a value from the 'ProductType' enumeration.'
...
+ Target object: Product, Member:
  - Detected by: [none]
  - Tag value: dateDue
  - Message: 'Date due must be between today and six months time.'

Again, we've omitted some of the duplication so that you can more easily see the result. Notice that there is no value available for the name of the member being validated or the validator that was used. This is a form of exception shielding that prevents external clients from gaining information about the internal workings of the service. However, the Tag value returns the name of the parameter that failed validation (the parameter names are exposed by the service), allowing you to see which of the values you sent to the service actually failed validation.

User Interface Validation Integration

The Validation block contains integration components that make it easy to use the Validation block mechanism and rules to validate user input within the user interface of ASP.NET, Windows Forms, and WPF applications. While these technologies do include facilities to perform validation, this validation is generally based on individual controls and values.

When you integrate the Validation block with your applications, you can validate entire objects, and collections of objects, using sets of rules you define. You can also apply complex validation using the wide range of validators included with the Validation block. This allows you to centrally define a single set of validation rules, and apply them in more than one layer and when using different UI technologies.

Note

The UI integration technologies provided with the Validation block do not instantiate the classes that contain the validation rules. This means that you cannot use self-validation with these technologies.

ASP.NET User Interface Validation

The Validation block includes the PropertyProxyValidator class that derives from the ASP.NET BaseValidator control, and can therefore take part in the standard ASP.NET validation cycle. It acts as a wrapper that links an ASP.NET control on your Web page to a rule set defined in your application through configuration, attributes, and self-validation.

To use the PropertyProxyValidator, you add the assembly named Microsoft.Practices.EnterpriseLibrary.Validation.Integration.AspNet to your application, and reference it in your project. You must also include a Register directive in your Web pages to specify this assembly and the prefix for the element that will insert the PropertyProxyValidator into your page.

<% @Register TagPrefix="EntLibValidators" Assembly="Microsoft.Practices.EnterpriseLibrary.Validation.Integration.AspNet"    Namespace="Microsoft.Practices.EnterpriseLibrary.Validation.Integration.AspNet"
%> 

Then you can define the validation controls in your page. The following shows an example that validates a text box that accepts a value for the FirstName property of a Customer class, and validates it using the rule set named RuleSetA.

<EntLibValidators:PropertyProxyValidator id="firstNameValidator"
   runat="server" ControlToValidate="firstNameTextBox"
   PropertyName="FirstName" RulesetName="RuleSetA"
   SourceTypeName="ValidationQuickStart.BusinessEntities.Customer" />

One point to be aware of is that, unlike the ASP.NET validation controls, the Validation block PropertyProxyValidator control does not perform client-side validation. However, it does integrate with the server-based code and will display validation error messages in the page in the same way as the ASP.NET validation controls.

For more information about ASP.NET integration, see the documentation installed with Enterprise Library and available online at https://go.microsoft.com/fwlink/?LinkId=188874.

Windows Forms User Interface Validation

The Validation block includes the ValidationProvider component that extends Windows Forms controls to provide validation using a rule set defined in your application through configuration, attributes, and self-validation. You can handle the Validating event to perform validation, or invoke validation by calling the PerformValidation method of the control. You can also specify an ErrorProvider that will receive formatted validation error messages.

To use the ValidationProvider, you add the assembly named Microsoft.Practices.EnterpriseLibrary.Validation.Integration.WinForms to your application, and reference it in your project.

For more information about Windows Forms integration, see the documentation installed with Enterprise Library and available online at https://go.microsoft.com/fwlink/?LinkId=188874.

WPF User Interface Validation

The Validation block includes the ValidatorRule component that you can use in the binding of a WPF control to provide validation using a rule set defined in your application through configuration, attributes, and self-validation. To use the ValidatorRule, you add the assembly named Microsoft.Practices.EnterpriseLibrary.Validation.Integration.WPF to your application, and reference it in your project.

As an example, you can add a validation rule directly to a control, as shown here.

<TextBox x:Name="TextBox1">
  <TextBox.Text>
    <Binding Path="ValidatedStringProperty" UpdateSourceTrigger="PropertyChanged">
      <Binding.ValidationRules>
        <vab:ValidatorRule SourceType="{x:Type test:ValidatedObject}" 
                           SourcePropertyName="ValidatedStringProperty"/>
      </Binding.ValidationRules>
    </Binding>
  </TextBox.Text>
</TextBox>

You can also specify a rule set using the RulesetName property, and use the ValidationSpecificationSource property to refine the way that the block creates the validator for the property.

For more information about WPF integration, see the documentation installed with Enterprise Library and available online at https://go.microsoft.com/fwlink/?LinkId=188874.

Creating Custom Validators

While the wide range of validators included with the Validation block should satisfy most requirements, you can easily create your own custom validators and integrate them with the block. This may be useful if you have some specific and repetitive validation task that you need to carry out, and which is more easily accomplished using custom code.

The easiest way to create a custom validator is to create a class that inherits from one of the abstract base classes provided with the Validation block. Depending on the type of validation you need to perform, you may choose to inherit from base types such as the ValueValidator or MemberAccessValidator classes, the Validator<T> base class (for a strongly typed validator) or from the Validator class (for a loosely typed validator).

You can also create your own custom validation attributes that will apply custom validators you create. The base class, ValidatorAttribute, provides a good starting point for this.

For more information on extending Enterprise Library and creating custom providers, see the documentation installed with Enterprise Library and available online at https://go.microsoft.com/fwlink/?LinkId=188874.

Summary

In this chapter we have explored the Enterprise Library Validation block and shown you how easy it is to decouple your validation code from your main application code. The Validation block allows you to define validation rules and rule sets; and apply them to objects, method parameters, properties, and fields of objects you use in your application. You can define these rules using configuration, attributes, or even using custom code and self-validation within your classes.

Validation is a vital crosscutting concern, and should occur at the perimeter of your application, at trust boundaries, and (in most cases) between layers and distributed components. Robust validation can help to protect your applications and services from malicious users and dangerous input (including SQL injection attacks); ensure that it processes only valid data and enforces business rules; and improve responsiveness.

The ability to centralize your validation mechanism and the ability to define rules through configuration also make it easy to deploy and manage applications. Administrators can update the rules when required without requiring recompilation, additional testing, and redeployment of the application. Alternatively, you can define rules, validation mechanisms, and parameters within your code if this is a more appropriate solution for your own requirements.

Next | Previous | Home | Community