When looking at this thread, and before considering to use PostSharp, I told myself that in order to implement the mix between IDataErrorInfo and FluentValidation, I should begin to put the validation logic in the base class of all my entities, namely BaseEntity:

public abstract class BaseEntity<TKey, TEntity> : IDataErrorInfo 
        where TEntity : BaseEntity<TKey, TEntity>
{
}

I would just have to add a generic argument named TEntityValidator in the class declaration, to eventually have something like:

public abstract class BaseEntity<TKey, TEntity, TEntityValidator> : IDataErrorInfo 
    where TEntity : BaseEntity<TKey, TEntity, TEntityValidator>
    where TEntityValidator : IValidator<TEntity>, new()
{
        public virtual string Error
        {
            get
            {
                IValidator<TEntity> validator = new TEntityValidator();
                var result = validator.Validate(this); 
                
                var errors = new StringBuilder();

                foreach (var validationFailure in result.Errors)
                {
                    errors.Append(validationFailure.ErrorMessage);
                    errors.Append(Environment.NewLine);
                }
                return errors.ToString();
            }
        }
        
        public virtual string this[string columnName]
        {
            get
            {
                IValidator<TEntity> validator = new TEntityValidator();
                var result = validator.Validate(this);

                var columnResult = result.Errors.FirstOrDefault<ValidationFailure>(x => string.Compare(x.PropertyName, columnName, true) == 0);
                return columnResult != null ? columnResult.ErrorMessage : string.Empty;
            }
        }
}

The problem is that this alteration compelled me to modify the declarations of a lot of classes :

This was a lot of changes.

This is how came to me the idea to use PostSharp to automate this, using the same principle as the example given on their website, with the implementation of INotifyPropertyChanged, which I have already talked about in another article.

What we are going to do, is to create an aspect which will implement the interface IDataErrorInfo on all of the entities which inherit from BaseEntity, and the aspect will use the fluent validation classes to validate these entities.

To begin, here is the skeleton of our aspect:

[Serializable]
[IntroduceInterface(typeof(IDataErrorInfo), OverrideAction = InterfaceOverrideAction.Ignore)]
[MulticastAttributeUsage(MulticastTargets.Class, Inheritance = MulticastInheritance.Strict)]
public sealed class DataErrorInfoAttribute : InstanceLevelAspect, IDataErrorInfo
{
    [IntroduceMember(Visibility = Visibility.Public, IsVirtual = true, OverrideAction = MemberOverrideAction.OverrideOrFail)]
    public string this[string columnName]
    {
        get {  }
    }

    [IntroduceMember(Visibility = Visibility.Public, IsVirtual = true, OverrideAction = MemberOverrideAction.OverrideOrFail)]
    public string Error
    {
        get {  }
    }
}

Let's begin by enumerating the attributes decorating our class:

  1. Like all aspects, it must be serializable.
  2. The interface IDataErrorInfo will be introduced (implemented). I guess this attribute is dispensable, as BaseEntity will anyway need to implement this interface, because the view-models will have to directly use in the code, the property Error.
  3. All derived classes of the one decorated with the attribute DataErrorInfo will implement the aspect too.

We now have to implement the call to the validation class peculiar to the entity. To allow the aspect to know what class it is, we have to give it the type of the validation class, so it can instantiate it, and use it to validate the instance of the entity on which the aspect has been applied.

In order to do that, we are going to add a constructor to the attribute, with one single parameter, which is the type of the validation class:

[Serializable]
[IntroduceInterface(typeof(IDataErrorInfo), OverrideAction = InterfaceOverrideAction.Ignore)]
[MulticastAttributeUsage(MulticastTargets.Class, Inheritance = MulticastInheritance.Strict)]
public sealed class DataErrorInfoAttribute : InstanceLevelAspect, IDataErrorInfo
{
    private readonly Type validatorType;

    public DataErrorInfoAttribute(Type validatorType)
    {
        this.validatorType = validatorType;
    }
}

To retrieve the result of the validation, we will create a function ValidateInstanceProperties, which will dynamically create a new instance of the validation class, and call the Validate function, giving it the instance of the entity as a parameter, which we will get through the property InstanceLevelAspect.Instance:

private ValidationResult ValidateInstanceProperties()
{
    return ((IValidator) Activator.CreateInstance(validatorType)).Validate(Instance);
}

Then, we just have to call this function in the property Error and in the indexer, using more or less what has been done earlier:

[IntroduceMember(Visibility = Visibility.Public, IsVirtual = true, OverrideAction = MemberOverrideAction.OverrideOrFail)]
public string this[string columnName]
{
    get
    {
        var validationResults = ValidateInstanceProperties();

        if (validationResults == null)
            return string.Empty;

        var columnResults = validationResults.Errors.FirstOrDefault(x => string.Compare(x.PropertyName, columnName, true) == 0);

        return columnResults != null
            ? columnResults.ErrorMessage
            : string.Empty;
    }
}

[IntroduceMember(Visibility = Visibility.Public, IsVirtual = true, OverrideAction = MemberOverrideAction.OverrideOrFail)]
public string Error
{
    get { return ErrorsToString(ValidateInstanceProperties()); }
}

The function ErrorsToString allows to format the validation errors list.

To use this aspect, we just have to decorate each of our entities with the attribute DataErrorInfo, giving to the constructor the type of the class which validates this entity:

[DataErrorInfo(typeof(PlayerValidator))]
public class Player : BaseEntity<int, Player>
{
    //...
}

I hope it will help!

See you soon...