DataObjects.Net includes consistency validation framework allowing to validate single property values, entire entities and entity graphs.
Each transaction contains its own ValidationContext – class responsible for validation of all entities changed in this transaction. Entity class implements IValidaionAware interface allowing it to be validated by validation context. When some entity is changed, it calls Validate method to inform the appropriate validation context. Validation context can be in one of following states:
If validation is failed, validation infrastructure will throw AggregateException with list of validation errors found in validated entities:
try {
using (var transactionScope = session.OpenTransaction()) {
using (var inconsistencyRegion = session.DisableValidation()) {
// Change your entities here
inconsistencyRegion.Complete();
}
transactionScope.Complete();
}
}
catch(AggregateException exception) {
Console.WriteLine("Following validation errors were found:");
foreach (var error in exception.GetFlatExceptions())
Console.WriteLine(error.Message);
}
You can also enforce validation of all changed entities inside inconsistency region by calling Session.Validate() method.
Continuous validation can be useful in some cases, but often it is enough to validate all changed entities when transaction is being committed. In such cases you can switch validation mode to OnDemand instead of Continuous. To do this change ValidationMode property of DomainConfiguration instance before domain is built:
domainConfiguration.ValidationMode = ValidationMode.OnDemand;
When this mode is switched on, single inconsistency region is opened for each transaction, so validation context will always be in inconsistent state and entities will be validated on transaction commit only. But you still can validate changed entities with Session.Validate() method any time you want.
To define validation rules you should implement object-level validation logic in OnValidate() method that will be called on entity validation. This method should check entity state and throw an exception if it is invalid.
[HierarchyRoot]
public class Person : Entity
{
// ...
[Field]
public bool IsSubscribedOnNews { get; set;}
[Field]
public string Email { get; set;}
protected override void OnValidate()
{
base.OnValidate();
if (IsSubscribedOnNews && string.IsNullOrEmpty(Email))
throw new Exception("Can't subscribe on news (email is not specified).");
}
}
Another way to define validation rules is to mark entity properties by special attributes – property constraints. Property constraints are special property-level validation aspects that can be applied to any property by marking it with appropriate attribute. Each constraint implements some simple validation rule for property value. For example NotNullConstraint ensures that property value is not null, RegexConstraint ensures that string value matches specified regular expression pattern.
[LengthConstraint(Min = 2, Max = 128)]
[NotNullOrEmptyConstraint]
public string FirstName { get; set;}
[PastConstraint]
public DateTime BirthDay { get; set; }
[EmailConstraint]
public string Email { get; set;}
Each property constraint attribute has two additional properties: Message and Mode.
Message property specifies message for exception that should be thrown if property value is invalid. Message can contain some special variables, that will be replaced automatically: Property value – {value}, property name – {PropertyName} and constraint parameters – {[parameter name]}.
[PastConstraint(Message = "Birth day must be in the past.")]
public DateTime BirthDay { get; set; }
[RangeConstraint(
Min = 0.8,
Max = 2.13,
Message = "Incorrect '{PropertyName}' value: {value}, " +
"it can not be less than {Min} and greater than {Max}.")]
public double Height { get; set;}
If we set height property to 2.5, we’ll get exception with such message: “Incorrect ‘Height’ value: 2.5, it can not be less than 0.8 and greater than 2.13.”
By default property constraints are used when entity is being validated, but it’s also possible to throw exception on setting new value to the property. PropertyConstraintAspect has special property Mode of ConstrainMode type. It has two available values: OnValidate (default value) and OnSetValue.
[PastConstraint(Mode = ConstrainMode.OnSetValue)]
public DateTime BirthDay { get; set; }
DataObjects.Net validation framework includes following predefined constraints:
It is possible to implement your own property constraints inheriting it from abstract class PropertyConstraintAspect.
[Serializable]
public class PhoneNumberConstraint : PropertyConstraintAspect
{
private const string PhoneNumberPattern = "^[2-9]\\d{2}-\\d{3}-\\d{4}$";
[NonSerialized]
private Regex phoneNumberRegex;
public override bool IsSupported(Type valueType)
{
return valueType==typeof(string);
}
public override bool CheckValue(object value)
{
string phoneNumber = (string) value;
return
string.IsNullOrEmpty(phoneNumber) ||
phoneNumberRegex.IsMatch(phoneNumber);
}
protected override string GetDefaultMessage()
{
return "Phone number is incorrect";
}
protected override void Initialize()
{
base.Initialize();
phoneNumberRegex = new Regex(PhoneNumberPattern, RegexOptions.Compiled);
}
}
We create regular expression instance in Initialize() method instead of constructor, we do this since constraints is PostSharp aspects and it is created in compile time also, but we need regex to be created in runtime only.
[PhoneNumberConstraint]
public string Phone { get; set;}