An Implementation
The validation engine I created for this article is called Validator. You can download it from my
open-source web site (look under Products >> Documentation >> C# >> CleanCode.Forms.Validator). This engine validates form controls (TextBox or DataGridView Controls) using attributes you specify in the
Tag property as described earlier. When errors occur, it displays both an individual error indication for each invalid entry, and can disable a designated common form submittal button (typically an OK button). You can display validation errors in either individual Label controls unique to each control, or with a standard ErrorProvider component, at your discretion. Using an ErrorProvider carries the advantage that you do not have to create and associate a Label component with each validating component, but the disadvantage that you must use the mouse to see the validation error, as discussed earlier. You can also
download the sample application described in this article, which illustrates how to use the Validator.
To validate a control, you add it to a Validator instance, then call the
Validate() method inside either the
TextChanged or
Validating event handler for your control. MSDN documentation suggests the
Validating event handler is the appropriate place, but
Validating fires only when the focus leaves a control. If you need finer control (such as on every keystroke) then
TextChanged is a better choice.
The validation engine evaluates all validation attributes (business rules) for a given control, but reports only one error per control at a time. For example, if a single entry fails both the maximum length test and the maximum value test, the engine reports only one of those failures to the user. After the user corrects that error, then the remaining error condition will be displayed, if still present.
Coding the Example
Here's how to put all this into practice. For the simple example form given earlier, here is the user code (as distinct from the designer generated code). Each short method shown is an event handler, so it needs to be connected to an appropriate event (which should be evident from the method naming).
using System;
using System.ComponentModel;
using System.Windows.Forms;
using CleanCode.Forms;
namespace ValidationDemo
{
public partial class Form1 : Form
{
private Validator validator;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
validator = new Validator(okButton, errorProvider);
validator.Add(dataGridView);
validator.Add(leftTextBox);
validator.Add(rightTextBox);
}
private void okButton_Click(object sender, EventArgs e)
{
MessageBox.Show("OK clicked");
}
private void cancelButton_Click(object sender, EventArgs e)
{
MessageBox.Show("cancel clicked");
}
private void leftTextBox_Validating(object sender, CancelEventArgs e)
{
if (!validator.Validate(leftTextBox)) { e.Cancel = true; }
}
private void rightTextBox_TextChanged(object sender, EventArgs e)
{
validator.Validate(rightTextBox);
}
private void dataGridView_CellValidating(
object sender, DataGridViewCellValidatingEventArgs e)
{
if (!validator.Validate((DataGridView)sender, e))
{
// e.Cancel = true; // this really traps the user in there!
}
}
}
}
That's all the validation code need for the sample application (see
Figure 1) within Visual Studio 2005. The
Validator contains the actual validation code. The only other entries you need to make are the business rules, which I'll cover shortly.
In the preceding code, the Form1_Load event handler instantiates the validation engine, then adds each control to validate to it. Note that the Validator constructor accepts the form's OK button. The Validator uses that reference to enable the OK button when all
Controls validate and disable it (or "grey it out") if any controls do not validate. The constructor also accepts an ErrorProvider, which the validation engine uses to display errors.
The
leftTextBox and
rightTextBox controls use different event handlers merely to show the behavior differences discussed earlierthe former validates when focus leaves the field; the latter validates at every keystroke. The
leftTextBox handler also prevents users from leaving as long as an error is present. You could, in fact, use the same event handler method for many controls if you want them all to act on the same event and behave the same way.
For the DataGridView controla control containing multiple data valuescalling the method to perform validation is no more complicated than a singleton control, but the business rule set requires more information as you'll see next. The possibilities involved in handling DataGridView validation are much more sophisticated, particularly with bound DataGridViews, but those are beyond the scope of this article.
Business Rules for the Example
In addition to the code discussed earlier, you must instrument the
leftTextBox,
rightTextBox, and
dataGridView controls with business rules. Specifically, you set the
Tag property of these controls as shown in Table 3:
| Table 3. Sample Tag Property Values: The table lists the Tag settings for the three TextBox controls in the sample application. |
| Control |
Tag property |
| leftTextBox |
pattern=^\w[\w\.]*\@\w+\.\w[\w\.]*$;minLen=2 |
| rightTextBox |
pattern=^-?(?:\d*\.?\d+|\d+\.)$ |
| dataGridView |
[Column]pattern=^\w+$;[MatchExpr]pattern=\(.*\) |
These rules statein the language of regular expressionsthat the
leftTextBox must have an entry resembling an email address; "foo@bar.com" will pass muster, as will "e@a.b.c.d". The
rightTextBox business rule requires numeric input, for example,
5 or
-25 or
-3.9999 or
.81. The
dataGridView may have business rules for each columnbut does not
have to. In this case, the rules specify that the column generically called "Column" must contain only letters or digits and the column called "MatchExpr" must contain an opening and a closing set of parentheses somewhere in the input.
| Author's Note: In Table 3, notice the format used to embed multiple sets of business rules into one propertyeach column name is in brackets followed by its business rule set. |
Notes on Running the Example
Build the application and execute it to see what happens. The button will be disabled if any validation rule fails. In this case, the Validator uses an ErrorProvider to display validation errors. Because your code creates the ErrorProvider independently, you are free to add further validation checks in your event handlers that are beyond the scope of the business rule categories provided by the validation engine.
At this point, I hope you understand both how and why to separate business rules from code, using a validation engine to help do that. I encourage you to study the
source code to understand the mechanisms of the validation engine itself. This is actually a simplified version of an earlier validation engine called "PageValidator," implemented in Perl and JavaScript for use on a web site. The same engine runs on both the client and the server, providing for both rapid response and security.