Navigation:  Application Architecture > Putting it All Together > Designing an Application Component >

Adding validation

Previous pageReturn to chapter overviewNext page

For your own application component, you may want to add some way of validating its properties and provide some visual feedback to the user as to what needs to be done to correct the values that have been entered.  The Framework provides a number of built-in validation attributes that can be applied to properties of application components to handle both validation and showing messages to the user next to the appropriate UI element (e.g. the one bound to the property).  Each property in an application component can be decorated with any number of validation attributes.

The text editor component is a relatively trivial example and even the Filename would likely not need validation if we show a proper open file dialog box, but for the purposes of illustration, we shall let the user type in any free text.  A quick glance at the documentation for File.WriteAllText tells us that we would need to check if the filename is null, empty, or contains invalid characters.  To start using the built-in application component validation, we simply decorate the Text property with a ValidateRegexAttribute, like this:

[ValidateRegex("[<>]", SuccessOnMatch = false, Message = "Cannot have < or > characters.")]
public string Filename
{
   get { return _filename; }
   set
   {
      _filename = value;
      this.Modified = true;
   }
}

 

The SuccessOnMatch property indicates whether or not validation should succeed when the property matches the pattern.  In this case, it is false because we want validation to fail when the property matches the pattern.

In addition to decorating properties with validation attributes, there is one more step that needs to be taken, which is to change the Save() method to:

public void Save()
{
   if (base.HasValidationErrors)
   {
      base.ShowValidation(true);
   }
   else
   {
      File.WriteAllText(_filename, _text);
      this.Exit(ApplicationComponentExitCode.Accepted);
   }
}

 

This stops the user from saving the text until the validation errors have been resolved.  You can call ApplicationComponent.ShowValidation(bool) at any time to show/hide the validation icons in the UI.  The ApplicationComponent.HasValidationErrors property is evaluated each time it is called so that the validation messages in the UI are always correct.

Now, when you click Save, you will see the validation icon to the right of the text box if the filename contains any angle brackets.

 

The built-in validation attributes provided by the framework are:

ValidateRegexAttribute, which leverages the flexibility of regular expressions to validate the property value, but can be complicated for those who aren't familiar with regular expressions.
ValidateGreaterThanAttribute, which checks that the property value is greater than a certain value.
ValidateLessThanAttribute, which checks that the property value is less than a certain value.
ValidateLengthAttribute, which checks that the length of the property value is within a specified range.
ValidateNotNullAttribute, which checks that the property value is non-null.

 

We still need to check the filename to ensure that it is not null or empty.  To do so, we shall add a ValidateLengthAttribute and specify a minimum of one character.

[ValidateLength(1, Message = "Cannot be empty.")]
[ValidateRegex("[<>]", SuccessOnMatch = false, Message = "Cannot have < or > characters.")]
public string Filename
{
   get { return _filename; }
   set
   {
      _filename = value;
      this.Modified = true;
   }
}

 

Note also that, for every validation attribute's Message property, you can specify the name of a string resource rather than the actual message text.  The framework will automatically resolve the resource and show the resource's value in the validation icon's tooltip.

Often, these attributes are sufficient to implement validation for an application component.  However, you may come across situations where they're not; for example, when a particular property's validity depends on the value of another property, or when another method provides some black box validation logic.  Suppose now, that there needs to be a restriction added to the text editor to disallow text with less than 3 words to be saved, but the validation icon needs to appear next to the text box to indicate that the text should be changed.  This poses a bit of a problem because, in order for the icon to appear next to the text box, the validation has to be done on the Text property, not the WordCount property, which is purely calculated and shouldn't be editable by the user.  The ValidationMethodForAttribute, which decorates a method rather than a property, allows the property it applies to to be specified, essentially so that the validation icon will appear in the right place.  Adding this code to the text editor component will achieve the desired result:

 

[ValidationMethodFor("Text")]

private ValidationResult ValidateMinimumWordCount()

{

   if (_wordCount < 3)

      return new ValidationResult(false"There must be 3 or more words.");

   else

      return new ValidationResult(true"");

}

 

The ValidationMethodForAttribute is infinitely flexible for any kind of custom validation.  However, if you find yourself repeatedly doing the same validation task in many of your application components, you will probably start looking for a way to encapsulate the common validation task into a reusable construct.  To this end, you could implement a custom validation attribute, which can be used throughout your code.  Also, for simple text validation, you may find it easier to implement your own custom attributes rather than trying to figure out the appropriate regular expression.  Previously, we used a regular expression to validate that there were no angle brackets in the Text property, but the .NET System.IO.Path class defines many other illegal filename characters.  We could create our own custom validation attribute specifically to validate filenames like so:

 

using System.Reflection;
using ClearCanvas.Desktop;
using ClearCanvas.Desktop.Validation;
using Path=System.IO.Path;
 
namespace MyPlugin.TextEditor
{
   public class ValidateFilenameAttribute : ValidationAttribute
   {
      public ValidateFilenameAttribute() {}
 
      protected override IValidationRule CreateRule(PropertyInfo property, PropertyGetter getter, string customMessage)
      {
         base.CheckPropertyIsType(property, typeof (string));
 
         string message = customMessage ?? "Value is not a valid filename.";
 
         return new ValidationRule(property.Name,
                                   delegate(IApplicationComponent component)
                                      {
                                         string value = ((string) getter(component)) ?? string.Empty;
 
                                         if (string.IsNullOrEmpty(value)
                                             || value.IndexOfAny(Path.GetInvalidPathChars()) >= 0
                                             || value.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0)
                                         {
                                            return new ValidationResult(false, message);
                                         }
                                         else
                                         {
                                            return new ValidationResult(true, string.Empty);
                                         }
                                      });
      }
   }
}

 

And now our example from above becomes:

 

[ValidateFilename(Message = "Invalid filename.")]
public string Filename
{
   get { return _filename; }
   set
   {
      _filename = value;
      this.Modified = true;
   }
}

 

There is one other scenario that may arise occasionally for which none of these solutions is particularly convenient.  Suppose you need to do the exact same validation task on many properties, but the validation always depends on some other property of the application component.  A custom validation attribute won't work because direct access to the application component is abstracted away.  You could use the ValidationMethodForAttribute, but then you would have to define the same method over and over, one for each property that is to be validated.  This problem is solved by implementing your own custom IValidationRule.  There isn't really any practical example we can construct using the text editor, so instead we'll go straight to the code base.  If you look at the AnonymizeStudyComponent, you will find the following inner class:

 

private class ValidateAnonymizationRule : IValidationRule
{
   private readonly AnonymizeStudyComponent _parent;
   private readonly string _property;
   private readonly ValidationFailureReason _validationReason;
 
   public ValidateAnonymizationRule(AnonymizeStudyComponent parent, string property, ValidationFailureReason validationReason)
   {
      _parent = parent;
      _property = property;
      _validationReason = validationReason;
   }
 
   #region IValidationRule Members
 
   public string PropertyName
   {
      get { return _property; }
   }
 
   public ValidationResult GetResult(IApplicationComponent component)
   {
      ReadOnlyCollection<ValidationFailureDescription> failures = _parent.GetValidationFailures();
      foreach (ValidationFailureDescription failure in failures)
      {
         if (failure.PropertyName == _property && _validationReason == failure.Reason)
         {
            if (failure.Reason == ValidationFailureReason.EmptyValue)
               return new ValidationResult(false, "The value cannot be empty.");
            else
               return new ValidationResult(false, "The value conflicts with the original value.");
         }
      }
 
      return new ValidationResult(true, "");
   }
 
   #endregion
}

 

 

IValidationRules can be added directly to the base application component's Validation member, which is a collection of IValidationRules.  This is where the component actually stores the rules it creates from the ValidationAttribute and ValidationMethodForAttribute.  Typically, you should add your custom IValidationRules to the base class in the Start method, like this:

 

 

public override void Start()
{
   // other initialization
 
   base.Start();
 
   base.Validation.Add(new ValidateAnonymizationRule(this, "PatientId", ValidationFailureReason.EmptyValue));
   base.Validation.Add(new ValidateAnonymizationRule(this, "PatientsName", ValidationFailureReason.EmptyValue));
   base.Validation.Add(new ValidateAnonymizationRule(this, "AccessionNumber", ValidationFailureReason.EmptyValue));
   base.Validation.Add(new ValidateAnonymizationRule(this, "StudyDescription", ValidationFailureReason.EmptyValue));
   base.Validation.Add(new ValidateAnonymizationRule(this, "StudyDate", ValidationFailureReason.EmptyValue));
   base.Validation.Add(new ValidateAnonymizationRule(this, "PatientsBirthDate", ValidationFailureReason.EmptyValue));
 
   base.Validation.Add(new ValidateAnonymizationRule(this, "PatientId", ValidationFailureReason.ConflictingValue));
   base.Validation.Add(new ValidateAnonymizationRule(this, "PatientsName", ValidationFailureReason.ConflictingValue));
   base.Validation.Add(new ValidateAnonymizationRule(this, "AccessionNumber", ValidationFailureReason.ConflictingValue));
   base.Validation.Add(new ValidateAnonymizationRule(this, "StudyDescription", ValidationFailureReason.ConflictingValue));
   base.Validation.Add(new ValidateAnonymizationRule(this, "StudyDate", ValidationFailureReason.ConflictingValue));
   base.Validation.Add(new ValidateAnonymizationRule(this, "PatientsBirthDate", ValidationFailureReason.ConflictingValue));
}

 

The implementation of the TextEditorComponent class is now complete.  The next step is to bind it to its associated view.