Search code examples
c#asp.netasp.net-mvcvalidationfluentvalidation

Fluent Validator WithMessage and singleton instance


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.


Solution

  • 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.