Silverlight 4 and Multiple Duplex Clients






4.67/5 (5 votes)
Be able to push information to an individual browser page or to all browser pages
- Download source - 2.91 MB
- Download Movie.txt - 348.71 KB (rename to Movie.rar - zip file size too big to upload)
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.
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
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.
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.
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<System.Double>"/>
/// 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
- I did not implement any validation on the power station name (this is used as a collection key).
- I do not fully clean-up after a browser is closed (calling the "
Unsubscribe
" method) - thus leaving somestatic
data in thestatic
collection - that may cause issues later when pushing data to the client. - 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