Web clients with MatriX and SignalR

Persistent realtime connections for ASP.NET in code behind to the web browser were hard to implement in the past. There hasn’t been a ASP.NET component for this for a while.

With SignalR there is an asynchronous realtime signaling library for ASP.NET now that a team at Microsoft is working on. Using SignalR its pretty easy to build real-time web applications.

Using SignalR and MatriX its very easy to write scalable XMPP web applications with a very small amount of JavaScript code.

Let’s get started:

Create a new ASP.NET MVC web application project first. Then install SignalR using the NuGet Package Console with the following command:

install-package SignalR

This will add SignalR with all required references to the new project we just created.

Now we create a Hub for MatriX. Hubs are a higher level framework API to exchange different messages between server (code behind) and client (web browser) bidirectional in realtime.

We First implement the IConnected and IDisconnect interfaces to the Hub. Our Hub has a static Dictionary for all XmppClients instances. Whenever a new client connects to the bidirectional realtime channel of SignalR we create a new XmppClient instance for it, setup the event handlers and add the client to the dictionary. When a browser disconnects from SignalR we disconnect all event handlers and remove the XmppClient from the dictionary.

public class MatrixHub : Hub, IConnected, IDisconnect
{
    private static readonly Dictionary<string, XmppClient> XmppClients = new Dictionary<string, XmppClient>();

    public Task Disconnect()
    {
        if (XmppClients.ContainsKey(Context.ConnectionId))
        {
            var xmppClient = XmppClients[Context.ConnectionId];

            xmppClient.OnReceiveXml -= xmppClient_OnReceiveXml;
            xmppClient.OnSendXml -= xmppClient_OnSendXml;
            xmppClient.OnMessage -= xmppClient_OnMessage;

            XmppClients.Remove(Context.ConnectionId);
        }

        return Clients.leave(Context.ConnectionId, DateTime.Now.ToString());
    }

    public Task Connect()
    {
        if (!XmppClients.ContainsKey(Context.ConnectionId))
        {
            var xmppClient = new XmppClient();

            xmppClient.OnReceiveXml += xmppClient_OnReceiveXml;
            xmppClient.OnSendXml += xmppClient_OnSendXml;       
            xmppClient.OnMessage += xmppClient_OnMessage;
            
            XmppClients.Add(Context.ConnectionId, xmppClient);
        }

        return Clients.joined(Context.ConnectionId, DateTime.Now.ToString());
    }

    public Task Reconnect(IEnumerable<string> groups)
    {
        return Clients.rejoined(Context.ConnectionId, DateTime.Now.ToString());
    }    
}

Now we add a Open and Close function to our Hub which can be executed from the webpage via JavaScript. The JavaScript passes the username, password and XMPP domain as a string to the Open function.

Context.ConnectionId is the Id of the JavaScript client calling this Hub. We use this ids for our XmppClient dictionary and need it to lookup the correct XmppClient instance from the dictionary. After retrieving the XmppClient instance we set Username, Password and XmppDomain. Then we call Open() and MatriX starts to login to the XMPP server with the given credentials.

The same applies to the Close function. We first lookup the correct XmppClient and then call Close on it to close the XMPP stream.

public void Open(String username, String password, String xmppDomain)
{
    XmppClient xmppClient = XmppClients[Context.ConnectionId];
    xmppClient.Username = username;
    xmppClient.Password = password;
    xmppClient.XmppDomain = xmppDomain;

    xmppClient.Open();
}

public void Close()
{
    XmppClient xmppClient = XmppClients[Context.ConnectionId];
    xmppClient.Close();
}

Now we implement the OnSendXml and OnReceiveXml handlers we subscribed to in the Connect Task. This is server side C# code telling the client to call sendXml() and receiveXml() JavaScript functions.

Clients is a dynamic object of SignalR. Clients[Context.ConnectionId] gets the correct client and executes the function on this single client only.

void xmppClient_OnSendXml(object sender, Matrix.TextEventArgs e)
{
    var text = HttpUtility.HtmlEncode(String.Format("Send: {0}", e.Text));
   
    Clients[Context.ConnectionId].sendXml(text);
}

void xmppClient_OnReceiveXml(object sender, Matrix.TextEventArgs e)
{
    var text = HttpUtility.HtmlEncode(String.Format("Recv: {0}", e.Text));
   
    Clients[Context.ConnectionId].receiveXml(text);
}

The last step before we goto our JavaScript is the OnMessage function. SignalR can also send complex objects. So lets create a simple Message class to send the message properties as an object to our JavaScript. The Message class has 2 simple String properties. Body for the Text of the message and From for the full Jid of the sender.

public class Message
{
    public string Body;
    public string From;
}

Whenever MatriX receives a message in the OnMessage handler we raise the callback onMessage in our JavaScript and pass a new instance of the Message object we just created.

void xmppClient_OnMessage(object sender, MessageEventArgs e)
{
    Clients[Context.ConnectionId].onMessage(
           new Message
           {
           		From = e.Message.From,
           		Body = e.Message.Body
           }
    );       
}

The backend and code behind stuff is done now. Now we have to add JavaScript to our web page to setup the SignalR connection.

First we have to include some JavaScript files. We need jquery, the signalR script and the hubs which get created dynamically by SignalR on the server.

<script src="@Url.Content("~/Scripts/jquery-1.6.4.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.signalR-0.5.2.js")" type="text/javascript"></script> 
<script src="@Url.Content("~/signalr/hubs")" type="text/javascript"></script>

The following code shows how to create our MatriX Hub in JavaScript and connect it to the SignalR server. matrixHub is defined in the dynamically created JavaScript file we added before.

<script type="text/javascript">
    $(function () {
        var matrixHub = $.connection.matrixHub;
        
		// TODO, callbacks and hub invoker
        
        $.connection.hub.start();
    });
</script>

Now we add the JavaScript code which gets executed when we press the Login and Logout buttons on our wegpage.

The Login button executes the Open function in our C# Hub and passes username, password and XmppDomain. The Logout button executes the Close function without any parameters.

This is all that has to be done to execute a Hub function from JavaScript in realtime.

$("#loginButton").click(function () {
    matrixHub.open(
        $("#txtUsername").val(),
        $("#txtPassword").val(),
        $("#txtXmppDomain").val());
});

$("#logoutButton").click(function () {
    matrixHub.close();
});

The last pieces which are missing are the JavaScript functions we execute form the Hub to send data to the webpage from the server.

The sendXml and readXml callbacks append the Xml debug to a div. The onMessage callbacks appends a incoming message to a div.

// signalR callback for outgoing xml debug
matrixHub.sendXml = function (message) {
    $("#log").append("<span class='log send'>" + message + "</span><br/>");
};

// signalR callback for incoming xml debug
matrixHub.receiveXml = function (message) {
    $("#log").append("<span class='log recv'>" + message + "</span><br/>");
};

matrixHub.onMessage = function (msg) {
    $("#messages").append(
            "<span class='from'>" + msg.From + ":</span>" +
            "<span class='message'>" + msg.Body + "</span>" +
            "</br>"
    );
};

SignalR can also send data to all connected clients or a group of clients, but we don’t need these features.

SignalR handles all the connection stuff for the client and the server automatically for us, and keeps the connection always alive. It chooses the right connection technique for your browser and scales on the server with modern async and await techniques.

Under the hood SignalR is using the same techology as BOSH. On the web server MatriX connects to the XMPP server directly over the default TCP transport. This means you can connect to any XMPP server including Google Talk, Facebook or Windows Live Messenger without using a BOSH proxy from your web page.

The complete example is included in the latest MatriX download.

This post refers to a SinalR version 0.5.x. There were many incompatible API changes in SignalR, so many stuff in this post in not compatible with the current SingalR versions.
The example included in the MatriX download should be always up to date.

Leave a comment

8 Comments.

  1. Looks like this was built using a very early build of SignalR. Any chance of getting this updated to work with the supported version of SignalR (I loaded 1.1 and there is no Iconnected or Idisconnect Interface and the Clients interaction has changed too)

  2. When this post was written the SingalR version was up to date. When you download the SDK then you can find a the WebClient example application with a recent SingalR version.

  3. Ah. Thank you.

    The DropBox link at the end of the article contains the old version and you can’t really walk through the tutorial since installing signalR via NuGet configures the latest version of signalR which is not compatible with the code examples.

    The new sample works great.

    Thanks again,
    Sam

  4. I’ll add a note to this post

  5. Thanks for wonderful sample. Does it include send part? I don’t see it. I’m not able to select a contact and send message to it. Am I missing something? Also why does it include aspnetdb? It doesn’t seem to use it. Thanks!

  6. This is only a basic example which shows you how to get started. Not a complete reusable chat client. Sending a message is pretty simple as well. Just invoke a method on the server from the client over SignalR and do there:

    void SendMessage(Jid to, string text)
    {
    xmppClient.Send(new Message { To = to, type = messageType.chat, Body = text};
    }

  7. Time to update this to SignalR 2.0?

    • its always good to use the latest version. The concepts of SignalR did not change. So its should be pretty easy to port our examples or this tutorial to the latest version.

Leave a Reply to gnauck Cancel reply

Your email address will not be published. Required fields are marked *


This site uses Akismet to reduce spam. Learn how your comment data is processed.