Search code examples
c#castlecastle-validators

How to build a custom Validator for Castle Validation on client-side?


I'm using castle validation and I'd like to know why my validator is not working :

[Serializable]
    public class PositiveIntegerValidator : AbstractValidator
    {


    public override bool IsValid(object instance, object fieldValue)
    {
        if (fieldValue == null || !(fieldValue is int))
            return false;
        return ((int)fieldValue) > 0;
    }

    public override bool SupportsBrowserValidation
    {
        get { return true; }
    }

    public override void ApplyBrowserValidation(BrowserValidationConfiguration config, InputElementType inputType, IBrowserValidationGenerator generator, System.Collections.IDictionary attributes, string target)
    {
        base.ApplyBrowserValidation(config, inputType, generator, attributes, target);


        generator.SetValueRange(target, 0,int.MaxValue, ErrorMessage);
    }

    protected override string BuildErrorMessage()
    {
        return ErrorMessage;
    }
}
public class ValidatePositiveIntegerAttribute : AbstractValidationAttribute
{
    public ValidatePositiveIntegerAttribute(string msg) :base(msg){}
    public override IValidator Build()
    {
        PositiveIntegerValidator positiveIntegerValidator = new PositiveIntegerValidator();
        ConfigureValidatorMessage(positiveIntegerValidator);
        return positiveIntegerValidator;
    }
}

And my field

  public class PackageViewModel
        {
//            ...
            [ValidateNonEmpty,ValidatePositiveInteger("The package count must be positive")]
            public int nbPackage { get; set; }
//...
}

And my view

 $FormHelper.TextField("package.nbPackage","%{size='3',value='1'}")

The ValidateNonEmpty validate on both client and server side, but the ValidatePositiveInteger is not.

I've seen this thread Min Length Custom AbstractValidationAttribute and Implementing Castle.Components.Validator.IValidator , but I can't see any difference between my code and his.


Solution

  • I finally found it after browsing Castle Validation source code :

    The default validation is PrototypeWebValidator, and this validator uses PrototypeValidationGenerator for client side validation, and this implementation doesn't do anything when you call "SetValueRange" :

      public void SetValueRange(string target, int minValue, int maxValue, string violationMessage)
          {
          }
    

    so that seems logical that my client side validation is not working (the PrototypeValidationGenerator didn't implement this functionnality mainly because the underlying validation API, https://github.com/atetlaw/Really-Easy-Field-Validation didn't impement it as well).

    So I decided to extend this because it's a framework and it's the purpose of a framework : provide a frame and let you work in it.

    So I created the generator

    public class PrototypeValidationGeneratorExtension : PrototypeWebValidator.PrototypeValidationGenerator
    {
        private PrototypeWebValidator.PrototypeValidationConfiguration _config;
        public PrototypeValidationGeneratorExtension(PrototypeWebValidator.PrototypeValidationConfiguration config, InputElementType inputType, IDictionary attributes)
            :base(config,inputType,attributes)
        {
            _config = config;
        }
        public new void SetValueRange(string target, int minValue, int maxValue, string violationMessage)
        {
                        this._config.AddCustomRule("validate-range",violationMessage,string.Format("min : {0}, max : {1}", minValue, maxValue));
        }
    }
    

    The validator provide some functionnality for adding custom validation The Validator that is needed by the formhelper :

     public class PrototypeWebValidatorExtension : PrototypeWebValidator
    {
        public new IBrowserValidationGenerator CreateGenerator(BrowserValidationConfiguration config, InputElementType inputType, IDictionary attributes)
        {
            return new PrototypeValidationGeneratorExtension((PrototypeWebValidator.PrototypeValidationConfiguration)config, inputType, attributes);
        }
    }
    

    And you wire castle to your validator on your base controller like this :

            protected override void Initialize()
            {
                ((FormHelper)this.Helpers["Form"]).UseWebValidatorProvider(new PrototypeWebValidatorExtension());
    //    ...
        }
    

    I have a BaseController but this solution is not the best, but I couldn't find out how to inject it in the castle monorail's configuration.

    I'll try to commit this to the source base of Castle Monorail ...