Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Silverlight 4 and Multiple Duplex Clients

0.00/5 (No votes)
3 Feb 2011 1  
Be able to push information to an individual browser page or to all browser pages

Introduction

This project will demonstrate the client-server approach using a duplex mechanism, where the server will keep track of each (client) browser and update the client, either individually or all together. But the trick is to use a push (long polling) architecture rather than the client polling the server on a periodical time-range.

Look & Feel

In the image below (Fig 1), you can see the application as it is working. In this project, I am basing the project on a power station scenario, in that the central office (server) will update the client power stations.

SL4DuplexMultipleClients/Fig_1_App_Running.JPG

Fig. 1

In the application (screenshot above), the server has created two client power stations (browsers), "Greenfield" and "Finnharps". From the server page, I can then click the respective tab (station) and change its power wattage, or if I like, click the "All Browsers" check-box and change all the stations at once (for e.g. send a popup to each station of an impending shutdown).

Project Visual Studio Structure

SL4DuplexMultipleClients/Fig_2_Project_Files.JPG

Fig. 2

Project Modules

Server (Silverlight) Module

The server is really made up of two XAML pages, "MainPage" and "TabContents". When the user creates a new station, a new tab is created to maintain that station - thus a new control is inserted into the Microsoft tab control. At the same time, a new browser is opened (passing the name of the station in the URL for displaying purpose in the client browser) and the name will be used as a key to store the browser details in a collection. So, when the user is in a particular tab - the selected tab name is the key that is used to get the browser details from the collection and push the new (class "ClientBrowserInfo") details (in json format) to the client.

SL4DuplexMultipleClients/Fig_3_Server_Page.JPG

Fig. 3

Client (Silverlight) Module

When the browser is initially opened (from the user clicking the "Add Station" button on the server), it will create a duplex poll between the client and the server and create a callback event to handle communication from the server. Deserializing the json back into a ("ClientBrowserInfo") class, from which the client can tell what to change and what the new value is to be.

SL4DuplexMultipleClients/Fig_4_Client_Page.JPG

Fig. 4

Server Hosing Module

As well as containing the HTML pages that will host the respective Silverlight projects, the duplex and browser services will be contained within this module. Each browser's state will be maintained with the aid of a static class\methods and a static collection. The duplex service will provide the mechanism for pushing the data to each (or all) client(s).

Third Party Control

For the dial, I used Telerik's free Gauge control (http://www.telerik.com/products/free-silverlight-controls.aspx).

Snippets Of Code

The code below will create and open a new client, and insert the "TabControl" into the Tab.

/// <summary>
/// Handles the Click event of the btnSubmitNewPowerStation control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> 
/// instance containing the event data.</param>
private void btnSubmitNewPowerStation_Click(object sender, RoutedEventArgs e)
{
	tabStations.Visibility = System.Windows.Visibility.Visible;

	TabContents newStationControl = new TabContents();
	TabItem newStationTabItem = new TabItem();

	newStationTabItem.Header = this.txtNewStation.Text;
	newStationTabItem.Content = newStationControl;
	this.tabStations.Items.Add(newStationTabItem);
	newStationTabItem.IsSelected = true;

	// open (power station) browser
	HtmlPopupWindowOptions options = new HtmlPopupWindowOptions();
	options.Menubar = false;
	options.Resizeable = false;
	options.Status = false;
	options.Toolbar = false;
	options.Directories = false;
	options.Scrollbars = false;
	options.Location = false;
	options.Height = 500;
	options.Width = 500;
	HtmlPage.PopupWindow(new Uri
	("http://localhost:62115/PowerStationClient.html?name=" + 
	this.txtNewStation.Text, UriKind.RelativeOrAbsolute), "_blank", options);     
}  

One of the events in the "TabControl" is to catch a slider value change and then post it to the respective (or all) clients.

/// <summary>
/// Handles the ValueChanged event of the sliderWattage control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The 
/// <see cref="System.Windows.RoutedPropertyChangedEventArgs&lt;System.Double&gt;"/> 
/// instance containing the event data.</param>
private void sliderWattage_ValueChanged(object sender, 
		RoutedPropertyChangedEventArgs<double> e)
{
	TabItem selectedTab = (TabItem)this.Parent;
	string clientKey = selectedTab.Header.ToString();

	lblSliderValueSelected.Content = Convert.ToInt32(e.NewValue * 100).ToString();

	if (this.imgStatus.Tag == null && e.NewValue == 0) 
				this.bthStatus.IsEnabled = false;
	else if (this.imgStatus.Tag == null && e.NewValue > 0) 
				this.bthStatus.IsEnabled = true;
	else if (this.imgStatus.Tag.ToString() == "off" && e.NewValue == 0) 
				this.bthStatus.IsEnabled = false;
	else this.bthStatus.IsEnabled = true;

	DuplexService.DuplexControllerClient client2 = 
			new DuplexService.DuplexControllerClient();
	ClientBrowserInfo browserData = new ClientBrowserInfo();
	browserData.BrowserKey = clientKey;
	browserData.GaugeValue = int.Parse(lblSliderValueSelected.Content.ToString());
	browserData.NotificationMessage = "";
	browserData.PopupType = PopupStatus.NoPopup;
	browserData.Operation = OperationType.Gauge;
	
	if (!(bool)this.chkAllbrowsers.IsChecked) 
		client2.SendTriggerAuditDataAsync(SerializeObject(browserData), 
		browserData.BrowserKey);
	else client2.SendTriggerAuditDataAsync(SerializeObject(browserData), "");    
}  

The following snippet of code will get the respective browser based on the key and perform the push (it also determines if all browsers need to be updated).

/// <summary> 
/// Sends the trigger audit data.
/// </summary>
/// <param name="data">The data.</param>
public void SendTriggerAuditData(string data, string sessionId)
{
	try
	{
		// loop through channels (browsers) and make a call 
		// to their callback method
		if (BrowserHelper.GetCallbackChannels().Count() > 0)
		{
			lock (syncRoot)
			{
				if (sessionId.Equals(string.Empty))
				{
					IEnumerable
					<IDBNotificationCallbackContract> 
					allChannels = BrowserHelper.
						GetCallbackChannels();
					allChannels.ToList().ForEach(c => 
						c.SendNotificationToClients(data));
				}
				else
				{
					// send to one browser
					IDBNotificationCallbackContract 
						channels = BrowserHelper.
						GetCallbackChannel(sessionId);
					channels.SendNotificationToClients(data);
				}
			}
		}
	}
	catch (Exception) { /*log and display error message*/}
}  

The following code (from the client) will notify the server that it is interested in a callback and that it is registering itself using duplex-polling.

/// <summary> 
/// Initialises the page functionality.
/// </summary>
void InitialisePageFunctionality()
{
	this.lblStationName.Content = string.Empty;
	this.lblStationName.Content = "Station Name: " + 
			HtmlPage.Document.QueryString["name"].ToString();

	// reference the notifications service and create the 
	// callback hook for database changes
	client = new DBNotificationClient(new PollingDuplexHttpBinding(), 
		new EndpointAddress
		("http://localhost:62115/BrowserController/DBNotificationService.svc"));
	client.SendNotificationToClientsReceived += (sender, e) =>
	{
		if (e.data != null) 
			UpdatedAuthorsReceived(e.data); // something to process
	};

	this.Subscribe(); // make the browser (instance) known to server
}   

Gotchas

  1. I did not implement any validation on the power station name (this is used as a collection key).
  2. I do not fully clean-up after a browser is closed (calling the "Unsubscribe" method) - thus leaving some static data in the static collection - that may cause issues later when pushing data to the client.
  3. High scalability is possible with the use of a background thread (from client) and using Windows 2008 server (not used in this project).

History

  • 3rd February, 2011: Initial post

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here