Try fast search NHibernate

28 October 2009

NHibernate.Validator : Customizing messages (Message Interpolator)

The message interpolator is the responsible of the messages translation/composition. In the coming soon version (NHV-1.2.0) we had increase its power (and refactorized its implementation).

Has default NHV has two implementations: DefaultMessageInterpolatorAggregator and DefaultMessageInterpolator.

What is the usual you have seen in NHibernate’s eco-system ? Yes, you are right: Injectability!!!

So far the injectability in NHV is not so extreme as in NHibernate but we are closer…very closer ;)

If you only need to override some behavior you can inherit from DefaultMessageInterpolator. If you want implement a completely different way to create/translate/composite messages you can implements your own IMessageInterpolator.

In this post I will write about another case: the composition of your own behavior with the default behavior.

The needs

As you saw, in the previous post, we have solved the problem of magic-strings so and so…but was only for yesterday. Today I would manage all messages through my convention, and composite it only in the strings-resource-file, for all cases where possible.

The possible convention
  • The key for a class name will be : friendly.class.<TypeName> (ex.: friendly.class.Employee)
  • The key for a property name will be : friendly.property.<PropertyName> (ex: friendly.property.Salary) ; note: two properties with the same name should have the same meaning no matter which is the owner class (IMO).
  • The key for the message of a entity-validator (validate the instance) will be: validator.<TypeName>
  • The key for the message of a property-validator will be: validator.<TypeName>.<PropertyName>
  • The key for the message of a validator will be: validator.<ValidatorName>

For <ValidatorName> I mean the class name of the validator without the post-fix “Validator” (that is the convention used inside NHV).

There is a special case for the <ValidatorName> (as any good convention); using lambdas the implementation of the validator is ever the same so, in this case, the <TypeName> will be the type where the validation was specified.

Note: all reusable validators, using the Satisfier or not, should define its key.

Messages redefinition

In NHV the default message for the constraint NotNullNotEmpty is:

key: validator.notNullNotEmpty

value: may not be null or empty

what I would like is:

The [here the friendly name of the property] of the [here the friendly name of the entity] is mandatory.

The custom IMessageInterpolator

First I must define the syntax to use in my interpolator; there are three possible variables: [EntityName], [PropertyName], [PropertyValue]. In practice in my strings-resource-file I will have this:

CustomInterResource1

and in case I need a sub-property I would have something like this

CustomInterResource2

The implementation

public class ConventionMessageInterpolator : IMessageInterpolator
{
private const string EntityValidatorConvention = "{{validator.{0}}}";
private const string EntityPropertyValidatorConvention = "{{validator.{0}.{1}}}";
private const string EntityNameConvention = "{{friendly.class.{0}}}";
private const string PropertyNameConvention = "{{friendly.property.{0}}}";
private const string PropertyValueTagSubstitutor = "${{{0}{1}}}";
private static readonly int PropertyValueTagLength = "PropertyValue".Length;

private readonly Regex substitutions =
new Regex(@"\[EntityName\]|\[PropertyName\]|(\[PropertyValue([.][A-Za-z_][A-Za-z_0-9]*)*\])"
, RegexOptions.Compiled);

#region IMessageInterpolator Members

public string Interpolate(InterpolationInfo info)
{
string result = info.Message;
if(string.IsNullOrEmpty(result))
{
result = CreateDefaultMessage(info);
}
do
{
info.Message = Replace(result, info.Entity, info.PropertyName);
result = info.DefaultInterpolator.Interpolate(info);
}
while (!Equals(result, info.Message));
return result;
}

#endregion

public string
CreateDefaultMessage(InterpolationInfo info)
{
return string.IsNullOrEmpty(info.PropertyName) ?
string.Format(EntityValidatorConvention, GetEntityValidatorName(info))
:
string.Format(EntityPropertyValidatorConvention, info.Entity.Name, info.PropertyName);
}

private string GetEntityValidatorName(InterpolationInfo info)
{
var entityValidatorName = info.Entity.Name;
var validatorType = info.Validator.GetType();
if (validatorType.IsGenericType)
{
entityValidatorName = validatorType.GetGenericArguments().First().Name;
}
entityValidatorName = CleanValidatorPostfix(entityValidatorName);
return entityValidatorName;
}

private string CleanValidatorPostfix(string entityValidatorName)
{
var i = entityValidatorName.LastIndexOf("Validator");
return i > 0 ? entityValidatorName.Substring(0, i) : entityValidatorName;
}

public string Replace(string originalMessage, Type entity, string propName)
{
return substitutions.Replace(originalMessage, match =>
{
if ("[EntityName]".Equals(match.Value))
{
return string.Format(EntityNameConvention, entity.Name);
}
else if (!string.IsNullOrEmpty(propName) && "[PropertyName]".Equals(match.Value))
{
return string.Format(PropertyNameConvention, propName);
}
else if (!string.IsNullOrEmpty(propName) && match.Value.StartsWith("[PropertyValue"))
{
return string.Format(PropertyValueTagSubstitutor, propName,
match.Value.Trim('[', ']').Substring(PropertyValueTagLength));
}
return match.Value;
});
}
}

The configuration

To use both, my custom interpolator and my custom strings-resource-file the Loquacious configuration is:

var configure = new FluentConfiguration();
configure
.SetMessageInterpolator<ConventionMessageInterpolator>()
.SetCustomResourceManager("YourProd.Properties.ValidationMessagesConv", Assembly.Load("YourProd"))
.SetDefaultValidatorMode(ValidatorMode.UseExternal);

Results

Having a chunk of strings-resource-file as this

CustomInterResource3

I can write a clean definition like

public class AddressValidation: ValidationDef<IAddress>
{
public AddressValidation()
{
Define(a => a.Street).NotNullableAndNotEmpty();
Define(a => a.Number).GreaterThanOrEqualTo(1);
}
}

public class EntreCallesValidation : ValidationDef<IEntreCalles>
{
public EntreCallesValidation()
{
ValidateInstance.By(IsValid);
}

public bool IsValid(IEntreCalles subject, IConstraintValidatorContext context)
{
if(subject == null)
{
return true;
}
var calleA = subject.CalleA == null ? string.Empty : subject.CalleA.Trim();
var calleB = subject.CalleB == null ? string.Empty : subject.CalleB.Trim();
return !(string.Empty.Equals(calleA) ^ string.Empty.Equals(calleB));
}
}

public class DireccionArgentinaValidation: ValidationDef<DireccionArgentina>
{
public DireccionArgentinaValidation()
{
Define(da => da.CodigoPostal)
.MatchWith("[A-Z][0-9]{4}[A-Z]{3}")
.WithMessage("No es un codigo postal Argentino.");
}
}

public class EmployeePositionValidation : ValidationDefEx<EmployeePosition>
{
public EmployeePositionValidation()
{
const decimal avgSalary = 4000m;
const decimal salaryGap = 1500m;

Define(e => e.Description).NotNullableAndNotEmpty();
Define(ep => ep.Salary)
.IsValid()
.And
.NotEmpty()
.And
.GapLessThanOrEqualTo(salaryGap)
.And
.Include(avgSalary);
}
}

public class StandUpMeetingValidation : ValidationDefEx<StandUpMeeting>
{
public StandUpMeetingValidation()
{
TimeSpan meetingTime = TimeSpan.FromMinutes(20);

Define(m => m.Lapse)
.IsValid()
.And
.NotEmpty()
.And
.Satisfy(
r => (new Range<DateTime>
{
LowLimit = r.LowLimit.Date.AddHours(9),
HighLimit = r.LowLimit.Date.AddHours(17).AddMinutes(30)
}).Includes(r)
)
.WithMessage("{validator.StandUpMeeting.Lapse.WorkingTime}")
.And
.GapLessThanOrEqualTo(meetingTime);
}
}

public class EmployeeValidation : ValidationDef<Employee>
{
public EmployeeValidation()
{
Define(e => e.Name).NotNullableAndNotEmpty()
.And
.LengthBetween(2, 30);

Define(e => e.Salary).GreaterThanOrEqualTo(1000m);
}
}

to have invalid messages as:

Must specify both streets or none.
No es un codigo postal Argentino.
The street of the address is mandatory.
The number must be greater than or equal to 1

The description of the employee position is mandatory.
The rage salary should include:4000

The meeting should happen in working time.
The duration of standup-meeting was too long. Expected less than :00:20:00

with all advantages a strings-resource-file, give me.

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…

26 October 2009

NHibernate.Validator : Extending ValidationDef

In various applications I’m using my implementation of Range.

public interface IRange<T> : IEquatable<IRange<T>> where T : IComparable<T>
{
T LowLimit { get; }
T HighLimit { get; }
bool IsEmpty { get; }
bool Includes(T value);
bool Includes(IRange<T> other);
bool Overlaps(IRange<T> other);
}

As you can imagine I’m using it to represent various kind of ranges and in each usage I need to validate the range in various ways. Two simply examples:

public class EmployeePosition
{
public string Description { get; set; }
public IRange<decimal> Salary { get; set; }
}

public class StandUpMeeting
{
public ICollection<Employee> Employees { get; set; }
public IRange<DateTime> Lapse { get; set; }
}

To validate IRange<T> properties, through NHV-loquacious-configuration, I can’t extend some NHV’s interfaces because NHV doesn’t know my type (IRange<T>) and a simple entity-validator (as showed here) is not enough because I need to validate various situation of the range; the way to go is create my own set of constraints.

Custom constraints (extending ValidationDef)

public interface IRangeConstraints<T> : ISatisfier<IRange<T>, IRangeConstraints<T>>
where T : IComparable<T>
{
IChainableConstraint<IRangeConstraints<T>> IsValid();
IChainableConstraint<IRangeConstraints<T>> NotEmpty();
IChainableConstraint<IRangeConstraints<T>> Include(T value);
IChainableConstraint<IRangeConstraints<T>> Include(IRange<T> range);
IChainableConstraint<IRangeConstraints<T>> Overlaps(IRange<T> range);
}

Defined the interface I need an entry point to integrate it with the Loquacious configuration. The “natural” extension-point seems the class ValidationDef<T>. There are various way to extend the ValidationDef<T> but in my opinion the most clear, the most useful and the most easy is a simple inheritance.

public class ValidationDefEx<T> : ValidationDef<T> where T : class
{
public IRangeConstraints<decimal> Define(Expression<Func<T, IRange<decimal>>> property)
{
return null;
}

public IRangeConstraints<DateTime> Define(Expression<Func<T, IRange<DateTime>>> property)
{
return null;
}
}

Now I’m ready to check the API.

public class EmployeePositionValidation : ValidationDefEx<EmployeePosition>
{
public EmployeePositionValidation()
{
const decimal avgSalary = 4000m;

Define(e => e.Description).NotNullableAndNotEmpty();
Define(ep => ep.Salary)
.IsValid()
.WithMessage("The {property.salary} should be valid but was ${Salary}.")
.And
.NotEmpty()
.And
.Include(avgSalary)
.WithMessage("The {property.salary} should be around " + avgSalary);
}
}

public class StandUpMeetingValidation : ValidationDefEx<StandUpMeeting>
{
public StandUpMeetingValidation()
{
Define(m => m.Lapse)
.IsValid()
.WithMessage("The {property.lapse} should be valid but was ${Lapse}.")
.And
.NotEmpty();
}
}

The base API work fine; I can go to tests and implementation.

public class RangeConstraints<TR> : BaseConstraints<IRangeConstraints<TR>>, IRangeConstraints<TR>
where TR : IComparable<TR>
{
#region Implementation of IRangeConstraints<TR>

public RangeConstraints(IConstraintAggregator parent, MemberInfo member) : base(parent, member) {}

public IChainableConstraint<IRangeConstraints<TR>> IsValid()
{
return Satisfy(r => r.LowLimit.CompareTo(r.HighLimit) <= 0)
.WithMessage("{validator.range.IsValid}");
}

public IChainableConstraint<IRangeConstraints<TR>> NotEmpty()
{
return Satisfy(r => !r.IsEmpty)
.WithMessage("{validator.range.NotEmpty}");
}

public IChainableConstraint<IRangeConstraints<TR>> Include(TR value)
{
return Satisfy(r => r.Includes(value))
.WithMessage("{validator.range.Include}" + value);
}

public IChainableConstraint<IRangeConstraints<TR>> Include(IRange<TR> range)
{
return Satisfy(r => r.Includes(range))
.WithMessage("{validator.range.Include}" + range);
}

public IChainableConstraint<IRangeConstraints<TR>> Overlaps(IRange<TR> range)
{
return Satisfy(r => r.Overlaps(range))
.WithMessage("{validator.range.Overlaps}" + range);
}

#endregion

#region
Implementation of ISatisfier<IRange<TR>,IRangeConstraints<TR>>

public IChainableConstraint<IRangeConstraints<TR>> Satisfy(Func<IRange<TR>, IConstraintValidatorContext, bool> isValidDelegate)
{
var attribute = new DelegatedValidatorAttribute(new DelegatedConstraint<IRange<TR>>(isValidDelegate));
return AddWithConstraintsChain(attribute);
}

public IChainableConstraint<IRangeConstraints<TR>> Satisfy(Func<IRange<TR>, bool> isValidDelegate)
{
var attribute = new DelegatedValidatorAttribute(new DelegatedSimpleConstraint<IRange<TR>>(isValidDelegate));
return AddWithConstraintsChain(attribute);
}

#endregion
}

and the my validation definition extension look like

public class ValidationDefEx<T> : ValidationDef<T> where T : class
{
public IRangeConstraints<decimal> Define(Expression<Func<T, IRange<decimal>>> property)
{
return new RangeConstraints<decimal>(this, TypeUtils.DecodeMemberAccessExpression(property));
}

public IRangeConstraints<DateTime> Define(Expression<Func<T, IRange<DateTime>>> property)
{
return new RangeConstraints<DateTime>(this, TypeUtils.DecodeMemberAccessExpression(property));
}
}

Work done!! Now I have my own set of constraints for my IRange<T> and I can use it with NHV.

Ups!!! … new request: I must validate the gap of the Salary and the time of the standup-meeting.

Extending the Extension

public static class RangeConstraintsExtensions
{
public static IChainableConstraint<IRangeConstraints<decimal>>
GapLessThanOrEqualTo(this IRangeConstraints<decimal> definition, decimal value)
{
return
definition.Satisfy(r => r.HighLimit - r.LowLimit <= value)
.WithMessage("{validator.range.GapLessThanOrEqualTo}" + value);
}

public static IChainableConstraint<IRangeConstraints<decimal>>
GapGreaterThanOrEqualTo(this IRangeConstraints<decimal> definition, decimal value)
{
return
definition.Satisfy(r => r.HighLimit - r.LowLimit >= value)
.WithMessage("{validator.range.GapGreaterThanOrEqualTo}" + value);
}

public static IChainableConstraint<IRangeConstraints<DateTime>>
GapLessThanOrEqualTo(this IRangeConstraints<DateTime> definition, TimeSpan value)
{
return
definition.Satisfy(r => r.HighLimit - r.LowLimit <= value)
.WithMessage("{validator.TimeRange.GapLessThanOrEqualTo}" + value);
}

public static IChainableConstraint<IRangeConstraints<DateTime>>
GapGreaterThanOrEqualTo(this IRangeConstraints<DateTime> definition, TimeSpan value)
{
return
definition.Satisfy(r => r.HighLimit - r.LowLimit >= value)
.WithMessage("{validator.TimeRange.GapGreaterThanOrEqualTo}" + value);
}
}

And now my two definitions can look like

public class EmployeePositionValidation : ValidationDefEx<EmployeePosition>
{
public EmployeePositionValidation()
{
const decimal avgSalary = 4000m;
const decimal salaryGap = 1500m;

Define(e => e.Description).NotNullableAndNotEmpty();
Define(ep => ep.Salary)
.IsValid()
.WithMessage("The {property.salary} should be valid but was ${Salary}.")
.And
.NotEmpty()
.And
.GapLessThanOrEqualTo(salaryGap)
.WithMessage("The gap of {property.salary} should be " + salaryGap)
.And
.Include(avgSalary)
.WithMessage("The {property.salary} should be around " + avgSalary);
}
}

public class StandUpMeetingValidation : ValidationDefEx<StandUpMeeting>
{
public StandUpMeetingValidation()
{
TimeSpan meetingTime = TimeSpan.FromMinutes(20);

Define(m => m.Lapse)
.IsValid()
.WithMessage("The {property.lapse} should be valid but was ${Lapse}.")
.And
.NotEmpty()
.And
.Satisfy(
r =>(new Range<DateTime>
{
LowLimit = r.LowLimit.Date.AddHours(9),
HighLimit = r.LowLimit.Date.AddHours(17).AddMinutes(30)
}).Includes(r)
)
.WithMessage("The meeting should happen during working time")
.And
.GapLessThanOrEqualTo(meetingTime)
.WithMessage("The {entity.StandUpMeeting} was too long.");
}
}

Conclusion

Now you know one reason because NHibernate.Validator is part of my Gum-Architecture: it is compressible, extensible, ball-able, cube-able, flat-able… ;) and the story does not end here...