Try fast search NHibernate

24 October 2009

NHibernate.Validator : The “Satisfy” way of custom Validators

The Satisfy (perhaps it sound familiar to some SharpTestsEx users ;) ).

To show how it work the example is a constraint for Person.Name: a Name is significant when it has more than three characters, is composed by letters and the first letter is in uppercase.

To create a that validator, in the classic way, we should create an Attribute implementing IRuleArgs and then a class implementing IValidator, or combine some existing validators for strings (in this case min-length and regex or only a regex).

Using the Satisfier the work to do is so hard as write a lambda:

public class PersonValidation: ValidationDef<Person>
{
public PersonValidation()
{
Define(p => p.Name)
.Satisfy(name => name != null && name.Length >= 3 && Regex.IsMatch(name,"[A-Z][a-z]*"))
.WithMessage("The Name is not significant.");
}
}
The Satisfier is available for those property-types where we consider it has sense.

Implemented as is the Satisfier appear as not reusable; if we want use the same, identical, validation for a pet's name we should rewrite the lambda in the validation definition for our Pet class. hmmm… perhaps we shouldn’t.

We can extend Loquacious:

public static IChainableConstraint<IStringConstraints> Significant(this IStringConstraints definition)
{
return definition.Satisfy(s => s != null && s.Length >= 3 && Regex.IsMatch(s, "[A-Z][a-z]*"));
}

Now we can use the extension in this way:

public class PersonValidation: ValidationDef<Person>
{
public PersonValidation()
{
Define(p => p.Name).Significant()
.WithMessage("The Name is not significant.");
}
}

public class PetValidation : ValidationDef<Pet>
{
public PetValidation()
{
Define(p => p.Name).Significant()
.WithMessage("The Name is not significant.");
}
}

Note: Don’t take care about the duplication of the massage, that will be matter of another post.

If you want make the Significant constraint a little bit more flexible, you can simply use parameters as:

public static IChainableConstraint<IStringConstraints> Significant(
this IStringConstraints definition,
int minLength)
{
return definition.Satisfy(
s => s != null && s.Length >= minLength && Regex.IsMatch(s, "[A-Z][a-z]*"));
}

and use it as

public class PersonValidation: ValidationDef<Person>
{
public PersonValidation()
{
const int minNameLength = 3;
Define(p => p.Name).Significant(3)
.WithMessage(
string.Format("The Name is not significant (min length {0}).", minNameLength));
}
}

public class PetValidation : ValidationDef<Pet>
{
public PetValidation()
{
const int minNameLength = 4;
Define(p => p.Name).Significant(minNameLength)
.WithMessage(
string.Format("The Name is not significant (min length {0}).",minNameLength));
}
}

As you can see, define a custom-reusable Validator with NHibernate.Validator, is a piece of cake.

3 comments:

  1. Well done!
    Loquacios is the way, definitely.

    ReplyDelete
  2. Hi Fabio,

    Is there any reason so IRelationshipConstraints does not derive from ISatisfier as many other constraints interfaces do?

    ReplyDelete
  3. I don't remember exactly but, probably, is because the relation should have its own validation; you shouldn't validate a relation through the aggregate.

    ReplyDelete