Showing posts with label Validation Application Block. Show all posts
Showing posts with label Validation Application Block. Show all posts

Tuesday, 4 November 2008

Validating Entity Framework Objects Using Reflection and Generics and the Validation Application Block

I’ve spent much of today trying to minimise the amount of work I’m going to be required to do during my project and this is what I’ve come up with.  We are working with the Entity Framework and Linq to Entities, however we also want to implement some custom validation of fields where instead of writing validation rules.

We found this to be a little difficult.  One thing about the validation application block, it does not support inheritance.  If you have a number of objects that inherit from a base object you may want to define a type for the  base object so that it will pick up all seven types in validation time and validate them against the base type.  But it doesn’t work.  For example in the code below.

BaseClass objectToValidate = new SuperClass();
Validation.Validate(objectToValidate)

This code will not validate against the type BaseClass (assuming BaseClass is the base type of SuperClass).  You can modify your code so that you tell it which class to validate against like so:

SuperClass objectToValidate = new SuperClass();
Validation.Validate<BaseClass>(objectToValidate)

But this is quite difficult if you’re trying to remain generic.  For example in our application we are wrapping up the Validation application block in an Object Method design pattern to hide the implementation from the user and allow simple unit testing.

In our case, this method will not work because it thinks that the object type is System.Object.

public ValidationResults Validate(object toValidate)
{
    ValidationResults msResults = Validation.Validate(toValidate);
    return returnList;
}

I wouldn't recommend that you use the validation application block directly as it will make unit testing quite difficult.  To unit test effectively you will need to mock out an interface into validation for the rest of your code where the testing of validation is not relevant.  You can work around this with Generics.

Using Generics implementation, the method above turns into this.

public ValidationResults Validate<T>(T toValidate)
{
    ValidationResults msResults = Validation.Validate(toValidate);
    return returnList;
}

This will work and your object will remain of its own type.  It also allows you to validate against other object types (like base classes) by defining the object type in the method call using angle brackets.

How does this relate to the entity framework?  Well all objects in the entity framework inherit from a singular type of System.Data.Objects.DataClasses.EntityObject.  I’ve covered before how you can create your own custom validators with the Validation Application Block.  If we are able to create a custom database validator, catch the EntityObject and then investigate the object to discern its members from the entity framework we will be able to validate it against its database types.

I think it’s easier to explain by example, so here is my DoValidate method of my custom validator.

protected override void DoValidate(object objectToValidate, object currentTarget, string key, 
    ValidationResults validationResults)
{
    // Get the type of the object.
    string typeName = objectToValidate.GetType().Name;
 
    // Get the parameters
    PropertyInfo[] propertyInfos = objectToValidate.GetType().GetProperties();
 
    foreach (PropertyInfo propertyInfo in propertyInfos)
    {
        // Ignore entity state and entity key, they're variables of the entity framework.
        if ((!propertyInfo.Name.Equals("EntityState") && !propertyInfo.Name.Equals("EntityKey")))
        {
            if (propertyInfo.PropertyType.Equals(typeof(string)))
            {
                ValidateString(objectToValidate, typeName, propertyInfo.Name, 
                    (string)propertyInfo.GetValue(objectToValidate, null), validationResults);
            }
        }
    }
 
 
    rfSysObjectFieldDetail field = _FieldDetailRepository.GetField("MockEntityObject", "ID");
}

.NET Reflection is used (System.Reflection) to look into the properties of the object I’m validating.  We ignore EntityState and EntityKey as they’re base types and do not reference the database.  If you’re pulled your Entity Model straight from the database then the rest of the types will directly reference the database fields with the class name referencing the table.  The method above only validates strings so far.

The method ValidateString just looks into the database for the field definition and verifies that the string is within the correct length parameters.

private void ValidateString(object objectToValidate, string tableName, string fieldName, 
    string fieldValue, ValidationResults validationResults)
{
    rfSysObjectFieldDetail field = _FieldDetailRepository.GetField(tableName, fieldName);
 
    int fieldLength = fieldValue.Length;
 
    // Check the field length.
    if (fieldValue.Length > field.MaximumLength)
    {
        // TODO These must be loaded from the messages object
        ValidationResult result = new ValidationResult(
            "String length too long, maximum length is " + field.MaximumLength,
            objectToValidate, "", "", this);
 
        validationResults.AddResult(result);
    }
 
    if (fieldValue.Length < field.MinimumLength)
    {
        // TODO This must be loaded from the messages objects
        ValidationResult result = new ValidationResult(
            "String length too short, minimum length is " + field.MinimumLength,
            objectToValidate, "", "", this);
 
        validationResults.AddResult(result);
    }
 
}

And that’s about it.  Hopefully it’s useful to someone other than just myself.

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.