In the MVC project im working I use Fluent Validation to implement validation logics and unity as dependency injection container
In my validator class there are some complex business validation rules
public class ServicerRequestViewModelValidator : AbstractValidator<ServiceRequestViewModel>
{
public ServiceRequestViewModelValidator(ILocalizationService localizationService)
{
RuleFor(x => x.IdStato).NotNull().WithMessage(string.Format(localizationService.GetMessage("Validation.General.MandataryField"), localizationService.GetMessage("ServiceRequestDetail.State")));
// other business validations rule with localized error message
}
}
The rule sets an error message localized according to the user's language
JeremySkinner says:
Instantiation of validators is an expensive process due to the expression tree compilation and parsing within the RuleFor definitions. Because of this, it's recommended that you use validator instances as singletons- once instantiated they should be cached and reused, rather than being instantiated multiple times.
Validators do not contain any shared state, so it should also be safe to reuse them in multithreaded scenarios too. The best approach to caching the validator instances would be to use an IoC container (eg, StructureMap) to manage the instance lifecycles.
So i registered in the container the validator with ContainerControlledLifetimeManager (singleton)
container.RegisterType<IValidator<ServiceRequestViewModel>, ServiceRequestViewModelValidator>(new ContainerControlledLifetimeManager());
But doing like that a problem arise:
first time ServiceRequestViewModelValidator
is resolved the constructor is executed and the localized error message will be cached according to the language of the user and subsequent users will get the message localized according to the language of the user that instantiated the singleton class.
I created a new WithMessage
extension method that receive a delegate insted of a string, taking advantage of LazyStringSource
class
public static IRuleBuilderOptions<T, TProperty> WithMessage<T, TProperty>(this IRuleBuilderOptions<T, TProperty> rule, Func<string> errorMessage)
{
return rule.Configure((Action<PropertyRule>)(config =>
{
config.CurrentValidator.ErrorMessageSource = (IStringSource)new LazyStringSource(errorMessage);
}));
}
Then i changed my ServiceRequestValidator like that:
public class ServicerRequestViewModelValidator : AbstractValidator<ServiceRequestViewModel>
{
public ServiceRequestViewModelValidator(ILocalizationService localizationService)
{
RuleFor(x => x.IdStato).NotNull().WithMessage(()=>string.Format(localizationService.GetMessage("Validation.General.MandataryField"), localizationService.GetMessage("ServiceRequestDetail.State")));
}
}
Doing like that in the constructor is set the delegate that will be call during the validation process to resolve the localized error message based on the user language and not directly the localized error message string.