Manish Dalal's blog

Exploring .net!

Silverlight Business Application Part 4: Validation (async)

This is part four of Building Business Application with Silverlight series that showcases the basic building blocks of a data centric application.  

Series Link: Part 0, Part 1, Part 2, Part 3

In the previous post we saw how to validate data on client side in a sync fashion. User enters data, it is immediately validated and any errors are displayed to user. However not all data can be validated on client side.

Consider for example, we may be required to validate city from list of cities stored in some back end data store or a web service. In that case we will have to call back end application server and check for validity of city.

Lets add a Silverlight enabled WCF Service to the SilverlightWeb project to validate the City field.

image

CityService has just one function, IsCityValid to validate the city. Add following code to CityService.svc.cs file

[ServiceContract(Namespace = "")]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class CityService {
    [OperationContract]
    public bool IsCityValid(string city) {
        return ("Springfield" == city || "Shelbyville" == city) ;
    }
}

Now add Service Reference to Silvelight project (click on Discover button to display list of available services)

image

Now modify City setter in Person.cs class to call the service and validate city as follows (also add using Silverlight.CityServiceReference; and using System.Windows.Browser; namespace directives)

private string _city;

public string City {
    get { return _city; }
    set {
        if (value == _city) return;
        _city = value;
        OnPropertyChanged("City");
        ValidateCity(_city);
    }
}


private void ValidateCity(string city) {
    CityServiceClient proxy = new CityServiceClient();
    proxy.IsCityValidCompleted += new EventHandler<IsCityValidCompletedEventArgs>(proxy_IsCityValidCompleted);
    proxy.IsCityValidAsync(city, city);
}

void proxy_IsCityValidCompleted(object sender, IsCityValidCompletedEventArgs e) {
    if (!e.Result) {
        HtmlPage.Window.Alert(e.UserState.ToString() + " is not valid, please correct...");
    }
}

Now when user enters an invalid city, they are shown an error message.

image

Normally in a traditional application, you will just make a sync call, which will block the UI and on call return inform user if validation failed.

However with Silverlight, all calls to back end systems are async. That means that, as soon as call is issued, call returns and UI is no longer blocked and user is free to continue. Now in general situation, this is a good thing, you do not want to block the UI when carrying out lengthy backend operation.

But let say that we need to block the flow and let user know of the results of validation. We will need some way to prevent user from interaction with page and inform of the operation in progress. We will use a custom user control, Wait Control to accomplish this.

WaitControl for Async Validation

Add a new user control to the Silverlight project and name it WaitControl

image

Add following XAML code in WaitControl.xaml

<UserControl x:Class="Silverlight.WaitControl"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    >
    <Grid x:Name="LayoutRoot">
        <Rectangle HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Opacity="0.75" Fill="WhiteSmoke" />
        <Border CornerRadius="30" Background="CornflowerBlue" Width="600" Height="250">
            <TextBlock Text="{Binding}" Padding="60" FontSize="30"/>
        </Border>
    </Grid>
</UserControl>

We are using Rectangle to fill out the entire background. It is made semi transparent with Opacity of 0.75.

Add following code to WaitControl.xaml.cs (code behind)

public void StartWait() {
    this.Visibility = Visibility.Visible;
}

public void StopWait() {
    this.Visibility = Visibility.Collapsed;
}

On Start method we show the control by toggling the Visibility and on Stop we hide it.

Ok, now that we have the WaitControl, lets add it to the main page. First add xml name space declaration xmlns:src="clr-namespace:Silverlight" in the UserControl start tag as shown

<UserControl x:Class="Silverlight.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
    xmlns:src="clr-namespace:Silverlight"
    >

Now insert following code just before Grid closing tag in Page.xaml

<src:WaitControl Grid.RowSpan="2" x:Name="waitControl" Visibility="Collapsed"/>
 
And finally we need to provide methods to start and end wait. Add following code to Page.xaml.cs
 
public void StartWait(string message) {
    this.waitControl.DataContext = message;
    this.waitControl.StartWait();
}

public void EndWait(string message) {
    this.waitControl.DataContext = message;
    this.waitControl.StopWait();
}

With above in place, it is easy to display screen when making async call and hide it on call back. Modify City validation code in Person.cs class as follows

private void ValidateCity(string city) {
    CityServiceClient proxy = new CityServiceClient();
    proxy.IsCityValidCompleted += new EventHandler<IsCityValidCompletedEventArgs>(proxy_IsCityValidCompleted);
    ((Page)Application.Current.RootVisual).StartWait("Please wait, validating City");
    proxy.IsCityValidAsync(city, city);
}

void proxy_IsCityValidCompleted(object sender, IsCityValidCompletedEventArgs e) {
    ((Page)Application.Current.RootVisual).EndWait(null);
    if (!e.Result) {
        HtmlPage.Window.Alert(e.UserState.ToString() + " is not valid, please correct...");
    }
}

Now when you make a change to city filed, it will display a waiting screen and block the user.

During the call

image

At end of the call

image

Above is certainly not the only option, though easiest! Other option would be to just block city cell (make is read only) and disable any action that would force UI/page changes(like save etc), but leave rest of the UI as is. This way if user is doing data entry, they can continue doing work and while UI is validating data in background.

If validation call to back end returns really fast as in our example, user just sees screen flicker because of WaitScreen. It will be nice to prevent WaitScreen from showing if validation code returns back in few seconds. One easy way to accomplish that behavior is with use of animation.

Add following xaml code to WaitControl.xaml

<UserControl x:Class="Silverlight.WaitControl"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    >
    <UserControl.Resources>
        <Storyboard x:Name="showStoryBoard">
            <DoubleAnimation 
                Storyboard.TargetName="LayoutRoot"
                Storyboard.TargetProperty="Opacity"
                From="0.0" To="1.0" BeginTime="0:0:1" Duration="0:0:1" AutoReverse="False"  />
        </Storyboard>
    </UserControl.Resources>
    <Grid x:Name="LayoutRoot" Opacity="0.0">
        <Rectangle HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Opacity="0.75" Fill="WhiteSmoke" />
        <Border CornerRadius="30" Background="CornflowerBlue" Width="600" Height="250">
            <TextBlock Text="{Binding}" Padding="60" FontSize="30"/>
        </Border>
    </Grid>
</UserControl>
 
Here we are delaying start using BeginTime animation property. Now we just call begin animation at start of wait. Replace current StartWait and StopWait functions in WaitControl.xaml.cs code behind with following ones.
 
public void StartWait() {
    this.Visibility = Visibility.Visible;
    this.showStoryBoard.Begin();
}

public void StopWait() {
    this.showStoryBoard.Stop();
    this.Visibility = Visibility.Collapsed;
}
 
Now when you call StartWait, display is delayed for 1 sec. If call return back in under 1 sec, animation is stopped, and user never sees the wait screen and the resulting flicker.
Other Validation Considerations

We have just seen how to validate individual field/data values, but we also need to track overall state of the object. For instance if Age is not valid, that instance of Person will also not be valid and should not be saved to database.

There are couple of options. Easiest is to maintain separate invalid flag for each field and/or business rules being validated. Then have a property that returns a validate state flag. One nice thing would be to display that as an indicator to user. You can even go further and aggregate various states - error, new, changed and update indicator color by data binding to state!

It will also be good idea to refractor all validation logic into a separate validator class, PersonValidator and delegate validation and error state management to that class, thus separating domain model from validation logic. This has side benefit of being able to validate data at any time(if needed), not just during property changes.

Comments

Ben Hayat said:

Excellent articles (all of them). Hopefully you can extend these using ADO.Net Data Services.

Thanks!

..Ben

# September 5, 2008 2:23 PM

Ben Hayat said:

in the following code:

private void ValidateCity(string city) {

   CityServiceClient proxy = new CityServiceClient();

   proxy.IsCityValidCompleted += new EventHandler<IsCityValidCompletedEventArgs>(proxy_IsCityValidCompleted);

   ((Page)Application.Current.RootVisual).StartWait("Please wait, validating City");

   proxy.IsCityValidAsync(city, city);

}

when calling the StartWait method, why can't you prefix that with waitcontrol.StartWait instead of ((Page)Application.Current.RootVisual).StartWait

Thanks!

..Ben

# September 5, 2008 2:38 PM

manish.dalal said:

Hi Ben,

You can certainly call waitcontrol.StartWait, reason for ((Page)Application.Current.RootVisual).StartWait is to server as indirection.

Acutally idea is to abstract out code to a seperate class like ApplicaitonServices that provides StartWait, EndWait, Altert and other methods, I just decided to make it easy to follow and not introduce indirection.

-Manish

# September 5, 2008 5:36 PM

Ben Hayat said:

Thank you Manish;

Your article were very direct and useful. And thanks for the answer!

..Ben

# September 5, 2008 6:58 PM

jemiller said:

Thanks for the article. There is a problem though. The problem is that the setter is still setting the value to an invalid one. IMHO, this should never happen. Any idea why Microsoft left ValidationRule out of Silverlight? I hope they add it.

# September 10, 2008 4:56 PM

jemiller said:

I created a Connection Suggestion asking that ValidationRule be added to Silverlight. If you agree with me, please rate it.

connect.microsoft.com/.../ViewFeedback.aspx

# September 10, 2008 5:01 PM

manish.dalal said:

jemiller,

Actually setting invalid value is by design. This is so that user is informed as to what they entered and which value it is not valid. This does move burden on to application to track invalid values, but user is not surprised by disappearing values!

# September 10, 2008 7:37 PM

... said:

Gute Arbeit hier! Gute Inhalte.

# March 3, 2009 7:02 PM