Wednesday, 24 September 2008

Microsoft Enterprise Library #3 – Validation Block

Apparently the Validation block is new, as I've never used the library before it's no newer to me than any of the others. I did find that there were fewer examples of the more difficult stuff on the web for me to find, hopefully I can provide one or two here. I had my reservations about the validation block before I started this entry, I'm curious to see if they were valid. Data validation is very important to our new project here, the validation block appears at a first glance to fill the need, but we don't want to create a maintenance nightmare between the database changes and the code changes. Adjusting the length of a field could cause us no end of trouble when validating, unless any framework we use can check the database for the field length.

My Requirements

This time my requirements are a little more difficult to fill. I'm getting a good handle on how to do things with the enterprise library so I'm confident that I'll be able to get over any problems I encounter. 1. Validate data formats (like email addresses) 2. Validate fields against the database (strings against length for example) 3. Validate my POCs (Plain Old C# Objects) to keep a strong level of abstraction

Step 1 - Data Format Validation

Seems like the easiest to work with to start. I can also fill requirement 3 while I'm at it. I've created a class called Employees and added it to my project. The class is as so:

public class Employee
{
 public string Name { get; set; }
 public int Age { get; set; }
 public string EmailAddress { get; set; }
}

Pretty straight forward. I want to validate the EmailAddress field. Now this is a POC object so if I can do this then I'm filling requirement 3 also. Make sure your class is public or the configuration tool won't be able to see it. First thing to do is add a Validation Application Block to the App.config file with the enterprise library configuration tool. Once added, right click the application block and add a new type. You'll have to load the assembly for your project to see your public class, in my case I selected the exe file and there was my Employee class. Some people have noted a problem that the class didn't show until restarting, I didn't get this problem. Just make sure you've built your application first. Once the type is there add a new Rule Set.

Make sure you set this ruleset as the default ruleset, or you'll feel like a fool when you see that answer after going to Google to find out why it doesn't work. Trust me, I know. Next, right click the ruleset and under new is the option to choose members. I want to validate the email address so I chose that member from the list. For an email address I want to use a regular expression to validate that it is correct, so I right click the field I just added and add a new regular expression validator.

Lucky for me there is a pattern for email addresses so I don't have to devise my own. I set the message template to something useful and my settings ended up looking something like this:

 

Ok, now to test it. I added the assembly for validation as I did for logging and exceptions (this time Microsoft.Practices.EnterpriseLibrary.Validation and Microsoft.Practices.EnterpriseLibrary.Validation.Configuration). The following code shows how to test it.

Employee myEmployee = new Employee();
myEmployee.EmailAddress = "memine.net";
ValidationResults results = Validation.Validate(myEmployee);
if (!results.IsValid)
{
foreach (ValidationResult result in results)
{
Console.WriteLine(result.Message);
}
}
 

When I run this, the address memine.net will fail and write the error out to the console "Email Address Invalid". If I change the EmailAddress to me@mine.com it comes back valid. That was remarkably simple, I now have email address validation on my Employee object and I'm using POCs to do it. Requirement 1 and 3 satisfied.

Step 2 - Validating Against the Database

Ok so at first this was my main reservation with the validation block. There is no default validator to validate a POC field against it's corresponding database field. I knew I'd have to write my own. I figured it was going to be hard, and I wasn't dissappointed. There are a few gotcha's, and hopefully this post might help someone figure out theirs faster than it took me. I decided that the employee name was a good field to create a custom validator for. So I added the field to the employee type in the app.config using the Enterprise Library Config tool and added a custom validator to the field. When you add a custom validator you need to choose an object to validate with, upon loading my assembly it specified that no objects that inherit from Validator were found, so I had a starting point. A little code inspection led me to this:

namespace Microsoft.Practices.EnterpriseLibrary.Validation
{
public abstract class Validator
{
 protected Validator(string messageTemplate, string tag);
 
 protected abstract string DefaultMessageTemplate { get; }
 public string MessageTemplate { get; set; }
 public string Tag { get; set; }
 
 protected internal abstract void DoValidate(object objectToValidate, object currentTarget, string key, ValidationResults validationResults);
 protected virtual string GetMessage(object objectToValidate, string key);
 protected void LogValidationResult(ValidationResults validationResults, string message, object target, string key);
 protected void LogValidationResult(ValidationResults validationResults, string message, object target, string key, IEnumerable nestedValidationResults);
 public void Validate(object target, ValidationResults validationResults);
}
}

So I was going to have to override the DoValidate method and the DefaultMessageTemplate property. Didn't seem so hard. I created my class:

public class DatabaseValidator : Validator
{
protected DatabaseValidator(string messageTemplate, string tag)
: base(null, null) { }
 
protected override string DefaultMessageTemplate
{
get
{
return "";
}
}
 
protected override void DoValidate(object objectToValidate, object
currentTarget, string key, ValidationResults validationResults)
{
}
}
 
And compiled. No Errors, fantastic. But when I tried to load the assembly to add the validator to the Name field as a type it didn't show. After some searching I found I needed to set a configuration element type on the class as so:

[ConfigurationElementType(typeof(CustomValidatorData))]
public class DatabaseValidator : Validator
{
...
{

After this it would show up just fine. Great I thought, I'm almsot there. I added the new validator type to the Name field (using custom validator and selecting the assembly exe file) and compiled. No errors, but when running the application, BANG:

Additional information: Constructor on type 'ApplicaitonBlocksTechDemo.Validators.DatabaseValidator' not found told me a little, my constructor has the wrong parameters. It took me a while to find the solution, but eventually I did. Your validator class will need a constructor that takes a NameValueCollection as a parameter. I added this in and now my class looks like so (I also added in a default constructor to be safe).

[ConfigurationElementType(typeof(CustomValidatorData))]
public class DatabaseValidator : Validator
{
public DatabaseValidator(NameValueCollection collection)
  : base(null, null) { }
public DatabaseValidator()
  : base(null, null) { }
protected DatabaseValidator(string messageTemplate, string tag)
  : base(null, null) { }
 
protected override string DefaultMessageTemplate
{
  get
  {
    return "";
  }
}
 
protected override void DoValidate(object objectToValidate,
  object currentTarget, string key, ValidationResults
  validationResults)
{
}
}
 

And recompiled. Success! The validator returned, and without any code the validation was considered successful. To implement the validator all you need to do is fill out the DoValidate method with whatever validation you require, in this case I'll tell it to go to the database, from the key (the field name) and the target (class name) I'll be able to discern the field and thus the limitations in the database to return correct validation.

Final Notes

With the success of the custom validator I'm actually quite excited to use the validation block in our new application, I can see the amount of code I can avoid having to write myself by using this tool, especially if we use ASP.NET MVC (the way it binds objects will lend itself to this very nicely). I hope this post helps someone.

1 comment:

Winston Smith said...

thank you your post helped me a lot