Using SignalR To Push StreamInsight Events to Client Browsers

I’ve spent some time recently working with the asynchronous web event messaging engine SignalR. This framework uses JavaScript (with jQuery) on the client and ASP.NET on the server to enable very interactive communication patterns. The coolest part is being able to have the server-side application call a JavaScript function on each connected browser client. While many SignalR demos you see have focused on scenarios like chat applications, I was thinking  of how to use SignalR to notify business users of interesting events within an enterprise. Wouldn’t it be fascinating if business events (e.g. “Project X requirements document updated”, “Big customer added in US West region”, “Production Mail Server offline”, “FAQ web page visits up 78% today”) were published from source applications and pushed to a live dashboard-type web application available to users? If I want to process these fast moving events and perform rich aggregations over windows of events, then Microsoft StreamInsight is a great addition to a SignalR-based solution. In this blog post, I’m going to walk through a demonstration of using SignalR to push business events through StreamInsight and into a Tweetdeck-like browser client.

Solution Overview

So what are we building? To make sure that we keep an eye on the whole picture while building the individual components, I’ve summarized the solution here.

2012.03.01signalr05

Basically, the browser client will first (through jQuery) call a server operation that adds that client to a message group (e.g. “security events”). Events are then sent from source applications to StreamInsight where they are processed. StreamInsight then calls a WCF service that is part of the ASP.NET web application. Finally, the WCF Service uses the SignalR framework to invoke the “addEventMsg()” function on each connected browser client. Sound like fun? Good. Let’s jump in.

Setting up the SignalR application

I started out by creating a new ASP.NET web application. I then used the NuGet extension to locate the SignalR libraries that I wanted to use.

2012.03.01signalr01

Once the packages were chosen from NuGet, they got automatically added to my ASP.NET app.

2012.03.01signalr02

The next thing to do was add the appropriate JavaScript references at the top of the page. Note the last one. It is a virtual JavaScript location (you won’t find it in the design-time application) that is generated by the SignalR framework. This script, which you can view in the browser at runtime, holds all the JavaScript code that corresponds to the server/browser methods defined in my ASP.NET application.

2012.03.01signalr04

After this, I added the HTML and ASP.NET controls necessary to visualize my Tweetdeck-like event viewer. Besides a column where each event shows up, I also added a listbox that holds all the types of events that someone might subscribe to. Maybe one set of users just want security-oriented events, or another wants events related to a given IT project.

2012.03.01signalr03

With my look-and-feel in place, I then moved on to adding some server-side components. I first created a new class (BizEventController.cs) that uses the SignalR “Hubs” connection model. This class holds a single operation that gets called by the JavaScript in the browser and adds the client to a given messaging group. Later, I can target a SignalR message to a given group.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

//added reference to SignalR
using SignalR.Hubs;

///
<summary> /// Summary description for BizEventController /// </summary>

public class BizEventController : Hub
{
    public void AddSubscription(string eventType)
    {
        AddToGroup(eventType);
    }
}

I then switched back to the ASP.NET page and added the JavaScript guts of my SignalR application. Specifically, the code below (1) defines an operation on my client-side hub (that gets called by the server) and (2) calls the server side controller that adds clients to a given message group.

$(function () {
            //create arrays for use in showing formatted date string
            var days = ['Sun', 'Mon', 'Tues', 'Wed', 'Thur', 'Fri', 'Sat'];
            var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'June', 'July', 'Aug', 'Sept', 'Oct', 'Nov', 'Dec'];

            // create proxy that uses in dynamic signalr/hubs file
            var bizEDeck = $.connection.bizEventController;

            // Declare a function on the chat hub so the server can invoke it
            bizEDeck.addEventMsg = function (message) {
                //format date
                var receiptDate = new Date();
                var formattedDt = days[receiptDate.getDay()] + ' ' + months[receiptDate.getMonth()] + ' ' + receiptDate.getDate() + ' ' + receiptDate.getHours() + ':' + receiptDate.getMinutes();
                //add new "message" to deck column
                $('#deck').prepend('</pre>
<div>' + message + '' + formattedDt + ' via StreamInsight</div>
<pre>
');
            };

            //act on "subscribe" button
            $("#groupadd").click(function () {
                //call subscription function in server code
                bizEDeck.addSubscription($('#group').val());
                //add entry in "subscriptions" section
                $('#subs').append($('#group').val() + '</pre>

<hr />

<pre>');
            });

            // Start the connection
            $.connection.hub.start();
        });

Building the web service that StreamInsight will call to update browsers

The UI piece was now complete. Next, I wanted a web service that StreamInsight could call and pass in business events that would get pushed to each browser client. I’m leveraging a previously-built StreamInsight WCF adapter that can be used to receive web service request and call web service endpoints. I built a WCF service and in the underlying class, I pull the list of all connected clients and invoke the JavaScript function.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;

using SignalR;
using SignalR.Infrastructure;
using SignalR.Hosting.AspNet;
using StreamInsight.Samples.Adapters.Wcf;
using Seroter.SI.AzureAppFabricAdapter;

public class NotificationService : IPointEventReceiver
{
	//implement the operation included in interface definition
	public ResultCode PublishEvent(WcfPointEvent result)
	{
		//get category from key/value payload
		string cat = result.Payload["Category"].ToString();
		//get message from key/value payload
		string msg = result.Payload["EventMessage"].ToString();

		//get SignalR connection manager
		IConnectionManager mgr = AspNetHost.DependencyResolver.Resolve();
		//retrieve list of all connected clients
		dynamic clients = mgr.GetClients();

		//send message to all clients for given category
		clients[cat].addEventMsg(msg);
		//also send message to anyone subscribed to all events
		clients["All"].addEventMsg(msg);

		return ResultCode.Success;
	}
}

Preparing StreamInsight to receive, aggregate and forward events

The website is ready, the service is exposed, and all that’s left is to get events and process them. Specifically, I used a WCF adapter to create an endpoint and listen for events from sources, wrote a few queries, and then sent the output to the WCF service created above.

The StreamInsight application is below. It includes the creation of the embedded server and all other sorts of fun stuff.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.ComplexEventProcessing;
using Microsoft.ComplexEventProcessing.Linq;
using Seroter.SI.AzureAppFabricAdapter;
using StreamInsight.Samples.Adapters.Wcf;

namespace SignalRTest.StreamInsightHost
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(":: Starting embedded StreamInsight server ::");

            //create SI server
            using(Server server = Server.Create("RSEROTERv12"))
            {
                //create SI application
                Application app = server.CreateApplication("SeroterSignalR");

                //create input adapter configuration
                WcfAdapterConfig inConfig = new WcfAdapterConfig()
                {
                    Password = "",
                    RequireAccessToken = false,
                    Username  = "",
                    ServiceAddress = "http://localhost:80/StreamInsightv12/RSEROTER/InputAdapter"
                };

                //create output adapter configuration
                WcfAdapterConfig outConfig = new WcfAdapterConfig()
                {
                    Password = "",
                    RequireAccessToken = false,
                    Username = "",
                    ServiceAddress = "http://localhost:6412/SignalRTest/NotificationService.svc"
                };

                //create event stream from the source adapter
                CepStream input = CepStream.Create("BizEventStream", typeof(WcfInputAdapterFactory), inConfig, EventShape.Point);
                //build initial LINQ query that is a simple passthrough
                var eventQuery = from i in input
                                 select i;

                //create unbounded SI query that doesn't emit to specific adapter
                var query0 = eventQuery.ToQuery(app, "BizQueryRaw", string.Empty, EventShape.Point, StreamEventOrder.FullyOrdered);
                query0.Start();

                //create another query that latches onto previous query
                //filters out all individual web hits used in later agg query
                var eventQuery1 = from i in query0.ToStream()
                                  where i.Category != "Web"
                                  select i;

                //another query that groups events by type; used here for web site hits
                var eventQuery2 = from i in query0.ToStream()
                                  group i by i.Category into EventGroup
                                  from win in EventGroup.TumblingWindow(TimeSpan.FromSeconds(10))
                                  select new BizEvent
                                  {
                                      Category = EventGroup.Key,
                                      EventMessage = win.Count().ToString() + " web visits in the past 10 seconds"
                                  };
                //new query that takes result of previous and just emits web groups
                var eventQuery3 = from i in eventQuery2
                                  where i.Category == "Web"
                                  select i;

                //create new SI queries bound to WCF output adapter
                var query1 = eventQuery1.ToQuery(app, "BizQuery1", string.Empty, typeof(WcfOutputAdapterFactory), outConfig, EventShape.Point, StreamEventOrder.FullyOrdered);
                var query2 = eventQuery3.ToQuery(app, "BizQuery2", string.Empty, typeof(WcfOutputAdapterFactory), outConfig, EventShape.Point, StreamEventOrder.FullyOrdered);

                //start queries
                query1.Start();
                query2.Start();
                Console.WriteLine("Query started. Press [Enter] to stop.");

                Console.ReadLine();
                //stop all queries
                query1.Stop();
                query2.Stop();
                query0.Stop();
                Console.Write("Query stopped.");
                Console.ReadLine();

            }
        }

        private class BizEvent
        {
            public string Category { get; set; }
            public string EventMessage { get; set; }
        }
    }
}

Everything is now complete. Let’s move on to testing with a simple event generator that I created.

Testing the solution

I built a simple WinForm application that generates business events or a user-defined number of simulated website visits. The business events are passed through StreamInsight, and the website hits are aggregated so that StreamInsight can emit the count of hits every ten seconds.

To highlight the SignalR experience, I launched three browser instances with two different group subscriptions. The first two subscribe to all events, and the third one subscribes just to website-based events. For the latter, the client JavaScript function won’t get invoked by the server unless the events are in the “Web” category.

The screenshot below shows the three browser instances launched (one in IE, two in Chrome).

2012.03.01signalr06

Next, I launched my event-generator app and StreamInsight host. I sent in a couple of business (not web) events and hoped to see them show up in two of the browser instances.

2012.03.01signalr07

As expected, two of the browser clients were instantly updated with these events, and the other subscriber was not. Next, I sent in a handful of simulated website hit events and observed the results.

2012.03.01signalr08

Cool! So all three browser instances were instantly updated with ten-second-counts of website events that were received.

Summary

SignalR is an awesome framework for providing real-time, interactive, bi-directional communication between clients and servers. I think there’s a lot of value of using SignalR for dashboards, widgets and event monitoring interfaces. In this post we saw a simple “business event monitor” application that enterprise users could leverage to keep up to date on what’s happening within enterprise systems. I used StreamInsight here, but you could use BizTalk Server or any application that can send events to web services.

What do you think? Where do you see value for SignalR?

UPDATE:I’ve made the source code for this project available and you can retrieve it from here.

Author: Richard Seroter

Richard Seroter is Director of Developer Relations and Outbound Product Management at Google Cloud. He’s also an instructor at Pluralsight, a frequent public speaker, the author of multiple books on software design and development, and a former InfoQ.com editor plus former 12-time Microsoft MVP for cloud. As Director of Developer Relations and Outbound Product Management, Richard leads an organization of Google Cloud developer advocates, engineers, platform builders, and outbound product managers that help customers find success in their cloud journey. Richard maintains a regularly updated blog on topics of architecture and solution design and can be found on Twitter as @rseroter.

14 thoughts

  1. I was playing with signalR and found this interesting information. I was trying to implement something like this with SQL Server Service Broker but your solution is more reliable

    Thanks

  2. I seemed to have a slight problem with your example. Here’s what I did:

    1. Have 2 browsers open; one subscribes to the Web cateogry and the other “Doc Repository Event”

    2. Use the Windows app to send in a “New Design Spec Version” event
    3. One browser correctly displays the event
    4. Send it 10 web hits event
    5. The other browser subscribed to “Web” shows nothing
    6. Send in another “New Design Spec Version” event
    7. Now the browser subscribed to “Web” shows “10 web hits”

    It seems for some reason the “Web” event won’t get broadcasted until another type of event has been sent.

    1. Hmm. Well, StreamInsight pushes out events when other events come in and advance it’s application clock. It doesn’t use “system time” but relies on inbound events (and corresponding CTIs) to move time forward. In your case, is it only when a different event type comes in?

  3. Very nice solution. Is there any information available on how to set up the wcf endpoint you reference for WCFAdapter input – “http://localhost:80/StreamInsightv12/RSEROTER/InputAdapter” ? Seems as tho there may be another project or setup I am missing, or just a lack of knowledge on StreamInsight.\. I can get this sample to run when “run as Administrator” is used, or from Visual Studio, but not with normal creds across different machines. The endpoint comes up missing.

    1. Hi Dave, I think I used the WCF adapter that comes in the StreamInsight samples (from Codeplex), judging from my “using” statements. If the WCF service isn’t already up, you may need elevated credentials to listen on port 80.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.