Print

Validator inheritance while using Validation Application Block configuration files

This article describes how to build an IConfigurationSource implementation that allows validators, defined in a base class or interface, to be inherited by subclasses and implementations.

Enterprise Library Validation Application Block (VAB) is a great framework. As I wrote before, it allows many complex scenarios. There’s one shortcoming of VAB though, that I find particularly annoying. While it allows validation attributes to be inherited from base types to derived types, inheritance is not supported for validations defined in configuration files. This means that when validating a type, all validations defined through configuration on base classes of that type are ignored. Even the coming 5.0 release will lack this feature.

This behavior, or lack of functionally, is caused by the internal workings of VAB. While the internal MetadataValidatorBuilder uses reflection on types to find the defined validators of a type, the internal ConfigurationValidatorBuilder simply finds a type’s validators by it’s type name. From the perspective of the ConfigurationValidatorBuilder, there are no types, just strings. For being able to find base types of a certain type, you need the .NET type system. In other words, the ConfigurationValidatorBuilder would first need to create a Type object from a string. And while the type name is mandatory in the VAB configuration system, the type’s assembly name is optional. This makes creating an actual type rather time consuming, because all types in the current AppDomain need to be iterated and matched by there name.

According to the Enterprise Library FAQ, there actually is a simple solution to this problem:

replicate the validation specification for the subclasses.

This of course is a brittle and error prone solution. Every time you create a new derived type, you have to think about duplicating that logic. It’s annoying and time consuming. There must be other possibilities.

In a previous article about VAB I showed a way to validate a set of objects using VAB. The code looked like this:

public IEnumerable<ValidationResult> Validate(
IEnumerable<object> entities)
{
return
from entity in entities
let type = entity.GetType()
let validator = CreateValidator(type)
let results = validator.Validate(entity)
where !results.IsValid
from result in results
select result;
}

private static Validator CreateValidator(Type type)
{
string ruleSet = string.Empty;

return ValidationFactory.CreateValidator(type, ruleSet,
ConfigurationSource);
}

The code iterates all given entities and validates each instance by retrieving the validator for the type of that instance. We can add quick and dirty inheritance support by also iterating the type hierarchy of each instance and validate that instance against the validator for that particular type:

public IEnumerable<ValidationResult> Validate(
IEnumerable<object> entities)
{
return
from entity in entities
from type in GetTypeHierarchyOf(entity.GetType())
let validator = CreateValidator(type)
let results = validator.Validate(entity)
where !results.IsValid
from result in results
select result;
}

// Return the type and all its base types
private static IEnumerable<Type> GetTypeHierarchyOf(Type type)
{
while (type != null)
{
yield return type;
type = type.BaseType;
}
}

While this seems easy and great, there are a couple of problems with this code. First of all this code generates duplicate error messages with attribute based validation. Reason for this is that inheritance is already supported by VAB for attribute based validation. Second, when validating graphs of objects using the [ObjectValidator] and [ObjectCollectionValidator], validations of base types will not be checked. Reason for this is that the ObjectValidator and ObjectCollectionValidator simply request a validator for the current type, and not for the type and al its base types.

Although the given code might work in certain scenarios, the lack of object graph validation is a pretty big drawback. Let’s take a totally different approach here.

When you read my previous article about merging multiple configuration files into a single VAB configuration, you know a lot can be done by rebuilding ValidationSettings objects. Using this exact approach we can create a solution to this inheritance problem. Note however, that this takes an awful lot more code than what was needed with the previous code snippet. Nice thing though is that I can reuse a lot of my code from my previous article.

Please note that when you want to use the code in this article, you will also need the code of my previous article, because I won’t repeat it here.

Please also note that while the solution below works pretty well, the solution itself has some shortcomings. Please note the following:

  • While the solution even allows validations defined on interfaces to work on implementations of that interface, validation might fail when members are implemented explicitly.
  • The solution iterates all types in all referenced assemblies of the current AppDomain, to find out whether a type derives from a certain base type. This means that the solution will not work for types that are generated during runtime and types in assemblies that are loaded dynamically.
  • The solution creates a new ValidationSettings configuration where it adds all derived types of a base type in the configuration. When many such types exist, the generation process could become time consuming and could take a lot of memory. For instance, don’t add the .NET interfaces IComparable, IConvertible, IFormattable, ISerializable, and IDisposable to the configuration :-)
These short comes can probably only be solved when the Enterprise Library get native support for configuration inheritance. So until that moment has come, the code below can save your day.

Below is the implementation of the InheritanceValidationConfigurationSource. It is in fact a decorator that wraps a supplied IConfigurationSource. Within the constructor the construction of the new ‘flattened’ configuration is delegated to the ValidationSettingsTypeHierarchyFlattener class. The returned ValidationSettings object is cached and returned on each call to GetSection.

public class InheritanceValidationConfigurationSource
: IConfigurationSource
{
private readonly ValidationSettings flattenedValidationSettings;

public InheritanceValidationConfigurationSource(
IConfigurationSource source)
{
var settings = source.GetSection(ValidationSettings.SectionName)
as ValidationSettings;

this.flattenedValidationSettings =
ValidationSettingsTypeHierarchyFlattener.Flatten(settings);
}

public ConfigurationSection GetSection(string sectionName)
{
if (sectionName == ValidationSettings.SectionName)
{
return this.flattenedValidationSettings;
}

return null;
}

#region IConfigurationSource Members

// Rest of the IConfigurationSource members left out.
// Just implement them by throwing an exception from
// their bodies; they are not used.

#endregion
}

Below is the code of the ValidationSettingsTypeHierarchyFlattener class. Its logic is rather straightforward. It starts by making a copy of the supplied ValidationSettings. By making a copy, the original settings stay unmodified. Next, it finds all (non-interface) types in the configuration that have no base types in the configuration: the root types. For each root type the inheritance tree is walked (breadth-first) and for each type in that tree the configuration of its base type is copied to / merged with that type. The type is added to the configuration when it doesn’t exist. The last step in the flattener’s logic is finding all interface types in the configuration. For each interface type, all implementations (in the current AppDomain) are iterated and the configuration of that interface is copied to / merged with that type. Here is the code:

internal class ValidationSettingsTypeHierarchyFlattener
{
private readonly ValidationSettings settings;

private ValidationSettingsTypeHierarchyFlattener(
ValidationSettings settings)
{
this.settings = Copier.MakeCopy(settings);
}

public static ValidationSettings Flatten(ValidationSettings settings)
{
var flattener =
new ValidationSettingsTypeHierarchyFlattener(settings);

flattener.DuplicateConfigurationForDerivedTypes();

flattener.DuplicateInterfaceConfigurationToImplementations();

return flattener.settings;
}

private void DuplicateConfigurationForDerivedTypes()
{
var rootClasses = this.FindRootClassesInConfiguration();

foreach (Type rootClass in rootClasses)
{
this.DuplicateConfigurationToDerivedTypesOf(rootClass);
}
}

private void DuplicateConfigurationToDerivedTypesOf(Type root)
{
var unorderedDescendants =
TypeFinder.GetDerivedClassesFor(root);

var sorter = new DistanceToBaseTypeComparer(root);

var descendants = unorderedDescendants.OrderBy(t => t, sorter);

foreach (Type descendant in descendants)
{
this.DuplicateConfigurationTo(descendant);
}
}

private void DuplicateConfigurationTo(Type descendant)
{
ValidatedTypeReference baseReference =
this.settings.Types.Get(descendant.BaseType.FullName);

ValidatedTypeReference descendantReference =
this.settings.Types.Get(descendant.FullName);

bool descendantAlreadyExistsInConfiguration =
descendantReference != null;

if (descendantAlreadyExistsInConfiguration)
{
new TypeMerger(this.settings, baseReference)
.MergeInto(descendantReference);
}
else
{
var copy =
MakeCopyOfReferenceForType(baseReference, descendant);

this.settings.Types.Add(copy);
}
}

private void DuplicateInterfaceConfigurationToImplementations()
{
var interfaces = this.FindInterfacesInConfiguration();

foreach (Type intrface in interfaces)
{
this.DuplicateConfigurationForImplementationsOf(intrface);
}
}

private void DuplicateConfigurationForImplementationsOf(Type intrface)
{
var implementations =
TypeFinder.GetImplementationsOfInterface(intrface);

foreach (Type implementation in implementations)
{
this.DuplicateConfigurationForImplementation(intrface,
implementation);
}
}

private void DuplicateConfigurationForImplementation(Type intrface,
Type implementation)
{
ValidatedTypeReference interfaceReference =
this.settings.Types.Get(intrface.FullName);

ValidatedTypeReference implementationReference =
this.settings.Types.Get(implementation.FullName);

bool implementationAlreadyExistsInConfiguration =
implementationReference != null;

if (implementationAlreadyExistsInConfiguration)
{
new TypeMerger(this.settings, interfaceReference)
.MergeInto(implementationReference);
}
else
{
var copy = MakeCopyOfReferenceForType(interfaceReference,
implementation);

this.settings.Types.Add(copy);
}
}

private Type[] FindRootClassesInConfiguration()
{
var configuredTypes = new HashSet<Type>(this.GetConfiguredTypes());

return
(from configuredType in configuredTypes
where !configuredType.IsInterface
where IsRootType(configuredType, configuredTypes)
select configuredType).ToArray();
}

private Type[] FindInterfacesInConfiguration()
{
var configuredTypes = new HashSet<Type>(this.GetConfiguredTypes());

return
(from configuredType in configuredTypes
where configuredType.IsInterface
select configuredType).ToArray();
}

private static bool IsRootType(Type type, HashSet<Type> configuredTypes)
{
var baseTypesForTypeInConfiguration =
from baseType in GetBaseTypesFor(type)
where configuredTypes.Contains(baseType)
select baseType;

return !baseTypesForTypeInConfiguration.Any();
}

private static IEnumerable<Type> GetBaseTypesFor(Type type)
{
Type baseType = type.BaseType;

while (baseType != null)
{
yield return baseType;
baseType = baseType.BaseType;
}
}

private IEnumerable<Type> GetConfiguredTypes()
{
return
from reference in this.settings.Types
select GetTypeFromReference(reference);
}

private static ValidatedTypeReference MakeCopyOfReferenceForType(
ValidatedTypeReference reference, Type targetType)
{
var copy = Copier.MakeCopy(reference);

copy.Name = targetType.FullName;
copy.AssemblyName = targetType.Assembly.FullName;

return copy;
}

private static Type GetTypeFromReference(
ValidatedTypeReference reference)
{
// VAB doesn't need AssemblyName in order to validate a type.
if (!String.IsNullOrEmpty(reference.AssemblyName))
{
// Fast O(1) lookup with assembly name.
return GetTypeFromReferenceByFullyQualifiedName(reference);
}
else
{
// Slow lookup, but needed when AssemblyName is missing.
return GetTypeFromReferenceByName(reference);
}
}

private static Type GetTypeFromReferenceByFullyQualifiedName(
ValidatedTypeReference reference)
{
var fqn = reference.Name + ", " + reference.AssemblyName;

try
{
const bool ThrowOnError = true;
return Type.GetType(fqn, ThrowOnError);
}
catch (Exception ex)
{
throw new ConfigurationErrorsException(
string.Format(CultureInfo.InvariantCulture,
"The configuration file references a type '{0}' " +
"that could not be found in the AppDomain. {1}",
fqn, ex.Message), ex);
}
}

private static Type GetTypeFromReferenceByName(
ValidatedTypeReference reference)
{
var typesWithName =
TypeFinder.GetAllTypesInCurrentAppDomain()
.Where(t => t.FullName == reference.Name)
.ToArray();

if (typesWithName.Length == 1)
{
return typesWithName[0];
}

if (typesWithName.Length > 1)
{
throw new ConfigurationErrorsException(
string.Format(CultureInfo.InvariantCulture,
"The configuration file references a type '{0}' " +
"that is found multiple times in the current App" +
"Domain. Try specifying the AssemblyName as well.",
reference.Name));
}
else
{
throw new ConfigurationErrorsException(
string.Format(CultureInfo.InvariantCulture,
"The configuration file references a type '{0}' " +
"that could not be found in the AppDomain.",
reference.Name));
}
}
}

The flattener class references the small TypeFinder helper class. This class contains a few utility methods that search for types. For instance it enables loading all derived types of a particular type or finding all implementations of a particular interface. Below is the code for the TypeFinder class:

internal static class TypeFinder
{
public static IEnumerable<Type> GetAllTypesInCurrentAppDomain()
{
return
from assembly in AppDomain.CurrentDomain.GetAssemblies()
from type in GetAllTypesFor(assembly)
select type;
}

public static IEnumerable<Type> GetDerivedClassesFor(Type baseType)
{
return
from type in GetAllTypesInCurrentAppDomain()
where type.IsClass
where type.IsSubclassOf(baseType)
select type;
}

public static IEnumerable<Type> GetImplementationsOfInterface(
Type interfaceType)
{
return
from type in GetAllTypesInCurrentAppDomain()
where !type.IsInterface
where interfaceType.IsAssignableFrom(type)
select type;
}

[DebuggerStepThrough]
public static Type[] GetAllTypesFor(Assembly assembly)
{
try
{
return assembly.GetTypes();
}
catch (ReflectionTypeLoadException)
{
// GetTypes could throw an ReflectionTypeLoadException.
// In that case we just skip the assembly.
return Type.EmptyTypes;
}
}
}

As I described earlier, the ValidationSettingsTypeHierarchyFlattener walks the inheritance tree of a particular type in breadth-first order. While depth-first would also work, processing the collection of types in the hierarchy must have a certain order. By making sure a certain type is always processed after its base type, it allows us to copy the complete configuration from the type’s base type. Copying configuration becomes a waterfall where all configuration flows down the hierarchy. Not doing it this way, would make it very hard to yield correct results.

Because the TypeFinder class does not return the list of base types in a guaranteed order, the flattener class orders the list. It uses the DistanceToBaseTypeComparer class for this. This comparer compares two types based on their distance to the supplied base type. Here is the implementation:

internal sealed class DistanceToBaseTypeComparer : IComparer<Type>
{
private readonly Type root;

public DistanceToBaseTypeComparer(Type root)
{
this.root = root;
}

public int Compare(Type x, Type y)
{
int distanceOfX = this.CalculateDistanceToRoot(x);
int distanceOfY = this.CalculateDistanceToRoot(y);

return distanceOfX.CompareTo(distanceOfY);
}

private int CalculateDistanceToRoot(Type derivedType)
{
if (this.root.IsInterface)
{
return this.CalculateDistanceToInterface(derivedType);
}
else
{
return this.CalculateDistanceToBaseType(derivedType);
}
}

private int CalculateDistanceToInterface(Type derivedType)
{
int distance = 1;

this.CheckIfTypeIsImplementationOfRoot(derivedType);

var baseType = derivedType.BaseType;

while (baseType != null && this.root.IsAssignableFrom(baseType))
{
distance++;

baseType = baseType.BaseType;
}

return distance;
}

private int CalculateDistanceToBaseType(Type derivedType)
{
int distance = 0;

while (derivedType != this.root)
{
derivedType = derivedType.BaseType;
distance++;
}

return distance;
}

private void CheckIfTypeIsImplementationOfRoot(Type derivedType)
{
if (!this.root.IsAssignableFrom(derivedType))
{
throw new InvalidProgramException(
string.Format(CultureInfo.InvariantCulture,
"An internal error occurred. Type {0} is not an " +
"implementation of interface {1}.",
derivedType, this.root));
}
}
}

The last two classes missing from the equation are the Copier and TypeMerger classes. You can find them in my previous article.

Happy validating!

- .NET General, C#, Enterprise Library, Validation Application Block - two 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.

two comments:

Your code rocks! Many thanks for the post, I believe it saved me more than a day of work (and anger and frustration too :))
I've found a trivial bug in the CalculateDistanceToBaseType mthod, where the line
type = derivedType.BaseType;
I believe should be
type = type.BaseType;

But apart from that everything works fine.
If someone, like me, is using some derived proxy on the entities (let say NHibernate lazy load Castle dynamicproxy), than he should pay attention that the method
GetDerivedClassesFor in the class TypeFinder, will find this proxy and will try to instantiate validation configuration for it :).
Just put a condition in linq statement to keep the proxy implentation out of method results!

Thanks again,
Marco
mCasamento (URL) - 21 06 11 - 09:41

Thanks Marco,

There was indeed a quite obvious bug. I updated the article.
Steven (URL) - 22 06 11 - 21:13


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.