2.2 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, ObjectCollection
Validator, 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.
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.Component Model.DataAnnotations Namespace" at http://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.aspx.
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.
[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 Validation Result
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.
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
attributes applied 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 attributes within
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 shows
the configuration console with the configuration used in the example
application. It defines a rule set named MyRuleset for the validated type (the Product class). MyRuleset is configured as the default rule
set, and contains a series of validators for all of the properties
of the Product type. These validators
include two Or Composite Validators (which contain other validators)
for the DateDue and Description properties, three validators that
will be combined with the And
operation for the ID property, and
individual validators for the remaining properties.
Note
When you highlight a rule, member, or validator in
the configuration console, it shows connection lines between the
configured items to help you see the relationships between
them.
Specifying Rule Sets When Validating
You can specify a rule set name when you create a type
validator that will validate an instance of a type. If you use the
ValidatorFactory facade to create a
type validator for a type, you can specify a rule set name as a
parameter of the CreateValidator
method. If you create an Object Validator or an Object Collection Validator programmatically by
calling the constructor, you can specify a rule set name as a
parameter of the constructor. Finally, if you resolve a validator
for a type through the Enterprise Library Container, you can specify
a rule set name as the string key value.
-
If you specify a rule set
name when you create a validator for an object, the
Validation block will apply only those validation
rules that are part of the specified rule set. It will, by
default, apply all rules with the specified name that it can
find in configuration, attributes, and self-validation. -
If you do not specify a rule set
name when you create a validator for an object, the
Validation block will, by default, apply all rules
that have no name (effectively, rules with an empty string as
the name) that it can find in configuration, attributes, and
self-validation. If you have specified one rule set in
configuration as the default rule set for the type you are
validating (by setting the DefaultRule property for that type to the
rule set name), rules within this rule set are also treated as
being members of the default (unnamed) rule set.
The one time that this default mechanism changes is if you
create a validator for a type using a facade other than ValidatorFactory. You can use the ConfigurationValidatorFactory,
AttributeValidatorFactory, or Validation AttributeValidatorFactory to
generate type validators. In this case, the validator will only
apply rules that have the specified name and exist in the specified
location.
For example, when you use a ConfigurationValidatorFactory and specify the
name MyRuleset as the rule set name
when you call the CreateValidator
method, the validator you obtain will only process rules it finds in
configuration that are defined within a rule set named MyRuleset for the target object type. If you
use an AttributeValidator Factory,
the validator will only apply Validation block rules located in
attributes and self-validation methods of the target class that have
the name MyRuleset.
Note
Configuring multiple rule sets for the same type
is useful when the type you need to validate is a primitive type
such as a String. A single application may have dozens of
different rule sets that all target String.
|