Monday, April 13, 2009

Spring.NET Short Circuiting Validators - Part 1

One of the features I've come to like in Spring.NET is the validation library, but it does come up short on at least one basic need. That is a straight forward way to configure short circuiting for <v:group>'s. Meaning a validator group that quits validating and returns false on the first failed validator and returns true otherwise. Out of the box if you wanted to setup a group to, let's say, validate the User.UserName property you might write something like,

    1 <objects xmlns="http://www.springframework.net"

    2         xmlns:v="http://www.springframework.net/validation">

    3   <v:group id="UserValidator">

    4     <v:group id="UserNameValidator">

    5       <v:required id="UserNameRequired" test="UserName">

    6         <v:message id="Errors.UserName.Required"  providers="UserNameError"/>

    7       </v:required>

    8 

    9       <v:regex id="UserNameMinmumComplexity" test="UserName">

   10         <v:property name="Expression" value="^.{5,20}$"/>

   11         <v:message id="Errors.UserName.MinimumComplexity" providers="UserNameError"/>

   12       </v:regex>

   13 

   14       <v:validator id="UserNameIsUnique" test="UserName" type="XXX.Infrastructure.Validation.IsUniqueUserNameValidator, XXX.Infrastructure">

   15         <v:property name="UserRepository" ref="UserRepository"/>

   16         <v:message id="Errors.UserName.IsUnique" providers="UserNameError"/>

   17       </v:validator>

   18     </v:group>

   19 

   20   ....

   21   </v:group

   22 

   23  ....

   24 </objects>



However when User.UserName is validated every validator will fire. There are many reasons why it makes sense to abandon validation on the first failed validator: some rules maybe more expensive than others to evaluate, maybe executing every action due to failed rules produces less than desirable results.

The approach I took was to create a custom validator that inherits from ValidatorGroup.

    1     public class ShortCircuitingValidatorGroup

    2         : ValidatorGroup

    3     {

    4         public ShortCircuitingValidatorGroup()

    5         {}

    6 

    7         public ShortCircuitingValidatorGroup(string when)

    8             : base(when)

    9         {}

   10 

   11         public ShortCircuitingValidatorGroup(IExpression when)

   12             : base(when)

   13         {}

   14 

   15         /// <summary>

   16         /// The non-shortcircuiting validator group

   17         /// </summary>

   18         public ValidatorGroup GroupToShortCircuit { get; set; }

   19 

   20 

   21         public override bool Validate(object validationContext,

   22                                       System.Collections.IDictionary contextParams,

   23                                       IValidationErrors errors)

   24         {

   25             GroupToShortCircuit.FastValidate = true;

   26             return GroupToShortCircuit.Validate(validationContext, contextParams, errors);

   27         }

   28 

   29     }



As you can see the class holds a reference to the group we want to short circuit and sets the FastValidate property, invoking the behavior I want, to true. The custom validator is setup in xml for the User.UserName property like this

    1 <objects xmlns="http://www.springframework.net"

    2         xmlns:v="http://www.springframework.net/validation">

    3   <v:group id="UserValidator">

    4     <v:validator id="UserNameValidator"

    5                 type="XXX.Infrastructure.Validation.ShortCircuitingValidatorGroup, XXX.Infrastructure">

    6       <v:property name="ValidatorGroup" ref="UserNameValidatorGroup"/>

    7     </v:validator>

    8     ...

    9   </v:group>

   10 

   11   <v:group id="UserNameValidatorGroup">

   12     <v:required id="UserNameRequired" test="UserName">

   13       <v:message id="Errors.UserName.Required"  providers="UserNameError"/>

   14     </v:required>

   15 

   16     <v:regex id="UserNameMinmumComplexity" test="UserName">

   17       <v:property name="Expression" value="^.{5,20}$"/>

   18       <v:message id="Errors.UserName.MinimumComplexity" providers="UserNameError"/>

   19     </v:regex>

   20 

   21     <v:validator id="UserNameIsUnique" test="UserName" type="XXX.Infrastructure.Validation.IsUniqueUserNameValidator, XXX.Infrastructure">

   22       <v:property name="UserRepository" ref="UserRepository"/>

   23       <v:message id="Errors.UserName.IsUnique" providers="UserNameError"/>

   24     </v:validator>

   25   </v:group>

   26   ...

   27 </objects>

1 comment:

MarkPollack said...

Hi,
I've suggested another workaround that would seem to be a bit less invasive, see http://forum.springframework.net/showthread.php?p=15362 for info. Thanks for taking the time to blog about it!!! Much appreciated.