Scriptico

.NET to JavaScript Data Push using WebSockets

With the latest WebORB release (.NET as well as JAVA), Midnight Coders announced the full support of the WebSockets technology. Briefly speaking, WebSockets allow bi-directional communication between server and client sides through TCP sockets. Specifically, your JavaScript client can exchange data with the server-side in the real-time! A web page can use JavaScript to establish a connection with the server and bi-directionally send data to and receive data from it without any page refreshes. It sounds awesome, doesn’t it?

In order to demonstrate how it works, we will use the following story:

Mr. John Doe has an online store “Electronics_R_US”. Since Mr. Doe is pretty busy guy, he has no time to call all the sales staff to check how things are going. On the other hand, he wants to keep a finger on the pulse and see all sales numbers NOW.  How many orders were placed from the beginning of the work day, how much money has been received, how many orders were processed by each sales person and so on. So, Mr. Doe decided to ask developers to create an online dashboard with all the reports available 24/7.

Since the software development department uses WebORB, developers found a solution within minutes and created a simple, light-weight dashboard prototype for Mr. Doe within a few hours. The dashboard displays real-time reports and displays the detailed information split by the sales person. Finally, the developers added an option to automatically refresh detailed report every 10 seconds. The application screenshot is shown below.

websockets javascript application

We know the story, so let’s look inside the code in order to understand how it works. As I pointed above, the server-side is powered by WebORB and the client is written in pure JavaScript. In order to to keep things simple, I did not use any UI client frameworks, however, the example can be extended to use UI components from a framework like Sencha or any other if you have a preference.

Server Side

The server exposes two methods of the StoreTrackerService class to client and the service class is shown below:

using StoreTracker.Entity;
using StoreTracker.Helper;
using Weborb.Messaging.Server.Adapter;
using Weborb.Activation;
using Weborb.V3Types.Core;
using Weborb.Messaging.PubSub;
using Weborb.Config;
using Weborb.Util.Logging;

namespace StoreTracker
{
    [ApplicationActivation]
    public class StoreTrackerService
    {
        private Timer _timer;

        private DestinationManager _manager;
        private MessagingDestination _destination;

        private LiveStatistic _liveStatistic = null;
        private Statistic _statistic = null;

        public void startLiveStatisticTimer()
        {
            if (_timer == null)
            {
                _timer = new Timer(3000);
                _timer.Elapsed += new ElapsedEventHandler(onTimerElapsed);
                _timer.Enabled = true;
            }
            else if (_timer.Enabled == false)
            {
                _timer.Enabled = true;
            }
            else
            {
                Log.log(LoggingConstants.INFO, "LiveStatistic timer is already started");
            }
        }

        public void stopLiveStatisticTimer()
        {
            if (_timer.Enabled)
                _timer.Enabled = false;
        }

        private void onTimerElapsed(object sender, ElapsedEventArgs e)
        {
            _liveStatistic = DataHelper.incrementLiveStatistic(_liveStatistic);

            if (_manager == null)
                _manager = ORBConfig.GetInstance().GetDataServices().GetDestinationManager();

            if (_destination == null)
                _destination = (MessagingDestination)_manager.GetDestination("StoreTrackerDestination");

            Object[] messageWrapper = new Object[] { _liveStatistic };
            _destination.GetServiceHandler().AddMessage(null, messageWrapper);
        }

        public Statistic getDetailedStatistic()
        {
            _statistic = DataHelper.randomizeStatistic(_statistic);
            return _statistic;
        }
    }
}

The [ApplicationActivation] annotation guarantees that the service will operate as a singleton, that is only one instance of the class will be created. The annotation is provided by WebORB and an additional information is available in the WebORB documentation.
The startLiveStatisticTimer() and stopLiveStatisticTimer() methods manage the timer task to push live data to all connected clients. I did not implement any logic to manage client subscriptions, but you can easily modify the code on your own (the full visual studio project is available at the end of the article). The server will push an initialized and randomized instance of the LiveStatistic class to the clients through the StoreTrackerDestination messaging destination every 3 seconds. You can find out how it works in my previous blog entry .NET to Flex Data Push using Messaging Destinations. The LiveStatistic class shown below:

namespace StoreTracker.Entity
{
    public class LiveStatistic
    {
        public double totalAmount { set; get; }
        public double receivedAmount { set; get; }
    }
}

Basically, the LiveStatistic instance stores dummy data and we randomly modify it before it is pushed to the clients as shown below:

public class DataHelper
    {
        private static Random _random = new Random();

        public static LiveStatistic incrementLiveStatistic(LiveStatistic candidate)
        {
           //randomly increments and returns the LiveStatistic object
           ...
        }

        public static Statistic randomizeStatistic(Statistic result)
        {
          //randomize and return the Statistic instance
          ...
        }

        private static Statistic initializeStatistic(Statistic result)
        {
            ...
        }
    }

The second exposed method getDetailedStatistic() returns detailed report to calling client. Generally, a client makes a remote method invocation and receives randomly initialized instance of the Statistic class. The Statistic class is a data wrapper:

namespace StoreTracker.Entity
{
    public class Statistic
    {
        public List unitData { set; get; }
        public List managerData { set; get; }
    }
}

Unit and Manager classes are shown below:

 public class Unit
    {
        public string Name { set; get;}
        public double ListPrice { set; get;}
        public double AverageSoldPrice { set; get;}
        public int TotalUnitsSold { set; get;}
        public int UnitsLeft { set; get;}
    }
namespace StoreTracker.Entity
{
    public class Manager
    {
        public string FullName { set; get; }
        public int ProcessedDeals { set; get; }
    }
}

That is it, but before moving forward to the client code, I would like to say a few words about how the data is represented on the client side. First of all, there is no JSON. The client side will deal with the JavaScript objects and we should not worry about data serialization/deserialization at all – it will be handled entirely by WebORB out of the box. Although I have nothing against JSON and I see no significant advantages to use the JSON format or plain JS objects. Secondly, … there is no secondly, we are ready to take a look at the client code.

Client Side

I would not want to jump deep into the client code, so I will emphasize the basic methods on the client side. The client uses the WebORB client library – WebORB.js. The library provides a well written API to communicate with the server side. In this application, I am using the Consumer and Async classes. The Consumer class allows to create a consumer and subscribe it to the server through WebSockets (the WebSocket API is available in the web browsers). The Async class allows to work with the server logic in the asynchronous mode.

The client code figuratively speaking has two parts. The first part is responsible to handle live reports. To receive live reports, the client does the following:

  1. makes a call to the server to start the live report broadcasting
  2. subscribes to the specified destination through WebSockets
  3. handles received messages.

The second part of the application is responsible for handling detailed reports. First of all, it makes a call to the server and receives data. When data is received, the client starts a timer and makes a call to get updated reports every 10 seconds. Since all the client code is about 300 lines, I will emphasize only the key methods:

app = {
	proxy : webORB.bind("StoreTracker.StoreTrackerService",
			"http://10.0.1.12/console/weborb.aspx"),

	subscriber : null,

	// manage view states

	updateLiveStatisticView : function(liveStatisticObject) {
		if (typeof liveStatisticObject != 'undefined') {
			var totalAmount = liveStatisticObject['totalAmount'];
			var receivedAmount = liveStatisticObject['receivedAmount'];

			$("#totalAmount").html(app.roundValue(totalAmount));
			$("#receivedAmount").html(app.roundValue(receivedAmount));
		}
	},

	onSubscribeClick : function() {
		var btnText = $("#subscribeBtn").text();
		if (btnText == "SUBSCRIBE") {
			...
			app.startLiveStatisticTimer(new Async(
					this.onStartLiveStatisticTimerResult,
					this.onStopLiveStatisticTimerFault));

		} else if (btnText == "UNSUBSCRIBE") {
			...
			app.unsubscribe();
		} else {
                   ....
		}
	},

	subscribe : function() {
		webORB.defaultMessagingURL = "ws://10.0.1.12:2037";
		app.subscriber = new Consumer("StoreTrackerDestination", new Async(
				app.onLiveStatisticReceived, app.onLiveStatisticFault));
		app.subscriber.subscribe(null, new Async(app.onSubscribedResult,
				app.onSubscribedFault, this));
	},

	unsubscribe : function() {
		app.subscriber.unsubscribe();
	},

	onSubscribedResult : function(result) {
		...
		app.setLiveStatVisibility(true);
	},

	onSubscribedFault : function(fault) {
		app.log("an exception has been occurred during the consumer subscribing");
	},

	onStartLiveStatisticTimerResult : function(result) {
		app.subscribe();
	},

	onStopLiveStatisticTimerFault : function(fault) {
		app.log("fault is received");
	},

	onLiveStatisticReceived : function(message) {
		var messageWrapper = message.body[0];
		var liveStatisticObject = messageWrapper[0];

		app.updateLiveStatisticView(liveStatisticObject);
	},

	onLiveStatisticFault : function(fault) {
		app.log("live statistic message was not delivered properly");
	},

	onGetDataClick : function() {
		app.stopTimer();
		...
		app.getDetailedStatistic();
	},

	onDetailedStatisticReceived : function(data) {
		app.updateDetailedStatisticView(data);
		app.setDetailedStatVisibility(true);
		app.updateAutoRefreshLabel();
		app.startAutoRefreshTimer();
	},

	// manage the application timer

	updateDetailedStatisticView : function(data) {
		app.drawTable(data.unitData);
		app.drawManagerBarChart(data.managerData);
	},

	drawTable : function(data) {
		// generates a table with data and places it to the dashboard view
	},

	drawManagerBarChart : function(data) {
		// invokes draw methods
	},

	onDetailedStatisticFault : function(fault) {
		app.log("detailed statistic was not retreived successfully");
	},

	startLiveStatisticTimer : function(asyncObj) {
		if (asyncObj)
			this.proxy.startLiveStatisticTimer(asyncObj);
		else
			return this.startLiveStatisticTimer.subscribe();
	},

	stopLiveStatisticTimer : function(asyncObj) {
		if (asyncObj)
			this.proxy.stopLiveStatisticTimer(asyncObj);
		else
			return this.proxy.stopLiveStatisticTimer();
	},

	getDetailedStatistic : function() {
		this.proxy.getDetailedStatistic(new Async(
				app.onDetailedStatisticReceived, app.onDetailedStatisticFault));
	},

       //few methods to draw a chart

};

When a customer clicks on the SUBSCRIBE button, the application handles it in the onSubscribeClick function and invokes the startLiveStatisticTimer method. The startLiveStatisticTimer(asyncObj) receives an instance of the Async class (to make the client-server invocation asynchronous), so I am passing a new Async(this.onStartLiveStatisticTimerResult, this.onStopLiveStatisticTimerFault) instance. The result will be processed in the onStartLiveStatisticTimerResult method. As soon as the result is received, the code subscribes to the messaging destination. In the subscribe function I provide a default messaging URL for my messaging (WebORB) server, initialize a new instance of the Consumer class, and provide two handlers to process messages from the server. When the subscriber is successfully subscribed to the destination, I enable the live report view and the application is waiting for new messages. All the received messages will be processed by the onLiveStatisticReceived method.
I should also mention how the application retrieves detailed reports from the server and processes it. To get the detailed reports, the customer should click on the GET DATA button. The click event is handled by the onGetDataClick function. The onGetDataClick invokes the getDetailedStatistic function and from this method, the application synchronously gets the detailed report from the server side. To process the received data, I created the onDetailedStatisticReceived handler. The onDetailedStatisticReceived method invokes methods to display the view and starts a timer to refresh the detailed reports.
That is it. The full client code is attached to the article, so you will easily figure out how it works if the description above was not very clear.

Resources

Category: C#, Development, Examples, HTML5, Web, WebORB (.NET), WebSocket

Tagged:

Leave a Reply

ERROR: si-captcha.php plugin: GD image support not detected in PHP!

Contact your web host and ask them to enable GD image support for PHP.

ERROR: si-captcha.php plugin: imagepng function not detected in PHP!

Contact your web host and ask them to enable imagepng for PHP.