Monitor Inside Your Running Services Using SignalR And WCF

Introduction

Windows services are long running processes that sit in the background doing their thing and not troubling anyone ... until they go wrong, and everyone starts scrambling. Services can be difficult to debug, and gaining insight into what is happening at a moment in time can be problematic. At best, visibility is usually relegated to spitting messages into the EventLog or a database and hitting refresh a dozen times (unless you have a situation where you are able to hook into remote debug, but that's another story...). The same can be true for other systems (for example ASP.NET) that are placed live on a remote server and then some unseen bug kicks in, that can't be replicated in your test environment and then.... BANG! .. .Oh dear! Here we go again.... <yup, been there...>.

This article demonstrates how to use a combination of Windows Communication Foundation (WCF) to send messages from any part of a system, either local, or remote, and then use SignalR and NancyFX to display the message in real time to a user (or yourself, doing the DevOps thing) in a browser. In addition to giving live insight into processes, the framework can also be used to facilitate central logging, and other general message processing.

Background

Over the years, I have had the need to be able to have a more real-time "deep view" into Windows services, plugin modules, remote applications etc ... not by digging through log tables, but by having a cool dashboard that I could put up on the wall that is a live insight to my running services and servers. This doesn't quite go that far, but does form the basis of a framework that could easily be built out to make something Matrix-like!

Here is an overview of the technologies used.

NancyFX -> this is a lightweight, drop-in web-server that is extremely easy to use. It is a framework for serving html page requests, and sending response. It is like IIS (Internet Information server), not MVC. The reason I use it in this case is that don't want to install IIS on the machine, and its self-contained within my service (or other app). It also comes without the heavy overhead that IIS has. IIS is great - but complete overkill for this particular type of job.

WCF and SignalR preform two different functions. WCF does a lot of heavy-lifting of FTP/HTTP/TCP/Enterprise-bus, etc and is extremely useful to getting messages from the outside world into one place.

SignalR does something very powerful, very simply - it enables bi-directional communication between server and browser.

The overall the process is as follows.

  • WCF listens for incoming messages on named-pipe (but could be any protocol WCF supports)
  • On receipt of message, it passes the message to SignalR.
  • SignalR takes the message and broadcasts it to any connected clients in the browser.

I'm not going to go into the background of creating Windows services etc. - there're enough examples on CodeProject and the Interwebs in general. The best thing to do is to download the code, restore any nuget packages and play with it! What the article will do is show you how I put this solution together and talk you through the highlights, and possibilities. ... ok, let's get stuck in ... it is quite simple, and I'm sure you'll find a use for it :)

Startup

We have three main parts of the solution.

  • WCF to accept incoming messages
  • SignalR to send messages to the browser
  • NancyFX to act as a web-host to serve the data to the browser

In my service, I have a start-up method where the scaffolding framework for these three components gets constructed.

Because there is the very slight possibility that a message might come into the stack before I am setup to deal with it. I put things together in the following order (the comments make the code self explanatory).

  1. public void DoStart()  
  2. {  
  3.     // setup SignalR on port 8059  
  4.     string url = "http://localhost:8059";  
  5.     // start SignalR  
  6.     WebApp.Start(url);  
  7.     // setup NancyFX on port 8080  
  8.     string URL = "http://localhost:8080";  
  9.     Nancyhost = new NancyHost(new Uri(URL));  
  10.     // start Nancy  
  11.     Nancyhost.Start();  
  12.     // setup WCF listening server  
  13.     // nb1  
  14.     ServiceHost host = new ServiceHost(  
  15.         typeof(MessageHub),  
  16.             new Uri[]{  
  17.             new Uri("net.pipe://localhost")  
  18.         });  
  19.     // nb2  
  20.     host.AddServiceEndpoint(typeof(IMessageHub),  
  21.          new NetNamedPipeBinding(), "MessageHubEndPath");  
  22.     host.Open();  
  23. }  

There are two things to watch here.

  1. The serviceHost I am creating is using a simple class type I created called "MessageHub" ... it's important that while initiating the ServiceHost, you use the implementation of your class, *not* the Interface (see next..)

  2. When adding the endpoint for your WCF host, you use the Interface, not the implementation (flip flop...)

    Note that in this example, I am using a "named pipe" as the message transport method. This is because in this instance I want to send inter-process messages between different services/possible asp code/desktop interaction etc. The beauty of WCF however is that you can use it to send/receive messages using a host of methods (named pipes, tcp, http,ftp....). The fantastic multiple protocol implementation of WCF makes it ideal for this project, especially if you want to scale it.

The message sink

For the sake of this article, I am keeping things simple. I assume that we have one or more services we want to monitor and get insight to. The first thing is to set up a simple interface to accept messages:

  1. [ServiceContract]  
  2. public interface IMessageHub  
  3. {  
  4.     [OperationContract]  
  5.     void MessageRelay(string ServiceName, string ProcessName, string Message,  
  6.     LogMessageType LMType);  
  7. }  
The contract is a simple method called MessageRelay that takes in for example the name of the Service sending the message, a process that's being executed, and some message you want to view/log.

We then construct a class to handle the incoming WCF

  1. public class MessageHub : IMessageHub  
  2. {  
  3.     // this is the main method that gets called by the contract  
  4.     public void MessageRelay(string ServiceName, string ProcessName, string Message,  
  5.            LogMessageType LMType = LogMessageType.Broadcast)  
  6.     {  
  7.         // depending on the message type received, we either use SignalR to send the  
  8.         // message to the browser, or log it, or both.  
  9.         if (LMType == LogMessageType.Broadcast)  
  10.             {  
  11.             BroadcastMessage(ServiceName, ProcessName, Message);  
  12.             }  
  13.         else if (LMType == LogMessageType.Log)  
  14.             {  
  15.             LogMessage(ServiceName, ProcessName, Message);  
  16.         }  
  17.         else if (LMType == LogMessageType.BroadcastAndLog)  
  18.             {  
  19.             BroadcastMessage(ServiceName, ProcessName, Message);  
  20.             LogMessage(ServiceName, ProcessName, Message);  
  21.         }  
  22.     }  

So - that's the setup and message receiving taken care of ... next we'll discuss the route part, SignalR in a bit more detail.

The message router

To finish the class, we implement a simple broadcast of the message using SignalR.

  1. // take the message and send it out to all listening browser clients  
  2. private void BroadcastMessage(string ServiceName, string ProcessName,  
  3.          string Message) {  
  4.     var context = GlobalHost.ConnectionManager.GetHubContext<logmessagehub>();  
  5.     context.Clients.All.addMessage(ServiceName, ProcessName, Message);  
  6. }  
  7.   
  8. // simple method to log a message - into Log4Net for example  
  9. private void LogMessage(string ServiceName, string ProcessName, string Message)  
  10. {  
  11.     // log the message here if necessary for example  
  12. }  
Note what is happening in Broadcast message. We are telling SignalR to call a *REMOTE JAVASCRIPT METHOD* in the browser, called "addMessage". We grab the context of the SignalR hub. 
  1. var context = GlobalHost.ConnectionManager.GetHubContext<logmessagehub>();  
and using that context, we then broadcast the message/data received, to the listening browser method...
  1. context.Clients.All.addMessage(ServiceName, ProcessName, Message);  

NB

In this example, I haven't done anything about security, you need to implement that yourself.

There is one other part to note, and that is the SignalR "hub" class that hides away but is key. It provides the "context" that we hooked into earlier in the class.

  1. public class LogMessageHub : Hub  
  2. {  
  3.     public void Send(string ServiceName, string ProcessName, string Message)  
  4.     {  
  5.         Clients.All.addMessage(ServiceName, ProcessName, Message);  
  6.     }  
  7. }  
There is a *big fat gotcha* here to note!.... When we get to the browser and put the client part of the communications in place, we have to refer to the SignalR hub ... from the browser/Javascript side, we must LOWER the first letter of the method name... so instead of calling "LogMessageHub", we actually call "logMessageHub" (note the lowered first letter "l" !!!!) 
 

The host

I use NancyFX as my light-weight embedded web-server of choice. There is a link at the bottom of this page to a more in-depth article on using NancyFX in services - if you are interested, please read it. The only real gotcha for Nancy, is to ensure that when you add resources that need to be accessed by the browser (ie: html pages, css, javascript, image files etc), these MUST be marked as "Copy to output directory - copy if newer" in the property pages. If you don't, Nancy can't see them (she's a bit blind that way).



The front-end

For the article, I have kept things simple. Rather than drop in gauges and charts and dials, we are simply flowing messages into a stack, and displaying this in a table. I am using an extremely well written and useful front-end table management tool called "Datatables.net" .. for simple implementations it is superb. It also has some plugins to enable the user to take table data and download it in CSV, Txt and PDF format among others - very useful.

We start with a simple html table, and then hook DataTables.net onto the table.

  1. <table border="1" cellpadding="5" class="stripe" id="tblLogMessages">  
  2.     <thead>  
  3.         <tr>  
  4.             <th>Received</th>  
  5.             <th>Service</th>  
  6.             <th>Process name</th>  
  7.             <th>Message</th>  
  8.         </tr>  
  9.     </thead>  
  10.     <tbody>  
  11.     </tbody>  
  12. </table>  
To hook DataTables onto the table, we initialize a small bit of Jquery goodness on document ready.

NB

Here's a gotcha... (wow, this is getting tiresome!) ...... you MUST input the "thead" structure when declaring the table or it wont render correctly with the DataTables.net plugin. (this might change, but at time of writing this was as I found it). 
  1. $(function () {  
  2.     var logTable = $('#tblLogMessages').DataTable({  
  3.         dom: '<it<t>lp>',  
  4.         "sScrollY": 400,  
  5.         "tableTools": {  
  6.             "sSwfPath""/swf/copy_csv_xls_pdf.swf"  
  7.         }  
  8.     });  

There are a wealth of settings for DataTables, the ones I used are.

  • dom .. this allows me to control the layout of the tables, and place headers/footers etc.
  • sScrollY .. the scroll window size the table sits in
  • tableTools .. this is the DataTables plugin that permits export of data in CSV etc.

As I want the messages then they are added to the table, to show the most recent at the top of the table, I also initialised the ordering

  1. logTable.order([0,'desc']);  

SignalR goodness on the frontend

Ok, now we need to implement SignalR browser-side so the messages the MessageHub receives and sends out are shown to the user. It's really simple ....

(1) Ensure that you have a script in that points back to the host/ip and port SignalR is listening on (remember we set this up on the OnStartup method of the service)

  1. <script src="http://localhost:8059/signalr/hubs"></script>  
(2) Create a connection to the "SignalR HUB" on the server (this is the "context" we discussed earlier)
 
  1. //Set the hubs URL for the connection  
  2. $.connection.hub.url = "http://localhost:8059/signalr";  
(3) Create a proxy that references the hub
remember the lowering of the first letter of the server-side method name? .. here's where it comes in... 
  1. var messageSink = $.connection.logMessageHub;  
(4) Finally, we will create a method *that can be called by the server via SignalR fairy-dust magic* (!) to broadcast messages... and kick it to start. 
  1. messageSink.client.addMessage = function (serviceName, processName, message) {  
  2.   
  3. var dt = moment().format("dddd, MMMM Do YYYY, h:mm:ss:SSS a");  
  4.   
  5. if (Paused) {  
  6.     logTable.row.add([dt, serviceName, processName, message]);  
  7. }  
  8. else {  
  9.     logTable.row.add([dt, serviceName, processName, message]).draw();  
  10. }  
  11.   
  12. // Start the connection.  
  13. $.connection.hub.start();  

In the method above, instead of directly adding rows to our html table, we add them directly to the DataTables.net data-manager, and this takes care of updating the render.

The only other small thing to note is I implemented "pause" functionality. When messages are streaming down from the server, you may want to pause the flow in the browser, check something out, then let it run again. This works by telling the DataTable to draw() or not, depending on if my pause checkbox is ticked on or off. 

  1. var Paused = false;  
  2.   
  3. $('#chkMessageFlow').click(function () {  
  4.     Paused = !Paused;  
  5.     logTable.draw();  
  6. });  
  7.   
  8. ...  
  9.   
  10. if (Paused) {  
  11.         // add rows, but don't draw  
  12.         logTable.row.add([dt, serviceName, processName, message]);  
  13.     }  
  14.     else {  
  15.         // add rows, and draw!  
  16.         logTable.row.add([dt, serviceName, processName, message]).draw();  
  17.     }  

And that ladies and gentlefolk, is it for the server (and embedded web-server) side! ... it's ready to test :)

Test client

Our test client is a basic console application. It sets up a WCF named-pipe client, asks the user to enter a number, and for the count of that number, sends a random message (using a GUID as the sample string) to the WCF server.

  1. ChannelFactory<imessagehub> pipeFactory =  
  2.     new ChannelFactory<imessagehub>(  
  3.         new NetNamedPipeBinding(),  
  4.             new EndpointAddress(  
  5.             // important! ... note the endpoint "name" must match WCF serverside  
  6.             "net.pipe://localhost/MessageHubEndPath"));  
  7. // can use any other WCF binding for http/ftp etc  
  8. // wrap with try/catch  
  9. IMessageHub pipeProxy = pipeFactory.CreateChannel();  
  10.   
  11. Console.WriteLine(@"Enter a valid number of times to send a random message eg: 25 <enter>");  
  12. int numCount = Int32.Parse(Console.ReadLine());  
  13.   
  14.   
  15. for (int i = 0; i < numCount; i++)  
  16. {  
  17.     Guid gu = Guid.NewGuid();  
  18.     pipeProxy.MessageRelay("PipeService""ClientProcess", gu.ToString(), LogMessageType.Broadcast);  
  19. }  

So, the test-client functionality, i.e. sending a short operational message, is what you replicate in your methods. Now, instead of Debug.console.write for example, you can now direct these kinds of messages to a message-hub, and from there to a browser, or log file, etc. The nice thing about the system is you can use any form of message transport that WCF caters to.

The end result can be seen in this video example of SignalR in action.

Wrap-up

There you have it - a little troika of technologies that carries out quite a useful function. The core concepts here can be used easily to build an extremely fancy dashboard like the one shown at the top of the article. The technologies presented here are easy to use ... so now, you have no excuse - Get excited ... go do live streaming things :)

Happy code streaming! :) 

Up Next
    Ebook Download
    View all
    Learn
    View all