Well, there’s nothing like a good round of standards abuse to get the blood flowing. Recently I’ve found myself in a situation where I can’t use part of the enterprise library as it was originally intended to be used. The Enterprise Library’s Validation Application Block is designed to be used in a few different ways. First, you can decorate your business entities with attributes that specify what rules should be applied to which properties, and the block will discover them through reflection at runtime and apply the rules. Second, you can describe your rules in an external configuration file which the VAB will read and match up to the objects at runtime (once again via reflection). Finally, you can decorate your class with the "HasSelfValidation" attribute, and the VAB will call any methods on that class decorated with the "SelfValidation" attribute.
The config file has the advantage of letting you change or modify rules without having to recompile the application. You could expand the length of a field in the database, change the rule in the config file, and everything will continue to work. Unfortunately it loses you quite a bit of type safety. For instance, renaming a property in your application won’t result in an error. Instead, that rule will simply stop being applied which is definitely a deal-breaker for my current project. If the VAB had a way to specify that we want to know if there’s anything in the config that doesn’t match reality, then this is definitely the way I’d want to go.
The attribute-based approach guarantees that the rules will continue to operate even if you rename or re-namespace the entities in question, but requires you to recompile the application in order to make any changes to the validation rules. It also won’t work if you’re dealing with generated partial classes (IE, Linq entities) where you can’t get to the properties to decorate them, or at least not without having to re-do it each time you touch the designer surface. If you could specify arbitrary attributes through the designer, then this would be my second choice.
The self-validation approach allows you to implement custom rules that can’t be expressed using the built-in validators, and aren’t worth writing a whole custom validator class for. The trouble is that they require you to do all the work yourself. You test your rule, and if it’s broken, then you create a ValidationResult object and add it to your ValidationResults instance.
So we have three approaches, none of which are ideal. Refactoring support and not losing rules due to renaming changes are a must for this project, but we’re using linq-generated entities, and can’t decorate them with the required attributes. Writing everything as custom self-validation methods means giving up the advantages of using the validation block in the first place, so what can we do? What we need is a code-based approach that can still leverage the built-in VAB validators to do the actual work.
Well, as it turns out, you actually can use the VAB validators from code, but the results are a bit less than ideal. For instance, to check whether a number is within a certain range, let’s say 1 to 10 for this example, you need to write the following code:
ValidationResults results = new ValidationResults(); RangeValidator validator = new RangeValidator(1, 10); validator.Validate(this.Size, results);
If the validation fails, then results will contain a new ValidationResult instance detailing the problem. There are a couple problems with this approach, though. First, this is a lot of code to write for each and every little property you want to validate. A simple, 20 line method has now become at least 40 plus some overhead. Since the built-in validators don’t have any kind of static methods you can call when you just want to perform a simple property validation from code, you have to instantiate a different validator for each and every unique combination of limits, bounds, or patterns you might need to test. Second, and more importantly, the resulting ValidationResult entities won’t have their Key or Tag properties filled in, and their Target properties will be pointing to the actual values that were tested rather than the objects which contained them. You can’t just fill them in after the fact, either, because these properties are all read-only, and can only be set from the constructor.
During "normal" operation of the VAB, the values for these properties are known to the VAB because it’s either been reflecting over the assembly looking for properties with validator attributes on them, or it’s been reflecting over the assembly looking for properties that match the descriptions stored in the config file. Either way, the "Key" property of the ValidationResult gets set to match the name of the property, and that is figured out via reflection. In order to fill these properties in manually, we’re going to have to iterate over our results, and make new ones that look just like them, but better:
ValidationResults betterResults = new ValidationResults(); foreach (ValidationResult result in results) { betterResults.AddResult(new ValidationResult(result.Message, target, key, tag, result.Validator)); }
This is getting kind of messy by this point, but don’t give up yet, things are about to get better. What we need is a good old-fashioned helper class, or perhaps a new class derived from ValidationResults which could encapsulate some of this grunt-work for us. The ValidationResults class isn’t sealed, so there’s nothing to stop us from deriving a new class from that, adding our helper methods, and going on about our merry way, but since this is .net 3.5, we’re going to do something far more sinister, and implement our helpers as extension methods instead. Why? Because it’s there, that’s why.
I have, in the past, followed a general pattern when it comes to validation coding. I try to make it look as much like a unit test as I can. It’s already familiar to most developers, it’s easy to understand, and I think it encapsulates things rather nicely. I have done this by creating multiple "AssertXyz" methods on a business rule collection of some sort. If the rule is broken, then the collection adds a new BusinessRule object to itself. When you’re done asserting rules, you just check the number of rules that ended up in the collection, and if it’s zero, then the object is valid. It seems simple enough, and I’ve had a lot of success doing it this way. We’ll try to make something similar, but instead of writing all my own comparison logic as I’ve done in the past, I’m going to try to reuse the existing VAB validators to do the work for me. This will ensure consistent results whether the rules have been specified in attributes, configuration or code.
I won’t include the entire class here because it would be terribly long, but you’ll get the idea:
public static void AssertRange(this ValidationResults results, object target, IComparable value, string key, string tag, IComparable lowerBound, RangeBoundaryType lowerBoundType, IComparable upperBound, RangeBoundaryType upperBoundType, string messageTemplate, bool negated) { if ((lowerBoundType != RangeBoundaryType.Ignore) || (value != null)) { // Compose a basic error message. // The default message generated by the VAB does not include the name of the field. if (string.IsNullOrEmpty(messageTemplate) && !string.IsNullOrEmpty(key)) { messageTemplate = string.Format("{0} must {1}be between {2} ({3}) and {4} ({5}).", key, negated ? "not " : string.Empty, lowerBound, lowerBoundType, upperBound, upperBoundType); } ValidationResults tempResults = new ValidationResults(); RangeValidator validator = new RangeValidator(lowerBound, lowerBoundType, upperBound, upperBoundType, messageTemplate, negated); validator.Validate(value, tempResults); results.AddAllResults(tempResults, target, key, tag); } }
This method extends the ValidationResults class to test whether a given value falls within the specified range, and add a new ValidationResult to itself if not. You can see that I’ve also created an appropriate messageTemplate in code, since there’s no attribute of config file to define it in, and the default message generated by the VAB won’t include any of the key identifying information to let you know which property has been found in violation. A validator is then instantiated and used to validate the value argument. Finally, the results are added to the real ValidationResults instance via another couple of extension method that fill in the Target, Key and Tag properties on the way:
public static void AddAllResults(this ValidationResults results,
IEnumerable<ValidationResult> sourceValidationResults, object target, string key, string tag) { foreach (ValidationResult result in sourceValidationResults) { results.AddResult(result.Message, target, key, tag, result.Validator); } } public static void AddResult(this ValidationResults results, string message, object target, string key,
string tag, Validator validator) { results.AddResult(new ValidationResult(message, target, key, tag, validator)); }
All that’s left is to create multiple AssertRange overloads that take different sets of parameters and do more and more of the work for you:
public static void AssertRange(this ValidationResults results, object target, IComparable value, string key,
IComparable upperBound) { AssertRange(results, target, value, key, null, null, RangeBoundaryType.Ignore, upperBound,
RangeBoundaryType.Inclusive, null, false); } public static void AssertRange(this ValidationResults results, object target, IComparable value, string key,
IComparable upperBound, bool negated) { AssertRange(results, target, value, key, null, null, RangeBoundaryType.Ignore, upperBound,
RangeBoundaryType.Inclusive, null, negated); } public static void AssertRange(this ValidationResults results, object target, IComparable value, string key,
IComparable lowerBound, IComparable upperBound) { AssertRange(results, target, value, key, null, lowerBound, RangeBoundaryType.Inclusive, upperBound,
RangeBoundaryType.Inclusive, null, false); } public static void AssertRange(this ValidationResults results, object target, IComparable value, string key,
IComparable lowerBound, IComparable upperBound, bool negated) { AssertRange(results, target, value, key, null, lowerBound, RangeBoundaryType.Inclusive, upperBound,
RangeBoundaryType.Inclusive, null, negated); } public static void AssertRange(this ValidationResults results, object target, IComparable value, string key,
IComparable lowerBound, RangeBoundaryType lowerBoundType,
IComparable upperBound, RangeBoundaryType upperBoundType) { AssertRange(results, target, value, key, null, lowerBound, lowerBoundType, upperBound, upperBoundType,
null, false); } public static void AssertRange(this ValidationResults results, object target, IComparable value, string key,
IComparable lowerBound, RangeBoundaryType lowerBoundType,
IComparable upperBound, RangeBoundaryType upperBoundType, bool negated) { AssertRange(results, target, value, key, null, lowerBound, lowerBoundType, upperBound, upperBoundType,
null, negated); } public static void AssertRange(this ValidationResults results, object target, IComparable value, string key,
IComparable lowerBound, RangeBoundaryType lowerBoundType,
IComparable upperBound, RangeBoundaryType upperBoundType, string messageTemplate) { AssertRange(results, target, value, key, null, lowerBound, lowerBoundType, upperBound, upperBoundType,
messageTemplate, false); }
I tried to create one Assert method for each overloaded constructor on the validator class in question so that the extension methods expose all the different ways the original validators could be used. Lather, rinse, and repeat with the other validator types you need, and you’re all set.