Introduction
This is the first in a series of articles, code samples and presentations
that deal with design (and architecture?) ‘Below’ the enterprise level.
Enterprise patterns are valuable for applications that have requirements for
scalability, availability, maintainability, security and manageability, but
using them for applications with less strenuous requirements (over-engineering)
can have a negative impact on both developability and maintainability.
So I approached building this design sample in a different way. The primary
requirement is that it does the job; the secondary requirement is that it is
easy to explain/understand. Nothing else gets a look in. This has led to some
interesting design decisions – it doesn’t look like anything I have built for a
while. I’ll point these design points out as we go through it.
If you download simplevalidation.zip from the source files link above,
extract and open the SimpleValidation solution it contains using VS.NET 2003 (it
works OK in VS.NET 2005 as well) you will see the following projects:
- DataTransferObjects
- IntegerValidationRules
- OrderValidationRules
- ValidationTest
This solution implements a simple (passive – no eventing) way of performing
business layer validation. The objective here is to implement validation of
datasets, business object collections, XML documents or whatever else you are
using as your data transfer objects as an aspect. If you don’t understand where
this would fit in a layered service oriented design, I’ll cover ‘standard’
(simple?) layers and tiers in another article in a couple of weeks.
To see what it does, run the ValidationTest (default) project. This exercises
two different validation libraries, IntegerValidationRules and
OrderValidationRules.
Integer Validator
When you click on the Validate button in the Integer test it ends up
executing the following line of code:
Dim results As IntegerValidationRules.ValidationResultCollection =
IntegerValidationRules.Validator.Validate(ii, -1)
This calls the Validate method of the Validator
class in the
IntegerValidationRules assembly, passing in an object to validate and the
maximum number of validation failures we will put up with before giving up (-1
means any number).
The first part of the validate method (shown below) loads up the
ValidationRules
collection if required. This is the first
interesting decision. Normally I would construct a class
(Validator
) and call a method on it. Object construction is not
expensive in .NET so performance is not really a reason to use static methods.
IntegerValidationRules Validator – Part 1
public class Validator
{
public static SortedList ValidationRules;
public static ValidationResultCollection Validate(
object objectToValidate, int maxValidationFailures)
{
if (ValidationRules == null)
{
ValidationRules = new SortedList();
Assembly vAss = Assembly.GetExecutingAssembly();
foreach (Type typ in vAss.GetTypes())
{
if (typ.BaseType == typeof(ValidationRuleBase)) {
ValidationRuleBase rule =
(ValidationRuleBase) vAss.CreateInstance(typ.FullName);
ValidationRules.Add(rule.RulePriority.ToString() +
rule.RuleName, rule);
}
}
}
The reason the Validate
method is static is because the
ValidationRules
variable is static and there are no other member
variables of the class, so Validate
may as well be static (save a
line of code when calling it). I am using reflection to work out what the
validation rules in this assembly are and because reflection is expensive, I
only want to reflect on this assembly once and store the result in the static
ValidationRules
collection which ends up containing - after the
first time I execute this method - a list of all the classes in this assembly
that inherit from the ValidationRuleBase
class, sorted by priority.
Another option would have been just to populate a list of all the validation
rules in code. The reason I did it using reflection is because I feel it is more
obvious. The validation rules called by this assembly’s Validator’s
Validate
method are all the classes in the assembly that inherit
ValidationRuleBase
. The good news is that you can easily see what
these will be (look at the files in the assembly) the bad news is that you can’t
chop and change these at run time – but did you need to?
Once we have worked out what the validation rules are (or figured that they
are already loaded) we go ahead and call the Validate methods of each rule.
IntegerValidationRules Validator – Part 2
ValidationResultCollection results = new ValidationResultCollection();
int failures = 0;
foreach (ValidationRuleBase rule in ValidationRules.Values)
{
ValidationResultCollection ruleResults = rule.Validate(objectToValidate);
if ((ruleResults != null) && (ruleResults.Count > 0))
{
results.AddRange(ruleResults);
if (maxValidationFailures > -1)
{
foreach (ValidationResult result in ruleResults)
{
if (result.ResultType == ResultType.Failed)
{
failures++;
if (failures >= maxValidationFailures)
{
return results;
}
}
}
}
}
}
The validate methods return a collection of ValidationResults
,
which may contain zero or more results. This is to give maximum flexibility to
the individual rules. The collections of results from each rule are concatenated
into a collection of results for the whole Validator and returned.
As we return another interesting design decision is obvious. The types we are
using such as IntegerValidationRules.ValidationResult
,
IntegerValidationRules.RulePriority
,
IntegerValidationRules.ResultType
etc. are implemented inside each
Validation Rules library – they are not shared.
The bad thing about this is that we can’t for example easily combine the
results of one validation library with another, each one stands alone with its
own set of types. Again the reason this is done is for simplicity:
- We don’t have to make any references from the validation rules libraries and
their clients to a shared ‘base’ assembly
- We can easily change the way any individual validation rules library works,
say changing the priorities available for rules, without impacting any other
code
- Its more obvious (most important reason) what’s happening here
If you take a look at a ValidationRule such as
IntegerObjectsShouldBeEvenRule
you will notice that the
Validate
method (and the Validator
class’
Validate
) are not strongly typed (they take obects).
public override ValidationResultCollection Validate(object objectToValidate)
{
if (!(objectToValidate is int)) { return null; }
Normally I am a firm believer in typing everything, but objects are more
generic to start with and because we are sharing nothing between assemblies the
developer can change this in the Validator
and
ValidationRuleBase
classes for a specific rule library if required.
Order Validator
The second example shows validation of a strongly typed dataset (STDS). This
STDS would actually map to a couple of tables in the AdventureWorks sample
database but I didn’t want to mess with SQL Server, so the dataset is
constructed in code by the testbed’s form load method.
The DataTransferObjects (DTO) assembly contains the definition for the
dataset. We are simulating code that would run in the business layer of an SOA
application, and the DTO should define the (serializable of course) messages we
are sending between layers – in this case OrdersDataSet
.
When you click validate a call to the Validate
method of the
Validator
class of the OrderValidationRules
assembly
is called:
Dim results As OrderValidationRules.ValidationResultCollection =
OrderValidationRules.Validator.Validate(drOrder, -1)
This time we pass an Order datarow, which represents an order. Exactly what
the individual validations rules do is very open, they can check stuff inside
the object being validated (which the sample ones here do) or they can access a
database for more information, or they might call a web service – the validation
‘plumbing’ doesn’t care at all.
One thing the plumbing doesn’t do is muck around with the
DataTable
Error Collections. They are there and can be very useful, but adding
support for these to the generic part of the Validation framework would have
made it more complex. If the Validation rules want to work with these they can.
Instead we are using a more generic approach. The
ValidationResult
class includes
Reference
and
ReferenceHint
fields. These are used in the case of the
DataSet
to
indicate which row of which table the rule is complaining about.
Eventually we get back to the caller which then has the option of iterating
through the result collection and using the information available there to set
the datatable errors collections (it doesn’t).
Code Generation - Lite
OK, well how would you use this yourself?
If you download, unzip and run the exe from the template installer link
at the top of this article, it will install a new project type (“Validation
Rules Library”) and a new Code project item type (“Validation Rule”) for both VB
and C#.
Using the project types is a way to do code generation, and that is one of
the things that is happening here – code generation (which is good).
Well that's it (it's supposed to be 'Simple' after all). I hope that this
sample is useful to somebody! Check my blog at http://endintiers.com/ for more 'simple' .NET
designs.