Tải bản đầy đủ (.pdf) (16 trang)

Validation

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (221.47 KB, 16 trang )

C H A P T E R 6

■ ■ ■
129
Validation
Data integrity is a very important part of developing a software product. Users of software are human
beings and, being such, are universally fallible. Whenever requiring input from a user—especially free-
text input—the values that are supplied should be treated with extreme suspicion. When it comes to user
input, data lives in a totalitarian state: it is all guilty until proven innocent and must be vigorously
validated before being trusted.
■ Note Much of this chapter is aimed at WPF. However, the “Validation In Silverlight” section deals with Silverlight
directly, indicating which parts are common and what can be leveraged specifically in Silverlight. With the release
of Silverlight 4, validation is much improved and the gap between the two technologies is ever decreasing.
The best way to eliminate bad data is with the preventative measure of data validation. Before the
data is allowed to burrow deep into the model layer, it needs to be tested for correctness. Validation
cannot unequivocally say that a piece of data is exactly the right piece of data—such bold claims are the
remit of data verification. Validation simply informs whether data appears good enough on a more
superficial level, answering questions such as:
• Is it of the correct data type?
• Is it within a certain range or set of values?
• Does it contain only characters that are acceptable?
• Is it of a specific length?
Even such seemingly shallow tests can directly prevent problems that can be introduced by
validation errors. Imagine that an application wishes to provide a powerful reporting feature whereby
users with SQL knowledge can construct queries directly on the data source. Without validating the free-
text input received from the user, the nefarious ne’er-do-well user could enter the command in Listing
6–1 with disastrous effects.
Listing 6–1. A SQL Injection Attack
DROP TABLE Products;
This type of insecurity is so common that it has its own term: the SQL Injection Attack. One way of
solving the problem would be to disallow the user from entering plain SQL commands at all, but


validation can also be used to ensure that the entered command is harmless.
CHAPTER 6 ■ VALIDATION
130
■ Note I once worked for a multinational corporation on a project for their client that they billed into the hundreds
of millions of dollars. However, despite this vast expense, they not only opened themselves up to a SQL Injection
Attack by running plain, user-input SQL directly on the database, the interface for the command was a web
service. If they had gone live with that set up, it would not have been long before their database was irreparably
destroyed.
The Validation Process
A binding’s validation rules are fired when the source of the binding is updated. As explained in Chapter 2,
the catalyst for updating a binding source can be set using the UpdateSourceTrigger property. Once the
update has begun, validation progresses as outlined below. If a validation error occurs at any point
during the process, the process stops.
1. The binding engine collates all ValidationRule objects whose ValidationStep
value is RawProposedValue, calling their Validate method and halting if there is
an error or continuing if all rules pass.
2. At this point, any data converters attached to the Binding are executed. The
converter may fail at this point, stopping the validation process.
3. The binding engine then collates all attached ValidationRules whose
ValidationStep value is set to ConvertedProposedValue, calling the Validate
method and halting on an error.
4. At this point, the binding engine sets the binding source value.
5. The binding engine collates all the attached ValidationRules whose
ValidationStep value is set to UpdatedValue, calling their Validate method and
halting on an error. Any DataErrorValidationRules attached with a default
ValidationStep value (which is also UpdatedValue) are collated and tested for
validity at this point, too.
6. The final step is to call the Validate method on ValidationRules whose
ValidationStep value is set to CommittedValue, and halt on a validation error.
Clearly, there are many ways in which the validation process can be customized. Mainly, allowances

are made for testing the binding source value at a variety of stages during the binding process, with the
aid of the ValidationStep value. A more thorough explanation of the participant classes involved in this
process follows below.
Should a ValidationRule fail at any point during this process, the binding engine constructs a
ValidationError instance and adds it to the Validation.Errors collection of the target Control. If, at the
end of the process, this Validation.Errors collection contains at least one error, the
Validation.HasError property is set to true. Similarly, if the NotifyOnValidationError property is set to
true, the Validation.Error event is raised on the target Control.
CHAPTER 6 ■ VALIDATION
131
Binding Validation Rules
Binding objects have a ValidationRules property that accepts a list of ValidationRule objects. As
explained previously, these are iterated over to determine whether or not an input value is valid. There
are two implementations of the ValidationRule class supplied, one for handling exceptions and another
for hooking in to the IDataErrorInfo provision of validation. Of course, if neither of these facilities
suffice, custom ValidationRule implementations can be created to suit specific situations. Listing 6–2
shows the definition of a binding that contains a single ExceptionValidationRule. Note that the binding
is defined in long-form, because the ValidationRules value is a list.
Listing 6–2. Adding an Exception Handling Validation Rule to a Binding
<TextBox>
<TextBox.Text>
<Binding>
<Binding.ValidationRules>
<ExceptionValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
ValidationRule Class
The ValidationRule class provides an abstract base class for the implementation of validation rules that

can be applied to individual bindings. Although two implementations are provided, it may be necessary
to define custom implementations that correspond to specific validation rules. For example, if the user is
presented with a dialog for creating a new customer record, there may be a validation rule that disallows
certain characters in the person’s name, enforces a certain minimum or maximum age limit, or ensures
the validity of a zip or postal code.
Validate Method
The ValidationRule class specifies a single abstract method that must be overridden by all subclasses
(see Listing 6–3). It is supplied with the value of object type that requires validation and a CultureInfo
object in case the validation rule requires knowledge of the current culture.
Listing 6–3. The ValidationRule Method Signature
public abstract ValidationResult Validate(object value, CultureInfo cultureInfo)
The return value is of type ValidationResult. It is a class created for the sole purpose of allowing
validation methods to return two different values: a Boolean signifying the value’s validity and an object
that provides data about the error generated when the validation result is false. The ValidationResult
constructor typically accepts a 'true, null' pairing if the validation has succeeded, or a 'false,
"explanation" ' pairing if the validation has failed, where “explanation” is a string of text informing the
user what went wrong. Of course, a string explanation is not exclusively required; any sort of error data
could be provided.
Listing 6–4 shows an example implementation of the Validate method. It ensures that the supplied
input value is a string that can be parsed into an integer value. Note that the
CHAPTER 6 ■ VALIDATION
132
ValidationResult.ValidResult static property is used as a shortcut for new ValidationResult(true,
null).
Listing 6–4. A Trivial Implementation of a Validation Rule
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
ValidationResult result = new ValidationResult(false, "An unknown validation error
occurred");
if(value is string)

{
int integerValue = int.MinValue;
if(int.TryParse(value as string, out integerValue))
{
result = ValidationResult.ValidResult;
}
else
{
result = new ValidationResult(false, "The string provided could not be parsed as
an integer value");
}
}
else
{
result = new ValidationResult(false, "The value provided is not a string");
}
return result;
}
■ Tip The example in Listing 6–4 uses the Single Exit Pattern, which can often result in more understandable
code. The theory is that all methods should only contain a single
return
statement and it should be located right
at the end. More often than not, this is easily achievable and code is consequently more comprehensible, but it is,
as ever, a personal preference.
Constructors
The ValidationRule has two constructors. The default constructor is most commonly used and simply
initializes the class to use default values for the two properties that affect the behavior of this rule. These
are the ValidatesOnTargetUpdated and ValidationStep properties, which govern whether the validation
rule runs when the target of the associated Binding is updated and in which part of the validation
process the rule runs, respectively. Listing 6–5 shows an example.

Listing 6–5. Default ValidationRule Constructor
public ValidationRule()
{
ValidationStep = ValidationStep.RawProposedValue;
ValidatesOnTargetUpdated = false;
}
CHAPTER 6 ■ VALIDATION
133
An alternative constructor is provided to supply these two values on creation (see Listing 6–6).
Listing 6–6. Alternative ValidationRule Constructor
public ValidationRule(ValidationStep validationStep, bool validatesOnTargetUpdated)
The default constructor is more likely to be used in binding situations, as the validation rule is
declared in XAML. As the validation rule is constructed, the two properties can be set via the usual
methods, as shown in Listing 6–7.
Listing 6–7. Constructing a ValidationRule and Setting Its Behavioral Properties
<TextBox>
<TextBox.Text>
<Binding>
<Binding.ValidationRules>
<local:MyValidationRule ValidationStep="UpdatedValue"
ValidatesOnTargetUpdated="True" />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
Exceptions for Validation
When the target control of a binding is updated, an exception is sometimes thrown somewhere in the
process of setting the new value on the binding source. A common example is when the value entered
does not match the value required by the binding source. Whenever an exception is thrown in this
situation, it can be caught and handled as a validation error. The ExceptionValidationRule class is

provided for this purpose and can be added to a binding’s ValidationRules list, as shown in Listing 6–8.
Listing 6–8. Constructing a ValidationRule and Setting Its Behavioral Properties
<TextBox>
<TextBox.Text>
<Binding>
<Binding.ValidationRules>
<ExceptionValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
If the user entered a string value that could not be automatically parsed into an integer, yet the
binding source required an integer value, an exception would be thrown. Normally, this would present
your user with an unhelpful message that would likely cause confusion or irritation. By presenting the
exception as a broken validation rule, the user will readily understand the problem and can work to
rectify the error without ruining their experience.
Filtering Validation Exceptions
There may be a situation whereby certain specific exceptions should be handled in different ways.
Perhaps an InvalidCastException would prompt a different response than a DivideByZeroException.
CHAPTER 6 ■ VALIDATION
134
Both should be caught, but the user should be presented with a different response for each. Listing 6–9
shows how to add an UpdateSourceExceptionFilter to a Binding, which will then call the specified
UpdateSourceExceptionFilterCallback method in the code behind.
Listing 6–9. Filtering Exceptions that Occur When Updating a Binding Source
<TextBox>
<TextBox.Text>
<Binding UpdateSourceExceptionFilter="MyExceptionFilter">
<Binding.ValidationRules>
<ExceptionValidationRule />

</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
Listing 6–10 shows the method signature required for such callbacks.
Listing 6–10. The UpdateSourceExceptionFilterCallback Delegate
public delegate object UpdateSourceExceptionFilterCallback(object bindExpression,
Exception exception)
The delegate returns an object instance that has a different intention depending on the returned
value, as shown in Table 6–1.
Table 6–1. The Possible Exception Filter Return Values and Their Respective Intent
Return Value Intent
Null Creates a ValidationError using the exception as the ErrorContent
property and adds the error to the bound control’s Validation.Errors
collection.
Any object Creates a ValidationError using the returned object as the ErrorContent
property and adds the error to the bound control’s Validation.Errors
collection.
A ValidationError instance For fine-grained control of how the ValidationError is constructed (note
that it is automatically constructed in the prior examples), merely add
this ValidationError to the bound control’s Validation.Errors
collection.
The Exception object that is passed into the filter callback can then be used to determine the
specific subclass thrown and the response varies on that, as shown in Listing 6–11.
Listing 6–11. Testing the Thrown Exception’s Type so as to Respond Differently
public object MyExceptionFilter(object bindExpression, Exception exception)
{
if (exception is InvalidCastException)
{
// Respond to an invalid cast

Tài liệu bạn tìm kiếm đã sẵn sàng tải về

Tải bản đầy đủ ngay
×