This post is the second of a series where I share my thoughts on what I expect from a Validation framework, and how SpecExpress meets these expectations. In my last post I discussed how SpecExpress separates the validation logic from the instance being validated – creating a loosely coupled validation solution. In this post, I will discuss what should be returned when an instance is validated and is found to be in an invalid state.
More than a List of Strings
At first inclination, one might think that when we validate an object, we simply need a list of string describing what was invalid about the object. But what if I simply want to know if the object is valid? And what if the object I am validating references other objects that are invalid? Perhaps the object I am validating is a Contact, and a Contact references an Address. If the address is invalid, how do I want that represented in the list of strings? How do I represent the details about what is invalid about the address? Also, what if I want to somehow bind each message to a control on the user interface? Wouldn’t I need more meta data about each error message in order to discern the bindings?
When an object is validated in SpecExpress by calling ValidationCatalog.Validate, a ValidationNotification object is returned. Here is an example of the structure of the ValidationNotification:
The ValidationNotification has an IsValid flag indicating whether or not the instance is valid or not. It also contains a list of ValidationResult objects accessible through the Errors property. Each ValidationResult contains the following:
- “Message” property which is intended for display to user
- “Property” property which is a MemberInfo which encapsulates all the metadata about the property being validated
- “Target” property holding reference to the instance being validated
- “NestedValidationResults” which - if the property being validated is a reference to another object (such as Address) – will contain another collection of ValidationResults that define the items that are invalid with it. Thus the Results are hierarchical.
The ToString method of the ValidationNotification will result in a user readable summary of all the messages where the hierarchy is represented by indentation. However, this may not suit all cases, and in cases where it has not been suitable, I have simple created my own extension method to the ValidationNotifiation type to render it as I wish!
Message store and globalization
As a consumer of a Validation Framework, I want complete control over the messages rendered to the user. Of course, I want the ability to specify an error message when defining a rule, but doing so may paint me into a corner when it comes to globalization. Such hard coded error messages are often frowned upon. The preferred method would be to store such messages in a resource file which can be easily globalized.
SpecExpress, out of the box, stores its error messages in resource files. However, SpecExpress does not limit you to using resource files. The validation engine interacts with the resource files through an interface called IMessageStore. One can create their own implementation of IMessageStore that, perhaps gets the error messages from another source such as an XML file, WCF Service, database, or even any combination – the options are limitless.
The IMessageStore interface requires you only implement two methods: GetMessageTemplate and IsMessageInStore. GetMessageTemplate simply returns the message given the key, and IsMessageInStore simply returns a boolean indicating if a message for a given key exists in the store.
After implementing your IMessageStore, one simply add your implementation to the ValidationCatalog by doing the following:
ValidationCatalog.Configure(x => x.AddMessageStore(new MyCustomerMessageStore, "MyCustomMessageStore"));
Messages are accessed through message keys. These keys are simply the name of the rule or, if the rule is negated, the name of the rule is prefixed with “not_”. So, for instance, when the “Contains” rule fails it will look for the message keyed by “Contains”. If the rule is defines as “Not.Contains”, then the validation engine will call for the “Not_Contains” message.
You may add override the default messages by implementing your own repository, or adding another instance of the ResourceMessageStore pointing to your own resource file, and simply placing new message keyed by the same name in there. The validation engine will search all the added message stores for the file and if not found will use the default messages.
When an object is invalid, SpecExpress will return a ValidationNotification object which provides a rich set of information regarding what is invalid. The ValidationNotification is hierarchical in nature, reflecting the hierarchical nature of the object being validated and can be used to bind error results to the user controls bound to the model being validated.
SpecExpress also gets the error messages from a message store which is loosely coupled to the validation engine through the IMessageStore interface. By implementing your own instance of the IMessageStore, one can store the error messages anywhere they want and even override the default messages. SpecExpress provides a ResourceMessageStore that implements the IMessageStore interface and returning messages stored in resource files.
In my next post, I will discuss a few general features I would expect a Validation Framework to provide the developer.