Enterprise Library Validation – Custom Validators
In this post I am going to create a custom email validator that will integrate with the Enterprise Library Validation Application Block.
My goal here is to be able to use an [EmailValidator] attribute on any property of any class and have it do the expected email format validation through Enterprise Library. My other requirement is that an empty or null string will not fail validation (because of course I want to be able to have an optional email property).
Endgame:
1: public class EmailValTestClass
2: {
3: [EmailValidator]
4: public string Email { get; set; }
5: }
In order to create an Enterprise Library Validation extension, you need to create two classes: A validator class that does the heavy lifting, and an attribute that will allow you to decorate your properties as I did above.
Creating the Validator
The first class should inherit Validator<T> where T is the type of property to validate (I am going to use a string for email, but you could use any type or even IEnumerable for list types). This will require you to override the DefaultMessageTemplate string property and the void DoValidate() method. The DoValidate() is where the actual work is done:
1: namespace CAESArch.Core.Validators
2: {
3: public class EmailValidator : Validator<string>
4: {
5: static readonly Regex EmailRegex = new Regex(@"\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*");
6:
7: public EmailValidator(string tag) : base(string.Empty, tag) { }
8:
9: protected override string DefaultMessageTemplate
10: {
11: get { throw new NotImplementedException(); }
12: }
13:
14: /// <summary>
15: /// Validate that this string is a valid email address
16: /// RegEx: \w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*
17: /// </summary>
18: protected override void DoValidate(string objectToValidate, object currentTarget, string key, ValidationResults validationResults)
19: {
20: if (string.IsNullOrEmpty(objectToValidate))
21: {
22: return; //We are not going to validate for the null possibility (use a required validator for that)
23: }
24:
25: Match match = EmailRegex.Match(objectToValidate);
26:
27: if (!match.Success) //If the match does not succeed, then it is an invalid email address
28: {
29: LogValidationResult(validationResults, "Email Address Format Is Not Valid", currentTarget, key);
30: }
31: }
32: }
33: }
Creating the Attribute
The second class allows you to use your new validator as an attribute, and is pretty straight forward. Just inherit from ValidatorAttribute and override the DoCreateValidator method:
1: namespace CAESArch.Core.Validators
2: {
3: [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
4: public class EmailValidatorAttribute : ValidatorAttribute
5: {
6: protected override Validator DoCreateValidator(Type targetType)
7: {
8: return new EmailValidator(Tag);
9: }
10: }
11: }
That’s all you have to do! Now let’s run a few tests (which of course were created in advance, like any good TDD programmer would do…):
Testing your Validator
First let’s test our requirement that null or empty strings do not cause validation failure:
1: [TestMethod]
2: public void ClassValidIfEmailIsEmpty()
3: {
4: var testClass = new EmailValTestClass { Email = string.Empty };
5:
6: var isTestClassValid = Validation.Validate(testClass).IsValid;
7:
8: Assert.AreEqual(true, isTestClassValid, "blank email should pass validaton");
9: }
10:
11: [TestMethod]
12: public void ClassValidIfEmailIsNull()
13: {
14: var testClass = new EmailValTestClass {Email = null};
15:
16: var isTestClassValid = Validation.Validate(testClass).IsValid;
17:
18: Assert.AreEqual(true, isTestClassValid, "Null email should pass validaton");
19: }
Now we’ll run the tests for a few bad email addresses and one good email address and make sure the validation results are what we expect:
1: [TestMethod]
2: public void ClassInvalidIfEmailIsMissingDomain()
3: {
4: var testClass = new EmailValTestClass { Email = "invalidemail@google" };
5:
6: var isTestClassValid = Validation.Validate(testClass).IsValid;
7:
8: Assert.AreEqual(false, isTestClassValid);
9: }
10:
11: [TestMethod]
12: public void ClassInvalidIfEmailIsMissingAtSign()
13: {
14: var testClass = new EmailValTestClass { Email = "invalidemail.com" };
15:
16: var isTestClassValid = Validation.Validate(testClass).IsValid;
17:
18: Assert.AreEqual(false, isTestClassValid);
19: }
20:
21: [TestMethod]
22: public void ClassValidIfEmailIsValid()
23: {
24: var testClass = new EmailValTestClass { Email = "validemail@someplace.com" };
25:
26: var isTestClassValid = Validation.Validate(testClass).IsValid;
27:
28: Assert.AreEqual(true, isTestClassValid, "Email should pass validaton");
29: }
And since the tests all come up green, I can sleep peacefully tonight knowing that email addresses will be formatted properly before being stored.