Silverlight 4 + RIA Services - Ready for Business: Validating Data

 

To continue our series let’s look at data validation our business applications. Updating data is great, but when you enable data update you often need to check the data to ensure it is valid.  RIA Services as clean, prescriptive pattern for handling this.   First let’s look at what you get for free.  The value for any field entered has to be valid for the range of that data type.  For example, you never need to write code to ensure someone didn’t type is “forty-two” into a textbox bound to an int field. You also get nice looking and well behaved validation exposure in the UI. 

image_thumb[82]

Note: if you are not seeing this, ensure that “ValidatesOnExceptions=True” is in the binding expression for the each field

Of course, that sort of validation only goes so far, in  real application you need some more extensive validation.  And for this validation you absolutely have to check the validation before your business logic is run because you don’t know what client might be sending you the update, in addition, you want to check the validation on the client to give the user a really nice user experience and reduce the number of error-case hits to your server which reduces server load.   In traditional models, you need do error checking twice to cover both of these cases.  But that is obviously error prone and easy to get out of sync.  So RIA Services offers a common model for validation.  

The most common cases are covered by a set of custom attributes you apply to your model on the server.  These attributes are common across the .NET Framework supported by ASP.NET Dynamic Data, ASP.NET MVC and RIA Services.  You can find the full set in System.ComponentModel.DataAnnotations.    But to give you a flavor:

 [Display(Order = 0)]
[Required(ErrorMessage = "Please provide a name")]

public string Name { get; set; }
[Range(0, 999)]

public Nullable<decimal> Price { get; set; }
[RegularExpression(@"^http\://[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(/\S*)?$",
                    ErrorMessage = "Please use standard Url format")]
public string Uri { get; set; }

 

image_thumb[84]

As you an see from above the validations on the client are handled automatically, but they are also run again on the server BEFORE your Update method is called.  This allows you to keep the validation gunk out of your business logic for Update.   The other great thing is that these validations will apply in exactly the same way no mater where that entity is used in the UI because they are build into the model.    You can of course localize the error messages by passing a resource ID rather than a hard code string.  You can also read these validation metadata out of an external config file or database rather than using attributes in the code. 

But clearly that doesn’t cover all cases.  Many times you need to write some actual procedural code.  Let’s consider an example of validating the description to ensure it is really complete.  There is no custom attribute for that ;-).  So let’s write a bit of C# code.  First we need to indicate that the Description field has some custom validation. 

 [CustomValidation(typeof(PlateValidationRules),
                  "IsDescriptionValid")]
public string Description { get; set; }

Then you can easily create this class and implement the IsDescriptionValid method.

 

   1:         public static ValidationResult IsDescriptionValid(string description)
  2:         {
  3:             if (description != null && description.Split().Length < 5)
  4:             {
  5:                 var vr = new ValidationResult("Valid descriptions must have 5 or more words.",
  6:                     new string[] { "Description" });
  7:                 return vr;
  8:             }
  9: 
 10:             return ValidationResult.Success;
 11:         }
 12: 

 

Notice in line 1, the signature of the method must return a ValidationResult – this is a class from DataAnnotations that contains information about any validation errors.  The method also has to take one parameter that is the same type of the field it is being applied to.  You could do it on the entity level to do cross field validation.  

Next on line 3, i am implementing some very lame algorithm for determining if the description is valid or not. 

On line 5 and 6, I return an error and indicate which field this applies to. 

Now, run the application.  You will see that we can edit the description and tab off with no error, but if we submit, then we get back an error in exactly the same way as we saw before.  Notice I could have sent several entities and each of them could have errors.  RIA Services keeps up with each of them (we even give you a list) and as the user edits each one we show some UI like this. 

image_thumb[86]

 

Note, if you see this sort of dialog instead:

image_thumb[85]

It likely means you need to write a handler for SubmitChanges on your DomainDataSource. 

 

 <riaControls:DomainDataSource  SubmittedChanges="plateDomainDataSource_SubmittedChanges"

 

         private void plateDomainDataSource_SubmittedChanges(object sender, SubmittedChangesEventArgs e)
        {
            if (e.HasError &&
                e.EntitiesInError.All(t => t.HasValidationErrors))
            {
                e.MarkErrorAsHandled();
            }
            else
            {
                System.Windows.MessageBox.Show(e.Error.ToString(), "Load Error", System.Windows.MessageBoxButton.OK);
                e.MarkErrorAsHandled();
            }
        }

Now, this is very cool because we have the full power of .NET to write our validation rules.  But the down side is that I only get validation once the change is submitted.  What I’d really like to do in some cases,  is have some write some custom validation logic and have it execute on the server AND the client.    Luckily we have the power of .NET on the client and the server so we can use shared code validation.  To enable this simply change the name of the file that contains the validation rule to include “.shared.cs” post-fix.  This changes causes the RIA Services MSBuild logic to compile the file for the client and the server. 

image_thumb[88]

 

Now the exact same code will run on the client and the server.  So if there is a bad description, we no longer have to round-trip to the server to work that out. 

image_thumb[90]

Of course, in a real world case you are likely to have both server-only and shared validation rules and RIA Services fully supports that scenario as well.  Simply define any shared validation rules in .shared.cs files and any server-only validation rules in another file.

Comments

  • Anonymous
    March 23, 2010
    Sample for validation against database would be very helpful.

  • Anonymous
    March 23, 2010
    What about validating against a class from DomainDataSource created with new in code behind.

  • Anonymous
    March 23, 2010
    the custom validation is weird.  if it's on a property, why does it have to include the property name in the result?  not just a DRY issue, but also appears to keep you from being able to apply it to other properties, like if the class had 3 description fields, each would need its own validation method?

  • Anonymous
    March 24, 2010
    Is it possible to use DomainService methods? I am (struggling) to write a custom validation where a "date" selected on the screen is checked against MAXIMUM date found in database table. I am tryring to follow this algorithm.

  1. Add a CustomValidation.shared.vb class in Web project.
  2. Write a custom method GetMaximumDate using LINQ in DomainService class.
  3. Write a validation routine in CustomValidation.shared.vb that will call GetMaximumDate function.
  4. Use [CustomValidation) attribute in MetaData class. If you can provide some code sample to achive this, or some pointers, that will be really helpful. Thanks, Mahesh.
  • Anonymous
    March 24, 2010
    Fabulous posting I think I never saw any information like that ,So I really admired from your given information,I will bookmark it for further use . Thanks for sharing useful and valuable information.

  • Anonymous
    March 25, 2010
    I am also interested to know if it is possible to use Custom Validation as mentioned by Mahesh.

  • Anonymous
    April 23, 2010
    Hi Brad fantastic series of postings - thanks very much! When I got to this page, though, I started getting Code 4004 "An unhandled exception" errors. It seems this is down to not handling the error state in the domain context / SubmittedChanges() method. I've run through this at http://www.scottleckie.com/2010/04/code-4004-unhandled-error-in-silverlight-application/ if anyone is interested. Cheers, and thanks again for the series,  Scott

  • Anonymous
    May 13, 2010
    To answer the question: "why does it have to include the property name in the result? "   You don't have to put the field name in there. Instead, write the code like this: (Notice the extra ValidationContext parameter that is used instead of the hardcoded field name.) public static ValidationResult IsDescriptionValid(string description, ValidationContext context)  2:         {  3:             if (description != null && description.Split().Length < 5)  4:             {  5:                 var vr = new ValidationResult("Valid descriptions must have 5 or more words.",  6:                     new string[] { context.DisplayName });  7:                 return vr;  8:             }  9: 10:             return ValidationResult.Success; 11:         }