Real-time communication. Many software applications on variety of platforms & form factors have a genuine need for it. But persistent networks, real-time connectivity & asynchrony continue to challenge us developers in building such applications. Is there a silver lining? Could we have near-real-time communication in our mobile apps? Imagine the possibilities.
Live Demo
Now, let’s do something a little fun before we get to the crux of what this article is about.
How about a live demo of the sample application in action? Works from anywhere on earth with a data connection; in fact, more & diverse the user demographics, the merrier. Let’s do this:
- This demo will work with your Windows Phone emulator; but better with your unlocked Windows Phone that can side-load apps, given XAP or source code.
- Download the Source Code & extract the Windows Phone solution.
- Load in Visual Studio, build & Deploy to emulator/phone.
- The sample Windows Phone app essentially shares your location & connects you to a backend SignalR server for continuous real-time chat.
- Go to http://signalrserver.cloudapp.net/ and keep it open.
- Fire up the app and give yourself a name for the Chatroom.
- Share your location & join the fun.
- If on a Windows Phone, you should see a pushpin on the Bing map for your actual location (if allowed by your settings). On the emulator, you’ll start out at the mothership, that is, Seattle. Feel free to use the location simulator to fake your position.
- See yourself pop up in the chat dialog box?
- Now fire away what you want to say .. no profanity please.
- See what you say show up in your phone’s chat dialog & on the Server almost immediately? Fun, eh?
- Exit the chatroom and you’ll see yourself leave, along with a disappearing pushpin on map.
The result should be something like this:
I’m hoping if we keep the site running, we can see Pushpins show up on different parts of the world map; the Bing map used should zoom in & out to accommodate all pins. Having said that, the server side code isn’t hosted on massive hardware. I’ll try to keep it up; but if load or other issues force it down, please feel free unpack the server code & run it locally. It is a simple MVC3 app that should run just fine on your machine. Only change you would have to do is to point your Windows Phone app to Localhost, instead of the above server. So, let’s hope the site stays up & we can see folks from different places show up on live map & chatroom.
Introduction
SignalR is an async persistent connection library for client-server communication that aids in building real-time, multi-user connected applications across multiple platforms. The connection between clients & server is persisted over best possible transport mechanism, which could be a combination of long polling, periodic polling & sockets. The point is the transport layer is abstracted away to provide a seamless persistent connection for ease of development in making connected applications across the web, mobile & other form factors.
SignalR was started by Damian Edwards & David Fowler from the ASP.NET team and is now an OSS on GitHub (https://github.com/SignalR/SignalR). Want to chat with these & other cool folks working with SignalR? Join the conversations @ http://jabbr.net/#/rooms/signalr. The easiest way to start using SignalR is to utilize the Nugets as need in your application. They are:
- SignalR - Package for SignalR.Server and SignalR.Js
- SignalR.Server - Server components needed for SignalR endpoints
- SignalR.Js - Javascript client for SignalR
- SignalR.Client - .NET client for SignalR
- SignalR.Ninject - Ninject dependeny resolver for SignalR
These can be incorporated in your project by using the visual Nuget Library Manager or the command-based Management console. This one statement gets you all dependencies:
While there are other technologies that have tried to solve the real-time communication problem, like Socket.IO or HTML5 WebSockets, few do it as simply as SignalR and support cross-platform between the web and mobile platforms like Windows Phone, iOS & Android (through Mono). While the low-level PersistenceConnection class might give you more control, most times the abstraction over it called the Hub class & your implementation of it, will provide easy persistence between client & Server. Want to learn more about SignalR? Try these wonderful posts:
- http://bit.ly/qeJFyf
- http://jbeckwith.com/2011/10/12/building-a-user-map-with-signalr-and-bing/
- http://blog.maartenballiauw.be/post/2011/12/06/Using-SignalR-to-broadcast-a-slide-deck.aspx
- http://www.dotnetcurry.com/ShowArticle.aspx?ID=780
The Sample
So, having learnt a little about SignalR, do you think it would work with and have applications in Windows Phone development? Absolutely! Real-time communication with a backend server on a personal device like the Windows Phone has potentially tons of ways to leverage the technology in Apps. Allow me to show you two here – mapping Windows Phone App users across the globe and real-time chat dialog between multiple Windows Phones & the web. The rest is up to your imagination ..
The Server
You obviously have choices here. I started with a default MVC3 web application; but wanted to change the look to showing mapping & chat dialog. First up, let’s get it ready to be a SignalR server. Here’s how to add the Nuget package:
With that, we know what a Hub is. Let us extend it to make something our own. We essentially want to uniquely keep track of Windows Phone users using our app; so let’s define a class for the phone clients:
1: // Individual Phone clients.
2: public class PhoneClient
3: {
4: public string PhoneClientId { get; set; }
5: public float Latitude { get; set; }
6: public float Longitude { get; set; }
7: }
Next, let’s add functionality to add phone clients to Hub, disconnect & broadcast messages to all:
1: // The Hub of communication.
2: public class MessagingHub : Hub
3: {
4: private static List<PhoneClient> PhoneClientList = new List<PhoneClient>();
5:
6: public void JoinFromPhone(string phoneID, float latitude, float longitude)
7: {
8: PhoneClient phoneClientToAdd = new PhoneClient();
9: phoneClientToAdd.PhoneClientId = phoneID;
10: phoneClientToAdd.Latitude = latitude;
11: phoneClientToAdd.Longitude = longitude;
12:
13: PhoneClientList.Add(phoneClientToAdd);
14: Clients.addClient(phoneClientToAdd);
15: this.Caller.addClients(PhoneClientList.ToArray());
16: }
17:
18: public void Disconnect(string phoneID)
19: {
20: PhoneClient client = null;
21:
22: foreach (PhoneClient existingClient in PhoneClientList)
23: {
24: if (phoneID == existingClient.PhoneClientId)
25: {
26: client = existingClient;
27: break;
28: }
29: }
30:
31: PhoneClientList.Remove(client);
32: Clients.removeClient(client);
33: }
34:
35: public void PushMessageToClients(string message)
36: {
37: Clients.addChatMessage(message);
38: }
39: }
Wondering what some of the function calls are within our Hub? Well .. that’s the magic! These are actually JavaScript methods defined in our web UI view. So, let’s shoot for a UI like this (in the Index view off our Home controller).. Chat dialog on left & Bing Maps to show the pushpins on the right:
Here’s what’s on the view, along with subtle styling included in the downloadable code:
1: <script charset="UTF-8" type="text/javascript" src="http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=7.0"></script>
2: <script src="@Url.Content("~/Scripts/jquery-1.6.4.min.js")" type="text/javascript"></script>
3: <script src="@Url.Content("~/Scripts/jquery.signalR.min.js")" type="text/javascript"></script>
4: <script type="text/javascript" src="@Url.Content("~/signalr/hubs")"></script>
5:
6: <div id='container'>
7: <div id='chat'>
8: <b>Chatroom:</b>
9: <br /><br />
10: <input type="text" id="message" maxlength="20" />
11: <input type="button" id="broadcast" value="Broadcast" />
12: <input type="button" id="clear" value="Clear" />
13: <br /><br />
14: <ul id="chatdialog"></ul>
15: </div>
16: <div id='map'></div>
17: </div>
18:
19: <div id="legend">
20: <label for="userCount">Mapped Users: </label>
21: <span id="userCount">0</span>
22: </div>
The Bing Maps on the page would be using the Bing Maps Ajax/JavaScript SDK. If you need to use this on your own, please make sure to get yourself a developer ID that you could pass along to Bing; get it HERE.
Now, most of the connectivity magic offered by the SignalR server will be in the code bits below. You’ll see how we open a connection to our SignalR Hub from the web client and define JavaScript methods to manipulate the UI that will be used by the Hub. You’ll notice how we add/remove PushPins on the map using JavaScript & location of phone clients joining the Hub. Also, you’ll see the code sending off Chat messages typed in from the web and appending those received from the server. So, here goes:
1: <script type="text/javascript">
2:
3: var map = null;
4:
5: $(function ()
6: {
7: // Create the connection to our SignalR Hub.
8: var signalRHub = $.connection.messagingHub;
9:
10: // Define some Javascript methods the server-side Hub can invoke.
11:
12: // Add a new client to the map.
13: signalRHub.addClient = function (client)
14: {
15: var pins = getPushPins();
16: addClient(client);
17: centerMap();
18: $("#userCount").html(pins.length + 1)
19: };
20:
21: // Remove a client from the map.
22: signalRHub.removeClient = function (client)
23: {
24: for (var i = map.entities.getLength() - 1; i >= 0; i--) {
25: var pushpin = map.entities.get(i);
26: if (pushpin instanceof Microsoft.Maps.Pushpin)
27: {
28: if (pushpin.getLocation().latitude == client.Latitude && pushpin.getLocation().longitude == client.Longitude)
29: {
30: map.entities.removeAt(i);
31: var pins = getPushPins();
32: $("#userCount").html(pins.length)
33: }
34: }
35: }
36: };
37:
38: // Add chat messages from server to dialog.
39: signalRHub.addChatMessage = function (message)
40: {
41: $('#chatdialog').append('<li>' + message + '</li>');
42: };
43:
44: // Click event-handler for broadcasting chat messages.
45: $('#broadcast').click(function ()
46: {
47: // Call Server method.
48: signalRHub.pushMessageToClients($('#message').val());
49: });
50:
51:
52: // Start the SignalR Hub.
53: $.connection.hub.start(function ()
54: {
55: showMap();
56: });
57:
58: // Click event-handler for clearing chat messages.
59: $('#clear').click(function ()
60: {
61: $('ul li').remove();
62: });
63: });
64:
65:
66:
67: function addClient(client)
68: {
69: var location = new Microsoft.Maps.Location(client.Latitude, client.Longitude);
70: var pushpin = new Microsoft.Maps.Pushpin(location, { text: 'P'});
71: map.entities.push(pushpin);
72: }
73:
74: function showMap()
75: {
76: var mapOptions = {
77: credentials: "AuoGTNP56_kmS3IKUa0E-p_PBX1oWJO6WTpw2xFd-hZqFsffSh3FhKGXhFHQD52y",
78: center: new Microsoft.Maps.Location(39.80, -98.55),
79: zoom:5
80: }
81: map = new Microsoft.Maps.Map(document.getElementById("map"), mapOptions);
82: }
83:
84: function centerMap()
85: {
86: var pins = getPushPins();
87:
88: var locations = [];
89: for (var i = pins.length-1; i>=0; i--)
90: locations.push(pins[i].getLocation());
91:
92: map.setView({
93: bounds: Microsoft.Maps.LocationRect.fromLocations(locations),
94: zoom: 6
95: });
96:
97: }
98:
99: function getPushPins()
100: {
101: var pins = [];
102: for (var i = map.entities.getLength() - 1; i >= 0; i--)
103: {
104: var pushpin = map.entities.get(i);
105: if (pushpin instanceof Microsoft.Maps.Pushpin)
106: {
107: pins.push(pushpin);
108: }
109: }
110: return pins;
111: }
112:
113: </script>
The Windows Phone Client
Now that we have the SignalR server all set up and waiting for interaction from phone clients, let us develop our Windows Phone client app. We’ll build a regular one page Windows Phone application just to demo functionality & connectivity to the SignalR Hub. Let’s begin by adding the appropriate SignalR nuget as below:
As we launch the app, we ask for a one-time user name so the phone client can be identified in the chatroom. For brevity, let us skip showing the simple XAML markup; you may find it in the downloadable code. Both UIs as below:
As the user indicates with button click that he/she wants to join, we use location awareness in Windows Phone to figure out current geo-coordinates of the user with a physical device or read mock emulator inputs. Here’s some code:
1: private GeoCoordinateWatcher gcw;
2: private double phoneLatitude;
3: private double phoneLongitude;
4:
5: private void connectButton_Click(object sender, RoutedEventArgs e)
6: {
7: if (gcw == null)
8: {
9: // Look for present geo-location with GPS.
10: gcw = new GeoCoordinateWatcher(GeoPositionAccuracy.High);
11: gcw.MovementThreshold = 10;
12: gcw.StatusChanged += new EventHandler<GeoPositionStatusChangedEventArgs>(gcw_StatusChanged);
13: }
14:
15: gcw.Start();
16: }
Once we have location, it’s time to fire up the reference to the SignalR server and try to connect. Here’s the sample code:
1: IHubProxy SignalRServerHub;
2: HubConnection connection = new HubConnection("http://localhost:49968/");
3:
4: private void gcw_StatusChanged(object sender, GeoPositionStatusChangedEventArgs e)
5: {
6: if (e.Status == GeoPositionStatus.Ready)
7: {
8: // Get position from watcher & stop it to conserve battery.
9: phoneLatitude = gcw.Position.Location.Latitude;
10: phoneLongitude = gcw.Position.Location.Longitude;
11: gcw.Stop();
12:
13: // Reference to SignalR Server Hub & Proxy.
14: var messagingHub = connection.CreateProxy("SignalRServer.MessagingHub");
15: SignalRServerHub = messagingHub;
16:
17: // Fire up SignalR Connection & share location.
18: connection.Start().ContinueWith(task =>
19: {
20: if (task.IsFaulted)
21: {
22: // Oopsie, do some error handling.
23: }
24:
25: // Join the Server's list of mapped clients.
26: messagingHub.Invoke("JoinFromPhone", deviceID, phoneLatitude, phoneLongitude).Wait();
27:
28: // Tell the chatroom that you joined.
29: SignalRServerHub.Invoke("PushMessageToClients", App.Current.userName + " just joined!").Wait();
30: });
31: }
32: }
Notice how we build a proxy on the fly (not tight-binding though) and invoke the “JoinFromPhone()” method of the server from the phone app, passing in some identifying information & location coordinates. The point is, you can pass in whatever your server needs. The unique qualifier that we pass in for every phone client to the server is the unique Device ID of the phone. Once pinged, the SignalR Hub wakes up, adds the incoming phone client to an internal collection (which you can persist) and reaches back to each web client (MVC3 view) to invoke some JavaScript functions that actually add a Pushpin on the map. Next up, we also announce on the Chatroom that a new user has joined the Hub by invoking the “PushMessageToClients()” server method. Simple enough, right?
When the user types in some content and hits the Chat button, we got to let the SignalR server know, so that the chat message gets posted on the web front-end & all connected Windows Phone clients. Here’s how we achieve that .. notice that we are making some of these server calls on background threads so as to not hose up our UI:
1: private void chatButton_Click(object sender, RoutedEventArgs e)
2: {
3: if (chatTextbox.Text != string.Empty && chatTextbox.Text != null)
4: {
5: // Fire off background thread to post message to server Chatroom.
6: chatMessage = chatTextbox.Text.Trim();
7: chatTextbox.Text = string.Empty;
8: ChatBackgroundDataWorker.RunWorkerAsync();
9: }
10: }
11:
12: private void ChatBackgroundDataWorker_DoWork(object sender, DoWorkEventArgs e)
13: {
14: // Post message to server chatroom.
15: SignalRServerHub.Invoke("PushMessageToClients", chatMessage).Wait();
16: chatMessage = string.Empty;
17: }
Now, this takes care of the chat message posting part where the server informs all other clients. However, if someone posts a chat message from the web front-end or other phone clients, how does our app know to update the chat dialog with content? There has to be a trigger, right? Unlike JavaScript clients which the SignalR server can directly reach out to in case something of interest happens, there isn’t an easy proposition for .NET clients since we do not have a reference. But, we do have a persistent connection and there is an “On” method on the connection that can be rigged up to fire anytime a certain event happens on the server. Let us do that any time the server posts a message to all clients. Here’s the code .. you’ll be surprised how instantaneously this mechanism posts the chat message to all connected Windows Phone clients:
1: // Listen in on when Server posts anything to Chatroom.
2: SignalRServerHub.On<string>("addChatMessage", message =>
3: {
4: // Add to local ChatRoom.
5: chatDialog.Text += "\r\n";
6: chatDialog.Text += message.Trim();
7: });
Now, when you are done chit-chatting with your fellow geeks, you would like to disconnect officially and take your Pushpin out, right? Let’s add this code to disconnect, so that the corresponding Disconnect method is fired on the server. Again, notice how we do this on the background thread and officially sign out of the chatroom:
1: private void disconnectButton_Click(object sender, RoutedEventArgs e)
2: {
3: DisconnectBackgroundDataWorker.RunWorkerAsync();
4:
5: chatTextbox.IsEnabled = false;
6: chatButton.IsEnabled = false;
7: disconnectButton.IsEnabled = false;
8: connectButton.IsEnabled = true;
9: }
10:
11: private void DisconnectBackgroundDataWorker_DoWork(object sender, DoWorkEventArgs e)
12: {
13: // Send Farewell to Chatroom.
14: SignalRServerHub.Invoke("PushMessageToClients", App.Current.userName + " just left!").Wait();
15:
16: // Disconnect from Hub.
17: SignalRServerHub.Invoke("Disconnect", deviceID).Wait();
18: }
Conclusion
That’s it. A functional Windows Phone client that connects to a SignalR server backend and allows us to see the interactions on a web front-end. The SignalR magic simply provides the persistent connectivity through whatever transport means feasible. Now, could there be potential benefit in leveraging such technology in our Windows Phone apps? Think, think ...
About the Author
Samidip Basu (@samidip) is a technologist & gadget-lover working as a Manager & Solutions Lead for Sogeti USA out of the Columbus Unit. With a strong developer background in Microsoft technology stack, he now spends much of his time in spreading the word to discover the full potential of the Windows Phone platform & cloud-backed mobile solutions in general. He passionately runs the Central Ohio Windows Phone User Group (http://cowpug.org), labors in M3 Conf (http://m3conf.com/) organization and can be found with at-least a couple of hobbyist projects at any time. His spare times call for travel and culinary adventures with the wife. Find out more at http://samidipbasu.com.