BackGround
The article is an another look that explains ASP.Net SignalR collaboration with ASP.NET Web API technology. You may read about the technology before and even using it. The article will be an addition to the existing articles written about the technology in CodeProject(
ASP.Net SignalR). The article basically will explain how 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.
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.
public interface IBroadCast
{
void BroadCast(MessageRequest messageRequest);
event EventHandler<BroadCastEventArgs> MessageListened;
}
public class BroadCaster : IBroadCast
{
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
public class MessageBroadCastController : ApiController
{
private IBroadCast _broadCast;
public MessageBroadCastController(IBroadCast broadCast)
{
_broadCast = broadCast;
}
[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.
public class BroadCastHub : Hub
{
public BroadCastHub(IBroadCast broadCast)
{
if (broadCast == null)
throw new ArgumentNullException("BroadCast object is null !");
BeginBroadCast(broadCast); }
private void BeginBroadCast(IBroadCast broadCast)
{
broadCast.MessageListened += (sender, broadCastArgs)
=>
{
RegisterMessageEvents(broadCastArgs);
};
}
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); throw new Exception(errorMessage); }
}
}
}
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.
public class NInjectDependencyResolver : NInjectScope, IDependencyResolver
{
private readonly IKernel _kernel;
public NInjectDependencyResolver(IKernel kernel)
: base(container)
{
_kernel = kernel;
}
}
public class NInjectSignalRDependencyResolver : DefaultDependencyResolver
{
private readonly IKernel _kernel;
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
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
var kernel = new StandardKernel();
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());
GlobalConfiguration.Configuration.DependencyResolver = new NInjectDependencyResolver(kernel);
GlobalHost.Configuration.MaxIncomingWebSocketMessageSize = null;
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" />
-->
</sectionGroup>
</configSections>
<hubConfigurations>
<messageListenerConfiguration>
<hubUrl url="http://localhost/RESTfulSignalRService/" />
<hubName name="BroadcastHub" />
<hubEventName eventName="onMessageListened" />
--> -->
</messageListenerConfiguration>
<insertListenerConfiguration>
<hubUrl url="http://localhost/RESTfulSignalRService/" />
<hubName name="BroadcastHub" />
<hubEventName eventName="onInserted" />
</insertListenerConfiguration>
-->
</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.
public class BroadCastListener : IBroadCastListener, IDisposable
{
public BroadCastListener(IHubConfiguration hubConfiguration)
{
}
public string ListenHubEvent(Action<object, BroadCastEventArgs> hubEvent)
{
try
{
hubConnection.Start().
ContinueWith(task
=>
{
if (task.IsFaulted)
{
throw task.Exception;
}
else
{
if (_hubConfiguration.HubEventName != EventNameEnum.UNKNOWN)
{
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);
});
}
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
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 using XMLHttpRequest
object and built-in SQL server store procedures such as sp_OACreate
and sp_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
SELECT
@id = i.ID,
@name = i.Name,
@value = i.Value
FROM
inserted i
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
and sp_OAMethod
, they should be configured by using global configuration setting store procedure called sp_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.
public class MemoryCacheManager
{
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>());
}
}
public static void DBListener(IDBListener dbListener)
{
policy = new CacheItemPolicy() { AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(20) }; HookDBListeners(dbListener);
}
private static void HookDBListeners(IDBListener dbListener)
{
if (dbListener != null)
{
dbListener.InsertListener.ListenHubEvent(InsertListenerEvent);
dbListener.UpdateListener.ListenHubEvent(UpdateListenerEvent);
dbListener.DeleteListener.ListenHubEvent(DeleteListenerEvent);
}
}
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
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