DataObjects.Net includes consistency validation framework allowing to validate single property values, entire entities and entity graphs.
Each session contains its own ValidationContext – class responsible for validation of all entities changed in this session. Since the context is session-level, validation can be turned on or off independantly for each session by including or excluding SessionOption.ValidateEntitites option. By default the option is included in all session profiles.
Validation context can validate entities:
Following example shows where to expect exceptions in both cases
- try {
- using (var transactionScope = session.OpenTransaction()) {
var user = session.Query.All<User>().First(u => u.Id == currentUserId); try {
// both names have immediate validator for value not being null or empty user.FirstName = updatedFirstName; user.LastName = updatedLastName;} catch(ArgumentException argException){
Console.WriteLine(“Following validation error was found on attempt to update entity:”); Console.WriteLine(argException.Message); throw;} // email validator is not immediate user.Email = updateEmail; user.LastUpdateOn = DateTime.UtcNow;
transactionScope.Complete();
}
} catch(ValidationFailedException exception) {
Console.WriteLine(“Following validation errors were found:”); foreach (var errorInfo in exception.ValidationErrors) {
var entity = errorInfo.Target; Console.WriteLine(“- {entity.ToString()}:”); foreach (var validationResult in errorInfo.Errors) {
var errorMessage = validationResult.ErrorMessage; var fieldName = validationResult.Field.Name; Console.WriteLine($” Field {fieldName} caused ‘{errorMessage}’”);} Console.WriteLine();
}
}
You can aslo perfrom validation manually. Entity.Validate() will validate separate entity, if validation failed it will throw ValidationFailedException. Session.Validate() will validate all entities registered in the validation context at once. Sometimes you also might want to have errors in form of collection instead of exception, if so the Session.ValidateAndGetErrors() method will be useful, it will give you error information like it presented in ValidationFailedException.ValidationErrors.
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 additional properties: - IsImmediate. Set to true, it tells to validate corresponding
field imediately on attempt to set value. false will postpone until transaction commit
[PastConstraint(
IsImmediate = true,
ValidateOnlyIfModified = true)]
public DateTime BirthDay { get; set; }
[RangeConstraint(
Min = 0.8,
Max = 2.13,
IsImmediate = true,
SkipOnTransactionCommit = true)]
public double Height { get; set;}
DataObjects.Net validation framework includes following predefined constraints:
It is possible to implement your own property constraints inheriting it from abstract class PropertyValidator.
[Serializable]
public class PhoneNumberConstraint : PropertyValidator
{
private const string PhoneNumberPattern = "^[2-9]\\d{2}-\\d{3}-\\d{4}$";
private static readonly Regex Validator = new Regex(PhoneNumberPattern);
public override void Configure(Domain domain, TypeInfo type, FieldInfo field)
{
base.Configure(domain, type, field);
if (field.ValueType!=typeof (string))
ThrowConfigurationError(string.Format(Strings.FieldShouldBeOfTypeX, typeof (string).FullName));
}
public override ValidationResult Validate(Entity target, object fieldValue)
{
var value = (string) fieldValue;
var isValid = string.IsNullOrEmpty(value) || Validator.IsMatch(value);
return isValid ? Success() : Error("Phone number is incorrect", fieldValue);
}
public override IPropertyValidator CreateNew()
{
return new PhoneNumberConstraint {
IsImmediate = IsImmediate,
};
}
}
In Configure method we can check whether constraint is applied properly, for instance here we check that it is applied to field of string type.
Validate defines validation algorithm itself, as a result you can
return Success or Error with error message or exception instance and actual value that caused the error. CreateNew is used by infrastructure to clone instance of attribute,
you just need to return new instance of the validator with exact same settings.
[PhoneNumberConstraint]
public string Phone { get; set;}