News, examples, tips, ideas and plans.
Thoughts around ORM, .NET and SQL databases.

Monday, July 06, 2009

Property constraints

Let us suppose that a Person class has Age property of type int and its value can not be negative. We certainly can implement this check in two different ways: Check value in property setter or in OnValidate method.

In first case we have to expand auto-property, and write a code like this:

[Field]
public int Age
{
  get { return GetVieldValue<int>("Age"); }
  set {
    if (value < 0)
      throw new Exception(string.Format(
        "Incorrect age ({0}), age can't be less than {1}.",
        value, 0);
    SetVieldValue<int>("Age", value);
  }
}


Second way is:

[Field]
public int Age { get; set; }

public override OnValidate()
{
  if (Age < 0)
    throw new Exception(string.Format(
      "Incorrect age ({0}), age can't be less than {1}.",
      Age, 0);
}


Validation behavior in those two ways is not the same, exceptions will be thrown in different stages: setting property value or validating the object. There is no single point of view, which way is preferable.

Property constraints are property-level aspects integrated with the validation system, that allow to simplify value checks implementation of those kinds. Property constraint aspect is automatically applied to properties marked by appropriate attributes. In our example constraint declaration will look like this:

[Field]
[RangeConstraint(Min = 0,
  Message = "Incorrect age ({value}), age can not be less than {Min}.",
  Mode = ValidationMode.Immediate]
public int Age { get; set; }


or

[Field]
[RangeConstraint(Min = 0,
  Message = "Incorrect age ({value}), age can not be less than {Min}.")]
public int Age { get; set; }


Each constraint attribute contains two general properties: Message and Mode. Message property value is used as the exception message when check is failed. We also plan to add the ability to get messages from strings resources, this feature will be useful for applications localization.

Mode property value determines whether immediate (check in setter) or delayed (check on object validating) should be used. All property constraints on a particular instance can also be checked with CheckConstraints() extension method;

Constraints are designed not only to work with our entities, but with any classes, that implement IValidationAware interface.

By now following property constraints are available:

[EmailConstraint]Ensures that email address is in correct format
[FutureConstraint]Ensures that date value is in the future
[LengthConstraint]Ensures string or collection length fits in specified range
[NotEmptyConstraint]Ensures that string value is not empty
[NotNullConstraint]Ensures property value is not null
[NotNullOrEmptyConstraint]Ensures property value is not null or empty
[PastConstraint]Ensures that date value is in the past
[RangeConstraint]Ensures that numeric value fits in the specified range
[RegexConstraint]Ensures property value matches specified regular expression


Other constraints can be easily implemented as PropertyConstraintAspect descendants.

Following example illustrates the variety of property constraints on a Person class:

[NotNullOrEmptyConstraint]
[LengthConstraint(Max = 20,
  Mode = ValidationMode.Immediate)]
public string Name { get; set;}

[RangeConstraint(Min = 0,
  Message = "Incorrect age ({value}), age can not be less than {Min}.")]
public int Age { get; set;}

[PastConstraint]
public DateTime RegistrationDate { get; set;}

[RegexConstraint(Pattern = @"^(\(\d+\))?[-\d ]+$",
  Message = "Incorrect phone format '{value}'")]
public string Phone { get; set;}

[EmailConstraint]
public string Email { get; set;}

[RangeConstraint(Min = 1, Max = 2.13)]
public double Height { get; set; }

No comments:

Post a Comment