Click here to Skip to main content
Click here to Skip to main content

WebSockets, WCF, and Silverlight 5

By , 12 Aug 2011
 

Introduction

With this tutorial, I want to explain how you can create a WebSocket application. I have chosen Silverlight as my client, but you can use any framework that interacts with JavaScript.

Prerequisites

  1. Silverlight 5 (like I mentioned above, you do not have to use Silverlight but I have chosen the latest version of Silverlight as my client)
  2. SuperWebSocket (you do not have to download this WebSocket framework, but it is worth visiting, to view the designer's intentions of use etc.)
  3. Visual Studio 2010 (you can also use the Express edition)
  4. VS2010 Silverlight Tool
  5. JMeter (performance testing)

Project Structure

WebSocketsSilverlight/ProjectStructure.JPG

  • In the above solution, there is a 'Client' (Silverlight 5 project) that prompts the user for a unique store name.
  • The 'Client.Web' project that hosts the Silverlight (Client) project and contains the JavaScript code (that makes the WebSocket calls) within the hosting ASPX page.
  • A common 'SharedClasses' project, where a class is shared between the WCF service and the Silverlight 'Client' project.
  • A WCF service called 'WcfServicereverse' that will perform some processing.

Running the Application

If you run the application, you will be present with a log in screen (below) where you just enter a unique name - the name is not validated here - but that is something that can be easy implemented - all we are after is a unique name that can be later used to indicate who pushed an update from the client on to the GUI.

WebSocketsSilverlight/LoginScreen.JPG

Once logged into the application, the main screen will be displayes. It contains a grid, with cell foreground colors converted based on their cell values. A number of gauges are displayed below the grid that reflect the values within the grid itself. Below the gauges is a textbox and button to update the 'Fan' gauge (push the value to the server and then onto each connected session). At the bottom is a textblock that will display all the transactions from other stores\users or auto generated by the server and pushed to all the clients.

WebSocketsSilverlight/MainScreen.JPG

To update a 'Fan' and have it displayed in all the stores, enter a value between -20 and 20 and click 'Update Fan' (I do not validate the user input). This will force an update to the server that will push the update to all the clients; see below where the temperature was updated to -10.

WebSocketsSilverlight/UpDatedFan.JPG

If you have multiple browsers open, this change will also be reflected in those browsers.

Below, you can see the data that is being generated in Visual Studio IDE and pushed to the clients:

Code Explanation

Client (Silverlight) Code

namespace Client
{
    [ScriptableType]
    public partial class ClientPage : Page
    {
        private ObservableCollection<ThermoTemps> thermoCollection;
       
        public ClientPage()
        {
            InitializeComponent();
            ThermoCollection = new ObservableCollection<ThermoTemps>();
            this.gridThermo.ItemsSource = ThermoCollection;
            HtmlPage.RegisterScriptableObject("myObject", this);
        }

        private void button1_Click(object sender, RoutedEventArgs e)
        {
            HtmlPage.Window.Invoke("sendMessage", this.txtFan.Text);
        }

        [ScriptableMember]
        public void UpdateText(string result)
        {
            try
            {
                string jsonString = result.Substring(result.IndexOf('{'));
                ThermoTemps myDeserializedObj = new ThermoTemps();

                DataContractJsonSerializer dataContractJsonSerializer = 
                  new DataContractJsonSerializer(typeof(ThermoTemps));
                MemoryStream memoryStream = 
                  new MemoryStream(Encoding.Unicode.GetBytes(jsonString));
                myDeserializedObj = 
                  (ThermoTemps)dataContractJsonSerializer.ReadObject(memoryStream);
                ThermoCollection.Add(myDeserializedObj);

                // set the needle
                this.radialBarCoolVent.Value = myDeserializedObj.CoolingVent;
                this.radialBarFan.Value = myDeserializedObj.Fan; // set the needle
                this.radialBarFreezer.Value = myDeserializedObj.Freezer; // set the needle
                this.radialBarFridge.Value = myDeserializedObj.Fridge; // set the needle
                this.radialBarIceMaker.Value = myDeserializedObj.IceMaker; // set the needle
            }
            catch (Exception ex) { }

            mytextblock.Text += result + Environment.NewLine;
        }
    }
}

The above code is run within the client browser as a Silverlight object, but all that is happening is that it makes a hook call to the JavaScript method SendMessage when the user clicks the 'Update Fan' button, passing the value as an object. The UpdateText method is scriptable, meaning that it can be called from the JavaScript code - this is were the JSON string is passed to and deserialised into the shared class Thermotemps and added to the observable collection, which is bound to the DataGrid.

The DataGrid performs a converter on each cell to give the text its colour (passing the cell value as a parameter).

Server Side Session Management

public class Global : System.Web.HttpApplication
{
    private List<WebSocketSession> m_Sessions = new List<WebSocketSession>();
    private List<WebSocketSession> m_SecureSessions = new List<WebSocketSession>();
    private object m_SessionSyncRoot = new object();
    private object m_SecureSessionSyncRoot = new object();
    private Timer m_SecureSocketPushTimer;
    private CommunicationControllerService.WebSocketServiceClient commService;
    void Application_Start(object sender, EventArgs e)
    {
        LogUtil.Setup();
        StartSuperWebSocketByConfig();            
        var ts = new TimeSpan(0, 0, 5);
        m_SecureSocketPushTimer = new Timer(OnSecureSocketPushTimerCallback, 
                new object(), ts, ts); // push sdata from the server every 5 seconds
        commService = new CommunicationControllerService.WebSocketServiceClient();
    }

    void OnSecureSocketPushTimerCallback(object state)
    {
        lock (m_SessionSyncRoot)
        {                
            ThermoTemps temp = commService.GetTemperatures(null);
            System.Web.Script.Serialization.JavaScriptSerializer oSerializer = 
                 new System.Web.Script.Serialization.JavaScriptSerializer();
            string sJSON = oSerializer.Serialize(temp);
            SendToAll("Computer Update: " + sJSON);
        }
    }

    void StartSuperWebSocketByConfig()
    {
        var serverConfig = 
          ConfigurationManager.GetSection("socketServer") as SocketServiceConfig;
        if (!SocketServerManager.Initialize(serverConfig))
            return;

        var socketServer = 
          SocketServerManager.GetServerByName("SuperWebSocket") as WebSocketServer;
        Application["WebSocketPort"] = socketServer.Config.Port;

        socketServer.CommandHandler += new CommandHandler<WebSocketSession, 
                     WebSocketCommandInfo>(socketServer_CommandHandler);
        socketServer.NewSessionConnected += 
          new SessionEventHandler<WebSocketSession>(socketServer_NewSessionConnected);
        socketServer.SessionClosed += 
          new SessionClosedEventHandler<WebSocketSession>(socketServer_SessionClosed);
        if (!SocketServerManager.Start()) SocketServerManager.Stop();
    }

    void socketServer_NewSessionConnected(WebSocketSession session)
    {
        lock (m_SessionSyncRoot)
            m_Sessions.Add(session);
    }

    void socketServer_SessionClosed(WebSocketSession session, CloseReason reason)
    {
        lock (m_SessionSyncRoot)
            m_Sessions.Remove(session);

        if (reason == CloseReason.ServerShutdown)
            return;
    }

    // sends data
    void socketServer_CommandHandler(WebSocketSession session, 
                      WebSocketCommandInfo commandInfo)
    {            
        int? value = (int.Parse(commandInfo.Data.ToString()));
        ThermoTemps temp = commService.GetTemperatures(value);
        System.Web.Script.Serialization.JavaScriptSerializer oSerializer = 
               new System.Web.Script.Serialization.JavaScriptSerializer();
        string sJSON = oSerializer.Serialize(temp);
        SendToAll(session.Cookies["name"] + ": " + sJSON);
    }

    void SendToAll(string message)
    {
        lock (m_SessionSyncRoot)
        {
            foreach (var s in m_Sessions) s.SendResponseAsync(message);
        }
    }

    void Application_End(object sender, EventArgs e)
    {
        m_SecureSocketPushTimer.Change(Timeout.Infinite, Timeout.Infinite);
        m_SecureSocketPushTimer.Dispose();
        SocketServerManager.Stop();
    }
}

In Global.axa, where each new session comes in, you will create new handlers for each event that the WebSocket is interested in (CommandHandler, NewSessionConnected, SessionClosed). To improve on this, you would move the code into a separate class rather than execute it all in Global.axa. Here, I make calls to the WCF service to perform some processing and the return is pushed to the open (session) connections that is in our collection - the method socketServer_CommandHandler initiates most of the pushing for us.

JavaScript (On Hosting Page)

The following JavaScript code is executed when the main page is loaded, thus creating the glue between the client (Silverlight) and the server-side websockets. The onMessage method would be the main method here, as it will push the data it receives from the server into the Silverlight C# method and update the GUI.

<script type="text/javascript">
    var noSupportMessage = "Your browser cannot support WebSocket!";
    var ws;
 
    function connectSocketServer() {
        if (!("WebSocket" in window)) {
            alert(noSupportMessage);
            return;
        }

        // create a new websocket and connect
        ws = new WebSocket('ws://<%= Request.Url.Host %>:' + 
                           '<%= WebSocketPort %>/Sample');

        // when data is comming from the server, this metod is called
        ws.onmessage = function (evt) {

            // call to c# code to populate textblock
            var control = document.getElementById("silverlightControl");
            control.Content.myObject.UpdateText(evt.data);            
        };

        // when the connection is established, this method is called
        ws.onopen = function () {
            var control = document.getElementById("silverlightControl");
            control.Content.myObject.UpdateText('Connection open');
        };

        // when the connection is closed, this method is called
        ws.onclose = function () {
            var control = document.getElementById("silverlightControl");
            control.Content.myObject.UpdateText('Connection closed');
        }          
    }

    function sendMessage(message) {
        if (ws) ws.send(message);            
        else alert(noSupportMessage);        
    }

    window.onload = function () {                
        connectSocketServer();
    }
        
</script>

Shared Class

public class ThermoTemps
{
    public int IceMaker { get; set; }
    public int Fridge { get; set; }
    public int Freezer { get; set; }
    public int Fan { get; set; }
    public int CoolingVent { get; set; }
}

The above class is used by the service and the client - serializing JSON string to a class object that is added to a collection, which is bound to a grid.

Service Code

The service just performs some action\process to manipulate the data that is then returned to the server and onto the client. Here, we would generally listen to database changes (SQLNotification or have a data access layer inform us of a change - that we would then forward to the respective clients).

public class WebSocketService : IWebSocketService
{
    public string ReverseCommunication(string communication)
    {
        return communication.Aggregate("", (acc, c) => c + acc);
    }

    public ThermoTemps GetTemperatures(int? fan = null)
    {
        Random rnd = new Random();            
        ThermoTemps temperatures = new ThermoTemps();

        // determine if fan temp past in
        if (fan != null) temperatures.Fan = (int)fan;
        else temperatures.Fan = rnd.Next(-20, 20);

        temperatures.CoolingVent = rnd.Next(-20, 20);
        temperatures.Freezer = rnd.Next(-20, 20);
        temperatures.Fridge = rnd.Next(-20, 20);
        temperatures.IceMaker = rnd.Next(-20, 20);

        return temperatures;
    }
}

Performance Testing

The end caveat for using WebSockets is multiple open connections. This is something that has been available in the Java world for a number of years now (non-blocking IO.Jar or using DWR, for example). But, in the .NET world, this is something that has lacked behind other languages, until now. To test that you can handle 1000+ open connections, download JMeter and run a script that will open multiple instances of the main page (alter your code so that it has a cookie already saved and bypass the log-in page). You will see that it can handle well over 1000 connections.

Improvements

The improvements come more so from what we can do with WebSockets. In the attached project, we are sending all connected sessions the same data. Ideally, we would like to differentiate between sessions. This is possible by having a collection of classes and one of the properties is the web session object, but having other properties in the class to allow us to determine what this session should be sent etc.

Future Work

When Microsoft does release a scale-able version of their Web Sockets, you can easily alter the above project - as stated within the W3C consortium for HTML5, there are methods that have to be implemented (server-side) by any flavor of WebSockets - in that OnMessage, OnError etc. The above project has already implemented these methods. All that needs to be changed are the DLLs that your will point to.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Bert O Neill
Architect
Ireland Ireland
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionAbout WCF Websocketsmemberkireina_30127 Nov '12 - 20:02 
Hi Bert,
thanks for your good article. Very interesting.
here, I have some questions about WCF Websockets,
i have a WCF multiple game service.the clients connect to server and call some methods. if one client lost connection,how to reconnect to the server?is there any event for connection lost? how server resume its session and return result of method call that was called before disconnection? sorry for bad English. thanks for help
 
regards,
QuestionNot working on my computermemberMember 77167881 Nov '11 - 9:39 
Hello
I like your codes,but cannot run it on my computer.I followed the previous instructions,remove all binares and recompiled.Used VS2010 + silverlight 5 + coogle chrome as browser. After clicking Monitor button got a message "connection closed" and that is it.Can this be caused by offline work ??
internet is not hooked to computer.
Thanks
Boris
AnswerRe: Not working on my computermemberitz_1 Mar '12 - 4:53 
I have the same problem.
QuestionCan't get it to work :/memberMember 468721930 Jul '11 - 15:07 
I downloaded the code and it compiles fine (even cleaned and rebuilt solution).
 
The first page works okay, when I run it in Visual Studio 2010, the first page pops up and I get to pick a shop name.
 
But on the next page, if I enter any number (5,10, ...) in the box and click Update, I get a "Unknown Command" message in the messagebox.
 
Any idea what I could be doing wrong? Or is there more to getting this to work?
AnswerRe: Can't get it to work :/ [modified]memberBert ONeill31 Jul '11 - 9:32 
OK, try a couple of things for me :-
1) clean out your browser cache
2) delete the xap file in the web project's ClientBin folder and rebuild all projects
3) delete the bin and obj folder in the web project (in explorer - can't do this in VS2010)
 
Can you debug through and maybe put in a 'try catch' in the 'Command Handler' method in c# server side - Global.asax
 
You using Silverlight 5 too? (though this shouldn't cause the error that you are getting but just checking)
 
Before you cleaned the code up - were you able to run the application (through VS2010)
 
Also, can you deploy the application onto your (local) IIS and run the application that way instead of running through VS2010 to see if you get the same results.
GeneralRe: Can't get it to work :/memberMember 46872194 Aug '11 - 5:36 
Hi Bert,
 

Thanks for the quick reply! I've tried your suggestions, sadly it won't work (yet Wink | ;) ).
 
The try/catch method is in place in Global.asax in socketServer_CommandHandler, but the command never gets called (breakpoint doesn't get hit).
 
I have looked at the Output window and in there I see that HTTP messages do get exchanged (status 200 OK) and I see the XML for the fan settings (Envelope and the fans with their respective temperatures etc.). Yet it doesn't seem to arrive at the fans/webpage and it doesn't update.
 
My Silverlight plugin: Installed version: Silverlight 5 (5.0.60401.0)
Using the latest Google Chrome browser.
I'm using Visual Studio 2010
 
I have a 64-bit Windows 7, but I don't think that should influence this project.
 
Deploying it on my local IIS gave me a slew of other errors relating to configuration issues, so no help there. (Even the standard SuperWebSocket chat gives me these errors).
QuestionNice one [modified]memberMember 45654336 Jul '11 - 11:11 
But with the advancements of HTML and JavaScript GUI widget libraries, will Silverlight really be needed ?
 
Problem with WebSockets is it'll take a while for all browsers to support it (without users having to alter config files etc.) and it'll be a while before users adopt the latest browsers.
But for LOB Intranet apps, then it’s a winner, well as long as IE10 has it enabled by default.
 
Nice article Bert, have a 5

modified on Friday, July 8, 2011 3:57 AM

GeneralMy vote of 5memberFilip D'haene5 Jul '11 - 9:52 
Excellent article and coding!
 
Thanks for sharing. Smile | :)
GeneralMy vote of 5memberMember 42026435 Jul '11 - 0:36 
Another excellent article!
GeneralMy vote of 5memberHalil ibrahim Kalkan4 Jul '11 - 11:03 
Very good article.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130523.1 | Last Updated 13 Aug 2011
Article Copyright 2011 by Bert O Neill
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid