SignalR is an Open Source Framework for Real Time WebApps. The main problem with Real-Time in the web is the canal between Browser and Server. If you never had to deal with SignalR and this problem before here is a brief introduction:
The problem
Traditionally the browser initiates the request to the server and the server reacts with an answer. If the server tries to send some information in return the options are limited.
The “solution”
The oldest way is via “Comet” which is a collection of tricks. That means the client initiates a request and it won’t close until informations from the server are transmitted. Principally the connection is open all the time. Well this isn’t the classiest option.
Another way is to use the so called “Server Sent Events” which are supported by almost every browser – besides IE. Older IEs have to use the “Comet” way.
The latest alternatives are so called WebSockets. A TCP connection between server and client is build and at the same time a bidirectional in the opposite direction.
The problem with WebSockets
WebSockets have to be supported by not only the browser but also by the server. In IIS they are official supported since IIS 8.0. That means only with Windows Server 2012 and above. Before that there is no way to activate them in the IIS pipeline.
The browser supports WebSockets since IE 10. Chrome and Firefox offer the support also in earlier versions and the feature is implemented in all modern browsers.
SignalR – real time for everyone
Since there are several ways of “protocol-types” that depend on support from both the client and the server it is not inappropriate to build and App. That’s where SignalR gets into the game. SignalR builds the best possible connection automatically and also it has a very impressive program model. SignalR is open source and the code is available on GitHub. Still you will get support from Microsoft (if needed).
SignalR DemoHub
That’s an example from the GitHub account:
public class DemoHub : Hub 2: { 3: public override Task OnConnected() 4: { 5: return Clients.All.hubMessage("OnConnected " + Context.ConnectionId); 6: } 7: 8: public override Task OnDisconnected() 9: { 10: return Clients.All.hubMessage("OnDisconnected " + Context.ConnectionId); 11: } 12: 13: public override Task OnReconnected() 14: { 15: return Clients.Caller.hubMessage("OnReconnected"); 16: } 17: 18: public void SendToMe(string value) 19: { 20: Clients.Caller.hubMessage(value); 21: } 22: 23: public void SendToConnectionId(string connectionId, string value) 24: { 25: Clients.Client(connectionId).hubMessage(value); 26: } 27: 28: public void SendToAll(string value) 29: { 30: Clients.All.hubMessage(value); 31: } 32: 33: public void SendToGroup(string groupName, string value) 34: { 35: Clients.Group(groupName).hubMessage(value); 36: } 37: 38: public void JoinGroup(string groupName, string connectionId) 39: { 40: if (string.IsNullOrEmpty(connectionId)) 41: { 42: connectionId = Context.ConnectionId; 43: } 44: 45: Groups.Add(connectionId, groupName); 46: Clients.All.hubMessage(connectionId + " joined group " + groupName); 47: } 48: 49: public void LeaveGroup(string groupName, string connectionId) 50: { 51: if (string.IsNullOrEmpty(connectionId)) 52: { 53: connectionId = Context.ConnectionId; 54: } 55: 56: Groups.Remove(connectionId, groupName); 57: Clients.All.hubMessage(connectionId + " left group " + groupName); 58: } 59: 60: public void IncrementClientVariable() 61: { 62: Clients.Caller.counter = Clients.Caller.counter + 1; 63: Clients.Caller.hubMessage("Incremented counter to " + Clients.Caller.counter); 64: } 65: 66: public void ThrowOnVoidMethod() 67: { 68: throw new InvalidOperationException("ThrowOnVoidMethod"); 69: } 70: 71: public async Task ThrowOnTaskMethod() 72: { 73: await Task.Delay(TimeSpan.FromSeconds(1)); 74: throw new InvalidOperationException("ThrowOnTaskMethod"); 75: } 76: 77: public void ThrowHubException() 78: { 79: throw new HubException("ThrowHubException", new { Detail = "I can provide additional error information here!" }); 80: } 81: 82: public void StartBackgroundThread() 83: { 84: BackgroundThread.Enabled = true; 85: BackgroundThread.SendOnPersistentConnection(); 86: BackgroundThread.SendOnHub(); 87: } 88: 89: public void StopBackgroundThread() 90: { 91: BackgroundThread.Enabled = false; 92: } 93: }
The server defines a hub and it calls “client-functions” with the API like for example “hubMessage”.
The method is defined in Javascript and SignalR caters for the call:
1: function writeError(line) { 2: var messages = $("#messages"); 3: messages.append("<li style='color:red;'>" + getTimeString() + ' ' + line + "</li>"); 4: } 5: 6: function writeEvent(line) { 7: var messages = $("#messages"); 8: messages.append("<li style='color:blue;'>" + getTimeString() + ' ' + line + "</li>"); 9: } 10: 11: function writeLine(line) { 12: var messages = $("#messages"); 13: messages.append("<li style='color:black;'>" + getTimeString() + ' ' + line + "</li>"); 14: } 15: 16: function getTimeString() { 17: var currentTime = new Date(); 18: return currentTime.toTimeString(); 19: } 20: 21: function printState(state) { 22: var messages = $("#Messages"); 23: return ["connecting", "connected", "reconnecting", state, "disconnected"][state]; 24: } 25: 26: function getQueryVariable(variable) { 27: var query = window.location.search.substring(1), 28: vars = query.split("&"), 29: pair; 30: for (var i = 0; i < vars.length; i++) { 31: pair = vars[i].split("="); 32: if (pair[0] == variable) { 33: return unescape(pair[1]); 34: } 35: } 36: } 37: 38: $(function () { 39: var connection = $.connection.hub, 40: hub = $.connection.demoHub; 41: 42: connection.logging = true; 43: 44: connection.connectionSlow(function () { 45: writeEvent("connectionSlow"); 46: }); 47: 48: connection.disconnected(function () { 49: writeEvent("disconnected"); 50: }); 51: 52: connection.error(function (error) { 53: writeError(error); 54: }); 55: 56: connection.reconnected(function () { 57: writeEvent("reconnected"); 58: }); 59: 60: connection.reconnecting(function () { 61: writeEvent("reconnecting"); 62: }); 63: 64: connection.starting(function () { 65: writeEvent("starting"); 66: }); 67: 68: connection.stateChanged(function (state) { 69: writeEvent("stateChanged " + printState(state.oldState) + " => " + printState(state.newState)); 70: var buttonIcon = $("#startStopIcon"); 71: var buttonText = $("#startStopText"); 72: if (printState(state.newState) == "connected") { 73: buttonIcon.removeClass("glyphicon glyphicon-play"); 74: buttonIcon.addClass("glyphicon glyphicon-stop"); 75: buttonText.text("Stop Connection"); 76: } else if (printState(state.newState) == "disconnected") { 77: buttonIcon.removeClass("glyphicon glyphicon-stop"); 78: buttonIcon.addClass("glyphicon glyphicon-play"); 79: buttonText.text("Start Connection"); 80: } 81: }); 82: 83: hub.client.hubMessage = function (data) { 84: writeLine("hubMessage: " + data); 85: } 86: 87: $("#startStop").click(function () { 88: if (printState(connection.state) == "connected") { 89: connection.stop(); 90: } else if (printState(connection.state) == "disconnected") { 91: var activeTransport = getQueryVariable("transport") || "auto"; 92: connection.start({ transport: activeTransport }) 93: .done(function () { 94: writeLine("connection started. Id=" + connection.id + ". Transport=" + connection.transport.name); 95: }) 96: .fail(function (error) { 97: writeError(error); 98: }); 99: } 100: }); 101: 102: $("#sendToMe").click(function () { 103: hub.server.sendToMe($("#message").val()); 104: }); 105: 106: $("#sendToConnectionId").click(function () { 107: hub.server.sendToConnectionId($("#connectionId").val(), $("#message").val()); 108: }); 109: 110: $("#sendBroadcast").click(function () { 111: hub.server.sendToAll($("#message").val()); 112: }); 113: 114: $("#sendToGroup").click(function () { 115: hub.server.sendToGroup($("#groupName").val(), $("#message").val()); 116: }); 117: 118: $("#joinGroup").click(function () { 119: hub.server.joinGroup($("#groupName").val(), $("#connectionId").val()); 120: }); 121: 122: $("#leaveGroup").click(function () { 123: hub.server.leaveGroup($("#groupName").val(), $("#connectionId").val()); 124: }); 125: 126: $("#clientVariable").click(function () { 127: if (!hub.state.counter) { 128: hub.state.counter = 0; 129: } 130: hub.server.incrementClientVariable(); 131: }); 132: 133: $("#throwOnVoidMethod").click(function () { 134: hub.server.throwOnVoidMethod() 135: .done(function (value) { 136: writeLine(result); 137: }) 138: .fail(function (error) { 139: writeError(error); 140: }); 141: }); 142: 143: $("#throwOnTaskMethod").click(function () { 144: hub.server.throwOnTaskMethod() 145: .done(function (value) { 146: writeLine(result); 147: }) 148: .fail(function (error) { 149: writeError(error); 150: }); 151: }); 152: 153: $("#throwHubException").click(function () { 154: hub.server.throwHubException() 155: .done(function (value) { 156: writeLine(result); 157: }) 158: .fail(function (error) { 159: writeError(error.message + "<pre>" + connection.json.stringify(error.data) + "</pre>"); 160: }); 161: }); 162: });
What’s new in SignalR 2.0?
Damian Edwards created a nice demo project on his GitHub account:
Azure Websites & Websockets
Since about a month Azue Websites support Websockets as well. In default mode the websocket support is deactivated. You can change the settings in the Azure management portal:
If you run the SignalR demo application without the websocket support that’s what the traffic looks like:
And with the support:
What’s great on SignalR: the “transportation way” is unappealing because SignalR takes care of this for you so you can concentrate on the main functionalities.
SignalR Resources
For more informations follow these links:
- SignalR “JabbR” room . Chat (build with SignalR) where the developers often hang out
This video by two of the main developers of SignalR is very impressive: