So let's take a pretty common use-case: validating an email address. Email addresses are a slippery fish, so I'll make a DataAnnotations validator to make sure that an input's value looks like it's probably in a valid email address format.
I start with a regex validator that I think I pulled off of Scott Guthrie's blog:
public class RegexAttribute: ValidationAttribute
{
public string Pattern { get; set; }
public RegexOptions Options { get; set; }
public RegexAttribute(string pattern)
: this(pattern, RegexOptions.None) {}
public RegexAttribute(string pattern, RegexOptions options)
{
Pattern = pattern;
Options = options;
}
public override bool IsValid(object value)
{
//null or empty is valid, let Required handle null/empty
var str = (string)value;
return string.IsNullOrEmpty(str) ||
new Regex(Pattern, Options).IsMatch(str);
}
}
Simple enough, let's extend it and make an Email validator:
public class EmailAttribute : RegexAttribute
{
public EmailAttribute() :
base(@"^(([^<>()[\]\\.,;:\s@\""]+(\.[^<>()[\]\\.,;:\s@\""]+)*)|(\"".+\""))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$",
RegexOptions.None) {}
}
Pretty simple. I bet I could get away with putting it all on one line since no one actually reads regular expressions.
Now here's the magic part. I'm going to use Reflection to grab all the attributes that are of the type ValidationAttribute and see if those attributes are valid. If they aren't, I'll make a list of ErrorInfo objects. I call it my ValidationBuddy.
public static class ValidationBuddy
{
public static IList<ErrorInfo> GetTypeErrors(object instance)
{
var attributes = TypeDescriptor.GetAttributes(instance)
.OfType<ValidationAttribute>();
return (from attribute in attributes
let result = attribute.GetValidationResult(instance,
new ValidationContext(instance, null, null))
where result != null
select new ErrorInfo(instance.GetType().ToString(),
attribute.FormatErrorMessage(string.Empty),
instance))
.ToList();
}
public static IList<ErrorInfo> GetPropertyErrors(object instance)
{
return (from prop in TypeDescriptor.GetProperties(instance)
.Cast<PropertyDescriptor>()
from attribute in prop.Attributes
.OfType<ValidationAttribute>()
where !attribute.IsValid(prop.GetValue(instance))
select new ErrorInfo(prop.Name,
attribute.FormatErrorMessage(prop.DisplayName), instance))
.ToList();
}
public static IList<ErrorInfo> GetErrors(object instance)
{
var list = GetPropertyErrors(instance);
foreach (var error in GetTypeErrors(instance))
list.Add(error);
return list;
}
}
The ErrorInfo class is pretty simple, too:
public class ErrorInfo
{
public ErrorInfo(){}
public ErrorInfo(string propertyName, string errorMessage, object instance)
{
PropertyName = propertyName;
ErrorMessage = errorMessage;
Instance = instance;
}
public string PropertyName { get; set; }
public string ErrorMessage { get; set; }
public object Instance { get; set; }
}
And finally, a single unit test:
[TestMethod]
public void TestEmailValidator05()
{
// Arrange
var model = GetModel();
model.EmailAddress = "uncommonTLD@domain.travel";
// Act
var errors = ValidationBuddy.GetErrors(model);
// Assert
Assert.IsTrue(!errors.Any());
}
In my actual test class, I have quite a few more tests, but this should get you started in writing your own DataAnnotations unit tests.