At some time or another we all have to code all the little rules, probably most of them already enforced in the database, into our business objects. FirstName cannot be null or empty, Age must be greater than 21, StartDate must be less than EndDate, etc etc... Wouldn't it be lovely if we could just declare these rules in one place and have them decoupled yet cross cutting the whole application, in the database, in the business layer, in the UI? Yeah it would, but I don't really have a solution for that and is it really possible/practical anyway? If it's possible it's sure not clean and easy, and if it's practical we'd all be doing it already!
What I have here is a very simple and lightweight way of validating these little rules, certainly not enterprise grade but certainly of some use in small scale scenarios.
In An Ideal World
Ideally what we want to do is associate rules with properties of our objects. This way we can hook into our NotifyPropertyChanged behaviour and validate anything that goes through each property. Wouldn't it be cool if we could pass in lambdas or delegates to attributes? Something like this maybe:
[ValidationRule(x => x.SomeNumber > 5, "SomeNumber must be greater than 5.")]
public Int32 SomeNumber { get; set; }
Unfortunately we can't. Ok, how about we declare our own delegate type and then we can stick instances of it above our properties and then use reflection or some other trickery to parse the order of members in our class so we know which rule is associated with each property. Then we could decorate our rule with an attribute containing the friendly human readable error message. Something like this maybe:
delegate Boolean ValidationRule<T>(T obj);
[ValidationRuleDescription("SomeNumber must be greater than 5.")]
ValidationRule<MyClass> rule = x => x.SomeNumber > 5;
public Int32 SomeNumber { get; set; }
Unfortunately we can't do that either. At least not in any pleasant, reliable and future proof way. It was getting a little messy anyway. In fact as far as the pile of my hair which was pulled out and left under my desk seems to indicate, there is no way of associating a method with a property.
Yes, we could start passing a string containing the property's name along with our rule, but that's just a maintenance problem waiting to happen. Is it really worth the possible gains when we'd have to maintain brittle string litteral mappings in our code files? In my opinion, in most cases, it's not. We have enough of that crap in all our Xml files already, I'm not about to bring it into my code if I can avoid it.
The Answer
The answer to all this? I have no idea, I don't have one. Instead I have some code which is type safe, clean, doesn't break under refactoring and lacking a feature that's probably not that important anyway. If anyone figures out how to associate a method with a property though, please please let me know!
Here is how it looks:
class Program
{
static void Main(string[] args)
{
MyClass c = new MyClass();
c.AnotherNumber = 10;
c.SomeNumber = 3;
List<String> result = SimpleValidator.Validate(c);
result.ForEach(x => Console.WriteLine(x));
Console.ReadKey();
}
}
public class MyClass
{
public MyClass()
{
SimpleValidator.AddRule<MyClass>(x => x.SomeNumber > 5
, "SomeNumber must be greater than 5.");
SimpleValidator.AddRule<MyClass>(x => x.SomeNumber > x.AnotherNumber
, "SomeNumber must be greater than AnotherNumber.");
}
public Int32 SomeNumber { get; set; }
public Int32 AnotherNumber { get; set; }
}
And the output looks like so:
The Code
And the code to achieve all this? First we have the RuleItem<T> class. This class is simply used internally to hold a representation of a rule and allow it to be validated.
internal class RuleItem<T> : IRuleItem
{
public RuleItem(Predicate<T> rule, String description)
{
this.rule = rule;
this.description = description;
}
private readonly Predicate<T> rule;
private readonly String description;
public String Description { get { return description; } }
public Boolean Validate(Object obj)
{
return rule((T)obj);
}
}
As you can see, this class implements IRuleItem. The reason for IRuleItem is to support polymorphism and allow us to create collections of RuleItem<T> by making a List<IRuleItem>. As you should all know, generics mess with polymorphism and we wouldnt be able to put an RuleItem<MyClass> and an RuleItem<MyOtherClass> into the same collection of List<RuleItem<T>>.
internal interface IRuleItem
{
Boolean Validate(Object obj);
String Description { get; }
}
The third and final part of the puzzle is SimpleValidator itself:
public static class SimpleValidator
{
private static Dictionary<Type, List<IRuleItem>> rules = new Dictionary<Type, List<IRuleItem>>();
public static void AddRule<T>(Predicate<T> rule, String description)
{
Type type = typeof(T);
List<IRuleItem> typesRules;
if (rules.ContainsKey(type))
typesRules = rules[type];
else
{
typesRules = new List<IRuleItem>();
rules.Add(type, typesRules);
}
typesRules.Add(new RuleItem<T>(rule, description));
}
public static List<String> Validate(Object obj)
{
Type type = obj.GetType();
if (!rules.ContainsKey(type))
return new List<String>();
List<IRuleItem> typesRules = rules[type];
List<String> validationFailures = new List<String>();
foreach (IRuleItem rule in typesRules)
{
if (!rule.Validate(obj))
validationFailures.Add(rule.Description);
}
return validationFailures;
}
}
In this class we maintain a collection of Dictionary<Type, List<IRuleItem>>. This holds a list of rules for each type we want to validate against. In AddRule<T> we check to see if we have a list of rules for this type in the dictionary already, if not we create one, then we add the given rule. In the Validate method we simply pull out the rules for the given type, if any, and then run them all. All done.
SimpleValidator.zip (7.43 kb)