Print

Mixing Validation Application Block With DataAnnotation: What About SelfValidation?

This article describes how to move the self validation methods of a type to its meta data type, using Validation Application Block 5.0. Using self validation methods inside Data Annotations' meta data type is something that is not supported out of the box.

The newest version of the Validation Application Block, version 5.0, now integrates with .NET Data Annotations (new since .NET 3.5 sp1). Data Annotations is a general model for annotating data using attributes, which is especially useful for validation. Attribute based validation is of course one of the options when doing validation using the Validation Application Block.

One of the nice things about Data Annotations is that it contains a feature called 'buddy classes', which allows you to put the validation attributes in a separate class that will function as meta data type. This is especially useful when dealing with auto-generated classes (think LINQ to SQL, Entity Framework). Validation Application Block 5.0 is now able to use this annotation model and we're therefore now able to do something like this with VAB:

// DataAnnotations Metadata attribute
[MetadataType(typeof(PersonMetaData))]
public partial class Person
{
public string Name { get; set; }
}

public class PersonMetaData
{
    // VAB validation attribute
[NotNullValidator]
public string Name { get; set; }
}

This works great, but what about self validation? VAB allows methods to be placed on objects that allow custom validation logic that can't be written as custom attributes. How do we move this logic to the PersonMetaData class?

The short answer is: you can't. VAB does not support this out of the box. However, for those of you who follow this series about the VAB, you already know by now that there is little that can't be done with the VAB with a bit of work.

Please note that I think that having such feature is probably only useful in a narrow range of scenarios, because self validation method can located in a partial class. For the sake of education and having fun with VAB, let's just do this anyway ;-)

This can be achieved by hooking into the framework using depedency injection and replace the existing AttributeValidationFactory implementation. Below is a complete working example of how to do this with VAB 5.0 in three 'simple' steps:

  1. Change the signature of your validation methods.
  2. Define a class that can replace the VAB's build-in AttributeValidationFactory.
  3. Reconfigure VAB in the start up path of your application.

1. Change the signature of your validation methods.

Because self validation methods are instance methods they are able to validate instance fields on the validated type. When you move the validation method to the meta data type you loose this ability. So for this to work we'll have to change the signature of the validation method to at least contain the object to validate as method argument. When we make this validation method static we can prevent a new instance of the meta data type from being created during the validation process. Here is an example of a type that has its self validation moved to its meta data type:

[MetadataType(typeof(PersonMetaData))]
public class Person
{
    public string Name { get; set; }
}

public class PersonMetaData
{
    [NotNullValidator]
    public string Name { get; set; }

    [SelfValidation]
    public static void SelfValidator(Person instance,
        ValidationResults results)
    {
        // TODO: Your validation here.
    }
}

2. Define a class that can replace the VAB's build in AttributeValidatorFactory.

When using attribute based validation, the AttributeValidationFactory type, that lives in the Microsoft.Practices.EnterpriseLibrary.Validation namespace, is responsible for creating new validators based on the attributes you decorated. When we replace it with an extended implementation that is able to handle self validations on the meta data class. Here is my ExtendedAttributeValidationFactory implementation:

public class ExtendedAttributeValidationFactory :
AttributeValidatorFactory
{
public ExtendedAttributeValidationFactory(
IValidationInstrumentationProvider provider)
: base(provider)
{
}

protected override Validator InnerCreateValidator(
Type targetType, string ruleset,
ValidatorFactory mainValidatorFactory)
{
var baseValidator = base.InnerCreateValidator(
targetType, ruleset, mainValidatorFactory);

var metaDataValidator =
CreateValidatorForMetaDataSelfValidation(
targetType, ruleset);

if (metaDataValidator == null)
{
return baseValidator;
}

return new AndCompositeValidator(baseValidator,
metaDataValidator);
}

private Validator CreateValidatorForMetaDataSelfValidation(
Type targetType, string ruleset)
{
var attributes = GetMetadataTypes(targetType).ToArray();

if (attributes.Length == 0)
return null;

var flags = BindingFlags.Public |
BindingFlags.NonPublic | BindingFlags.Static;

var selfValidators = (
from attribute in attributes
from method in attribute.MetadataClassType.GetMethods(flags)
where method.ReturnType == typeof(void)
let parameters = method.GetParameters()
where parameters.Length == 2
where parameters[1].ParameterType == typeof(ValidationResults)
from methodAtrtibute in
ValidationReflectionHelper.GetCustomAttributes(method,
typeof(SelfValidationAttribute), false)
.Cast<SelfValidationAttribute>()
where ruleset.Equals(methodAtrtibute.Ruleset)
select new MetaDataSelfValidationValidator(method)).ToArray();

if (selfValidators.Length == 0)
return null;

return new AndCompositeValidator(selfValidators);
}

private static IEnumerable<MetadataTypeAttribute> GetMetadataTypes(
Type targetType)
{
while (targetType != null)
{
var metadata = targetType.GetCustomAttributes(
typeof(MetadataTypeAttribute), false)
.FirstOrDefault();

if (metadata != null)
{
yield return (MetadataTypeAttribute)metadata;
}

targetType = targetType.BaseType;
}
}
}

The ExtendedAttributeValidationFactory class loops through all static methods of the meta data class and foreach method that follows the conventions and is of the correct ruleset, a new MetaDataSelfValidationValidator is created. These are the conventions a validation method must follow:

  • The method must be static.
  • The method must return void.
  • The method must contain two arguments.
  • The first argument must be of the validated type or a base type.
  • The second argument must be of type ValidationResults.
  • The method must be decorated with the [SelfValidationAttribute].

The MetaDataSelfValidationValidator class enables validation of an object by calling the static self validation method of the meta data type. Here's the code:

public class MetaDataSelfValidationValidator : Validator
{
private readonly MethodInfo methodInfo;

public MetaDataSelfValidationValidator(MethodInfo methodInfo)
: base(null, null)
{
this.methodInfo = methodInfo;
}

public override void DoValidate(object objectToValidate,
object currentTarget, string key,
ValidationResults validationResults)
{
if (objectToValidate != null)
{
this.methodInfo.Invoke(null,
new object[] { objectToValidate, validationResults });
}
}

protected override string DefaultMessageTemplate
{
get { return null; }
}
}

3. Reconfigure VAB in the start up path of your application.

Now we've written a new implementation of the AttributeValidationFactory, we will have to replace the current implementation with this new one. We can do this by reconfiguring the Unity container (the standard IoC framework for EntLib) to return a new instance of the ExtendedAttributeValidationFactory type when an AttributeValidationFactory is requested by VAB:

// Create the container
var container = new UnityContainer();

// Configurator will read Enterprise Library
// configuration and set up the container
var configurator = new UnityContainerConfigurator(container);

IConfigurationSource configurationSource =
new NullConfigurationSource();
EnterpriseLibraryContainer.ConfigureContainer(
configurator, configurationSource);

container.RegisterType<AttributeValidatorFactory,
ExtendedAttributeValidationFactory>();

// Wrap in ServiceLocator
IServiceLocator locator = new UnityServiceLocator(container);

// And set Enterprise Library to use it
EnterpriseLibraryContainer.Current = locator;

Happy self validating ;-)

- .NET General, C#, Enterprise Library, Validation Application Block - six comments / No trackbacks - §

The code samples on my weblog are colorized using javascript, but you disabled javascript (for my website) on your browser. If you're interested in viewing the posted code snippets in color, please enable javascript.

six comments:

Kevin, if you've got a problem with getting this to work, or VAB in general, please ask a question at Stackoverflow.com. SO is much more suited for this than my blog. Be as specific as you can about your problem. If you post a link to SO here, I will definitely take a look.
Steven (URL) - 05 10 10 - 14:04

Hey, this is an interesting and helpful article! However, it does not work with my implementation.

I copied the (unchanged) ExtendedAttributeValidationFactory and MetaDataSelfValidationValidator to a Namespace called VABExtension and put the reconfiguration (step 3) in the startup section of my program.

When trying to create a validator for Foobar using

ValidationFactory.CreateValidator()

an exception is thrown stating "Activation error occured while trying to get instance of type ValidatorFactory, key """

If it helps, i can also post the stack trace here.

Did I miss something adopting your solution?
ahue - 13 11 11 - 12:52

The call to EnterpriseLibraryContainer.ConfigureContainer was missing from the last code snippet. I added it.
Steven (URL) - 19 11 11 - 20:33

Hey Steven,
thank you very much for your help with that! I'm sorry I didn't respond earlier, but I couldn't test your fix until now. It works fine! Thank you so much, again, without you I would've been stuck!
Cheers!
ahue - 28 11 11 - 23:03

Hey,
another piece of feedback goes here: It seems that SelfValidation does not work for subclasses of the class, the SelfValidation was specified for. Consider the following case:

A type hierarchy:

[MetadataType(typeof(MetaA))]
class A {
public int alpha;
}
[MetadataType(typeof(MetaB))]
class B : A {
public int beta;
}

class MetaA {
[RangeValidator(...)]
public int alpha;

[SelfValidation]
public static void Self(A instance, ValidationResults results) {...}
}

class MetaB {
[RangeValidator(...)]
public int beta;
}

b = new B();
var vA = ValidationFactory.CreateValidator<A>();
var resA = vA.Validate(b);

var vB = ValidationFactory.CreateValidator<B>();
var resB = vB.Validate(b);

---
So, consider b has invalid values for alpha and beta, and validation of b would fail for SelfValidation "Self":

resA contains 2 results, one for alpha, and one for Self - everythin is fine.
resB contains 2 results, one for alpha, and one for beta, but the one for Self is missing. In my understanding of validation, Self should also be carried out for b, otherwise it'd be inconsistent since other validators in MetaA are applied to b.

Do you have any idea how to fix this?
ahue - 04 12 11 - 11:56

The ExtendedAttributeValidationFactory only took the first MetadataTypeAttribute it found and used that. I updated the code. Now it searches the inheritance hierarchy of the type looking for all MetadataTypeAttributes and searches all metadata types for self validation methods.
Steven (URL) - 04 12 11 - 12:52


No trackbacks:

Trackback link:

Please enable javascript to generate a trackback url


  
Remember personal info?

/

Before sending a comment, you have to answer correctly a simple question everyone knows the answer to. This completely baffles automated spam bots.
 

  (Register your username / Log in)

Notify:
Hide email:

Small print: All html tags except <b> and <i> will be removed from your comment. You can make links by just typing the url or mail-address.