Can I conditionally execute rulesets?

Topics: Validation Application Block
Oct 12, 2010 at 11:45 PM

I am using VAB to validate credit card data prior to passing it to a credit card authorization house.

 

My CreditCard type has the following properties:

  1. CardNumber is a string
  2. CreditCardType is an enum with values {Mastercard, Visa, Amex}

 

In the <validation> tag in the web.config, using the VAB Gui editor, I have create a configuration with 4 rulesets defined for the CreditCard type:

 

  1. VisaRuleSet
    1. Property: CardNumber

                                          i.    Validator:RegexValidator to validate a Visa formatted credit card number

  1. I have verified this ruleset properly validates if assigned as the Default Ruleset for the CreditCardType
  2. MasterCardRuleSet
    1. Property: CardNumber

                                          i.    Validator:RegexValidator to validate a MasterCard formatted credit card number

  1. I have verified this ruleset properly validates if assigned as the Default Ruleset for the CreditCardType
  2. AmexRuleSet
    1. Property: CardNumber

                                          i.    Validator:RegexValidator to validate a Amex formatted credit card number

  1. I have verified this ruleset properly validates if assigned as the Default Ruleset for the CreditCardType

 

So, as you can see, I have verified that all 3 rulesets work properly. Now the problem I have is how to setup the configuration so that they are ONLY invoked when the CreditCardType enumvalue on the CreditCard is equal to a specific Card type.

 

I know that I can use a Domain Validator to validate the CreditCardType against the {Mastercard, Visa, Amex}, however, that will only validate that the CardType is an appropriate value. So, how can I conditionally invoke the ruleset for the proper card. I tried the following nesting using a root Composite Or Validator and a set of 3 CompositeAndValidators nested underneath, thinking that if none of them evaluated to false, then the validation would fail.

 

  1. DefaultCreditCardRuleset
    1. Property CardType:

                                          i.    OrCompositeValidator – Check that at least one of the AndCompositeValidators below evaluates to true

  1. AndCompositeValidator – Ensure that both the card type matches and the invoked ruleset evaluates to true
    1. DomainValidator to check CreditCardType = MasterCard
    2. ObjectValidator to invoke MasterCardRuleSet
    3. AndCompositeValidator– Ensure that both the card type matches and the invoked ruleset evaluates to true
      1. DomainValidator to check CreditCardType = Visa
      2. ObjectValidator to invoke VisaRuleSet
      3. AndCompositeValidator– Ensure that both the card type matches and the invoked ruleset evaluates to true
        1. DomainValidator to check CreditCardType = Amex
        2. ObjectValidator to invoke AmexRuleSet

 

This does not work, even though I send an invalid Visa CreditCard number through it with the CreditCardType set to Visa, somehow, it’s evaluating to true…..thoughts?

 

When I look deeper into my code where _instanceToValidate is a valid T of type CrediCard, with an invalid visa cardnum and CreditCardType.Visa:

 Validator<T> validator;
validator = EnterpriseLibraryContainer.Current.GetInstance<ValidatorFactory>().CreateValidator<T>();
ValidationResults validationResults = new ValidationResults();
validationResults = validator.Validate(_instanceToValidate);

validationResults has a count of zero….what am I missing? Am I wrong about the expected behavior of the validators I have used?

And please do not provide an answer stating that I can accomplish this from code, I am aware that I could easily do this in C# code with a simple switch statement. I do not want ANY business logic in my code, I want to leave ALL validation in the VAB config if possible.

 

   <type name="Services.PaymentService.DataContracts.CreditCardDetail"
      defaultRuleset="DefaultCreditCardDetailValidationRuleSet" assemblyName="Services.PaymentService.DataContracts, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
      <ruleset name="DefaultCreditCardDetailValidationRuleSet">
        <properties>
          <property name="ExpirationMonth">
            <validator type="Microsoft.Practices.EnterpriseLibrary.Validation.Validators.NotNullValidator, Microsoft.Practices.EnterpriseLibrary.Validation, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
              messageTemplateResourceName="PS002055" messageTemplateResourceType="Services.PaymentService.ServiceImplementation.PaymentServiceErrorMessages, Services.PaymentService.ServiceImplementation, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
              tag="PS002055" name="CreditCardExpirationMonthNotNullValidator" />
          </property>
          <property name="ExpirationYear">
            <validator type="Microsoft.Practices.EnterpriseLibrary.Validation.Validators.NotNullValidator, Microsoft.Practices.EnterpriseLibrary.Validation, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
              messageTemplateResourceName="PS002054" messageTemplateResourceType="Services.PaymentService.ServiceImplementation.PaymentServiceErrorMessages, Services.PaymentService.ServiceImplementation, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
              tag="PS002054" name="CreditCardExpirationYearNotNullValidator" />
            <validator type="Microsoft.Practices.EnterpriseLibrary.Validation.Validators.RangeValidator, Microsoft.Practices.EnterpriseLibrary.Validation, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
              culture="en-US" lowerBound="0" upperBound="0" negated="true"
              messageTemplate="" messageTemplateResourceName="PS002054" messageTemplateResourceType="Services.PaymentService.ServiceImplementation.PaymentServiceErrorMessages, Services.PaymentService.ServiceImplementation, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
              tag="PS002054" name="CreditCardExpirationYearIsZeroRangeValidator" />
          </property>
          <property name="CreditCardType">
            <validator type="Microsoft.Practices.EnterpriseLibrary.Validation.Validators.OrCompositeValidator, Microsoft.Practices.EnterpriseLibrary.Validation, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
              messageTemplate="test 1" tag="tag" name="Or Composite Validator">
              <validator type="Microsoft.Practices.EnterpriseLibrary.Validation.Validators.AndCompositeValidator, Microsoft.Practices.EnterpriseLibrary.Validation, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
                name="MasterCard And Composite Validator">
                <validator type="Microsoft.Practices.EnterpriseLibrary.Validation.Validators.DomainValidator`1[[System.Object, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], Microsoft.Practices.EnterpriseLibrary.Validation, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
                  messageTemplate="test 2" tag="tag" name="MasterCardDomainValidator">
                  <domain>
                    <add name="MasterCard" />
                  </domain>
                </validator>
                <validator type="Microsoft.Practices.EnterpriseLibrary.Validation.Validators.ObjectValidator, Microsoft.Practices.EnterpriseLibrary.Validation, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
                  targetRuleset="MasterCardValidationRuleSet" messageTemplate="test 3"
                  tag="tag" name="MasterCardObjectValidator" />
              </validator>
              <validator type="Microsoft.Practices.EnterpriseLibrary.Validation.Validators.AndCompositeValidator, Microsoft.Practices.EnterpriseLibrary.Validation, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
                name="Visa And Composite Validator">
                <validator type="Microsoft.Practices.EnterpriseLibrary.Validation.Validators.DomainValidator`1[[System.Object, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], Microsoft.Practices.EnterpriseLibrary.Validation, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
                  messageTemplate="test 4" tag="tag" name="VisaDomainValidator">
                  <domain>
                    <add name="Visa" />
                  </domain>
                </validator>
                <validator type="Microsoft.Practices.EnterpriseLibrary.Validation.Validators.ObjectValidator, Microsoft.Practices.EnterpriseLibrary.Validation, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
                  targetRuleset="VisaValidationRuleSet" validateActualType="true"
                  messageTemplate="test 5" tag="tag" name="VisaObjectValidator" />
              </validator>
              <validator type="Microsoft.Practices.EnterpriseLibrary.Validation.Validators.AndCompositeValidator, Microsoft.Practices.EnterpriseLibrary.Validation, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
                name="AmericanExpress And Composite Validator">
                <validator type="Microsoft.Practices.EnterpriseLibrary.Validation.Validators.ObjectValidator, Microsoft.Practices.EnterpriseLibrary.Validation, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
                  targetRuleset="AmericanExpressValidationRuleSet" validateActualType="false"
                  messageTemplate="test 6" tag="tag" name="AmericanExpressObjectValidator" />
                <validator type="Microsoft.Practices.EnterpriseLibrary.Validation.Validators.DomainValidator`1[[System.Object, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], Microsoft.Practices.EnterpriseLibrary.Validation, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
                  messageTemplate="test 7" tag="tag" name="AmericanExpressDomainValidator">
                  <domain>
                    <add name="AmericanExpress" />
                  </domain>
                </validator>
              </validator>
              <validator type="Microsoft.Practices.EnterpriseLibrary.Validation.Validators.AndCompositeValidator, Microsoft.Practices.EnterpriseLibrary.Validation, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
                name="Discover And Composite Validator ">
                <validator type="Microsoft.Practices.EnterpriseLibrary.Validation.Validators.DomainValidator`1[[System.Object, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], Microsoft.Practices.EnterpriseLibrary.Validation, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
                  messageTemplate="test 8" tag="tag" name="DiscoverDomainValidator">
                  <domain>
                    <add name="Discover" />
                  </domain>
                </validator>
                <validator type="Microsoft.Practices.EnterpriseLibrary.Validation.Validators.ObjectValidator, Microsoft.Practices.EnterpriseLibrary.Validation, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
                  targetRuleset="DiscoverValidationRuleSet" validateActualType="false"
                  messageTemplate="test 9" tag="tag" name="DiscoverObjectValidator" />
              </validator>
            </validator>
          </property>
        </properties>
      </ruleset>
      <ruleset name="AmericanExpressValidationRuleSet">
        <properties>
          <property name="CardNumber">
            <validator type="Microsoft.Practices.EnterpriseLibrary.Validation.Validators.RegexValidator, Microsoft.Practices.EnterpriseLibrary.Validation, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
              pattern="^3[47][0-9]{13}$" messageTemplateResourceName="PS002050"
              messageTemplateResourceType="Services.PaymentService.ServiceImplementation.PaymentServiceErrorMessages, Services.PaymentService.ServiceImplementation, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
              tag="PS002050" name="AmericanExpressRegexValidator" />
          </property>
        </properties>
      </ruleset>
      <ruleset name="VisaValidationRuleSet">
        <properties>
          <property name="CardNumber">
            <validator type="Microsoft.Practices.EnterpriseLibrary.Validation.Validators.RegexValidator, Microsoft.Practices.EnterpriseLibrary.Validation, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
              pattern="^4[0-9]{12}(?:[0-9]{3})?$" patternResourceName="" patternResourceType=""
              messageTemplateResourceName="PS002049" messageTemplateResourceType="Services.PaymentService.ServiceImplementation.PaymentServiceErrorMessages, Services.PaymentService.ServiceImplementation, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
              tag="PS002049" name="VisaRegexValidator" />
          </property>
        </properties>
      </ruleset>
      <ruleset name="MasterCardValidationRuleSet">
        <properties>
          <property name="CardNumber">
            <validator type="Microsoft.Practices.EnterpriseLibrary.Validation.Validators.RegexValidator, Microsoft.Practices.EnterpriseLibrary.Validation, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
              pattern="^5[1-5][0-9]{14}$" messageTemplateResourceName="PS002047"
              messageTemplateResourceType="Services.PaymentService.ServiceImplementation.PaymentServiceErrorMessages, Services.PaymentService.ServiceImplementation, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
              tag="PS002047" name="MasterCardRegexValidator" />
          </property>
        </properties>
      </ruleset>
      <ruleset name="DiscoverValidationRuleSet">
        <properties>
          <property name="CardNumber">
            <validator type="Microsoft.Practices.EnterpriseLibrary.Validation.Validators.RegexValidator, Microsoft.Practices.EnterpriseLibrary.Validation, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
              pattern="^6(?:011|5[0-9]{2})[0-9]{12}$" messageTemplateResourceName="PS002048"
              messageTemplateResourceType="Services.PaymentService.ServiceImplementation.PaymentServiceErrorMessages, Services.PaymentService.ServiceImplementation, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
              tag="PS002048" name="DiscoverRegexValidator" />
          </property>
        </properties>
      </ruleset>
    </type>
 
Oct 13, 2010 at 12:31 AM

What happens is that the rulesets you are associating to the ObjectValidators doesn't really get triggered.  You have associated the ObjectValidators to the CreditCardType property so it's actually trying to look for rule sets configured for that property's type but the rulesets you used are for the CreditCardDetail type.

I don't see how you can accomplish this kind of validation in config without changing the structure of your class in such a way that the CreditCardType and the CardNumber is on a separate class.

 

Sarah Urmeneta
Global Technology and Solutions
Avanade, Inc.
entlib.support@avanade.com

Oct 13, 2010 at 1:53 AM

So the code assumes that an object validator is always attempting to use a ruleset defined for the parent object? Why? Shouldn't you be able to trigger the validation logic in a ruleset from anywhere within a validation hierarchy? This would seem to remove the need to write C# code and recompile assemblies to change validation logic. I think this would be a great feature to add.

In fact, I've been heavily leveraging the VAB in our current project and for the most part I love it. Some recommendations are:

1) Domain Validator: Change the domain value text entry to use a lookup field that parses enum values and let's you pick from them instead of having you type in the enum values as there is room for human error here

2) Resource File and Message Templates: Instead of having to type in the message template path in EACH validator in the config GUI, it would be nice if the entire validatior set could inherit from a master one and only override if need be

3) Having a preview functionlaity so that the user can see what exactly the value of the error message of the resource file is without having to look in the resx would be a nice touch

4) Introduce functoids (like biztalk), being able to have certain dynamic data comparisons would be nice. One great addition would be the ability to compare dates to DateTime.Now(), this would provide an enhanced level of validation capability and anytime I can avoid coding my basic data validation logic, the better.

5) Provide the ability to do conditional branching where you can have better control over the program execution of validator. The and and or is good for validation itself, but it would be nice if you had granular control over validator invocation. This would solve the problem I am currently having. Imagine if you could branch the execution of rulesets based on enumerated types, this could allow for some really awesome scenarios

Just some thoughts....

Oct 13, 2010 at 3:09 AM

The ObjectValidator validates based on the type of the property on which it is assigned to.  You can log those recommendations in the Issue Tracker.

 

Sarah Urmeneta
Global Technology and Solutions
Avanade, Inc.
entlib.support@avanade.com