Shawn Wildermuth

Author, Teacher, and Filmmaker
.NET Foundation Board Member

More Validation Fun - But Not Done Yet

More Validation Fun - But Not Done Yet

Url: http://silverlight.net/blogs/jesseliberty/archi...

I was reading Jesse Liberty's Validation post today and it reminded me of a discussion that I had with my students last week about doing validation. While performing the validation is interesting, for them they needed to worry about localization. In the face of this I suggested the new validation attributes. These attributes live in the System.ComponentModel.DataAnnotations namespace.  These attributes include:

  • Required
  • Range
  • RegularExpression
  • StringLength
  • CustomValidation

Each of these can be applied to properties like so:

public class Game
{
  [Required]
  [StringLength(255)]
  public string ProductName { get; set; }
  
  [Required]
  [Range(0m,250m)]
  public decimal ListPrice { get; set; }
     
  [CustomValidation(typeof(MyValidator), "CheckReleaseDate")]
  public DateTime ReleaseDate { get; set; }
}

 

In Jesse's example, instead of using attributes he used exceptions to notify the validation UI in Silverlight 3. This works fine but my students were concerned about localizing the string in the exception. While they could have done it with a string table, I suggested that what they might want instead were the validation attributes.  In each attribute you can specify the error message like so:

[Required(ErrorMessage="ProductName is required.")]
public string ProductName { get; set; }

 

But to localize it the trick is to actually specify the name message in the attribute like so:

[Required(ErrorMessageResourceName="PRODUCTNAMEREQUIRED")]
public string ProductName { get; set; }

 

But after asserting that this was a good solution, I didn't have a good example of how to force validation with these attributes.  Of course, in Silverlight 3 you could use the DataForm to do this but I wanted to be able to point my students at a way of using it anywhere.  (The DataForm is great, I just like to have options.)

After a couple of hours with Reflector, I had a workable solution:

public class Game
{
  string _name;
  decimal _listPrice;
  DateTime _releaseDate;

  [Required(ErrorMessageResourceName="PRODUCTNAMEREQUIRED")]
  public string ProductName 
  {
    get { return _name; }
    set
    {
      if (value != _name)
      {
        _name = value;
        Validate();
      }
    }
  }
  
  [Required]
  [Range(0m,250m)]
  public decimal ListPrice 
  {
    get { return _listPrice; }
    set
    {
      if (value != _listPrice)
      {
        _listPrice = value;
        Validate();
      }
    }
  }
     
  [CustomValidation(typeof(MyValidator), "CheckReleaseDate")]
  public DateTime ReleaseDate 
  {
    get { return _releaseDate; }
    set
    {
      if (value != _releaseDate)
      {
        _releaseDate = value;
        Validate();
      }
    }
  }

  void Validate()
  {
    var ctx = new ValidationContext(this, null, null);
    Validator.ValidateObject(this, ctx);
  }
}

 

The trick was to create a validation routine directly in the data class that I was binding to. The Validate method creates a ValidationContext object (which contains the metadata about the attributes) and use it to call ValidateObject on the Validator class. This reads the attributes and builds the data (as an exception) that the validation UI requires. The trick is to them call the Validate method in the setters of the object.

The problem I have is that this doesn't quite play well with Web Service or Data Service data (it would require that we modify the generated code). I am still looking for a better solution (like the MetaDataType solution that Dynamic Data uses).  When I find the right person at MS who can clue me in, i'll blog about it here.