In-Depth
Validate Business Objects Declaratively
Take advantage of .NET attributes to provide robust validation for your business objects, while generating user-interface validation automatically.
Technology Toolbox: C#, ASP.NET, Windows Forms, Reflection
One of the many benefits that object-oriented programming gives you is the ability to protect an object's private members by encapsulating them inside public properties.
However, it isn't enough to encapsulate the field; you must also perform validation in your property setters. Otherwise, you aren't doing much to "protect" your object's data.
I'll show you how to construct the Declarative Validation framework to validate business objects by decorating class properties with attributes. The framework consists of six built-in validator attributes: RequiredValidator, LengthValidator, RegexValidator, RangeValidator, CompareValidator, and CustomValidator. You can plug the framework into any architecture. It comes with two abstract base classes that you can use to make implementation trivial by querying the IsValid property. You can incorporate the framework in different ways. For example, you might want to perform immediate checks after every property is set in a Windows application, but check only in the postback when writing a Web application.
ASP.NET introduced validation controls that provide rich client-side and server-side validation. However, best practices dictate that you perform data checking on your business objects. You cannot blindly trust the UI developer to perform all necessary validation—even when you are the UI developer! For example, assume a malicious user finds a way to bypass the UI constraints. Or, consider what happens if you develop a Windows Forms application where you cannot utilize the handy ASP.NET validator controls. The Declarative Validation framework can take advantage of the attributes defined on class properties to generate ASP.NET validator controls for Web applications automatically. This saves you significant time as a developer, while reducing duplicate code logic. You can also incorporate the framework's attributes automatically in Windows applications by leveraging the System.ComponentModel.IDataErrorInfo interface. This means you will never have to write another line of validation code in the UI layer again.
The Declarative Validation framework consists of three major components. The first is the static Validation class that contains all data checking methods. The second component is the collection of validation attributes that all derive from a common base class: ValidatorAttribute. The third component is the ValidationManager, which is responsible for interpreting and executing all validator attributes.
The Validation class is a static class. This is a new feature in C# 2.0. The C# compiler enforces that all members of the class are static and marks the class as sealed and abstract at compile time. This prevents direct instantiation or inheritance. The Validation class's sole job is to provide methods for verifying data. The validators consume these methods, but you can use the methods anywhere in your solution.
Inside the Validation Class
The Validation class includes several generic methods. These generic methods enable type-safe code reuse across disparate types. For example, Int32 implements the IComparable<int> interface. Therefore, the generic validation method for comparisons can be reused in a type-safe manner for Int32, DateTime, Decimal, Guid, and so on, due to the generic constraints:
public static bool IsCompareValid<T>
(T value, T comparisonValue,
ValidationCompareOperator
oper) where T : struct,
IComparable<T>
{
int compareResult =
left.CompareTo(right);
return IsCompareResultValid(
compareResult, oper);
}
You can build your validator attributes that consume the Validation class once you have the Validation class in place. The six built-in attributes all derive from a common abstract base class: ValidatorAttribute (see Figure 1).
The key is that all subclasses must implement the EvaluateIsValid() method, where the data evaluation takes place. Note that the ASP.NET validator controls follow a similar pattern internally. Also, all attributes must define an ErrorMessage property in the constructor and whatever customized arguments each validator control requires. You can apply multiple validator attributes to a single property.
The RequiredValidatorAttribute is a basic attribute that allows the user to mark a property as required:
[RequiredValidator(
"First name is required.")]
public string FirstName
{
get { return firstName; }
set { firstName = value; }
}
The behavior of the RequiredFieldValidator adjusts depending on the datatype of the property. For example, a string property is invalid if it's null or an empty string, whereas an integer property is invalid if it has the default value of zero. Similar to the ASP.NET RequiredFieldValidator, the RequiredValidatorAttribute can specify an InitialValue property optionally.
You can only apply the LengthValidatorAttribute to string properties. You must specify a maximum length, and can optionally specify a minimum length:
[LengthValidator(2, 50,
"Last name must be between 2-50
characters.")]
public string LastName
{
get { return lastName; }
set { lastName = value; }
}
The EvaluateIsValid() override of the LengthValidatorAttribute illustrates a case where you delegate work to the Validation class:
protected internal override bool
EvaluateIsValid()
{
if (this.PropertyToValidate.Type
!= typeof(string))
{
throw new ArgumentException(
"Property must be a string to
be used for the Length
Validator");
}
string text =
this.PropertyToValidate.Value
as string;
return Validation.IsLengthValid(
text, this.min, this.max);
}
You can apply the RegexValidatorAttribute only to string properties. You can specify the error message and the validation expression with this attribute:
[RegexValidator(@"\w+([-+.]\w+)*@w+([-.]\w+)*\.\w+([-.]\w+)*",
"Invalid email format.")]
public string EmailAddress
{
get { return emailAddress; }
set { emailAddress = value; }
}
The RangeValidatorAttribute and CompareValidatorAttribute work in similar ways. They let you to specify comparison values:
[RangeValidator(10, 1000,
"Order must be between $10-
$1000.")]
public decimal OrderTotal
{
get { return orderTotal; }
set { orderTotal = value; }
}
[CompareValidator(18,
ValidationCompareOperator.
GreaterThanEqual,
"Age must be between 18-100
years old.")]
public int Age
{
get { return age; }
set { age = value; }
}
The attributes require constant values, so you cannot specify a DateTime value. However, if the string "04/05/2006" is specified on a DateTime property, the RangeValidatorAttribute will automatically convert this to a DateTime value at run-time so that a proper comparison can be executed.
Implement Complex Validations
Business objects often require validations that are more complex than analyzing a single value. For example, a VISA credit card number has different business requirements than an American Express credit card number. You can address this issue by using a CustomValidatorAttribute to specify the error message and a specific validation method to run:
[CustomValidator(
"Credit card is invalid.",
"CheckValidCreditCard")]
public string CreditCardNumber
{
get { return creditCardNumber; }
set { creditCardNumber = value; }
}
The framework invokes the CheckValidCreditCard() method automatically through a dynamic delegate to perform the validation (see Listing 1). This allows you to use multiple class members within a validation. Additionally, the CustomValidationEventArgs allows you to set a dynamic error message. The custom validator invokes this dynamic delegate using the new generic EventHandler<T> delegate with CustomValidationEventArgs within its EvaluateIsValid() override. This means that any method that conforms to the delegate signature is a candidate for a custom validation.
So far, you've built the validation methods and attributes. The final major component of the framework is the ValidationManager. This component is responsible for consuming all attributes and keeping track of any violations. It provides methods to validate a single property or all properties at once. The ValidationManager has a reference to the target class that it verifies. The ValidationManager creates a cache of all validation attributes when it is instantiated by leveraging Reflection (see Listing 2). Whenever a validation is requested, the ValidationManager retrieves the appropriate property from its internal cache and executes the validations for each validator attribute on the given property. It also dynamically caches any outstanding violations.
You can use the base classes interchangeably in Web and Windows applications, but the ValidatableBase class is your best choice when working with a Web application. The ValidatableBase class uses the ValidationManager as a private member and instantiates it the first time it is requested by passing in a reference to itself:
protected ValidationManager Validator
{
get
{
if (this.validator == null)
{
this.validator = new
ValidationManager(this);
}
return this.validator;
}
}
Next, the ValidateBase class exposes an IsValid property that delegates the IsValid call to the ValidationManager. You perform this validation at the time the client code invokes the IsValid property:
public virtual bool IsValid
{
get
{
.Validator.Validate();
return this.Validator.IsValid;
}
}
The property is virtual, so subclasses can override it if, for example, they need to validate child objects as well as themselves. This pattern has been shown to be effective in other frameworks such as CSLA.NET. Inheriting from ValidatableBase allows the subclasses to apply the attributes to the properties.
Incorporate Validation Code
The NotifyValidatableBase class inherits from ValidatableBase class and exposes additional functionality that Windows applications can leverage to incorporate validation code automatically. Specifically, it utilizes the INotifyPropertyChanged interface, which is new in .NET 2.0:
public interface INotifyPropertyChanged
{
event PropertyChangedEventHandler
PropertyChanged;
}
The NotifyValidatableBase exposes protected methods to allow subclasses to validate a specific property and to raise the PropertyChanged event of the INotifyPropertyChanged interface:
protected void NotifyAndValidate(
string propertyName)
{
this.Validator.Validate(
propertyName);
this.NotifyPropertyChanged(
propertyName);
}
A property of a subclass of NotifyValidatableBase might look like this:
[RequiredValidator(
"Last name is required.")]
[LengthValidator(12,
"Last name must be less than 12
characters.")]
public string LastName
{
get { return this.lastName; }
set
{
if (lastName != value)
{
this.lastName = value;
this.NotifyAndValidate(
"LastName");
}
}
}
This behavior isn't as appropriate for Web applications due to their stateless nature. For example, you need to recreate the object between postbacks to the server unless you serialize the entire object graph to some persistence medium (such as view state). This makes checking for changes by comparing the private member with an incoming value pointless.
One of the best aspects of the ASP.NET validator controls is that they provide client-side JavaScript that gives the user immediate feedback without requiring a postback to the server. However, re-implementing all validations with validator controls means duplicating significant amounts of business logic, while also making maintenance more difficult. If business rules change, then you would need to update the business logic in multiple places.
The good news is that the Declarative Validation framework provides a mechanism for generating the ASP.NET validator controls for you automatically. It does this through the ValidatorGenerator class, which includes a simple API (see Figure 2).
Consider a FormView control where you have already set up two-way databinding (see Listing 3). In this example, the ValidatorGenerator automatically creates numerous ASP.NET validators for you, as the framework understands how to translate the validator attributes into corresponding ASP.NET validator controls. For example, assume you bind a string property to a textbox. The string property includes three attributes: RequiredValidator, LengthValidator, and RegexValidator. The ValidationGenerator responds by creating three corresponding ASP.NET validator controls to validate that textbox.
In Windows applications, the ErrorProvider control is the primary visual indicator to the user for data errors. You often use this control in conjunction with the System.ComponentModel.IDataErrorInfo interface. The DataRowView is the most well-known consumer of the System.ComponentModel.IDataErrorInfo interface to provide error feedback to the user when a collection of data is bound to a grid control. However, frameworks such as CSLA.NET have shown that business objects can utilize this interface effectively for UI validation as well. Similarly, NotifyValidatableBase implements this interface, so you can tie the UI to the logic in the business object automatically. The good news: It requires surprisingly little effort to make this work. Begin by creating a method to bind the property and subscribe to the control's Validated event:
protected void Bind(Control ctl,
string ctlProp, object dataSource,
string dataMember)
{
ctl.DataBindings.Add(ctlProp,
dataSource, dataMember);
ctl.Validated +=
OnControlValidated);
}
You can call this method like this:
Bind(txtFirstName, "Text", person, "FirstName");
The definition of the OnControlValidated() event calls the ValidateControl() method, passing in a reference to the control:
private void ValidateControl(
Control ctl)
{
foreach (Binding binding in
ctl.DataBindings)
{
if (binding.IsBinding)
{
IDataErrorInfo errorInfo =
binding.DataSource as
IDataErrorInfo;
error.SetError(ctl,
errorInfo[binding.
BindingMemberInfo.
BindingField]);
}
}
}
This code shows or hides the ErrorProvider control automatically as appropriate, in conjunction with the violations that occur in the business object.
The heart of any object-oriented system lies in a well-designed business layer that is responsible for properly encapsulating its data. Utilizing Reflection with new C# 2.0 language features like generics, provides a powerful mechanism for accomplishing this goal. The framework provides opportunities for you to extend it with your own code. For example, you can create new validator attributes with checks that are more specific than the built-in types. You might create a validator to check for a certain currency format, or you might create the EmailValidatorAttribute, so the developer won't have to know the exact regular expression to use for an e-mail address. Another idea: You might extend the CompareValidatorAttribute to compare class properties rather than the constant values only. Finally, you might extend the ValidatorGenerator to provide more granular control over the way the ASP.NET validator controls are generated.
More Information
Download all the code for this article
here .