Silverlight Data and Controls Series
- Silverlight 4 Hands On Lab
- Silverlight 4 using MVVM Pattern, RIA Services and Custom Domain Service
- Silverlight Custom Authentication Domain Service with RIA Services
- Binding ComboBox and AutoComplete Controls in Silverlight DataGrid
- RIA Service Domain Service Method Life-Cycle
- Silverlight Localization and Resource Files
- Deep Dive into Custom RIA Service and Transactions
- Bind a Silverlight DataGrid Column to the User Control DataContext
- Project RIA Methods
- Silverlight RIA Data Validation Best Practices
Introduction
I have been building a pretty heavy forms Silverlight application recently and I wanted to share some simple approaches on how you can handle data validation. There are really tons of options and you can potentially get a lot for free. I am using Silverlight 4 with RIA Services to convert a large scale tradition rich client application to be web based. If we did not have a technology like Silverlight it would be pretty much impossible to build this application without creating significant amounts of Javascript and AJAX – both of which I just plain avoid in all my solutions because of the long term development and maintenance cost of them. Silverlight is what we have wanted since ASP.net can out.
Back to Silverlight validation; there are a couple options you have:
- Declarative - Validation Attribute on Property
- Custom Attribute Validation (Class or Property)
- RIA Service Validation
- Silverlight Client Validation (and Asynchronous Validation)
Note that I had to get an understanding of much of this because I had to build a custom RIA Service to hook into a large, legacy non-Microsoft bend database what was not going to change anytime soon. I could not benefit from much of the free stuff you get with Entity. In my case, I create custom DTOs that get filled in the service layer and then passed through a RIA Service to the Silverlight Application.
In this blog I will cover the pros and cons of each and provide simple examples to get you started.
1 - Declarative Validation - Validation Attribute on Property
This is probably the most simple and easiest validation you have in your arsenal. Much of the validations you have are located within System.ComponentModel.DataAnnotations. There are several however the ones that I effectively have used the most are:
- Key – Enforces uniqueness.
- Range – Sets a range of values that numeric type can be in.
- RegularExpression – Regular expression applied to a property.
- Required – Indicates if a value must be present.
- StringLength – Indicates the length that a string type property can be.
- Editable – Indicates if the property can be edited.
Here is a simple example. As you can see in this example I have some of the most basic validations I need.
public class Employee
{
[Key]
public int ID { get; set; }
[Required]
public DateTime DOB { get; set; }
[Required]
[RegularExpression(@"^(?!000)([0-6]\d{2}7([0-6]\d7[012]))([ -]?)(?!00)\d\d\3(?!0000)\d{4}$")]
public string SSN { get; set; }
[Required]
[StringLength(20)]
public string FirstName { get; set; }
[Required]
[StringLength(30)]
public string LastName { get; set; }
}
Pros:
- For Free - These validations pretty much kick in for free once you bind your object instance to a control in Silverlight. If any of these rules are violated when a value is set, the user will be displayed a message and the field will be highlighted in Red.
- No RIA Round Trips - If any of the validations have been violated, the user will not be able to submit changes back to the RIA Services. This is good because there is no round trip involved to RIA for the validations to kick in. The validations are actually projected into the Generated_Code of the DTO class type in the Silverlight Application.
Cons:
- Basic Only - You only get very basic validations.
2 - Custom Attribute Validation (Property or Class Level)
Custom validations can be created and the applied to the class in the similar manner as the declarative manner described above. You will quickly need to do something like this so that you can perform custom validations across multiple fields or against the whole class.
To implement you need to create a class and name the class file XXX.Shared.cs to the location where you have your DTOs defined. Then add some validation logic and reference as CustomValidation attribute on the DTO.
You will notice after you do a build, a copy of the XXX.Shared.cs file will be copied to the Generated_Code folder in the Silverlight Application. What this basically means is that the validation will be run on the Silverlight Application side and not require a round trip to RIA.
Here is an example where I have:
- I have a validation called IsDOBValid which will validate the date.
- I have a validation called IsPostEmployeementDateValid which will check to see PostEmployeementDate is correct.
- I also have an IsActiveValid which performs a similar validation.
I then have IsEmployeeValid which is a class level validation. I have no implementation because I could not come up with a good example but I wanted to show you that it can be used.
using System;
using System.ComponentModel.DataAnnotations;
namespace SilverlightApplication2.Web
{
public class EmployeeValidation
{
public static ValidationResult IsEmployeeValid(Employee employee, ValidationContext context)
{
//Place in Employee Scope validations
return ValidationResult.Success;
}
public static ValidationResult IsDOBValid(DateTime dob, ValidationContext context) {
if (dob == DateTime.MinValue)
{
return new ValidationResult("The Employee DOB is set.",
new string[] { "DOB" });
}
if (dob >= DateTime.Now)
{
return new ValidationResult("The DOB cannot be greater than today.",
new string[] { "DOB" });
}
return ValidationResult.Success;
}
public static ValidationResult IsPostEmployeementDateValid(DateTime postEmployeementDate, ValidationContext context)
{
if (context.ObjectInstance != null)
{
Employee employee = context.ObjectInstance as Employee;
return ValidatePostEmployeementDate(postEmployeementDate, employee.Active);
}
return ValidationResult.Success;
}
public static ValidationResult IsActiveValid(bool active, ValidationContext context)
{
if (context.ObjectInstance != null)
{
Employee employee = context.ObjectInstance as Employee;
return ValidatePostEmployeementDate(employee.PostEmployeementDate, active);
}
return ValidationResult.Success;
}
private static ValidationResult ValidatePostEmployeementDate(DateTime postEmployeementDate, bool active)
{
if (active == false && (postEmployeementDate == null postEmployeementDate == DateTime.MinValue))
{
return new ValidationResult("The Post Employeement Date must be set if the Employee is inactive.",
new string[] { "PostEmployeementDate", "Active" });
}
return ValidationResult.Success;
}
}
}
One observation is I use the value that is passed in to do the validation and not the value in the Employee object when the validation is used on a property. For instance on IsActiveValid I use active and not employee.Active. This is because the value passed in is the value that changed and employee.Active has the old value. This is actually helpful if you need to check to see how the values have changed.
The following modifications to the DTO I created above:
- First I put the class level validation at the Employee class level.
- Second I put the three property level validations.
[CustomValidation(typeof(EmployeeValidation), "IsEmployeeValid")]
public class Employee
{
[Key]
public int ID { get; set; }
[Required]
[CustomValidation(typeof(EmployeeValidation), "IsDOBValid")]
public DateTime DOB { get; set; }
[Required]
[RegularExpression(@"^(?!000)([0-6]\d{2}7([0-6]\d7[012]))([ -]?)(?!00)\d\d\3(?!0000)\d{4}$")]
public string SSN { get; set; }
[Required]
[StringLength(20)]
public string FirstName { get; set; }
[Required]
[StringLength(30)]
public string LastName { get; set; }
[Required]
[CustomValidation(typeof(EmployeeValidation), "IsActiveValid")]
public bool Active { get; set; }
[Required]
public DateTime HireDate { get; set; }
[CustomValidation(typeof(EmployeeValidation), "IsPostEmployeementDateValid")]
public DateTime PostEmployeementDate { get; set; }
}
There are several things you should know about these before jumping in and using them across the board. I recommend using these validations when you have simple/medium level validations you need to do.
Pros:
- Medium Complex Validations - These are great because they do allow you to put in medium complexity level sort of validations for your objects which was missing from the first example I presented.
- User Notification - Validations will be fired immediately when a value is set into a property level custom validation.
Cons:
- Class Level User Notification - Class level validations are executed on the client, however they are only evaluated when the context.SubmitChanges() method is called..
- DataGrid Limitation - If you are using a DataGird, none validations using this pattern will be fired immediately when a user sets a value. This is because the EndEdit event of the DataGrid row needs to be called first and then these validations will kick in. This will become a problem for you if the user needs to set a value in a grid, press tab and need to see the error immediately.
- Reference Other Library - Library dependencies are major limitation of this solution. You will be tempted to do a complex validation logic in the XXX.Shared.cs class file (i.e. call out to a database and check something). An example would be to reference a service layer library and call a method on it. Then based on the result send a validation error. This will probably compile in your DTO Library because it may have a reference to the service layer library. However when the XXX.Shared.cs is copied to the Silverlight Application into the Generated_Code folder, the Silverlight Application will not compile. This I because the Silverlight Application does not have a direct reference to you the service layer library DLL. Nor can you fix that because you service layer library DLL is written in straight up .NET code library which a Silverlight Application cannot reference.
- No Asynchronous Validations – This pattern cannot perform asynchronous validations either. This is again because XXX.Shared.cs is defined in the DTO project library and not in your Silverlight Application. So you cannot use any of the RIA services to assist you with you validations.
3 - RIA Service Validations
If you read my blog on RIA Service Method Lifecycle, there is a method called ValidateChangeSet which you can override and perform validations on just the RIA side. This is called before ExecuteChangeSet which is where much of your database transaction logic will reside.
The code below is not meant to be a good code sample, just so you the basic mechanics for working with overriding the ValidateChangeSet method.
protected override bool ValidateChangeSet()
{
bool isValid = base.ValidateChangeSet();
if (IsSomeValidation() == false) {
isValid = false;
ValidationResult validationResult = new ValidationResult("Blah Blah.", new string[] { "PostEmployeementDate" });
throw new ValidationException(validationResult, null, null);
}
return isValid;
}
Pros:
- Fail Safe Errors – If you have some validations that need to always occur and you want to make sure 100% they are evaluated before any commits occur, this is the place to put them.
Cons:
- User Will Not Immediately See – The user will not immediately see these errors and have to wait for a RIA round trip.
4 - Silverlight Client Validation (and Asynchronous Validation)
I have found through my Silverlight development that I have ultimately settled on doing validations this way more often than not. If you recall, the Employee DTO definition was projected by RIA to the Generated_Code folder.
If you go look in the generated class, you will see that all of the Employee DTO properties look similar to the following:
/// <summary>
/// Gets or sets the 'DOB' value.
/// </summary>
[CustomValidation(typeof(EmployeeValidation), "IsDOBValid")]
[DataMember()]
[Required()]
public DateTime DOB
{
get
{
return this._dob;
}
set
{
if ((this._dob != value))
{
this.OnDOBChanging(value);
this.RaiseDataMemberChanging("DOB");
this.ValidateProperty("DOB", value);
this._dob = value;
this.RaiseDataMemberChanged("DOB");
this.OnDOBChanged();
}
}
}
Here you see I pulled out the DOB property of the Employee class that is in the Generated_Code folder. There are some interesting things here you should be aware of:
- OnDOBChanging it a method that can be overridden to perform a task before the values is actually set.
- RaiseDataMemeberChange is a notification that the DOB value is about to be changed.
- ValidateProperty is a method that will execute all of the declarative validations (which I discussed earlier). If it fails, it will not continue.
- Next the value is set.
- RaiseDataMemberChanged is a notification that the value has changed. This will actually part of the INotifyPropertyChanged interface implementation and will notify the UI binding with the updated value.
- OnDOBChanged is a method you can override after the value has been set.
In this pattern we will simply extend the generated code by creating a partial class so that the code we create does not get lost every time the code the solution is built:
- First create a matching partial class in the Silverlight Application for the DTO.
- Next you override the OnXXXChanging or OnXXXChanged methods.
Here is a simple example:
using System;
using System.ServiceModel.DomainServices.Client;
using System.ComponentModel.DataAnnotations;
namespace SilverlightApplication2.Web
{
partial class Employee
{
ValidationResult someValidationResult;
partial void OnPostEmployeementDateChanged()
{
//Call RIA Method
SimpleContext _simpleContext = new SimpleContext();
_simpleContext.SomeValidation(this.PostEmployeementDate, SomeValidationComplete, null);
}
private void SomeValidationComplete(InvokeOperation<bool> io)
{
if (io.HasError)
{
io.MarkErrorAsHandled();
//do something
}
else
{
if (io.Value)
{
//Remove the error.
if (someValidationResult != null) {
this.ValidationErrors.Remove(someValidationResult);
someValidationResult = null;
}
}
else {
//There was an error
someValidationResult = new ValidationResult("Error Message.", new string[] { "PostEmployeementDate" });
this.ValidationErrors.Add(someValidationResult);
}
}
}
}
}
In the code you will see that:
- I called a simple RIA Method that returns a bool.
- I override the OnPostEmployeementDateChanged method and you will note that I did not have to make it is override. All I did was mark it as partial. Now this will be called after a value has been successfully set into the PostEmployeementDate property.
- In the call back, I check the results and will add a ValidationResult if there is an error.
Pros:
- Asynchronous Validation – This is a huge plus versus the other validation patterns. This is needed when you got to check an input value against some complex routine which may be database driven.
- Extending Your DTOs for the UI – This is a great solution because what you can do is define DTOs in the RIA layer with no functionality. Then in the Silverlight layer you can extend your DTOs to have much more functionality and business logic that is only associated to the Silverlight application.
Cons:
- Validation Not Evaluated on RIA Side – Using the approaches above the validations are fired on both the Silverlight Application side, as well as the RIA side. Obviously the benefit is that your DTOs will always have the rules applied.
19 comments:
Hi, what recommendation would you give to validate that a new record inserted doesn't have a duplicate key. For example, I am inserting a new pacient with his/her SSNum (is PK besides the autogen PK), and that pacient is already in the DB. Should I trap that exception once it's thrown in the Domain Service?
Thanks and great article.
Javier,
Thanks for the feedback.
We had to solve this for an Oracle database we are integrating with. In our scenario, sense to generate the key of the table when the object was created. This would occur well before the validation.
Doing validations in the Domain Service can be challenging because the service does not have knowlegde of why it is being called. Personally I would not want this validation have to fire or to be evaluated every time the domain service is called.
What I would do is follow the pattern I described in this blog to create a partial class for the patient. Then I would add a public method onto Patient DTO called IsSSNValid. In this method I would call RIA Service to peform the check for the duplicate SSN. If on the callback it succeeds, I would call SubmitChanged.
Jason
I created a partial Class in my Silverlight Client app to extend the generated SupportDbDTO but I keep getting this error:
No defining declaration found for implementing declaration of partial method 'SP3DRepos.SupportDbDTO.OnBackupInfoChanged()'
Any thoughts ??
Forget the last comment, I realised my namespace was off. Very nice article though...I was looking at a way to do the validation asynch & this cleared it up.
sjoshi,
I was just about to say your namespace was off but you figured it out. For other folks reading, the namespace for your partial class in the Silverlight application must match the namespace of where the DTO is define. You can also look at the projected (generated) code to see what the namespace.
Thanks for the feedback.
Jason
I don't understand This:
//Call RIA Method SimpleContext _simpleContext = new SimpleContext(); _simpleContext.SomeValidation(this.PostEmployeementDate, SomeValidationComplete, null);
Please Help me!
Alessandro - that is just an example. Rename the methods to what ever you want. It is basically demonstrating an async callback.
Jason
Hi Jason,
With the approach 4, do the validations fire as soon as user tabs out on the grid row? I am trying to implement said procedure but not successful in showing these messages on tab out of a cell.
Could you share your thoughts?
Thanks,
Subha
Subha,
Yes - option 4 will fire when values are changed in a grid cell. It is all based on bindings - so that worked very well for me when I was doing this.
Jason
Thank you for your response Jason.
One last question, is there anything specific that you need to do for int variables in DTO to show these custom messages? I have an int variable in my DTO and I specified custom message in onXXXChanged event, but it is still showing "Input is not in the correct format". How to get rid of this and show the defined message?
Thanks for your help in advance.
Subha
Subha,
Your question sounds very familiar but unfortunately I cannot recall what I did.
Sorry - but the code in this blog will work at showing error messages.
Thanks,
Jason
Hi Jason,
I am using a Silverlight Ria Domain Service Validation. I want to keep all the Validation Messages (for example:“The Post Employeement Date must be set if the Employee is inactive.
”) in a database and cache it in the application level. How can I retrieve a cached validation message and use it in the Ria Validation ( using RIA generated proxy)
You can go down that path, I choose not too in this implementation. You are going to have to do a bunch of extra code to load up all your messages from the database when the applications loads. Then in the code where I set the message text, you will need set in the messages. The problem with that approach will be if you have tons of messages, the load of the silverlight application will be slow. So you will have to move towards a solution that will load up a message from a database and then create some sort of singleton to managed loaded messages...
Jason,
Thanks for your suggestions, I understand that it is going to load up lots of messages in the Silverlight application. I am going to experiment on this and I will see the performance and update you the status.
Thanks again.
Sundhar
I do not mention, my solution in production was to use a resource file with all the messages centrally defined. That worked well. Use this as a reference - http://www.astaticstate.com/2010/04/silverlight-localization-and-resource.html
Hi Jason,
can you give any tips on how to do meta data validations while using stored procedures with wcf ria. My primary question regarding meta data is not having meta data available on server side while using stored procedures, so i can't specify any attributes on data members. Any work around that you know is appreciated.
Than you,
Fan of your blog.
Annadatha - thanks for the feedback. I had no clue this Silverlight series would take off the way it did. I just happened to do a Silverlight engagement between some SharePoint work and this was a series was based on it.
As for your question, not exactly sure. Note this entire project was about integrating custom Oralce stored procedures with Silverlight application. So all of these patterns I used for calling those stored procedures. You are just not seeing the actual calls to the data access layer we wrote. I have patterns in here to do the validations on the client side or on the RIA Services side. Sorry for not being able to give exact answer.
Hi Jason,
Firt I would like to thank you for this great article.
This is also realted to the duplicat field validation, I really would like to know your thought.
After a while trying to find a solution, the only to do it is as you suggested (async validation). However, this must be with one condition, you have to submit directly after commit the edits whcih means saving one record at a time. No way to take advantage of the changeset that comes with ria services. Swapping two records values, would be something impossible.
What do think ?
Thanks in advance.
Eyad
Eyad,
Thanks for the feedback.
Sorry for delayed response. Have to say these were the best patterns I could come up with at the time because there was really not much out. To be honest, I have not messed around with this code in a while, so my memory is limited :-)
Hopefully this will give you some good direction in finding your answer.
Thanks,
Jason
Post a Comment