NestedValidationResults never get populated

Topics: Validation Application Block
May 3, 2010 at 4:09 PM
I want to use the Enterprise Library Validation library and have the validation results tree stay in the same shape as the configured validation rules. This doesn't seem possible, as all validation results get flattened out into the enumerated ValidationResult in ValidationResults returned by the Validate method. NestedValidationResults never get populated, even if I use an OrCompositeValidator or AndCompositeValidator. How can I get the validators to put their child validation results in NestedValdiationResults?
May 4, 2010 at 3:43 AM

Hi,

NestedValidationResults would work with OrCompositeValidator but not with AndCompositeValidator (see thread http://entlib.codeplex.com/Thread/View.aspx?ThreadId=73721). I've also tried this using Entlib 5 and it seems to still have the same behaviour, so I assume this hasn't been addressed yet.

Now, I'm just curious why the OrCompositeValidator doesn't work on you? Could you give more details how you implement this?  NestedValdiationResults property is read-only, so there is no explicit way to put validation results in that property. I don't know any ways how to utilize this property in the way you want it, but I'll try to look for something that would help on this, though what I also would like to understand is what’s the reason/need behind this? :-).  

Gino Terrado
Global Technology and Solutions
Avanade, Inc.
 entlib.support@avanade.com

 

May 4, 2010 at 3:37 PM
Edited May 4, 2010 at 3:49 PM

I needed these to appear nested and maintain the validation tree structure so that I could differentiate between different validations of the same class under another class. I.E., let's say I have a UserInfo class, with properties PrimaryPhone and SecondaryPhone, both of types PhoneInfo, and I wanted to use the same validation rules set for both of those properties, there would be no way to differentiate between the validation results for each of those.

I succesffully created a NestedObjectValidator that sets the validation results of its object to validate in the NestedValidationResults:

[ConfigurationElementType(typeof(NestedObjectValidatorData))]
    public class NestedObjectValidator : ObjectValidator
    {
        private readonly Type targetType;
        private readonly string targetRuleset;
        private readonly Validator targetTypeValidator;
        private readonly ValidatorFactory validatorFactory;

        public NestedObjectValidator()
            : base()
        {
        }

        public NestedObjectValidator(ValidatorFactory validatorFactory)
            : base(validatorFactory)
        {
        }

        public NestedObjectValidator(ValidatorFactory validatorFactory, string targetRuleset)
            : base(validatorFactory, targetRuleset)
        {
            if (validatorFactory == null)
            {
                throw new ArgumentNullException("validatorFactory");
            }
            if (targetRuleset == null)
            {
                throw new ArgumentNullException("targetRuleset");
            }

            this.targetType = null;
            this.targetTypeValidator = null;
            this.targetRuleset = targetRuleset;
            this.validatorFactory = validatorFactory;
        }

        public NestedObjectValidator(Type targetType)
            : base(targetType)
        { }

        /// <summary>
        /// <para>Initializes a new instance of the <see cref="ObjectValidator"/> for a target type
        /// using the supplied ruleset.</para>
        /// </summary>
        /// <param name="targetType">The target type</param>
        /// <param name="targetRuleset">The ruleset to use.</param>
        /// <exception cref="ArgumentNullException">when <paramref name="targetType"/> is <see langword="null"/>.</exception>
        /// <exception cref="ArgumentNullException">when <paramref name="targetRuleset"/> is <see langword="null"/>.</exception>
        public NestedObjectValidator(Type targetType, string targetRuleset)
            : base(targetType, targetRuleset)
        { }

        /// <summary>
        /// <para>Initializes a new instance of the <see cref="ObjectValidator"/> for a target type
        /// using the supplied ruleset.</para>
        /// </summary>
        /// <param name="targetType">The target type</param>
        /// <param name="targetRuleset">The ruleset to use.</param>
        /// <param name="validatorFactory">Factory to use when building nested validators.</param>
        /// <exception cref="ArgumentNullException">when <paramref name="targetType"/> is <see langword="null"/>.</exception>
        /// <exception cref="ArgumentNullException">when <paramref name="targetRuleset"/> is <see langword="null"/>.</exception>
        /// <exception cref="ArgumentNullException">when <paramref name="validatorFactory"/> is <see langword="null"/>.</exception>
        public NestedObjectValidator(Type targetType, ValidatorFactory validatorFactory, string targetRuleset)
            : base(targetType, validatorFactory, targetRuleset)
        {
            if (targetType == null)
            {
                throw new ArgumentNullException("targetType");
            }
            if (validatorFactory == null)
            {
                throw new ArgumentNullException("validatorFactory");
            }
            if (targetRuleset == null)
            {
                throw new ArgumentNullException("targetRuleset");
            }

            this.targetType = targetType;
            this.targetTypeValidator = validatorFactory.CreateValidator(targetType, targetRuleset);
            this.targetRuleset = targetRuleset;
            this.validatorFactory = null;
        }

        /// <summary>
        /// Validates by applying the validation rules for the target type specified for the receiver.
        /// </summary>
        /// <param name="objectToValidate">The object to validate.</param>
        /// <param name="currentTarget">The object on the behalf of which the validation is performed.</param>
        /// <param name="key">The key that identifies the source of <paramref name="objectToValidate"/>.</param>
        /// <param name="validationResults">The validation results to which the outcome of the validation should be stored.</param>
        /// <remarks>
        /// If <paramref name="objectToValidate"/> is <see langword="null"/> validation is ignored.
        /// <para/>
        /// A referece to an instance of a type not compatible with the configured target type
        /// causes a validation failure.
        /// </remarks>
        public override void DoValidate(object objectToValidate,
            object currentTarget,
            string key,
            ValidationResults validationResults)
        {
            ValidationResults childValidationResults;
            ValidationResult thisValidationResult;
            if (objectToValidate != null)
            {
                Type objectToValidateType = objectToValidate.GetType();
                if (base.TargetType == null || base.TargetType.IsAssignableFrom(objectToValidateType))
                {
                    // build new validation results and pass to subclass.
                    childValidationResults = new ValidationResults();
                    // reset the current target and the key
                    this.GetValidator(objectToValidateType).DoValidate(objectToValidate, objectToValidate, null, childValidationResults);

                    // append those validation results as child validation results, if any of them failed
                    if (childValidationResults.Count > 0)
                    {
                        thisValidationResult = new ValidationResult(null, currentTarget, key, null, this, childValidationResults);
                        validationResults.AddResult(thisValidationResult);
                    }
                }
                else
                {
                    // unlikely
                    this.LogValidationResult(validationResults, Resources.ObjectValidatorInvalidTargetType, currentTarget, key);
                }
            }
        }

        private Validator GetValidator(Type objectToValidateType)
        {
            if (this.targetTypeValidator != null)
            {
                return this.targetTypeValidator;
            }
            else
            {
                return this.validatorFactory.CreateValidator(objectToValidateType, this.targetRuleset);
            }
        }

    }
May 4, 2010 at 3:40 PM
Edited May 4, 2010 at 3:50 PM

Ok, how do I use the "use the "Insert Code Snippet (</>)" button here when pasting a code."? Where's the button? 

Update: Figured this out.  This site doesn't work well in Chrome!

May 4, 2010 at 3:54 PM
Edited May 4, 2010 at 3:54 PM

I've created corresponding extension methods that will add these errors to the MVC ModelState, so that it lines up nicely with the properties of the class, thus correctly populating ValidationMessages:

public static class EntLibValidationExtensions
    {

        /// <summary>
        /// Validates an object and copies the validation results to the model state.
        /// </summary>
        /// <typeparam name="T">Type of object to validate</typeparam>
        /// <param name="validatorFactory">Validator factory to generate validator and run validation</param>
        /// <param name="objectToValidate">Object to validate</param>
        /// <param name="ruleSet">Name of ruleset to use.  If null, uses default rule set</param>
        /// <param name="modelState">Model state dictionary that is updated with errors</param>
        /// <returns>True if validation passed and visa-versa</returns>
        public static bool ValidateAndCopyToModelState<T>(this ValidatorFactory validatorFactory, T objectToValidate, string ruleSet, ModelStateDictionary modelState)
        {
            bool validationPassed;

            ValidationResults validationResults;

            validationResults = validatorFactory.Validate<T>(objectToValidate, ruleSet);

            // set if validation passed
            validationPassed = validationResults.IsValid;

            // if validation failed, copy errors to model state
            if(!validationPassed)
                CopyValidationResultsToModelState(validationResults, modelState, null);            

            return validationPassed;
        }

        /// <summary>
        /// Validates an object
        /// </summary>
        /// <typeparam name="T">Type of object to validate</typeparam>
        /// <param name="validatorFactory"></param>
        /// <param name="objectToValidate">Object to validate</param>
        /// <param name="ruleSet">Name of ruleset to use.  If null, uses default rule set</param>
        /// <returns></returns>
        public static ValidationResults Validate<T>(this ValidatorFactory validatorFactory, T objectToValidate, string ruleSet)
        {
            ValidationResults validationResults;
            Validator validator;

            validator = validatorFactory.CreateValidator<T>(ruleSet);

            validationResults = validator.Validate(objectToValidate);

            return validationResults;
        }

        /// <summary>
        /// Copies all validation results to the model state
        /// </summary>
        /// <param name="validationResults"></param>
        /// <param name="modelState">Model state dictionary that is updated with errors</param>
        /// <param name="keyPrefix">The prefix that should be appended to validation errors</param>
        public static void CopyValidationResultsToModelState(IEnumerable<ValidationResult> validationResults, ModelStateDictionary modelState, string keyPrefix)
        {
            string actualKeyPrefix;
            string childResultsPrefix;

            if (!String.IsNullOrEmpty(keyPrefix))
                actualKeyPrefix = keyPrefix + '.';
            else
                actualKeyPrefix = "";

            foreach (var validationResult in validationResults)
            {
                modelState.AddModelError(actualKeyPrefix + validationResult.Key, validationResult.Message);

                // if child validation results have a count
                if (validationResult.NestedValidationResults.Count() > 0)
                {
                    childResultsPrefix = actualKeyPrefix + validationResult.Key;
                    // recursive call: copy child validation results to model state
                    CopyValidationResultsToModelState(validationResult.NestedValidationResults, modelState, childResultsPrefix);
                }
            }
        }


    }