Try fast search NHibernate

27 October 2009

NHibernate.Validator : Customizing messages (bases)

One of the most powerful feature of NHibernate.Validator is its way to manage messages.

In this post are the bases of the massage customization.

The Price

In the last moth this blog was visited from 101 countries but NHibernate.Validator has only 8 translations. The available cultures are: en, es, it, fr, de, nl, lv, pl. Are you seeing the translation in your language ? you don’t ? What you are waiting for ?!!?!!?

From now on, feel in debt with us if you never sent the patch with the translation for your country. Now you can continue reading… ;)

The Message

The message represent what you want show to the user for an invalid value. For example:

public class EmployeeValidation : ValidationDef<Employee>
{
public EmployeeValidation()
{
Define(e => e.Name).NotNullableAndNotEmpty()
.WithMessage("The name of the employee is mandatory.")
.And
.LengthBetween(2,30)
.WithMessage("The length of the name of the employee should be between 2 and 30.");

Define(e => e.Salary).GreaterThanOrEqualTo(1000m)
.WithMessage("The salary should be greater than $1000.");
}
}

Without talk about multi-languages applications let me analyze these messages.

  1. First of all we have three beautiful magic-strings.
  2. If I want be more clear and instead print “name” I want “full name” I must remember to do it in the message of NotNullableAndNotEmpty and in the message of LengthBetween.
  3. If I want print “is mandatory” for any other NotNullableAndNotEmpty usage I must repeat it everywhere.
  4. If I need to change the limits in the LengthBetween constraint and in the GreaterThanOrEqualTo I must remember to change it even in the message.
  5. And if I want to print the actual length of the Name property in the message of LengthBetween constraint ? what should I do ?

The Message definition syntax

To make it short any valid string is a valid message and there are two special cases:

<EmbeddedValue> ::= ‘{’ <Identifier> ‘}’

<PropertyValue> ::= ‘$’‘{’ <PropertyPath> ‘}’

Embedded Value: The embedded value syntax is to solve some of above problems (p1, p2, p3, p4).

Property Value: The property value syntax is to solve p4.

Our RegEx, to recognize valid tokens, is: (?<![#])[$]?{(\w|[._0-9])*}

The Message composition

To solve the p2 and p3 : "The {friendly.property.name} of the employee {validator.NotNullableAndNotEmpty}."

Defined as is, NHV will look in a strings-resource-file (example below) for the value which key is “friendly.property.name” and then for for a value which key is “validator.NotNullableAndNotEmpty”.

If in the strings-resource-file we have :

CustomResource1


The translated message will be: The full name of the employee is mandatory.

To solve p4: "The length of the {friendly.property.name} of the employee should be between {Min} and {Max} characters."

If we look at the Attribute, linked to LengthBetween constraint, we will find that it has two public properties named exactly:

public int Min { get; set; }
public int Max
{
get { return max; }
set { max = value; }
}

So, defined as is, NHV will look in a strings-resource-file for the value which key is “friendly.property.name” and then will look to the public properties of the Attribute, linked to the constraint, to find the value of the property named Min and the property named Max.

Having the above entries, in our strings-resource-file, and giving Min=2 and Max=30 the translated message will be: The length of the full name of the employee should be between 2 and 30 characters.

To solve the p1: this point, perhaps, is more useful if you are developing a multi language application by the way…

Having

CustomResource2

your definition may look as:

public EmployeeValidation()
{
Define(e => e.Name).NotNullableAndNotEmpty()
.WithMessage("{message.employee.name.NotNullableAndNotEmpty}")

Note that the value of “message.employee.name.NotNullableAndNotEmpty” is composed by others variables and NHV will resolve everything recursively.

To solve p5: well… at this point you can imagine that what we need is only use the PropertyValue syntax… but a property of what ?

The property represent a full-property-path starting from the object under validation; that mean that giving a definition as:

.LengthBetween(2,30)
.WithMessage("{message.employee.name.length}");

and a strings-resource-file as

CustomResource3

running

var employee = new Employee {Name = new string('A',31) };
var iv = validatorEngine.Validate(employee);

the message of the invalid value will be:

The full name of the employee should have length between 2 and 30 characters but is 31.

The Resource bundle

How create a strings-resource-file (for mono/multi language applications) is outside the scope of this post; you can find more information starting from this link.

After create the file I have only one recommendation : immediately remove the auto-generated Resource.cs and then clean the property “Custom Tool” from the properties of the file (F4).

To configure your own strings-resource-file you can use

var configure = new FluentConfiguration();
configure.SetCustomResourceManager("YourAssmbly.Properties.ValidationMessages", Assembly.LoadFrom("YourAssembly"))

or

<property name='resource_manager'>
YourFullNameSpace.TheBaseNameOfTheResourceFileWithoutExtensionNorCulture, YourAssembly
</property>
The rules are the same of the ResourceManager class.

Conclusion

Perhaps you think we haven’t solved the problem of magic-strings, but is because define a good convention for message-naming is in your charge.

Perhaps you think “Man!! if I don’t need multi-language I want avoid all those ‘message.class.prop.constraint’ and I prefer to write the message directly.”

Perhaps you think “Man!! I need multi-language but all those ‘message.class.prop.constraint’ really annoys me.”

take it easy ;-)… the story does not end here…

2 comments:

  1. Does NHV have something that allow us to despascalize the property name?.... I mean, imagine we have a message like this:

    "The {property} is mandatory."

    Then, if we apply that message to a FullName property, we get:
    "The full name is mandatory."

    or if we apply to LastName:
    "The last name is mandatory."

    ReplyDelete
  2. José, probably my post is not so clear.
    In your custom message you put {aqui.pondra.NombreUsuario}.
    In the resource you have a key: aqui.pondra.UserName
    with the value:
    nombre usuario

    any way... the story does not end here…

    ReplyDelete