Freigeben über


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.

Comments

  • Anonymous
    December 18, 2007
    PingBack from http://msdnrss.thecoderblogs.com/2007/12/18/prioritising-validation-results-using-vab-aspnet-integration/

  • Anonymous
    December 18, 2007
    I'm not sure your password example is a good one ... It is a poor UI decision to only display the error messages incrementally as people enter the data, you are making people try, then find out they forgot a bit, then try again, and find they needed 2 extra characters, and try again and find out they needed numbers and so on ... turning a simple password entry into a long winded UI task ... For every error in this case that the UI can detect, it really should let the user know, that way next time they shouldn't get any of your four example messages again.

  • Anonymous
    December 18, 2007
    Casey - I agree with you to a point, and maybe it wasn't the best example. The idea here isn't to progressively reveal validation errors when the user would reasonably want to know about all of them in one go. Instead it's to reduce clutter and confusion in situations where there is one problem so fundamental (typically that a field was missed) that the other problems are not worth bothering people with. The reason we only have 2 levels of errors is because any more would start to look like your example.

  • Anonymous
    December 18, 2007
    Indeed - I think the example may not have been the best (I would still present all 4 error message for a blank password) - however the principles behind your solution are quite cool. I'm busy writing validation stuff for SharePoint at the moment and exploring how things like the VAB could help, so all this information is very useful.

  • Anonymous
    December 19, 2007
    Interesting ,but less-perfect way to resolve the issue. I wish , in Tom's case, the "secondary" validation should NOT run at all , instead of removing from the display list. I guess I am always concerned about the resource usage.

  • Anonymous
    December 19, 2007
    Walter - You could probably do this by creating 2 different rulesets, and building a PPV derivative which would run the two rulesets in turn.