When doing validation, There’s a number of options to how you approach it: you could simply have a series of conditional statements testing logical criteria, you could follow the chain of responsibility pattern, use some form of polymorphism with the strategy pattern, or even, as I outline below, try using the builder pattern.
Let’s first break down the options. We’ll start with the strategy pattern, because that’s where I started when I was looking into this. It’s a bit like a screwdriver - it’s usually the first thing you reach for and, if you encounter a nail, you might just just tap it with the blunt end.
Strategy Pattern
The strategy pattern is just a way of implementing polymorphism: the idea being that you implement some form of logic, and then override key parts of it; for example, in the validation case, you might come up with an abstract base class such as this:
public abstract class ValidatorBase<T>
{
public ValidationResult Validate(T validateElement)
{
ValidationResult result = new ValidationResult();
if (CheckIsValid(validateElement))
{
result = OnIsValid();
}
else
{
result = OnIsNotValid();
}
return result;
}
protected virtual ValidationResult OnIsValid()
{
return null;
}
. . .
You can inherit from this for each type of validation and then override key parts of the class (such as `CheckIsValid`).
Finally, you can call all the validation in a single function such as this:
public bool Validate()
{
bool isValid = true;
IEnumerable<ValidatorBase> validators = typeof(ValidatorBase)
.Assembly.GetTypes()
.Where(t => t.IsSubclassOf(typeof(ValidatorBase)) && !t.IsAbstract)
.Select(t => (ValidatorBase)Activator.CreateInstance(t));
foreach (ValidatorBase validator in validators)
{
ValidationResult result = validator.Validate();
if (!result.IsValid)
{
isValid = false;
Errors.AddRange(result.Errors);
}
if (result.StopValidation)
{
break;
}
}
return isValid;
}
There are good and bad sides to this pattern: it’s familiar and well tried; unfortunately, it results in a potential explosion of code volume (if you have 30 validation checks, you’ll need 30 classes), which makes it difficult to read. It also doesn’t deal with the scenario whereby one validation condition depends on the success of another.
So what about the chain of responsibility that we mentioned earlier?
Chain of responsibility
This pattern, as described in the linked article above, works by implementing a link between a class that validates your data, and the class that will validate it next: in other words, a linked list.
This idea does work well, and is relatively easy to implement; however, it can become unwieldy to use; for example, you might have a class like this:
private static bool InvokeValidation(ValidationRule rule)
{
bool result = rule.ValidationFunction.Invoke();
if (result && rule.Successor != null)
{
return InvokeValidation(rule.Successor);
}
return result;
}
But to build up the rules, you might have a series of calls such as this:
ValidationRule rule2 = new ValidationRule();
rule.ValidationFunction = () => MyTest();
ValidationRule rule1 = new ValidationRule();
rule.ValidationFunction = () => MyTest();
Rule.Successor = rule2;
As you can see, it doesn’t make for very readable code. Admittedly, with a little imagination, you could probably re-order it a little. What you could also do is use the Builder Pattern…
Builder Pattern
The builder pattern came to fame with Asp.Net Core; where during configuration, you could write something like:
services
.AddMvc()
.AddTagHelpersAsServices()
.AddSessionStateTempDataProvider();
So, the idea behind it is that you call a method that returns an instance of itself, allowing you to repeatedly call methods to build a state. This concept overlays quite neatly onto the concept of validation.
You might have something along the lines of:
public class Validator
{
private List<ValidationRule> \_logic;
public ValidatorEngine AddRule(Func<bool> validationRule)
{
ValidatorLogic logic = new ValidatorLogic()
{
ValidationFunction = validationRule
};
\_logic.Add(logic);
return this;
}
So now, you can call:
myValidator
.AddRule(() => MyTest())
.AddRule(() => MyTest2())
…
I think you’ll agree, this makes the code much neater.
References
http://blogs.tedneward.com/patterns/Builder-CSharp/
http://piotrluksza.com/2016/04/19/chain-of-responsibility-elegant-way-to-handle-complex-validation/