Share via


Prioritising Validation Results using VAB ASP.NET Integration

One of the many cool features of the Validation Application Block is the simple integration into ASP.NET web applications. By using the PropertyProxyValidator (PPV), you can associate input controls with specific properties of specific types, and the validation rules defined on those types (using either attributes or configuration) will automatically apply to the data in the controls.

While we've found this feature extremely useful on my current project, one issue we've faced is that the PPV will display messages for every single validator that fails. Of course from a technical perspective this behaviour is 100% correct, but in some situations this isn't very helpful. Consider what might happen if a user completing a registration form leaves the "Confirm Password" field blank. If the form was designed with a comprehensive set of validation rules, the following validation results may be displayed in the PPV control:

  • You must enter a value in the Confirm Password field
  • The Confirm Password field must contain a minimum of 7 characters
  • The Confirm Password field must contain at least one upper-case letter, one lower-case letter and one number
  • The Confirm Password field must match the Password field.

Every one of these rules is important and correct, but in this case we probably really just want to alert the user that they completely neglected to fill in the field. The other validation messages should only be displayed to users who at least went to the effort to enter something into the text box.

The bad news is that, out of the box, there is no way to do what I want. However the good news is that it's pretty simple to extend the block to do exactly this by leveraging the Tag property on every validator and building a derived version of the PPV.

Every validator has a Tag property, which is designed to let you attach arbitrary metadata that can be accessed from the ValidationResults if validation fails. We originally implemented this to allow people to specify the severity of validation rules. In this case we are doing something similar, although it's more about priority than severity. In my solution you need to specify a tag of "Primary" to every validator whose messages should be displayed first. If and only if all validators tagged with "Primary" are happy, the other validation results will be displayed. So for the example above, you might define the validators as follows:

  • [NotNullValidator(Tag="Primary", MessageTemplate="You must enter a value in the Confirm Password field")]
  • [StringLengthValidator(1, RangeBoundaryType.Inclusive, 9999, RangeBoundaryType.Ignore, Tag="Primary", MessageTemplate="You must enter a value in the Confirm Password field")]
  • [StringLengthValidator(7, RangeBoundaryType.Inclusive, 9999, RangeBoundaryType.Ignore, MessageTemplate="The Confirm Password field must contain a minimum of 7 characters")]
  • [RegExValidator(".....", MessageTemplate="The Confirm Password field must contain at least one upper-case letter, one lower-case letter and one number")]
  • [PropertyComparisonValidator("Password", ComparisonOperator.Equals, MessageTemplate="The Confirm Password field must match the Password field.")]

So far we are just using the Tag feature as it was designed as a place to store arbitrary metadata - but the PPV won't pay any attention to what you stick in here. The next step is to build a custom ASP.NET validator derived from the PropertyProxyValidator. I called mine the PrioritizedPropertyProxyValidator (PPPV, or P3V :-). This class will simply override the EvaluateIsValid method to check if any results have the magic Tag value, and filter the results accordingly;

 public class PrioritizedPropertyProxyValidator : PropertyProxyValidator
    {
        public const string PrimaryValidatorTag = "Primary";

        /// 
        /// Determines whether the content in the input control is valid.
        /// 
        protected override bool EvaluateIsValid()
        {
            Validator validator = new ValidationIntegrationHelper(this).GetValidator();

            if (validator != null)
            {
                ValidationResults validationResults = validator.Validate(this);

                if (!validationResults.IsValid)
                {
                    // Check if any results are tagged as Primary
                    ValidationResults filteredResults = validationResults.FindAll(TagFilter.Include, PrimaryValidatorTag);
                    if (!filteredResults.IsValid)
                    {
                        // Return only the primary results
                        validationResults = filteredResults;
                    }
                }

                this.ErrorMessage = FormatErrorMessage(validationResults, this.DisplayMode);
                return validationResults.IsValid;
            }
            else
            {
                this.ErrorMessage = "";
                return true;
            }
        }

        internal static string FormatErrorMessage(ValidationResults results, ValidationSummaryDisplayMode displayMode)
        {
             // Code can be copied from the original class.
             // Omitted from here for brevity.
             // Can't be reused since it's not marked as protected :-S
        }
    }

That's all there is to it! The PPPV can be used in the exact same way as the PPV, however it will prioritise the validation results to display primary results first, and the remaining (secondary) results only once all the primary validators have passed. If you wanted it would be easy enough to implement more than 2 priority levels, but so far two is plenty for what we're doing.