Silverlight – Easy Data Validation

It’s impossible to have a typical forms over data business application without handling validation in the UI.

Silverlight 3 has excellent simple to use support for implementing validation in your code.  This post will show a simple scenario to implement data validation.  The code for this post can be found here.

 

Steps:

1.    Create a Silverlight application.

2.    Add a class called Person and add the following code to the Person class:

 

        private string firstName;

        private string lastName;

        private string email;

 

        public string FirstName

        {

            get { return firstName; }

            set

            {

                if (string.IsNullOrEmpty(value))

                    throw new ArgumentException("First name in required.");

                firstName = value;

            }

        }

 

        public string LastName

        {

            get { return lastName; }

            set

            {

                if (string.IsNullOrEmpty(value))

                    throw new ArgumentException("Last name in required.");

                lastName = value;

            }

        }

 

        public string Email

        {

            get { return email; }

            set

            {

                System.Text.RegularExpressions.Regex r = new System.Text.RegularExpressions.Regex(@"[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?");

                if (!r.IsMatch(value))

                    throw new ArgumentException("A valid email is required.");

                email = value;

            }

        }

 

Note that this class has 3 properties – FirstName, LastName and Email.  Validation is carried out in the setter of each property to determine if a valid value is supplied.  If validation fails for a property an exception (in this case ArgumentException) is thrown.  This is the mechanism to tell Silverlight that something bad happened in setting the value of an object.

 

3.    Add the following XAML to the LayoutRoot Grid in the MainPage.xaml file:

 

        <Grid.ColumnDefinitions>

            <ColumnDefinition Width="50"></ColumnDefinition>

            <ColumnDefinition Width="100"></ColumnDefinition>

            <ColumnDefinition Width="100"></ColumnDefinition>

            <ColumnDefinition Width="50"></ColumnDefinition>

        </Grid.ColumnDefinitions>

        <Grid.RowDefinitions>

            <RowDefinition Height="50"></RowDefinition>

            <RowDefinition Height="25"></RowDefinition>

            <RowDefinition Height="25"></RowDefinition>

            <RowDefinition Height="25"></RowDefinition>

            <RowDefinition Height="25"></RowDefinition>

            <RowDefinition Height="100"></RowDefinition>

        </Grid.RowDefinitions>

        <TextBlock Text="First Name:" Grid.Column="1" Grid.Row="1" VerticalAlignment="Center" ></TextBlock>

        <TextBlock Text="Last Name:" Grid.Column="1" Grid.Row="2" VerticalAlignment="Center" ></TextBlock>

        <TextBlock Text="Email:" Grid.Column="1" Grid.Row="3" VerticalAlignment="Center" ></TextBlock>

        <TextBox x:Name="FirstNameTextBox" Grid.Column="2" Grid.Row="1" Text="{Binding Person.FirstName, Mode=TwoWay, ValidatesOnExceptions=True, NotifyOnValidationError=True}"></TextBox>

        <TextBox x:Name="LastNameTextBox" Grid.Column="2" Grid.Row="2" Text="{Binding Person.LastName, Mode=TwoWay, ValidatesOnExceptions=True, NotifyOnValidationError=True}"></TextBox>

        <TextBox x:Name="EmailTextBox" Grid.Column="2" Grid.Row="3" Text="{Binding Person.Email, Mode=TwoWay, ValidatesOnExceptions=True, NotifyOnValidationError=True}"></TextBox>

        <Button x:Name="SaveButton" Content="Save" Click="SaveButton_Click" Grid.Column="2" Grid.Row="4" ></Button>

        <ListBox x:Name="ErrorsListBox" Grid.Column="1" Grid.ColumnSpan="2" Grid.Row="5" BorderThickness="0"  DisplayMemberPath="ErrorContent" Height="auto" Foreground="Red" FontWeight="Bold" >

        </ListBox>

 

This XAML defines the UI for the application and binds 3 text boxes to FirstName, LastName and Email properties of an object of our Person type.

The ErrorsListBox control at the bottom will be used to display any validation errors.

 

The key points to note are on the text box bindings:

 

a.    First their mode = TwoWay – so when they are updated in the UI their data source is updated; i.e. the setters of the properties on the Person object are called with updated values.

b.    ValidatesOnExceptions = True – this tells Silverlight that if an exception occurs when the Silverlight Property System is updating the setters of a bound property, the validation template for the control is displayed to the user with the details of the exception (i.e. the exception’s message string) that was thrown in the bound setter.

c.    NotifyOnValidationError – True – this tells Silverlight to raise a routed event of  type BindingValidationError to be routed up the visual tree that can be handled by a parent control – in our case the LayoutRoot Grid.

 

4.    Add the following attribute to the LayoutRoot Grid:

 

BindingValidationError="LayoutRoot_BindingValidationError"

 

This adds the event handler for when the text boxes cause Silverlight to raise the routed event of type BindingValidationError and allows us to add logic to the event handler to handle the routed error.

 

5.    Replace the MainPage constructor in the MainPage class in the MainPage.xaml.cs class with the following code:

 

        private ObservableCollection<ValidationError> errors = new ObservableCollection<ValidationError>();

 

        public ObservableCollection<ValidationError> Errors

        {

            get { return errors; }

            set { errors = value; }

        }

        private Person person = new Person();

 

        public Person Person

        {

            get { return person; }

            set { person = value; }

        }

       

        public MainPage()

        {

            InitializeComponent();

            this.Loaded += new RoutedEventHandler(MainPage_Loaded);

        }

 

        void MainPage_Loaded(object sender, RoutedEventArgs e)

        {

            this.DataContext = this;

        }

 

This defines an ObservableCollection to hold the ValidationErrors that occur when binding validation fails (we will get to this in a moment).  The collection is made available through a property so it can be bound to by the ListBox.

A Person object is instantiated and made available through a property in this class so that the three text boxes can be bound to its properties.

When the page is loaded the DataContext of the UserControl is bound to this class.

 

6.    Paste the following code into the MainPage class:

 

        private void LayoutRoot_BindingValidationError(object sender, ValidationErrorEventArgs e)

        {

            switch(e.Action)

            {

                case ValidationErrorEventAction.Added:

                    errors.Add(e.Error);

                    break;

                case ValidationErrorEventAction.Removed:

                    errors.Remove(e.Error);

                    break;

            }

            ErrorsListBox.ItemsSource = this.errors;

        }

 

        private void SaveButton_Click(object sender, RoutedEventArgs e)

        {

            foreach (DependencyObject control in LayoutRoot.Children)

            {

                TextBox textbox = control as TextBox;

                if (textbox != null)

                {

                    BindingExpression expr = textbox.GetBindingExpression(TextBox.TextProperty);

                    expr.UpdateSource();

                }

            }

        }

 

This code defines two event handlers:

 

a.    The first is the event handler called when a child control of the LayoutRoot Grid control throws a binding exception.  For each exception thrown this event handler will be called by Silverlight.  Notice we identify if the ValidationError object passed into this event handler has been added or removed – i.e. a validation failed or passed (i.e. the error was removed by Silverlight).  If the validation error is added, it is added to our errors collection; if it was removed it is removed from our errors collection.  The errors collection is then bound to the list box so the error messages are displayed to the users.

b.    The second loops through the text boxes in the UI and forces their binding expressions to update their bound data sources – forcing the setters on the Person object to be called.

 

7.    Run the application:

 

 

8.    Click the save button:

 

 

See that all fields in the Person object threw exceptions that are displayed in the list box and the text boxes are highlighted in red.

 

9.    Set focus to the first name text box.

 

 

Note the popup label that displays the error message associated with the Person object property FirstName.

 

10.  Type a character in the first name textbox and tab out:

 

 

Notice the validation error label is no longer displayed for the first name property and the error message has disappeared from the list box.  Also note the textbox for the first name is no longer red.

 

11. Type a character in the last name textbox and tab out and enter a valid email address in the email text box and click the Save button:

 

 

Congratulations!  You have implemented validation in a custom object and hooked it up to use the standard Silverlight validation handlers.

 

What have we done?

 

We have created our own custom object with validation on its properties.

 

We have bound the custom object to Silverlight controls.

 

With configuration we have hooked up the standard Silverlight validation mechanisms for the bound controls.

 

We have hooked up the normal mechanism for a container control to trap and handle validation errors from child controls and display them to the user.

 

Conclusion

Silverlight has build in UI validation handling features that easily allow a developer to hook up validation in custom classes to controls in the UI layer and to capture and manage validation errors at a higher level in the control hierarchy in whatever way makes sense for their application.  Once again we see how Silverlight supports developing custom business applications with the infrastructure it delivers – with minimal coding by the developer to harness the value add supplied by Silverlight.

 

Silverlight - Opening and saving files

Many business applications need the ability to open and save files from and to the user’s local computers.  With internet based applications the issues surrounding this are based on security and the rights the application receives to affect the user’s computer.

Silverlight has the open and save file dialogs which enable the opening and saving of files to the user’s computer, while restricting the need for the application developer to need to know anything about the client environment.  This post will cover their simple and straightforward use and how to serialize and de-serialize data objects into and from those files using Silverlight’s support for JSON data serialization.  The code for this post can be found here.

 

Steps:

1.    Create a Silverlight application.

2.    Add a reference to the System.Runtime.Serialization and System.ServiceModel.Web .NET assemblies.  System.Runtime.Serialization adds support for the DataContract and DataMember attributes that define what classes and properties of those classes can be serialized.  System.ServiceModel.Web adds support for JSON serialization using the DataContractJsonSerializer class.

3.    Add a class called Person and overwrite the class with the following code:

 

using System.Runtime.Serialization;

 

namespace OpenAndSave

{

    [DataContract]

    public class Person

    {

        private string firstName;

        private string lastName;

        private string interestingFact;

 

        [DataMember]

        public string FirstName

        {

            get { return firstName; }

            set { firstName = value; }

        }

       

        [DataMember]

        public string LastName

        {

            get { return lastName; }

            set { lastName = value; }

        }

 

        [DataMember]

        public string InterestingFact

        {

            get { return interestingFact; }

            set { interestingFact = value; }

        }

    }

}

 

4.    In the MainPage.xaml file add the following reference at in the UserControl xaml tag:

 

xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"  

 

5.    In the MainPage.xaml file add the following xaml in the LayoutRoot grid:

 

        <Grid.ColumnDefinitions>

            <ColumnDefinition Width="20%"></ColumnDefinition>

            <ColumnDefinition Width="*"></ColumnDefinition>

            <ColumnDefinition Width="20%"></ColumnDefinition>

        </Grid.ColumnDefinitions>

        <Grid.RowDefinitions>

            <RowDefinition Height="20%"></RowDefinition>

            <RowDefinition Height="*"></RowDefinition>

            <RowDefinition Height="50"></RowDefinition>

            <RowDefinition Height="50"></RowDefinition>

            <RowDefinition Height="50"></RowDefinition>

            <RowDefinition Height="50"></RowDefinition>

            <RowDefinition Height="20%"></RowDefinition>

        </Grid.RowDefinitions>

        <data:DataGrid x:Name="PeopleDataGrid" Grid.Column="1" Grid.Row="1">

        </data:DataGrid>

        <Button x:Name="AddButton" Content="Add A Person" Grid.Column="1" Grid.Row="2" Click="AddButton_Click"></Button>

        <Button x:Name="RemoveButton" Content="Remove A Person" Grid.Column="1" Grid.Row="3" Click="RemoveButton_Click"></Button>

        <Button x:Name="LoadButton" Content="Load A Person List" Grid.Column="1" Grid.Row="4" Click="LoadButton_Click"></Button>

        <Button x:Name="SaveButton" Content="Save Person List" Grid.Column="1" Grid.Row="5" Click="SaveButton_Click"></Button>

 

The above xaml defines layout of the grid, adds a datagrid and four buttons that will be used to add a person object to the datagrid, remove the selected person object from the datagrid, save the person objects currently in the datagrid to a file on the user’s computer and load person objects from a file on the user’s computer into the datagrid.

 

6.    In the MainPage.xaml.cs file add the following using statements to the top of the file:

 

using System.Collections.ObjectModel;

using System.Runtime.Serialization.Json;

using System.IO;

 

7.    In the MainPage.xaml.cs file replace the constructor with the following code:

 

        private ObservableCollection<Person> people = new ObservableCollection<Person>();

 

        public MainPage()

        {

            InitializeComponent();

            this.Loaded += new RoutedEventHandler(MainPage_Loaded);

        }

 

        void MainPage_Loaded(object sender, RoutedEventArgs e)

        {

            PeopleDataGrid.ItemsSource = people;

        }

 

        private void AddButton_Click(object sender, RoutedEventArgs e)

        {

            people.Add(new Person());

        }

 

        private void RemoveButton_Click(object sender, RoutedEventArgs e)

        {

            if (PeopleDataGrid.SelectedItem != null)

                people.Remove((Person)PeopleDataGrid.SelectedItem);

        }

 

In this code we define an ObservableCollection to hold the person objects and when the page is loaded we bind the collection to the datagrid.

When the add button is clicked we add a new person object to the collection.

When the remove button is clicked we remove the selected person object from the collection.

 

8.    Run the application.  You can now add and remove person objects to and from the datagrid:

 

 

OK.  We have done nothing with serialization or opening and saving files yet.  That’s next.

 

9.    Add the following code to the MainPage class in the MainPage.xaml.cs file:

 

        private void SaveButton_Click(object sender, RoutedEventArgs e)

        {

            SaveFileDialog save = new SaveFileDialog();

            save.Filter = "Person Files (.SLJSONtxt)|*.SLJSONtxt|All Files (*.*)|*.*";

            save.FilterIndex = 1;

 

            bool? saveClicked = save.ShowDialog();

            if (saveClicked == true)

            {

                System.IO.Stream stream = save.OpenFile();

                DataContractJsonSerializer ser = new DataContractJsonSerializer(people.GetType());

                ser.WriteObject(stream, people);

                stream.Close();

            }

        }

 

There are two things to go over in this code:

 

a.    The SaveFileDialog object is used to handle all the displaying and configuration of the save file window displayed to the user.  Notice the Filter property that defines the extension of the file we are going to display and save the file on disk with (‘.SLJSONtxt’).

b.    If the user clicks OK in the save file window the result is true.

c.    The OpenFile member of the SaveFileDialog object returns an IO stream for the file that has been created by the user entering the name and location of the file in the save file window that was displayed.

d.    Once we have the stream of the open file, we get to use the DataContractJsonSerializer to serialize the collection of person objects into the file stream.

e.    The stream is closed and the JSON format serialized data is written to the file and location specified by the user.

 

10. Run the app, add some records and click the save person list button.

 

 

11. Enter the location and name of the file (I used c:\ and test) and click save.

12. Using Notepad.exe open the file you saved (mine was c:\test.SLJSONtxt):

 

 

Note the information from the person objects in the datagrid was successfully serialized in JSON format into the file we specified through the save file window!

 

13. Add the following code to the MainPage class in the MainPage.xaml.cs file:

 

        private void LoadButton_Click(object sender, RoutedEventArgs e)

        {

            OpenFileDialog open = new OpenFileDialog();

            open.Filter = "Person Files (.SLJSONtxt)|*.SLJSONtxt|All Files (*.*)|*.*";

            open.FilterIndex = 1;

 

            open.Multiselect = false;

 

            bool? openClicked = open.ShowDialog();

            if (openClicked == true )

            {

                Stream stream = open.File.OpenRead();

                DataContractJsonSerializer ser = new DataContractJsonSerializer(people.GetType());

                people = ser.ReadObject(stream) as ObservableCollection<Person>;

                stream.Close();

                PeopleDataGrid.ItemsSource = people;

            }

        }

 

You see that the use of the OpenFileDialog object is very similar to the use of the SaveFileDialog.

The primary difference is you use the OpenRead() member of the FileInfo object returned from the File property to open the IO stream of the file selected by the user.

Next the code de-serializes the stream object into the collection of persons using the DataContractSerializer object ReadObject method.  The deserialized collection of person objects are then bound to the datagrid.

 

14. Run the application, click the load a person list button and select the file we saved previously.

 

 

 

 

Congratulations!  You have implemented your own application to save and load data files.

 

 What have we done?

 

We have implemented our own custom serialize-able type.

 

We have implemented a user interface to manage a collection of objects of our type.

 

We have enabled the user to save a local copy of those objects in our own custom file type.

 

We have enabled the user to load a local copy of those objects from our own custom file type.

 

We have implemented JSON serialization and de-serialization of our custom type.

 

Conclusion

With very little coding we have taken advantage of Silverlight’s ability to allow users to open and save files and used Silverlight’s out of the box serialization capability for JSON.  These are common requirements of business applications for users to save a local copy of data and import it later into the application, once again showing Silverlight is a very easy technology to use to achieve such goals.

RIA Services with Silverlight for East Bay .NET User Group FUNdamentals

I was asked to present on RIA Services with Silverlight for the FUNdamentals session of the East Bay .NET User Group last week.  Even though I only had 30 minutes to present on the topic, I decided to show the full development of a CRUD application.  With RIA Services this is a straight forward thing to do and still allowed time to explain the concepts in use.

The solution found here is the code that was built during the demonstration at the user group.  It shows the use of Entity Framework to connect to a SQL Server database and generate a model for the Person table, combined with a Domain Service Class to handle the generation of the RIA Services CRUD operations to wrap the entity model for our project.

Once the meta data class for the Person class has been generated I added a StringLength attribute to specify a minimum and maximum length of the InterestingInfo property with a custom error message, we also added a shared partial class for the Person class adding a calculated read only property (FullName) - showing the use of the Person.shared.cs naming convention to define that the code should be shared on the Silverlight client as well as the web server.

The Silverlight application template used was the Silverlight Business Application.  The ~/Views/Home.xaml page had a datagrid added to it with two buttons - add and delete.  When the page is initialized the Person object graph of the Domain Service Context is loaded and bound to the items source of the datagrid. 

When the grid row exit edit event is fired the changes made in the datagrid are committed to the Domain Service Context and the changes are submitted so they are committed to the database.

When the add button is clicked a new Person object is added to the Person object graph of the Domain Service Context.  When the delete button is clicked the selected Person object in the datagrid is deleted from the Domain Service Context and the changes are submitted.

The above solution shows a simple CRUD application built with RIA Services to perform the heavy lifting and code generation for accessing shared functionality across a two tier environment.

 

Silverlight - Loading Control Libraries On Demand

As with any client delivered over the browser, one key part of user experience is enabling the user experience to start – i.e. not having the user wait 10 minutes until they can do anything.  The flip side is that RIAs many times have large assemblies and content that needs to be downloaded for to put the Rich in RIA.

 

Silverlight achieves this by allowing the developer to use a WebClient object to download a stream of whatever resource it has at a known Uri.  In our situation we want to download a Silverlight Control Library containing our Rich controls after the application has started.

 

The completed solution can be downloaded from here.

 

Steps:

 

1.    Create a Silverlight application project called LoadOnDemand and choose to add a web project for testing

2.    Note the physical location of the web project ClientBin folder - you will need this later

3.    Add a new project of type Silverlight Control Library to the LoadOnDemand solution called CustomControls

4.    Add a new user control to the CustomControls project called LoadOnDemandCustomControl

5.    In the LoadOnDemandCustomControl.xaml file remove the width and height attributes from the User Control declaration

6.    In the LoadOnDemandCustomControl.xaml file replace the LayoutRoot grid with the following XAML

 

    <Grid x:Name="LayoutRoot" >

        <Border BorderThickness="5" CornerRadius="12" BorderBrush="Firebrick"  Background="Orange" >

            <StackPanel Orientation="Vertical" >

                <TextBlock Text="Custom Control"></TextBlock>

                <TextBlock Text="Loaded @ " x:Name="txtLoaded"></TextBlock>

            </StackPanel>

        </Border>

    </Grid>

7.    In the LoadOnDemandCustomControl.xaml.cs file replace the constructor with the following code

 

        public LoadOnDemandCustomControl()

        {

            InitializeComponent();

            this.Loaded += new RoutedEventHandler(LoadOnDemandCustomControl_Loaded);

        }

 

        void LoadOnDemandCustomControl_Loaded(object sender, RoutedEventArgs e)

        {

            txtLoaded.Text += DateTime.Now.ToString();

        }

 

This displays the time the control is loaded in the display of the control.

 

8.    In the build properties of the CustomControls project, set the Output Path to the location of the web application’s ClientBin directory (the physical location you noted in step 2).

 

9.    In the LoadOnDemand project, add a reference to the CustomControls project and mark the Copy Local property to False.

 

10. In the MainPage.xaml file in the LoadOnDemand project replace the LayoutRoot Grid with the following XAML

 

    <Grid x:Name="LayoutRoot">

        <StackPanel x:Name="stkControls" Orientation="Vertical" Width="200" HorizontalAlignment="Center" VerticalAlignment="Center" >

            <Button Content="Load Control" x:Name="btnLoad" Click="btnLoad_Click"></Button>

        </StackPanel>

    </Grid>

 

This layout provides a stack panel containing a button.  When the button is clicked a Silverlight Control Library will be downloaded and a user control from that library will be added to the stack panel

 

11. In the MainPage.xaml.cs file paste the following code after the constructor

 

        private bool customControlsLoaded = false;

 

        private void btnLoad_Click(object sender, RoutedEventArgs e)

        {

            if (!customControlsLoaded)

            {

                WebClient wc = new WebClient();

                wc.OpenReadCompleted += new OpenReadCompletedEventHandler(wc_OpenReadCompleted);

                wc.OpenReadAsync(new Uri("CustomControls.dll", UriKind.Relative));

            }

            else

                AddCustomControl();

        }

 

        void wc_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)

        {

            if (e.Error == null && e.Result != null)

            {

                AssemblyPart part = new AssemblyPart();

                part.Load(e.Result);

                customControlsLoaded = true;

                AddCustomControl();

            }

        }

 

        private void AddCustomControl()

        {

            CustomControls.LoadOnDemandCustomControl ctrl = new CustomControls.LoadOnDemandCustomControl();

            stkControls.Children.Add(ctrl);

        }

 

If the CustomControls Silverlight Controls Library has not been loaded before, then the button click event creates a WebClient object and defines an event handler for the open read completed event.  The resource Uri for the CustomControls.dll Silverlight Control Library is provided for the OpenReadAsync function.  Note the Uri is relative as the Silverlight Control Library sits in the ClientBin folder of the web app – the same location as the XAP file that this Silverlight Application is loaded from and where we set the output path for the build of the CustomControls project.

Once the assembly has been downloaded it is loaded into an AssemblyPart (which loads the assembly into the current Silverlight application context).  An instance of the LoadOnDemandCustomControl from the control library is then created and added to the stack panel in the UI.

If the CustomControls library has already been loaded when the button is clicked then the library doesn’t need to be loaded and a custom control can simply be added to the stack panel.

 

12. Add a breakpoint to the btnLoadClick event in the MainPage.xaml.cs file to follow the code and verify whether the CustomControls library is reloaded after the first time.

13. Run the application

14. Click the button - the breakpoint will be hit and you will see that the CustomControls library is downloaded.  Follow the code to see the the custom control being added to the stack panel.  You will see the custom control displayed with the date and time when it was loaded.

15. Click the button a few more times and see that the custom control library is not downloaded again and see more custom controls displayed with their respective dates and times of loading.

 

    

 

Congratulations!!!! You have implemented loading on demand of a Silverlight Control Library

 

What have we done?

We have created a Silverlight Control Library and enabled it to be downloaded on demand.

 

We have created a Silverlight application that is coded with strong typing for the user controls in the Silverlight Control Library.

 

We have programmatically controlled the download of the Silverlight Control Library and it’s loading into the current application context of the Silverlight Application.

 

We have instantiated objects of the user control in the dynamically loaded Silverlight Control Library.

 

Conclusion

Using load on demand will allow you to control when resources and assemblies are downloaded within your application and will stop the user from having to wait for the whole application to download before being able to use it.  These methods allow the developer very granular control and logic of how and when to load their dependencies; for instance you could make choices of what logic to download based on the role of the logged in user or what functionality the user chooses to use.  Why load more than you need to consume?

Silverlight – Escaping the browser experience

Silverlight apps have the wonderful ability to be installed locally and run from the desktop.  This allows Silverlight apps to be deployed very easily over the web and allow thick client like deployment and operability.  Just with detached applications if you expect client server behavior – such as connecting to a network resource or service - you need to handle the situation when there is no network.  Also if you are running the application outside of the browser then you need to ensure you either gracefully limit any functionality dependent on the browser – e.g. using the HTML bridge for interoperation with web page DOM functionality.

 

The application that can be downloaded from here is a simple Silverlight application where the user can change the background of the page by selecting the color from a drop down list.  No rocket science here.  We are not handling the network connectivity detection issues as we are not going over the network.

 

Enabling this to run out of the browser is easy and this is what we will demonstrate with this post.

 

Silverlight will also validate when an application is run outside of the browser whether an updated version exists from the URI the application was hosted from and will download and install the updates for the user.  The developer does not need to do anything to handle the automated updates handling for the user – Silverlight provides it all for you.

 

Steps

1.    Create yourself a simple Silverlight application that does not interact with a service or interact with the HTML bridge.

2.    Open the properties for the application

3.    Select the “Enable running application out of the browser” check box on the Silverlight tab

 

 

4.    Click the “Out-of-Browser Settings…” button

 

 

5.    Configure whatever settings you deem appropriate.  Note if you don’t supply icon files then defaults will be used.

6.    Run the application

 

 

7.    Right click the application and select “Install [Application Name] onto this computer…”

 

 

I selected to install a shortcut on the desktop.

 

8.    Click OK and the application is installed and run

 

 

 

9.    Select a color and watch as the app works

 

10.  Close the application

11. Run the application from the icon installed on your desktop and select a color

 

12. Right click the application and choose the “Remove this application…” option

 

 

13. Click Yes.  The application will be removed from your computer.

 

Congratulations!!!! You have developed and deployed a Silverlight application running outside of the browser.

 

What have we done?

We have configured a Silverlight application to be able to be run outside of the browser.

We have installed the Silverlight application on our computer and run it outside of the browser.

We have removed the Silverlight application from our computer.

 

Conclusion

Silverlight has a great mechanism allowing applications to be installed on a computer and run outside of the browser.  Whether to run the application in the browser or install it and run it standalone from the browser is left to the user.  Silverlight provides the infrastructure to handle automatic updates to the application without the developer having to develop the infrastructure to manage it.  The only consideration the developer needs to handle is how to handle the disconnected scenario and any dependencies on the browser – e.g. the HTML Bridge for when the application runs in the browser interacting with the web page in which it is hosted.

HTML Bridge - Silverlight JavaScript Interop

When building RIAs there is sometimes both the need for complex rich interfaces and simpler standard html interfaces.  This is more common when introducing the rich experience into an existing web application where it makes sense to maintain some of the existing functionality and introduce the power of Silverlight in targeted functions.

In such a circumstance when we have business functionality in Silverlight and in ASP.NET/HTML pages - wouldn't it be great if we had the ability to interop between both the Silverlight and JavaScript in the browser?  Silverlight provides this ability in the HTML Bridge.

In this post, we'll explore how to use managed objects in JavaScript, how to attach managed code delegates to javascript events and how to call managed code functions from JavaScript.

Usage scenarios for this post:

a.    A user will enter information about a person (name and hobby) into fields in a Silverlight application, click a button and the information will be sent to a JavaScript function that will display the values in a HTML table

b.    A user will enter numeric values in the HTML page and then click a button whose click event will be handled in the Silverlight application, which will retrieve the vales from the HTML document, sum them and display the values and the result in the Silverlight application

c.    A user will select a color from a drop down in the HTML page and the on change JavaScript event will call a managed code function on the Silverlight page in displayed in the Silverlight application with the color selected, which will change the background color of the Silverlight page

Steps:

1.    Create a new silverlight application named HTMLBridge

2.    Choose to add a test web application for the new solution

3.     Replace the LayoutRoot grid in the MainPage.xaml file with the following XAML

  <Grid x:Name="LayoutRoot" Background="LightBlue" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"   >

        <Grid.ColumnDefinitions>

            <ColumnDefinition Width="100"></ColumnDefinition>

            <ColumnDefinition Width="*"></ColumnDefinition>

        </Grid.ColumnDefinitions>

        <Grid.RowDefinitions>

            <RowDefinition Height="30"></RowDefinition>

            <RowDefinition Height="30"></RowDefinition>

            <RowDefinition Height="30"></RowDefinition>

            <RowDefinition Height="30"></RowDefinition>

            <RowDefinition Height="30"></RowDefinition>

            <RowDefinition Height="30"></RowDefinition>

            <RowDefinition Height="30"></RowDefinition>

            <RowDefinition Height="30"></RowDefinition>

            <RowDefinition Height="30"></RowDefinition>

        </Grid.RowDefinitions>

 

        <TextBlock Text="Person Info" FontSize="20" Grid.Column="0" Grid.Row="0" Grid.ColumnSpan="2" ></TextBlock>

 

        <TextBlock Text="Name" Grid.Column="0" Grid.Row="1" ></TextBlock>

        <TextBlock Text="Hobby" Grid.Column="0" Grid.Row="2" ></TextBlock>

 

        <TextBox x:Name="txtName" Grid.Column="1" Grid.Row="1"></TextBox>

        <TextBox x:Name="txtHobby" Grid.Column="1" Grid.Row="2"></TextBox>

        <Button x:Name="btnSendPerson" Content="Send Person" Grid.Column="1" Grid.Row="3" Width="100" Click="btnSendPerson_Click"></Button>

 

        <TextBlock Text="Calculation" FontSize="20" Grid.Column="0" Grid.Row="4" Grid.ColumnSpan="2" ></TextBlock>

 

        <TextBlock Text="x" Grid.Column="0" Grid.Row="5" ></TextBlock>

        <TextBlock Text="y" Grid.Column="0" Grid.Row="6" ></TextBlock>

        <TextBlock Text="result" Grid.Column="0" Grid.Row="7" ></TextBlock>

 

        <TextBlock x:Name="lblX" Grid.Column="1" Grid.Row="5"></TextBlock>

        <TextBlock x:Name="lblY" Grid.Column="1" Grid.Row="6"></TextBlock>

        <TextBlock x:Name="lblResult" Grid.Column="1" Grid.Row="7"></TextBlock>

     

    </Grid>

 

This XAML defines entry fields for name and hobby for person information and a display section for calculation results and numeric values retrieved from the HTML document.

 

4.    Add the following line at the top of the MainPage.xaml.cs file

 

using System.Windows.Browser;

 

5.    Paste the following code after the constructor in the MainPage class

 

        private void btnSendPerson_Click(object sender, RoutedEventArgs e)

        {

            PersonInfo p = new PersonInfo() { Name = txtName.Text, Hobby = txtHobby.Text };

            HtmlPage.Window.Invoke("ReceivePersonInfo", p);

        }

 

        [ScriptableMember()]

        public void ChangeColor(string color)

        {

            switch (color)

            {

                case "Red":

                    this.LayoutRoot.Background = new SolidColorBrush(Colors.Red);

                    break;

                case "Green":

                    this.LayoutRoot.Background = new SolidColorBrush(Colors.Green);

                    break;

                case "Orange":

                    this.LayoutRoot.Background = new SolidColorBrush(Colors.Orange);

                    break;

                default:

                    this.LayoutRoot.Background = new SolidColorBrush(Colors.LightGray);

                    break;

            }

        }

 

6.    Paste the following code after the MainPage class within the HTMLBridge namespace

 

 

    [ScriptableType()]

    public class PersonInfo

    {

        public string Name { get; set; }

        public string Hobby {get; set; }

    }

 

 

At the bottom of this section of code is the PersonInfo class.  This is the managed type object that will be passed to a JavaScript function, which will display the Name and Hobby entered in the Silverlight application.  Please note the ScriptableType attribute which says that all public properties and methods on the class will be accessible by JavaScript.

 

At the top of this code is the click event handler for the button in the XAML.  When the user clicks it a new PersonInfo object is created with the information entered via the UI. Then the JavaScript function “ReceivePersonInfo” is called with the PersonInfo object as a parameter.  This is all we need to do to pass a managed object to JavaScript.

 

After the button click event handler is the function ChangeColor that accepts a string and based on its value changes the background color of the page.  Note that the function is decorated with the ScriptableMember attribute.  This makes this function available for JavaScript to call for this class.

 

7.    Add the following line at the top of the App.xaml.cs file

 

using System.Windows.Browser;

 

8.    Replace the code in the Application_Startup event handler with the following

 

            MainPage page = new MainPage();

 

            this.RootVisual = page;

 

            HtmlDocument doc = HtmlPage.Document;

 

            doc.GetElementById("btnSendCalculation").AttachEvent("click", new EventHandler((o, args) =>

            {

                double x = double.Parse(doc.GetElementById("txtX").GetAttribute("value"));

                double y = double.Parse(doc.GetElementById("txtY").GetAttribute("value"));

                double result = x + y;

 

                page.lblX.Text = x.ToString();

                page.lblY.Text = y.ToString();

                page.lblResult.Text = result.ToString();

 

            }));

 

            HtmlPage.RegisterScriptableObject("mainPage", page);

 

This code instantiates a MainPage object as the RootVisual of the application.  Next a reference to the Document object of the HTML page the Silverlight application is hosted in is retrieved.  The btnSendCalculation HTML button in the HTML document is referenced and an anonymous delegate is attached to the JavaScript click event of the button.  In the delegate the calculation x and y values are retrieved from the HTML document, added together and displayed along with the sum result in the MainPage page object.

Finally the MainPage page object is registered as a Scriptable Object so that JavaScript can directly call the ChangeColor function in the page object.

 

9.    Copy the following JavaScript functions within the script section in the head section in the HTMLBridgeTetPage.aspx file

 

        function ReceivePersonInfo(personInfo) {

            document.getElementById("spName").innerText = personInfo.Name;

            document.getElementById("spHobby").innerText = personInfo.Hobby;

        }

 

        var SLControl = null;

        function pluginLoaded(sender, args) {

            SLControl = sender.getHost();

        }

 

        function ColorChanged() {

            var colorSelIdx = document.getElementById("colorSelection").selectedIndex;

            var color = document.getElementById("colorSelection").options[colorSelIdx].text;

 

            SLControl.Content.mainPage.ChangeColor(color);

        }

 

The ReceivePersonInfo function is the one that is called on the button click event handler in the Silverlight MainPage class and displays the PersonInfo object members in the HTML page.

The pluginLoaded function will be called when the Silverlight object is loaded in the browser.  It simply gets a reference to the Silverlight object in the document.  This is then used in the ColorChanged JavaScript function to call the ChangeColor scriptable member function on the MainPage scriptable object.

 

10.  Replace the div with the id silverlightControlHost in the HTMLBridgeTestPage.aspx file with the following

 

        <div id="silverlightControlHost" style="float:left;">

        <fieldset style="width:200px; height:300px;">

        <legend >Silverlight Control</legend>

           

            <object data="data:application/x-silverlight-2," type="application/x-silverlight-2" width="100%" height="280">

                  <param name="source" value="ClientBin/HTMLBridge.xap"/>

                  <param name="onError" value="onSilverlightError" />

                  <param name="background" value="white" />

                  <param name="minRuntimeVersion" value="3.0.40624.0" />

                  <param name="autoUpgrade" value="true" />

                  <param name="onLoad" value="pluginLoaded" />

                  <a href="http://go.microsoft.com/fwlink/?LinkID=149156&v=3.0.40624.0" style="text-decoration:none">

                        <img src="http://go.microsoft.com/fwlink/?LinkId=108181" alt="Get Microsoft Silverlight" style="border-style:none"/>

                  </a>

              </object><iframe id="_sl_historyFrame" style="visibility:hidden;height:0px;width:0px;border:0px"></iframe>

        </fieldset>

          </div>

 

The key point to note here is the addition of the param object for the onLoad event that supplies the name of the pluginLoaded function to be called on the load of the plugin.

 

11. Add the following HTML after the above div

 

          <div style="float:right">

         <fieldset style="width:207px; height:300px;">

        <legend >HTML Controls</legend>

            <table style="background-color:Silver; height:280px;">

                <tr>

                    <td colspan="2" style="font-size:20px;"> Person Information</td>

                </tr>

                <tr>

                    <td >Name</td>

                    <td ><span id="spName"></span></td>

                </tr>

                <tr>

                    <td >Hobby</td>

                    <td ><span id="spHobby"></span></td>

                </tr>

                <tr>

                    <td >Color</td>

                    <td >

                        <select id="colorSelection" onchange="ColorChanged();">

                            <option selected="selected">[Select One]</option>

                            <option>Red</option>

                            <option>Green</option>

                            <option>Orange</option>

                        </select>

                        <input type="hidden" id="selectedColor" />

                    </td>

                </tr>

                <tr>

                    <td colspan="2" style="font-size:20px;">Calculation</td>

                </tr>

                <tr>

                    <td >x</td>

                    <td ><input type="text" id="txtX" /></td>

                </tr>

                <tr>

                    <td >y</td>

                    <td ><input type="text" id="txtY" /></td>

                </tr>

                <tr>

                    <td >&nbsp;</td>

                    <td ><input type="button" id="btnSendCalculation" value="Send Calculation" /></td>

                </tr>

            </table>

        </fieldset>

          </div>

 

This HTML defines the controls to enter the calculation  x and y values and select a color to change the background of the Silverlight control to.  The HTML controls to display the PersonInfo object received from the Silverlight control are also defined.

 

12. Run the application

 

 

13. Enter a name and a hobby for the person info in the Silverlight Control and click on the Send Person button.  The person info will be displayed in the Person Information section in the HTML Controls on the right.

 

 

14. Select a color from the Color drop down in the HTML Controls section and watch as the background of the Silverlight control changes to the selected color

 

 

15. Enter x and y values and click on the Send Calculation button and the values will be displayed with the summed result in the Silverlight control

 

 

Congratulations!!!! You have implemented interoperation between Silverlight and JavaScript and vice versa.

 

What have we done?

 

We have enabled the exposing of managed objects and their methods for consumption in JavaScript in the web page the Silverlight control is hosted in.

 

We have attached a managed code delegate to the JavaScript event of a HTML button.

 

We have called a managed code function in a scriptable object from JavaScript in the web page the Silverlight control is hosted in.

 

Conclusion

Silverlight enables the interoperation of Silverlight and JavaScript at both the type, property, method and event levels.  This allows the developer to truly integrate Silverlight with standard web technologies in the delivery of full featured thin client applications.

 

 

Server calling the client - duplex WCF Services in Silverlight

A very useful feature in Silverlight is the ability to use duplex services, so that the server can send messages to the Silverlight client.  This is really useful when the server should control the when the messages are sent and pushed to clients as opposed to the client having to manage the code to regularly poll a service to find out if the state it is interested in has changed.

 

In this post I we will implement a simple duplex service.  The service will allow a user to register to receive stock updates for MSFT or YHOO.  Once the client is registered the WCF service will send the Silverlight client updates every 5 seconds.  The user will also be able to call a non duplex function within the same WCF service to get an update when they want it – showing that duplex and non duplex servie methods can be implemented in the same service.

 

This app is built on Visual Studio 2008 SP1, Silverlight 3 and the Silverlight Toolkit using C#.

Steps:

 

1.       Create a new Silverlight application named Duplex.

2.       Choose to add a test web application (Duplex.Web) for the new solution.

3.       Add a WCF service to the web project

 

4.       Add a reference to the file %Program Files%\Microsoft SDKs\Silverlight\v3.0\Libraries\Server\System.ServiceModel.PollingDuplex.dll in your web project.  Ensure you use the assembly under the SERVER directory in the above path.

5.       In the WCF Service generated interface file IDuplexSvc.cs replace the existing interface with the following code

 

    [ServiceContract(Namespace = "Silverlight", CallbackContract = typeof(IDuplexSvcClient))]

    public interface IDuplexSvc

    {

        [OperationContract(IsOneWay = true)]

        void RegisterForStockUpdates(String ticker);

 

        [OperationContract()]

        Stock GetStockUpdate();

    }

 

    [ServiceContract]

    public interface IDuplexSvcClient

    {

        [OperationContract(IsOneWay = true)]

        void StockUpdated(Stock stock);

    }

 

IDuplexSvc is the interface for the WCF service on the server that the Silverlight client will call into.  Note the CallbackContract attribute defines the contract the duplex service will use to call back to the Silverlight client - IDuplexSvcClient.

 

Also note the RegisterForStockUpdates function has the attribute IsOneWay = true.  This defines that the caller will not wait for a response from the WCF service before continuing.  That said in Silverlight all out of process calls are asynchronous anyway.

 

I have also added a GetStockUpdate function that is not one way that returns immediately the stock information that the user requests – rather than the user waiting for the duplex call.  We’ll hook all this up later with the Silverlight UI.

 

In the IDuplexSvcClient interface we have the StockUpdated function, which is what the server uses to call the client.  It is also one way as we don’t want the server to wait for a response from the client.  The client must handle this function to receive the updated stock updates from the server when the server makes a duplex call to the client.

 

6.       In the DuplexSvc.cs file in the web project replace the DuplexSvc class with this updated code

 

    public class DuplexSvc : IDuplexSvc

    {

        IDuplexSvcClient client;

        String registeredTicker;

        Timer updateClient = null;

 

        #region IDuplexSvc Members

 

        public void RegisterForStockUpdates(String ticker)

        {

            client = OperationContext.Current.GetCallbackChannel<IDuplexSvcClient>();

            registeredTicker = ticker;

            updateClient = new Timer(5000);

            updateClient.Elapsed += new ElapsedEventHandler(updateClient_Elapsed);

            updateClient.Enabled = true;

            updateClient.Start();

        }

 

        public Stock GetStockUpdate()

        {

            Stock stock = new Stock();

            stock.StockTicker = registeredTicker;

 

            switch (registeredTicker)

            {

                case "MSFT":

                    stock.CompanyName = "Microsoft Corporation";

                    Random randM = new Random();

                    stock.StockValue = randM.Next(20,30);

                    break;

                case "YHOO":

                    Random randY = new Random();

                    stock.StockValue = randY.Next(10,20);

                    stock.CompanyName = "Yahoo! Inc";

                    break;

                default:

                    stock.CompanyName = "[Unknown]";

                    break;

            }

 

            return stock;

        }

 

        #endregion

 

        void updateClient_Elapsed(object sender, ElapsedEventArgs e)

        {

            client.StockUpdated(GetStockUpdate());

        }

 

    }

 

    public class Stock

    {

        public double StockValue;

        public String StockTicker;

        public String CompanyName;

        public String StockTime = DateTime.Now.ToLongTimeString();

    }

 

This code has two classes – the implemented DuplexSvc and the Stock class that will be used to hold the stock info and return it to the client in both the duplex and non duplex functions.

 

When the client calls the RegisterForStockUpdates function a reference to the calling client is retrieved using

 

 

            client = OperationContext.Current.GetCallbackChannel<IDuplexSvcClient>();

 

This allows the service object to retain a reference to the calling client for making duplex calls back to the client when the timer (created next)  fire it’s elapsed event.  Next the stock ticker string is recorded and a class timer is instantiated with an interval of 5 seconds.  The timer calculates a new stock update (in GetStockUpdate – declares a stock object with the ticker name, the appropriate company name and a random generated stock value).  The timer elapsed handler then calls the client.StockUpdated function passing the calculated stock object as a parameter.  Therefore in our example the server calls the client every fix seconds with updated stock information for the stock requested by the client.

 

Also don’t forget that the GetStockUpdate function is part of our service interface and can be called by the client directly to retrieve a new stock update any time.

 

7.        Build the web project

8.       Add a service reference to the Silverlight project for the DuplexSvc

 

 

9.       Add a reference to the file %Program Files%\Microsoft SDKs\Silverlight\v3.0\Libraries\Client\System.ServiceModel.PollingDuplex.dll in your web project.  Ensure you use the assembly under the CLIENT directory in the above path.

10.   In the MainPage.xaml file in the Silverlight project copy the following XAML

 

  <Grid x:Name="LayoutRoot" Background="LightBlue" >

        <Grid.RowDefinitions>

            <RowDefinition Height="30"></RowDefinition>

            <RowDefinition Height="*"></RowDefinition>

        </Grid.RowDefinitions>

        <Grid.ColumnDefinitions>

            <ColumnDefinition Width="100"></ColumnDefinition>

            <ColumnDefinition Width="*"></ColumnDefinition>

        </Grid.ColumnDefinitions>

        <TextBlock Grid.Column="0" Grid.Row="0" Text="Stock Ticker:"></TextBlock>

        <StackPanel Orientation="Horizontal" Grid.Column="1" Grid.Row="0" >

            <ComboBox x:Name="StockSelector">

                <ComboBoxItem Content="MSFT"></ComboBoxItem>

                <ComboBoxItem Content="YHOO"></ComboBoxItem>

            </ComboBox>

            <Button x:Name="btnRegisterTicker" Content="Register Stock" Click="btnRegisterTicker_Click" />

            <Button x:Name="btnGetUpdate" Content="Get Update" Click="btnGetUpdate_Click"  Visibility="Collapsed"  />

        </StackPanel>

        <TextBlock Grid.Column="0" Grid.Row="1" Text="Stock History:"></TextBlock>

        <ListBox x:Name="lstStockHistory" Width="200" Height="200" Grid.Column="1" Grid.Row="1" HorizontalAlignment="Left" VerticalAlignment="Top" ScrollViewer.HorizontalScrollBarVisibility="Hidden" ScrollViewer.VerticalScrollBarVisibility="Visible"  >

            <ListBox.ItemTemplate>

                <DataTemplate>

                    <Border BorderBrush="CadetBlue" BorderThickness="3" CornerRadius="3" Width="170" >

                        <StackPanel Orientation="Vertical" >

                            <StackPanel Orientation="Horizontal">

                                <TextBlock Text="{Binding StockTicker}" />

                                <TextBlock Text=": " />

                                <TextBlock Text="{Binding StockValue}" />

                            </StackPanel>

                            <TextBlock Text="{Binding CompanyName}" />

                            <TextBlock Text="{Binding StockTime}" Foreground="DarkOrange" FontSize="14" />

                        </StackPanel>

                    </Border>

                </DataTemplate>

            </ListBox.ItemTemplate>

        </ListBox>

  </Grid>

 

This provides our UI.  A combo box will allow the user to select MSFT or YHOO as the stock.  The user then clicks the Register Stock button.  The list box will show the details of the stock object retrieved from the WCF service – via the bound data template.  The user can also use the Get Update button to retrieve a stock update immediately.

 

11.   In the MainPage.xaml.cs file in the Silverlight project replace the MainPage class with the following code

 

    public partial class MainPage : UserControl

    {

        DuplexSvcClient proxy = null;

        System.Collections.ObjectModel.ObservableCollection<Stock> stockHistory = new System.Collections.ObjectModel.ObservableCollection<Stock>();

 

        public MainPage()

        {

            InitializeComponent();

            this.Loaded += new RoutedEventHandler(MainPage_Loaded);

        }

 

        void MainPage_Loaded(object sender, RoutedEventArgs e)

        {

            EndpointAddress svcAddress = new EndpointAddress("http://localhost:49346/DuplexSvc.svc");

 

            PollingDuplexHttpBinding svcBinding = new PollingDuplexHttpBinding();

           

            proxy = new DuplexSvcClient(svcBinding, svcAddress);

 

            proxy.RegisterForStockUpdatesCompleted += new EventHandler<System.ComponentModel.AsyncCompletedEventArgs>((o, ev) => {

                btnGetUpdate.Visibility = Visibility.Visible;

            });

            proxy.StockUpdatedReceived+=new EventHandler<StockUpdatedReceivedEventArgs>((o,ev)=> {

                UpdateStockHistory(ev.stock);

            });

            proxy.GetStockUpdateCompleted += new EventHandler<GetStockUpdateCompletedEventArgs>((o, ev) => {

                Stock stock = ev.Result as Stock;

                UpdateStockHistory(stock);

            });

        }

 

        private void UpdateStockHistory(Stock stock)

        {

            stockHistory.Add(stock);

            Dispatcher.BeginInvoke(new Action(() => {

                lstStockHistory.ItemsSource = stockHistory;

                if (stockHistory.Count > 1)

                {

                    Array stocks = stockHistory.ToArray();

                    if (stockHistory[stockHistory.Count - 1].StockValue > stockHistory[stockHistory.Count - 2].StockValue)

                        lstStockHistory.Background = new SolidColorBrush(Colors.Green);

                    else if (stockHistory[stockHistory.Count - 1].StockValue == stockHistory[stockHistory.Count - 2].StockValue)

                        lstStockHistory.Background = new SolidColorBrush(Colors.White);

                    else

                        lstStockHistory.Background = new SolidColorBrush(Colors.Red);

                }

                lstStockHistory.ScrollIntoView(stock);

            }));

        }

 

        private void btnRegisterTicker_Click(object sender, RoutedEventArgs e)

        {

            ComboBoxItem item = StockSelector.SelectedItem as ComboBoxItem;

            string ticker = item.Content as string;

 

            proxy.RegisterForStockUpdatesAsync(ticker);

            btnRegisterTicker.Visibility = Visibility.Collapsed;

        }

 

        private void btnGetUpdate_Click(object sender, RoutedEventArgs e)

        {

            proxy.GetStockUpdateAsync();

        }

    }

 

In this updated class we define a proxy to the WCF service and an ObservableCollection of stock object that will be used to hold all the stock object retrieved from the web service either through duplex communication or as a response to GetStockUpdate.

 

When the MainPage is loaded we instantiate our proxy defining the endpoint address and the binding of PollingDuplexHttpBinding – this binding enables the duplex functionality we require.  The callback handlers are then defined for the RegisterForStockUpdatesCompleted, StockUpdatedReceived and GetStockUpdateCompleted events.

 

Note – the StockUpdatedReceived handler handles the duplex call from the service when the timer fires every 5 seconds.  The naming convention for such handlers is <function defined in client callback interface>Received.

 

The handler for the RegisterForStockUpdatesCompleted makes the Get Update button visible.

 

The StockUpdatedReceived and GetStockUpdateCompleted handlers pass the stock object received respectively to UpdateStockHistory which updates the UI.  Note the use of Dispatcher to ensure the work done to interact with the UI is done on the UI thread.  The function adds the stock info to the observable collection to record all instances of the stock info received and then the observable collection is set to the itemssource of the list box to bind the entries.  We also have some logic to change the color of the list box – if the stock went up since the last stock update make the background green, if the stock went down make the background red.

 

The Register Stock button handler gets the ticker selected from the combo box and calls the RegisterForStockUpdates function in the WCF service – which starts the timer to perform duplex call backs to the client with stock info.  It also hides the Register Stock button to stock the user from registering twice.

 

Finally the Get Update button handler calls the GetStockUpdate WCF service to immediately retrieve stock information.

 

12.   Build and run the application