OrComposite to act as IgnoreNull

Topics: Validation Application Block
Jan 18, 2012 at 2:49 PM

I'm trying to configure my validation in the config, which is working fine for example;

<property name="BankAccountNumber">
  <validator type="Microsoft.Practices.EnterpriseLibrary.Validation.Validators.OrCompositeValidator, Microsoft.Practices.EnterpriseLibrary.Validation, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
              name="Or Composite Validator">
  <validator type="Microsoft.Practices.EnterpriseLibrary.Validation.Validators.NotNullValidator, Microsoft.Practices.EnterpriseLibrary.Validation, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
                negated="true" name="Not Null Validator" />
  <validator type="Microsoft.Practices.EnterpriseLibrary.Validation.Validators.StringLengthValidator, Microsoft.Practices.EnterpriseLibrary.Validation, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
                upperBound="25" lowerBound="6" messageTemplateResourceName="StringLength"
                messageTemplateResourceType="Common.MEL.Validation.MessageTemplates, Common.MEL.Validation, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
                name="String Length Validator" />
  </validator>
</property>

Now, the problem i'm having is that the client recieves the following message: "All validators failed for key "BackAccountNumber"."

This message is correct, how ever doesn't tell the user very much, i'd like to somehow forward the message from the StringLengthValidator, or both validators for all i care. Do i need to implement a custom validator for this? I know [IgnoreNulls] does this as an attribute but i don't want to configure our validation through attributes.

Jan 19, 2012 at 7:31 AM
Edited Jan 19, 2012 at 7:32 AM

If you specify the message properties (e.g. MessageTemplate) on the OrCompositeValidator itself you can customize the top level message that is displayed for the OrCompositeValidator.

<property name="BankAccountNumber">
    <validator type="Microsoft.Practices.EnterpriseLibrary.Validation.Validators.OrCompositeValidator, Microsoft.Practices.EnterpriseLibrary.Validation, Version=5.0.505.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
        name="Or Composite Validator" messageTemplate="OrComposite Bank Account Number must be 5 characters or less">
        <validator type="Microsoft.Practices.EnterpriseLibrary.Validation.Validators.NotNullValidator, Microsoft.Practices.EnterpriseLibrary.Validation, Version=5.0.505.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
            negated="true" 
            messageTemplateResourceName="" name="Not Null Validator"  />
        <validator type="Microsoft.Practices.EnterpriseLibrary.Validation.Validators.StringLengthValidator, Microsoft.Practices.EnterpriseLibrary.Validation, Version=5.0.505.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
            upperBound="5" messageTemplate="{1} must be between {3} and {5} characters."
            messageTemplateResourceName="" name="String Length Validator" />
    </validator>
</property>

This will output the following:

OrComposite Bank Account Number must be less than 5 characters.

However, you will lose the ability to use message template tokens and you may not want to duplicate the message template information.  If that is the case you could always use the NestedValidationResults property to get the individual ValidationResults.  If you only output the message from the NestedValidationResults it will output the following:

The value must be null.
BankAccountNumber must be between 0 and 5 characters.

Now the issue is that showing the message "The value must be null" would probably be confusing to the user.  If you want to not output the NotNullValidator message you could use the tag property (since it looks like you aren't using it) and exclude some messages.  So we could assign a value to the tag property indicating to suppress that message.  So you could steal the CSS display property and say if the tag is equal to "display:none" then to not display the message.  If we do that then we will only get the one message:

BankAccountNumber must be between 0 and 5 characters. 

The config:

<properties>
    <property name="BankAccountNumber">
        <validator type="Microsoft.Practices.EnterpriseLibrary.Validation.Validators.OrCompositeValidator, Microsoft.Practices.EnterpriseLibrary.Validation, Version=5.0.505.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
            name="Or Composite Validator" messageTemplate="OrComposite Bank Account Number must be less than 5 characters.">
            <validator type="Microsoft.Practices.EnterpriseLibrary.Validation.Validators.NotNullValidator, Microsoft.Practices.EnterpriseLibrary.Validation, Version=5.0.505.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
                negated="true" tag="display:none"
                messageTemplateResourceName="" name="Not Null Validator" />
            <validator type="Microsoft.Practices.EnterpriseLibrary.Validation.Validators.StringLengthValidator, Microsoft.Practices.EnterpriseLibrary.Validation, Version=5.0.505.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
                upperBound="5" messageTemplate="{1} must be between {3} and {5} characters."
                messageTemplateResourceName="" name="String Length Validator" />
        </validator>
    </property>
</properties>

And the code:

static void Main(string[] args)
{
    BankAccount bankAccount = new BankAccount() { BankAccountNumber = "1234343" };
           
    var validator = ValidationFactory.CreateValidator<BankAccount>();
    var validationResults = validator.Validate(bankAccount);

    PrintValidationResults(validationResults);
}

static void PrintValidationResults(IEnumerable<ValidationResult> validationResults)
{
    foreach (var validationResult in validationResults)
    {
        if (validationResult.NestedValidationResults.Count() == 0)
        {
            if (string.Compare(validationResult.Tag, "display:none", StringComparison.OrdinalIgnoreCase) != 0)
            {
                Console.WriteLine(validationResult.Message);
            }
        }
        else
        {
            PrintValidationResults(validationResult.NestedValidationResults);
        }
    }            
}

Perhaps this approach wouldn't work if you are using databinding.  If so, then a custom validator may let you do what you want.

--
Randy Levy
Enterprise Library support Engineer
entlib.support@live.com

Jan 19, 2012 at 8:28 AM

Well the biggest issue i'm having is that we use this to validate WCF requests to a webservice. I seem to not at all be getting the NestedValidationResults on the client side of things. Which is the main issue, i'll see if i can somehow find the NestedValidationResults and simulate my own ValidationFaults (which is what WCF returns).

I appoligise for not stating this is a situation involving WCF before.

Jan 19, 2012 at 8:42 AM
Edited Jan 19, 2012 at 9:37 AM

Seems i'm not the only one who has/had this issue;

http://entlib.codeplex.com/workitem/13397

At the moment i'm trying to alter the Validation.Intefration.WCF.2010 project specifically the ValidationParameterInspector.cs file;

I'm changing:

 

        private void AddFaultDetails(ValidationFault fault, string parameterName, ValidationResults results)
        {
            if (!results.IsValid)
            {
                foreach (ValidationResult result in results)
                {
                    fault.Add(CreateValidationDetail(result, parameterName));
                }
            }
        }

 

To:

 

        private void AddFaultDetails(ValidationFault fault, string parameterName, ValidationResults results)
        {
            if (!results.IsValid)
            {
                foreach (ValidationResult result in results)
                {
                    if(result.NestedValidationResults.Count() > 0)
                    {
                        foreach(ValidationResult nestedValidationResult in result.NestedValidationResults)
                        {
                            if (string.Compare(nestedValidationResult.Tag, "display:none", StringComparison.OrdinalIgnoreCase) != 0)
                            {
                                fault.Add(CreateValidationDetail(nestedValidationResult, parameterName));
                            }
                        }
                    }
                    if (string.Compare(result.Tag, "display:none", StringComparison.OrdinalIgnoreCase) != 0)
                    {
                        fault.Add(CreateValidationDetail(result, parameterName));
                    }
                }
            }
        }

 

Hopefully this will give the desired result. (i liked your Tag idea so i put that in there). I'll post here if it did.

Jan 19, 2012 at 9:15 AM
Edited Jan 19, 2012 at 9:44 AM

It does have the desired result, sadly the assemblies arent strong signed afterwards i'm hoping this won't cause any issues for now i'll leave this in place.

If anyone has a better idea how to do this without changing the VAB code itself that would be much appreciated.

Edit: I did get some issues while publishing to the test environment, i solved these by referencing the Validation.WCF.2010 project to the strongly signed assemblies.

Oct 2, 2012 at 1:45 PM

Hi Odeanathus,

Thanks for making this issue known and for replying to your thread so others have a solution to the issue.

My team is also experiencing the same issue with no out of the box solution in sight, 10 months later, do you have a better solution to the problem.

Thanks in advance

Oct 2, 2012 at 3:08 PM

Nope, afraid not. We still use the solution posted above.

If you find something better please let me know.

Oct 2, 2012 at 5:13 PM

I know there is a work item for this but Enterprise Library vNext is in the planning stages so please post a suggestion (for this or any other ideas!) at http://entlib.uservoice.com/forums/89245-enterprise-library-6-0-unity-3-0.

--
Randy Levy
Enterprise Library support Engineer
entlib.support@live.com