I'm working on a project where the API methods I write have to return different "views" of domain objects, like this:
namespace View.Product
{
public class SearchResult : View
{
public string Name { get; set; }
public decimal Price { get; set; }
}
public class Profile : View
{
public string Name { get; set; }
public decimal Price { get; set; }
[UseValidationRuleset("FreeText")]
public string Description { get; set; }
[SuppressValidation]
public string Comment { get; set; }
}
}
These are also the arguments of setter methods in the API which have to be validated before storing them in the DB. I wrote an object validator that lets the user define validation rulesets in an XML file and checks if an object conforms to those rules:
[Validatable]
public class View
{
[SuppressValidation]
public ValidationError[] ValidationErrors
{
get { return Validator.Validate(this); }
}
}
public static class Validator
{
private static Dictionary<string, Ruleset> Rulesets;
static Validator()
{
// read rulesets from xml
}
public static ValidationError[] Validate(object obj)
{
// check if obj is decorated with ValidatableAttribute
// if not, return an empty array (successful validation)
// iterate over the properties of obj
// - if the property is decorated with SuppressValidationAttribute,
// continue
// - if it is decorated with UseValidationRulesetAttribute,
// use the ruleset specified to call
// Validate(object value, string rulesetName, string FieldName)
// - otherwise, get the name of the property using reflection and
// use that as the ruleset name
}
private static List<ValidationError> Validate(object obj, string fieldName, string rulesetName)
{
// check if the ruleset exists, if not, throw exception
// call the ruleset's Validate method and return the results
}
}
public class Ruleset
{
public Type Type { get; set; }
public Rule[] Rules { get; set; }
public List<ValidationError> Validate(object property, string propertyName)
{
// check if property is of type Type
// if not, throw exception
// iterate over the Rules and call their Validate methods
// return a list of their return values
}
}
public abstract class Rule
{
public Type Type { get; protected set; }
public abstract ValidationError Validate(object value, string propertyName);
}
public class StringRegexRule : Rule
{
public string Regex { get; set; }
public StringRegexRule()
{
Type = typeof(string);
}
public override ValidationError Validate(object value, string propertyName)
{
// see if Regex matches value and return
// null or a ValidationError
}
}
Phew... Thanks for reading all of this. I've already implemented it and it works nicely, and I'm planning to extend it to validate the contents of IEnumerable fields and other fields that are Validatable.
What I'm particularly concerned about is that if no ruleset is specified, the validator tries to use the name of the property as the ruleset name. (If you don't want that behavior, you can use [SuppressValidation].) This makes the code much less cluttered (no need to use [UseValidationRuleset("something")] on every single property) but it somehow doesn't feel right. I can't decide if it's awful or awesome. What do you think?
Any suggestions on the other parts of this design are welcome too. I'm not very experienced and I'm grateful for any help.
Also, is "Validatable" a good name? To me, it sounds pretty weird but I'm not a native English speaker.