RESTful SignalR Service






4.92/5 (160 votes)
An article that explain hows to expose SignalR feature through an ASP.NET Web API, which helps applications that can use REST service and to broadcast a real-time message to their clients.
BackGround
Things that will be covered in the article
- Introduction to SignalR
- Inversion of Control/Dependency Inject including SignalR Injection
- Custom configuration section reader
- SQL Server (Table, Procedure and Trigger)
- A touch in WPF, INotifyPropertyChanged, ObservableCollection, DataGrid
- JQuery, AJAX
- Design principles and techniques
Introduction
It's been a while since we saw amazing interactive real time message exchange between users on some modern web applications such as Twitter, FaceBook, multi user online games and some other places. Users will exchange messages asynchronously without blocking each other in real time. SignalR makes such kind of interaction so simple that you can implement it anywhere necessary with little effort. Message interaction and persistent connection with two or muliple user/apps was not a new concepts to SignalR. It was attempted by different commuication techniques/protocols as well. To mention them Long polling, Server-Sent events WebSockets and Forever Frame. Each techniques are explained as follows.
- Long polling - Initiated by client and stay it's persitent connection until the server send the data to the client or till the connection timeout expired. Here the connection is dedicated for only receiving data from the server. If client want to send data it will open another HTTP connection parallelly.The advantange of having is that once server send the request data, the polling will be disconnected.
- Server-Sent events - as the name implies the server sent/response the message to the client in the form or event as soon as an update on the message is completed. The techique relies on an HTML5 API called Event Source. The communication is also initiated by client. http://caniuse.com/Server-Sent events
- WebSockets - This is another API that helps to establish a simultanous persistent bi-direction communication between client/server at anytime needed. http://caniuse.com/WebSockets
- Forever Frame/Comet - This is relies on a hidden iframe html tag in such a way that a long lived connection is establed to send chuck of message from the server to the client till all message content transfered completely.
So what does SignalR do differently? SignalR wraps all these communication protocols as a single(unified) framework. This makes easy for developers to concentrate on solving a problem that require a real time messaging without worrying the underlined communication between the client and the server. It also makes a best pick among the communication protocols upon initalization of the connection among client and server. The picking process is factored by the availability of the protocols on each side of the communicators. SignalR also uses a persistent connection that provides a mechanism to invoke/listen events to check if a connection is closed/opened or message is sent/received to/from clients. Once connection is established message can be sent and received synchronously/asynchronously.
Benefits of having SignalR inside Web API
As I stated earlier, the primary purpose of the article to expose SignalR capability through RESTful service so that client/server which relies on REST will have a chance to broadcast/send a message in real time. Benefit of having such implementation is that :-
- Database servers can broadcast/send any changes(insert/update/delete/other) in real time through REST service.
- Applications developed with other programming language which are capable of consuming REST service will have a chance to broadcast/send message in real time.
- IoT hardwares such as NetdunioPlus2, Arduino, Raspberry PI get a chance to send a real time message status regarding their states.
- Web applications/services that uses memory based caching mechanism will have a chance to listen/receive a real time changes to the cache before the cache expires or without restarting the application/service.
- Last but not list, it facilitates to design highly de-coupled systems that barely knows each other to have a capabilty of REST and SignalR technologies altogether.

Design and Implementation
The general idea of the solution is to define an ASP.NET Web API service that encapsulate SignalR real-time message broadcasting events. By using dependency injections(including SignalR DP), the SignalR hub context and the message broadcaster event REST API will be binded upon initialization of the service. In addition, the REST service consumers will have a ready and up running SignalR message broadcaster events without explicitly calling SignalR hub connection. The code is mainly divided into two sections, namely the RESTful SignalR Service and SignalR Broadcast Listener, a library that wraps SignalR Client library.
Defining RESTful SignalR service starts with defining IBroadCast
interface and its implementer BroadCaster
class which are shown below.
/// <summary>
/// IBroadCast interface
/// </summary>
public interface IBroadCast
{
/// <summary>
/// BroadCast messsage
/// </summary>
/// <param name="messageRequest">MessageRequest value</param>
void BroadCast(MessageRequest messageRequest);
/// <summary>
/// Message Listener event handler
/// </summary>
event EventHandler<BroadCastEventArgs> MessageListened;
}
/// <summary>
/// BroadCaster class
/// </summary>
public class BroadCaster : IBroadCast
{
// .........................................
// Full code is available in the source code
// .........................................
/// <summary>
/// BroadCaster class
/// </summary>
/// <param name="messageRequest">MessageRequest value</param>
public void BroadCast(MessageRequest messageRequest)
{
EventHandler<BroadCastEventArgs> handler;
lock (eventLocker)
{
handler = messageListenedHandler;
if (handler != null)
{
handler(this, new BroadCastEventArgs(messageRequest));
}
}
}
}
Then define Api controller class, MessageBroadCastController
that will pass the broadcasted message to the SignalR Hub
/// <summary>
/// Message broadcaster ApiController class
/// </summary>
public class MessageBroadCastController : ApiController
{
private IBroadCast _broadCast;
// .........................................
// Full code is available in the source code
// .........................................
/// <summary>
/// Message broadcaster ApiController class
/// </summary>
public MessageBroadCastController(IBroadCast broadCast)
{
_broadCast = broadCast;
}
/// <summary>
/// BroadCast message
/// </summary>
/// <param name="messageRequest">MessageRequested value</param>
/// <returns>string message</returns>
[HttpPost]
public string BroadCast(MessageRequest messageRequest)
{
string response = string.Empty;
try
{
_broadCast.BroadCast(messageRequest);
response = "Message successfully broadcasted !";
}
catch (Exception exception)
{
response = "Opps got error. ";
response = string.Concat(response, "Excepion, Message : ", exception.Message);
}
return response;
}
}
Then define the BroadCastHub
class, that registers the message events upon called by the clients.
/// <summary>
/// BroadCastHub class
/// </summary>
public class BroadCastHub : Hub
{
// .........................................
// Full code is available in the source code
// .........................................
/// <summary>
/// BroadCastHub class
/// </summary>
public BroadCastHub(IBroadCast broadCast)
{
if (broadCast == null)
throw new ArgumentNullException("BroadCast object is null !");
BeginBroadCast(broadCast); // This will avoid calling to initialize a hub in message broadcaster client
}
/// <summary>
/// Begin broadCast message
/// </summary>
/// <param name="broadCast">IBroadCast value</param>
private void BeginBroadCast(IBroadCast broadCast)
{
// Register/Attach broadcast listener event
broadCast.MessageListened += (sender, broadCastArgs)
=>
{
RegisterMessageEvents(broadCastArgs);
};
// .........................................
// Full code is available in the source code
// .........................................
}
/// <summary>
/// Register broadcasted message to SignalR events
/// </summary>
/// <param name="broadCastArgs">BroadCastEventArgs value</param>
private void RegisterMessageEvents(BroadCastEventArgs broadCastArgs)
{
if (broadCastArgs != null)
{
MessageRequest messageRequest = broadCastArgs.MessageRequest;
IClientProxy clientProxy = Clients.Caller;
if (messageRequest.EventName != EventNameEnum.UNKNOWN)
{
clientProxy.Invoke(messageRequest.EventName.EnumDescription(), messageRequest.Message);
}
else
{
string errorMessage = "Unknown or empty event name is requested!";
clientProxy.Invoke(EventNameEnum.ON_EXCEPTION.EnumDescription(), errorMessage); // Goes to the listener
throw new Exception(errorMessage); // Goes to the broadcaster
}
}
}
}
Once we define all the necessary classes and interfaces then register to global configuration to provide a well prepared message listener events through the service. But before that, lets define our dependency resolver that will assist the registration process.
/// <summary>
/// NInject dependency resolver class
/// </summary>
public class NInjectDependencyResolver : NInjectScope, IDependencyResolver
{
private readonly IKernel _kernel;
// .........................................
// Full code is available in the source code
// .........................................
/// <summary>
/// NInject dependency resolver class
/// </summary>
/// <param name="container">IKernel value</param>
public NInjectDependencyResolver(IKernel kernel)
: base(container)
{
_kernel = kernel;
}
}
/// <summary>
/// SignalR NInject dependency resolver class
/// </summary>
public class NInjectSignalRDependencyResolver : DefaultDependencyResolver
{
private readonly IKernel _kernel;
// .........................................
// Full code is available in the source code
// .........................................
/// <summary>
/// SignalR NInject dependency resolver class
/// </summary>
/// <param name="container">IKernel value</param>
public NInjectSignalRDependencyResolver(IKernel kernel)
{
_kernel = kernel;
}
}
And finally, bind the message broadcaster along with SignalR hub connection context under owin Startup
class. The important piece here is that to register the same NInject kernel instance for both of the dependency resolvers and wiring them to the global configuration.
[assembly: OwinStartup(typeof(RESTfulSignalRService.Startup))]
namespace RESTfulSignalRService
{
/// <summary>
/// OWIN startup class
/// </summary>
public class Startup
{
/// <summary>
/// Configuration value
/// </summary>
/// <param name="app">IAppBuilder value</param>
public void Configuration(IAppBuilder app)
{
var kernel = new StandardKernel();
// SignalR Hub DP resolver
var resolver = new NInjectSignalRDependencyResolver(kernel);
kernel.Bind(typeof(IHubConnectionContext<dynamic>)).
ToMethod(context =>
resolver.Resolve<IConnectionManager>().
GetHubContext<BroadCastHub>().Clients).
WhenInjectedInto<IBroadCast>();
kernel.Bind<IBroadCast>().
ToConstant<BroadCaster>(new BroadCaster());
// IBroadcast DP resolver
GlobalConfiguration.Configuration.DependencyResolver = new NInjectDependencyResolver(kernel);
GlobalHost.Configuration.MaxIncomingWebSocketMessageSize = null; // Unlimited incoming message size
app.Map("/signalr", map =>
{
map.UseCors(CorsOptions.AllowAll);
map.RunSignalR(new HubConfiguration()
{
EnableDetailedErrors = true,
Resolver = resolver
});
});
}
}
}
The SignalR Broadcast Listener library is nothing but a SignalR Hub event listener wrapper on top of SignalR .NET Client library.The library easies the way client listens hub events raised by the RESTful SignalR service. A major thing implemented around here is that client events will be attached to receive the message broadcasted by the RESTful SignalR service. Upon configuring the library with the required inputs(URL, hubName and event name) the broadcaster message will be transfered to the specified section on the client code. To facilitate these required inputs, we define a custom hub configuration reader class(HubConfigurationSection
) that can read these inputs from a configuration (app.config/web.config
) or assigning the inputs upon initialization of the class. The inputs for the custom hub configuration reader are defined as follows :
Configuration Name | Description | Example |
hubURL | a url where a message is broadcasted. In this case, the value is a message broadcaster REST service address. | <hubUrl url="http://localhost/RESTfulSignalRService/"/> |
hubName | an actual hub name where the message event defined. | <hubName name="BroadcastHub"/> |
hubEventName | an actual event name that is going to be listened. | <hubEventName eventName="onMessageListened" /> |
hubListeningIndicator | an indicator that enable/disable event listening. | <hubListeningIndicator isEnabled="false"/> |
Along with these configurable values, the client will pass an action(Action<object, BroadCastEventArgs>
) that captures the broadcasted message fires back to the client code.So how is the library implemented? First lets see the overall class diagram of the library.

As you can see from the class diagram, there are few classes and interfaces that facilitate to gather the necessary configurations which I already explained earlier. IBroadCastListener
facilitate the necessay operations for listening broadcasted message. IHubConfiguration
interface helps to read a custom hub configuration from app.config/web.config
file or be instantiated through it's implementer, HubConfigurationSection
class. The curstom configuration looks like as follows.
<configSections>
<sectionGroup name="hubConfigurations">
<section name="messageListenerConfiguration"
type="SignalRBroadCastListener.HubConfiguration.HubConfigurationSection,
SignalRBroadCastListener" />
<section name="insertListenerConfiguration"
type="SignalRBroadCastListener.HubConfiguration.HubConfigurationSection,
SignalRBroadCastListener" />
<!--
Define more hub sections
-->
</sectionGroup>
</configSections>
<hubConfigurations>
<messageListenerConfiguration>
<hubUrl url="http://localhost/RESTfulSignalRService/" />
<hubName name="BroadcastHub" />
<hubEventName eventName="onMessageListened" />
<!-- <hubListeningIndicator isEnabled="false" /> --> <!-- Default is enabled -->
</messageListenerConfiguration>
<insertListenerConfiguration>
<hubUrl url="http://localhost/RESTfulSignalRService/" />
<hubName name="BroadcastHub" />
<hubEventName eventName="onInserted" />
</insertListenerConfiguration>
<!--
Configure more hub section
-->
</hubConfigurations>
Once these configurations along with an event listener delegate passed, the BroadCastListener
class will initialze the SignalR .NET client related classes upon ListenHubEvent
method is called.
/// <summary>
/// BroadCastListener class
/// </summary>
public class BroadCastListener : IBroadCastListener, IDisposable
{
// .........................................
// Full code is available in the source code
// .........................................
/// <summary>
/// BroadCast listener class
/// </summary>
/// <param name="hubConfiguration">IHubConfiguration value </param>
public BroadCastListener(IHubConfiguration hubConfiguration)
{
}
/// <summary>
/// Listen hub event and attach the message to the client event
/// </summary>
/// <param name="hubEvent">Client hub event</param>
/// <returns>string value that shows status</returns>
public string ListenHubEvent(Action<object, BroadCastEventArgs> hubEvent)
{
// .........................................
// Full code is available in the source code
// .........................................
try
{
hubConnection.Start().
ContinueWith(task
=>
{
if (task.IsFaulted)
{
throw task.Exception;
}
else
{
// Register broadcast events
if (_hubConfiguration.HubEventName != EventNameEnum.UNKNOWN)
{
// Register/attach broadcast event
lock (eventLocker)
{
BroadCastListenerEventHandler += (sender, broadCastArgs)
=> hubEvent.Invoke(sender, broadCastArgs);
}
}
}
}, TaskContinuationOptions.OnlyOnRanToCompletion).Wait();
}
catch (AggregateException aggregateException)
{
throw aggregateException;
}
if (hubConnection.State == ConnectionState.Connected)
IsConnected = true;
proxyHub.On<string>(_hubConfiguration.HubEventName.EnumDescription(),
message =>
{
_broadCastListenerEventArgs = new BroadCastEventArgs(
new MessageRequest()
{
Message = message,
EventName = _hubConfiguration.HubEventName
});
OnMessageListened(_broadCastListenerEventArgs);
});
// .........................................
// Full code is available in the source code
// .........................................
}
private void OnMessageListened(BroadCastEventArgs broadCastArgs)
{
if (BroadCastListenerEventHandler != null)
BroadCastListenerEventHandler(this, broadCastArgs);
}
}
Notice the code around the ListenHubEvent
method. I used TaskContinuationOptions.OnlyOnRanToCompletion
to make sure all preceding tasks related to message broadcasting are completed and the appropriate message is received before firing back the content to the client code.
Client applications
1. Message Broadcaster
- An SQL Server database table that broadcast data changes to the respsective clients in real time fashion. The store procedure below is responsible for calling the RESTful SignalR service.
CREATE PROCEDURE [dbo].[USP_INVOKE_REST_SERVICE] @message NVARCHAR(MAX), @eventName NVARCHAR(20), @response NVARCHAR(1000) OUTPUT AS -- ......................................... -- Full code is available in the source code -- ......................................... -- Make sure the URL pointed to the right SignalR enabled service SET @url = CONCAT('http://localhost/restfulsignalrservice/messagebroadcast/broadcast?message=', @message,'&eventName=', @eventName) EXEC sp_OACreate 'MSXML2.XMLHTTP', @object OUT; EXEC sp_OAMethod @object, 'open', NULL, 'post', @url,'false' EXEC sp_OAMethod @object, 'send' EXEC sp_OAMethod @object, 'responseText', @response OUTPUT SELECT @response AS 'Response Text' EXEC sp_OADestroy @object
Basically the store procedure invokes the service usingXMLHttpRequest
object and built-in SQL server store procedures such assp_OACreate
andsp_OAMethod
. Then use this store procedure anywhere applicable. Suppose a database table(ConfigurationLookUp
) need to broadcast/send its changes then by defining an Insert trigger we can achieve the desired functionality as shown below.CREATE TRIGGER [dbo].[TRG_INSERTED_CONFIGURATION_LOOKUP] ON [dbo].[ConfigurationLookUp] FOR INSERT AS -- ......................................... -- Full code is available in the source code -- ......................................... -- Complex query can be applied SELECT @id = i.ID, @name = i.Name, @value = i.Value FROM inserted i -- JSONify the message SET @message = CONCAT('{"ID":',CAST(@id AS NVARCHAR(20)),',"Name":"',@name,'","Value":"',@value,'"}') SET @eventName = 'onInserted' EXEC [dbo].[USP_INVOKE_REST_SERVICE] @message, @eventName, @response OUTPUT
Update and Delete trigger can also be implemented similar way. Two import thing to note here :- Using a trigger will tell you the exact modified/changed record(row) out of the entire table records(rows) and it will broadcast this modified/changed records(rows)to the respective broadcast listner clients. This facilitates the listners to deal with only the modified/changed records(rows).
- A simple ADO.NET CRUD operation can invoke the service indirectly through the database and broadcast the change. This is also one example of a Virtual Message Broadcasting scenario.
- Note: In order to work with
sp_OACreate
andsp_OAMethod
, they should be configured by using global configuration setting store procedure calledsp_configure
. See https://msdn.microsoft.com/en-us/library/ms191188.aspx for how to enable them. - An IoT hardware such as Netdunio Plus 2 or Ardunio can call the service which enables any external app to listen the broadcasted message. A simple example that uses Netdunio Plus 2 is available in the source control.
- A simple html client app that uses ajax post to broadcast a message.The code is also included in the source control.
2. Message Listeners
- A Caching service that updates its cached data by listening the change source. In this case the source is a database.
/// <summary> /// Memory cache manager /// </summary>> public class MemoryCacheManager { // ......................................... // Full code is available in the source code // ......................................... /// <summary> /// Get ConfigurationLookUps caches /// </summary> public static List<ConfigurationLookup> ConfigurationLookUpCaches { get { cache = MemoryCache.Default; _configurationLookUpCaches = cache[CONFIGURATION_LOOKUP_CACHE_KEY] as List<ConfigurationLookup> if (_configurationLookUpCaches == null) { _configurationLookUpCaches = ConfigurationCacheDataAcces.GetConfigurationLookUps(); cache.Add(CONFIGURATION_LOOKUP_CACHE_KEY, _configurationLookUpCaches, policy); } return _configurationLookUpCaches ?? (_configurationLookUpCaches = new List<ConfigurationLookup>()); } } /// <summary> /// IDBListener initializer method /// </summary> /// <param name="dbListener">IDBListener value</param> public static void DBListener(IDBListener dbListener) { policy = new CacheItemPolicy() { AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(20) }; // A 20 min expiration policy HookDBListeners(dbListener); } /// <summary> /// ListenerEvent Hooker method /// </summary> /// <param name="dbListener">IDBListener value</param> private static void HookDBListeners(IDBListener dbListener) { if (dbListener != null) { dbListener.InsertListener.ListenHubEvent(InsertListenerEvent); dbListener.UpdateListener.ListenHubEvent(UpdateListenerEvent); dbListener.DeleteListener.ListenHubEvent(DeleteListenerEvent); } } /// <summary> /// Insert event listener /// </summary>> /// <param name="sender">Sender value</param> /// <param name="broadCastEventArgs">BroadCastEventArgs value</param> void static InsertListenerEvent(object sender, BroadCastEventArgs broadCastEventArgs) { lock (_locker) { ConfigurationLookup configurationLookUp; if (ConverterHelper.TryDeserialize<ConfigurationLookup>(broadCastEventArgs.MessageRequest.Message, out configurationLookUp)) { _configurationLookUpCaches.Add(configurationLookUp); _configurationLookUpCaches.OrderByDescending(cl => cl.ID); cache.Add(CONFIGURATION_LOOKUP_CACHE_KEY, _configurationLookUpCaches, policy); } } } }
Note : Such kind of implementation avoids unnecessary round trip to the entire database as well as a service restart action to reflect the changes made to the data.
- Similarly, a WPF app that listens the database changes and reflects the change to an observable collection and an animated datagrid control. A complete code is available in the source control.
- A simple html client app that uses SignalR JS Client to listen broadcasted message upon sent by a broadcaster.The code is also included in the source control.
Conclusion
For the past few years Microsoft Visual Studio team creates such an important technology to .NET echo system.It wasn't so easy to make client/server applications interactive in real time. Embedded plugins such as ActiveX, Flash, Silverlight was able to do the job. But they weren't elegant due to dependency on plugin that the client should enable them. Besides they are not fully supported for different environment like mobile and others.
Since SignalR introduced, too many difficult business scenarios that require real time communication are being resolved within few line of codes.It also worth to mention that latest and updated versions of browsers has lot of impact for such revolution.
References
- http://www.asp.net/signalr/overview/guide-to-the-api/handling-connection-lifetime-events
- http://cometdaily.com/2007/11/05/the-forever-frame-technique
- http://www.asp.net/signalr/overview/getting-started/supported-platforms
- http://www.netduino.com/downloads/
- http://netmf.github.io/
- http://netmftoolbox.codeplex.com/
- http://netmf.codeplex.com/
- http://www.amazon.com/SignalR-Programming-Microsoft-Developer-Reference/dp/0735683883 [Got free copy from the author]
History
- Apr 21, 2015 : First Version
- Updated on Apr 22, 2015 - Article format issue
- Updated onApr 26, 2015 - Article format issue
- Updated on May 19, 2015 - Article format issue
- Updated on May 28, 2015 - Article format issue
- Updated on May 29, 2015 - Article format issue
- Updated on Oct 27, 2015 - Broken download issue