Built in constraints
Hibernate Validator comes with some built-in constraints, which cover most of the basic data checks. As we'll see later, you're not limited to them, you can literally in a minute write your own constraints.
Table Built-in constraints
<tbody>
</tbody>
<tbody>
| Constraint |
Apply on |
Runtime checking |
NHibernate Metadata impact |
| Length(Min=, Max=) |
property (String) |
checks if the string length is between the min-max range |
Column length will be set to max |
| Max(Value=) |
property (numeric or string representation of a numeric) |
checks if the value is less than or equals to max |
Add a check constraint on the column |
| Min(Value=) |
property (numeric or string representation of a numeric) |
checks if the value is more than or equals to min |
Add a check constraint on the column |
| NotNull |
property |
checks if the value is not null |
Column(s) are not null |
| NotEmpty |
property |
checks if the string is not empty. |
|
| NotEmptyOrNull |
property |
Checks if the string is not null or not empty. |
|
| Past |
property (DateTime) |
check if the date is in the past |
|
| Future |
property (DateTime) |
check if the date is in the future |
|
| Pattern(Regex="regexp", Flag=) |
property (string) |
check if the property matches the regular expression given a match flag. The <tt>Flag</tt> parameter is a System.Text.RegularExpressions.RegexOptions enum. You can also declare multiple Pattern attributes on the same member. |
|
| Range(Min=, Max=) |
property (numeric or string representation of a numeric) |
check if the value is between Min and Max (included) |
|
| Size(Min=, Max=) |
property (array, collection, map) |
check if the element size is between Min and Max (included) |
|
| AssertFalse |
property |
check that the method evaluates to false (useful for constraints expressed in code rather than attributes) |
|
| AssertTrue |
property |
check that the method evaluates to true (useful for constraints expressed in code rather than attributes) |
|
| Valid |
property (object) |
perform validation recursively on the associated object. If the object is a Collection or an array, the elements are validated recursively. If the object is a Map, the value elements are validated recursively. |
|
| Email |
property (String) |
check whether the string conforms to the email address specification |
|
| Digits |
property (numeric or string representation of a numeric) |
checks whether the property is a number having up to integerDigits integer digits and fractionalDigits fractonal digits |
define column precision and scale |
</tbody>
Error messages
NHibernate Validator comes with a default set of error messages translated in several languages (if yours is not present, please send us a patch). You can add your own additional set of messages while writing your validator Attributes or at XML. If NHibernate Validator cannot resolve a key from your ResourceManager nor from ValidatorMessage, it falls back to the default built-in values.
Alternatively you can provide a ResourceManager while checking programmatically the validation rules on a bean or if you want a completly different interpolation mechanism, you can provide an implementation of NHibernate.Validator.Engine.IMessageInterpolator.
Writing your own constraints
Extending the set of built-in constraints is extremely easy. Any constraint consists of two pieces: the constraint descriptor (the attribute) and the constraint validator (the implementation class). Here is a simple user-defined descriptor:
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
[ValidatorClass(typeof(PhoneValidator))]
public class PhoneAttribute : Attribute, IRuleArgs
{
private string message = string.Empty;
public string Message
{
get { return message; }
set { message = value; }
}
}
To link a descriptor to its validator implementation, we use the [ValidatorClass] meta-attribute. Here you can see an implementation of a constraint validator:
public class PhoneValidator : IValidator
{
public bool IsValid(object value)
{
Regex regex = new Regex(@"^[2-9]\d{2}-\d{3}-\d{4}$");
if (value == null) return true;
return regex.IsMatch(value.ToString());
}
}
This PhoneValidator class is simple. The method IsValid() receives a string that should be validated with a Regex expression. The IsValid() method should return false if the constraint has been violated.
If you need to initialize the class validator you can implement the interface IInitializableValidator<A> and the class should look like this:
public class PhoneValidator : IInitializableValidator<PhoneAttribute>
{
private Regex regex;
public bool IsValid(object value)
{
if (value == null) return true;
return regex.IsMatch(value.ToString());
}
public void Initialize(PhoneAttribute parameters)
{
regex = new Regex(@"^[2-9]\d{2}-\d{3}-\d{4}$");
}
}
As you can see, implementing the IInitializableValidator<A> interface we have to implement the method Initialize(...) that initializes the validator, and it's executed first of all.
The IsValid() method should return false if the constraint has been violated. For more examples, refer to the built-in validator implementations.
We now have to implement the validator (ie. the rule checking implementation). A validation implementation can check the value of the a property (by implementing PropertyConstraint ) and/or can modify the NHibernate mapping metadata to express the constraint at the database level (by implementing PersistentClassConstraint)
We have only seen property level validation, but you can write a class level validation attribute. Instead of receiving the return instance of a property, the bean itself will be passed to the validator. To activate the validation checking, just put the validation attribute to the class itself instead of in a property. A small sample can be found in the unit test suite.
If your constraint can be applied multiple times (with different parameters) on the same property or type, you can use set the AllowMultiple parameter to true on the AttributeUsage attribute. An example of this it's the built-in validator Pattern, have a look:
[Serializable]
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)]
[ValidatorClass(typeof (PatternValidator))]
public class PatternAttribute : Attribute, IRuleArgs
{
…
}
Configuring your domain model with .Net Attributes
Since you are already familiar with attributes now, the syntax should be very familiar
public class Address
{
// a static non null field
[NotNull]
public static string blacklistedZipCode;
// field a not null string of 20 characters maximum
[Length(Max = 20), NotNull]
private string country;
private long id;
private bool internalValid = true;
private string line1;
private string line2;
private string state;
private string zip;
// a numeric between 1 and 2000
[Min(1), Range(Max = 2000)]
public long Id
{
get { return id; }
set { id = value; }
}
// respond to the constraint in the field: country
public string Country
{
get { return country; }
set { country = value; }
}
// a non null string
[NotNull]
public string Line1
{
get { return line1; }
set { line1 = value; }
}
// a not null string of 3 characters maximum
[Length(Max = 3), NotNull]
public string State
{
get { return state; }
set { state = value; }
}
// a not null numeric string of 5 characters maximum
// if the string is longer, the message will
// be searched in the resource bundle at key 'long'
[Length(Max = 5, Message = "{long}")]
[Pattern(Regex = "[0-9]+")]
[NotNull]
public string Zip
{
get { return zip; }
set { zip = value; }
}
// no constraint
public string Line2
{
get { return line2; }
set { line2 = value; }
}
// should always be true
[AssertTrue]
public bool InternalValid
{
get { return internalValid; }
set { internalValid = value; }
}
}
While the example only shows public property validation, you can also annotate fields of any kind of visibility. In the example MyBeanConstraint is a custom Class Validator, we will talk about this later.
[MyBeanConstraint(Max=45)]
public class Dog
{
[AssertTrue] private bool IsMale;
[NotNull] protected string Name { get {...} set{...} }
…
}
You can also annotate interfaces. NHibernate Validator will check all superclasses and interfaces extended or implemented by a given type to read the appropriate validator attributes.
public interface Named
{
[NotNull] string Name {get; set;}
...
}
public class Dog : Named
{
[AssertTrue] private bool isMale;
public string Name { get {...} set{...} }
}
The name property will be checked for nullity when the Dog type is validated.
Configuring your domain model with XML
You also can configurate your domain model with constraints using xml files: the nhv.xml files. We prefer to embed these Xml files into the assemblies, and NHibernate Validator is prepared to inspect them and retrieve the resources with constrains, like NHibernate does with hbm.xml files.
<?xml version="1.0" encoding="utf-8" ?>
<nhv-mapping xmlns="urn:nhibernate-validator-1.0"
namespace="NHibernate.Validator.Tests.Base"
assembly="NHibernate.Validator.Tests">
<class name="Address">
<property name="blacklistedZipCode">
<not-null/>
</property>
<property name="country">
<length max="20"/>
<not-null />
</property>
<property name="floor">
<range min="-2" max="50" message="{floor.out.of.range} (escaping #{el})" />
</property>
<property name="Id">
<min value="1" />
<range max="2000" />
<max value="2500"/>
</property>
<property name="Line1" >
<not-null/>
</property>
<property name="State">
<not-null/>
<length max="3"/>
</property>
<property name="Zip">
<length max="5" message="{long}"/>
<pattern regex="[0-9]+" />
<not-null/>
</property>
<property name="InternalValid">
<asserttrue />
</property>
</class>
</nhv-mapping>
We strongly recommend to validate every nhv.xml file of your application with the respective XSD file. Every nhv.xml file should be validated with the nhv-mapping.xsd file included within the NHibernate Validator distribution whether in source or binaries. Indeed, if you are using an IDE like Visual Studio it offers IntelliSense support in the nhv.xml file if you add the schema to Visual Studio's xml schemas folder.
Xml beauties: The nicest part of Xml for validation is that you can put xml validators in a different dll so you don't need to recompile your domain when validation rules change. Only the dll with the Xml files..