devLux

Self-Tracking Entities Part 3 - Validation

In the previous two posts we have seen how to implement first a Repository that supports change tracking and then a more sophisticated version with self-tracking entities. Having an abstract base class for entities we are able to improve the functionality by adding some means of validation based on Microsoft’s Data Annotations library.

Introduction

Microsoft provides the Data Annotations library in the assembly System.ComponentModel allowing to mark class properties with validation attributes in real Aspect Oritented Programming (AOP) manner. As it is quite popular we are going to base our design on that library as well.

We extend our existing IEntity interface with the following methods:

bool Validate();
bool IsPropertyValid(string propertyName);
ICollection<ValidationResult> GetValidationResults(string propertyName);

ValidationResult is member of the Data Annotations namespace and represents a container for the results of a validation request.

Unit Tests

In the spirit of Test Driven Development (TDD) we describe the unit tests before starting the implementation. The unit tests reflect the expected behavior in detail and assert a high level of quality, especially if combined with Continous Integration (CI).

Before diving into the unit tests, let us first define a fake entity class that depicts how we are going to use the Data Annotations. Please note that only public properties participate in change tracking and therefore non-public properties are not validated.

internal class ValidationFakeEntity : EntityBase
{
  public ValidationFakeEntity(bool setNotAccessibleProperty = true)
  {
    if (setNotAccessibleProperty)
    {
      NotAccessibleProperty = DateTime.Now;
    }

    EnableChangeTracker(); 
  }
  public ValidationFakeEntity(string name, int year, bool setNotAccessibleProperty = true)
  {
    if (setNotAccessibleProperty)
    {
      NotAccessibleProperty = DateTime.Now;
    }

    _name = name;
    _year = year;

    EnableChangeTracker();
  }

  // Currently only public properties participate in change tracking
  [ChangeTracker]
  [Required]
  private DateTime? NotAccessibleProperty { get; set; }

  private string _name;

  [ChangeTracker]
  [Required]
  public string Name
  {
    get { return _name; }
    set
    {
      _name = value;
      OnEntityValueChanged(() => Name);
    }
  }

  private int? _year;

  [ChangeTracker]
  [Required]
  public int? Year
  {
    get { return _year; }
    set
    {
      _year = value;
      OnEntityValueChanged(() => Year);
    }
  }
}
[TestFixture]
public class EntityBaseValidationTest
{
  [Test]
  public void Validate_Returns_True_If_All_Validated_Properties_Are_Valid()
  {
    ValidationFakeEntity entity = new ValidationFakeEntity("Luca", 1978);

    Assert.IsTrue(entity.Validate());
  }

  [Test]
  public void Validate_Returns_False_If_At_Least_One_Validated_Property_Is_NotValid()
  {
    ValidationFakeEntity entity = new ValidationFakeEntity();

    Assert.IsFalse(entity.Validate());
  }

  [Test]
  public void Validate_Returns_True_Even_If_NotAccessible_Validated_Properties_Are_NotValid()
  {
    ValidationFakeEntity entity = new ValidationFakeEntity("Luca", 1978, false);

    Assert.IsTrue(entity.Validate());
  }

  [Test]
  public void IsPropertyValid_Returns_True_If_Property_Is_Valid()
  {
    ValidationFakeEntity entity = new ValidationFakeEntity("Luca", 1978);

    Assert.IsTrue(entity.IsPropertyValid("Name"));
    Assert.IsTrue(entity.IsPropertyValid("Year"));
  }

  [Test]
  public void IsPropertyValid_Returns_False_If_Property_Is_NotValid()
  {
    ValidationFakeEntity entity = new ValidationFakeEntity();

    Assert.IsFalse(entity.IsPropertyValid("Name"));
    Assert.IsFalse(entity.IsPropertyValid("Year"));
  }

  [Test]
  [ExpectedException(typeof(ArgumentException))]
  public void IsPropertyValid_Throws_ArgumentException_If_Property_Is_NotAvailable()
  {
    ValidationFakeEntity entity = new ValidationFakeEntity("Luca", 1978);

    entity.IsPropertyValid("NotAvailableProperty");
  }

  [Test]
  [ExpectedException(typeof(ArgumentException))]
  public void IsPropertyValid_Throws_ArgumentException_If_Property_Is_NotAccessible()
  {
    ValidationFakeEntity entity = new ValidationFakeEntity("Luca", 1978);

    entity.IsPropertyValid("NotAccessibleProperty");
  }

  [Test]
  public void GetValidationResults_Returns_Empty_Collection_For_Valid_Property()
  {
    ValidationFakeEntity entity = new ValidationFakeEntity("Luca", 1978);

    Assert.IsTrue(entity.GetValidationResults("Name").Count == 0);
    Assert.IsTrue(entity.GetValidationResults("Year").Count == 0);
  }

  [Test]
  public void GetValidationResults_Returns_NonEmpty_Collection_For_NotValid_Property()
  {
    ValidationFakeEntity entity = new ValidationFakeEntity();

    Assert.IsTrue(entity.GetValidationResults("Name").Count > 0);
    Assert.IsTrue(entity.GetValidationResults("Year").Count > 0);
  }

  [Test]
  [ExpectedException(typeof(ArgumentException))]
  public void GetValidationResults_Throws_ArgumentException_If_Property_Is_NotAvailable()
  {
    ValidationFakeEntity entity = new ValidationFakeEntity("Luca", 1978);

    entity.GetValidationResults("NotAvailableProperty");
  }

  [Test]
  [ExpectedException(typeof(ArgumentException))]
  public void GetValidationResults_Throws_ArgumentException_If_Property_Is_NotAccessible()
  {
    ValidationFakeEntity entity = new ValidationFakeEntity("Luca", 1978);

    entity.GetValidationResults("NotAccessibleProperty");
  }
}

Entity

The Validate method iterates through run-time reflection over all available properties marked with a ValidationAttribute. Each property is then validated by calling IsPropertyValid, which in turn relies on GetValidationResults. As you probably also noticed, we could improve the implementation of Validate by inspecting the class properties only once per instance, e.g. in the class constructor, or even better only once per type, e.g. in the static class constructor.

public virtual bool Validate()
{
  foreach (PropertyInfo propertyInfo in this.GetType().GetProperties().Where(p => p.GetCustomAttributes(typeof(ValidationAttribute), true).Count() > 0))
  {
    if (!IsPropertyValid(propertyInfo.Name))
    {
      return false;
    }
  }

    return true;
}

public virtual bool IsPropertyValid(string propertyName)
{
  if (propertyName == null)
  {
    throw new ArgumentNullException("propertyName");
  }

  return GetValidationResults(propertyName).Count == 0;
}

The main method for validation is GetValidationResults that uses Validator.TryValidateProperty from the Data Annotations namespace to validate a property value according to its property attribute. Of course we must use property attributes that extend the base class ValidationAttribute.

public ICollection<ValidationResult> GetValidationResults(string propertyName)
{
  if (propertyName == null)
  {
    throw new ArgumentNullException("propertyName");
  }

  ICollection<ValidationResult> result = new List<ValidationResult>();

  PropertyInfo propertyInfo = this.GetType().GetProperty(propertyName);

  object value = null;
  try
  {
    value = propertyInfo.GetValue(this, null);
  }
  catch (Exception)
  { }

  Validator.TryValidateProperty(
    value,
    new ValidationContext(this, null, null)
    {
      MemberName = propertyName
    },
    result
  );

  return result;
}

Again we add some convenience methods for child classes to pass Lambda Expressions instead of property names.

protected ICollection<ValidationResult> GetValidationResults<TEntity>(Expression<Func<TEntity>> property)
{
  if (property == null)
  {
    throw new ArgumentNullException("property");
  }

  MemberExpression memberExpression = property.Body as MemberExpression;
  if (memberExpression == null || memberExpression.Member == null || memberExpression.Member.MemberType != MemberTypes.Property)
  {
    throw new ArgumentException("Provide a property", "property");
  }

  return GetValidationResults(memberExpression.Member.Name);
}

protected bool IsPropertyValid<TEntity>(Expression<Func<TEntity>> property)
{
  if (property == null)
  {
    throw new ArgumentNullException("property");
  }

  MemberExpression memberExpression = property.Body as MemberExpression;
  if (memberExpression == null || memberExpression.Member == null || memberExpression.Member.MemberType != MemberTypes.Property)
  {
    throw new ArgumentException("Provide a property", "property");
  }

  return IsPropertyValid(memberExpression.Member.Name);
}

Review and Outlook

Wow, that was quite easy and fun to implement! In future we could extend the definition to let also non-public properties participate in change tracking and validation.

Even though the approach of annotating class properties with validators seems quite interesting on the first sight, we will show in a future article that in practice it lacks important flexibility. Herefore we will introduce the fluent validation API which fills this gap perfectly.

In the next article we will design a composite entity, a container for other entities, that also implements our self-tracking features. Afterwards we will finally present how to use all those bits and bytes in user interface development.

As usual feel free to download the source code described in this article.

References

  1. Data Annotations
  2. Aspect Oriented Programming (AOP)
  3. Test Driven Development (TDD)
  4. Continous Integration (CI)
  5. Lambda Expressions