Adding real time statistics to SurveyWizz using SignalR

,

SurveyWizz stores a fair old amount of information about people that respond to surveys or visit a website with the survey widget on it.  The information is displayed in several ways including graphs, bar charts etc. but the statistics are only refreshed when the customer moves between pages or manually refreshes the page.

I want to update these statistics on the fly without page refreshes and to do that I’m going to use SignalR.

You can down load the sample project from here.

Note:  This post is based on an old version of SignalR and the details have CHANGED.  Go to SignalR documentation for the latest information.

 

Defining our goals

I’m going to start off small and avoid some of the more complex statistics until I’m comfortable with the technology.  The screen shot below is of the main survey listing page, it’s ideal for what we need,  limited statistics about each survey and a running total of visit and responses since the user last visited the site.

 SWDashboard

The goals are:

1. When a person visits a survey page or a page with a survey widget on then the number of visits will be incremented and displayed on the page in real time.

2. If the person fills out a survey then the number of customers completing a survey will be incremented and displayed to the page below in real time.

3. If either 1 or 2 occurs the “Since you last visit” section should also be updated.

Creating a prototype

Next, I’m going to create a prototype, specifically I want to test calling a web service or an action method to update the number of responses or visits for a survey.  I want to ensure that only specific clients statistics are updated and not broadcast to everyone. 

1. Create an MVC3 project from scratch.

2. From package manager console type Install-Package SignalR.

3. In the _layout.cshtml file add the following references (see screen-shot below):

  • jquery-1.6.4.js
  • jquery.signalR-0.5.0.js
  • signalr/hubs

 

<!DOCTYPE html>
<html>
<head>
    <title>@ViewBag.Title</title>
    <link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" />
    <script src="../../Scripts/jquery-1.7.2.js" type="text/javascript"></script>
</head>
    <body>
        <h1>This is a test</h1>
        @RenderBody()
        <script src="../../Scripts/jquery.signalR-0.5.0.js" type="text/javascript"></script>
        <script src="../../signalr/hubs"></script>
    </body>
</html>

4. Create a home controller.  This will be used to render a test page.

public class HomeController : Controller
{
    //
    // GET: /Home/
    public ActionResult Index()
    {
        return View();
    }
}

5. Add a view for the Index action method in the home controller.  This view will display a random number generated by our server, along with the client Id generated by SignalR.

@{
    ViewBag.Title = "Index";
}
<script type="text/javascript">
    $(function () {
        // Create connection
        var connection = $.connection.swStats;
        // Add call-back method for the server to call.
        connection.updateValue = function (testValue) {
            $('#update-me').html(testValue);
        };
        // Start the connection
        $.connection.hub.start().done(function () {
            // Fetch client id and display
            var myClientId = $.connection.hub.id;
            $('#my-id').html(myClientId);
        });
    })
</script>
<h2>
    Test client updating value</h2>
<div id="my-id">
    0
</div>
<div id="update-me">
    0
</div>

Note.  the connection below is using swStats, notice that it starts with lower case.  This is automatically generated from the class name in step 6 or the HubName attribute.  Regardless it should start with a lower case character.

var connection = $.connection.swStats;

6. Create a new folder and call it Hubs or whatever you like.  In that folder add a new class and inherit from hub. 

namespace SignalRPrototype.Hubs
{
    [HubName("swStats")]
    public class SWStats : Hub
    {
    }
}

7. Create a controller called Response.  This is going to perform a similar operation to the SurveyWizz web service used for recording visits and responses.   We are going to add two methods to this class.

The first SendResponse is going take a parameter of clientId which will be used to update a specific client with a random number.  When this is implemented on SurveyWizz the random number will be retrieved from a stats table and the survey details will be used to identify the client.  Bit more on the client id in step 8.

public ActionResult SendResponse(string clientId = "")
{
    // Random will be replaced by a call to the database to retrieve a count 
    // for a specific survey.
    var random = new Random();
    // Call the client call back method "updateValue" with a random number.
    GetClients(clientId).updateValue(random.Next());
    return null;
}

Next, lets add a method to the controller to retrieve a specific client connection…

private dynamic GetClients(string clientId = "")
{
    // If clientId is empty then return all registerd clients.
    if (clientId == "")
    {
        return GlobalHost.ConnectionManager.GetHubContext().Clients;
    }
    // Return a specific client to broadcast to.
    var clients = GlobalHost.ConnectionManager.GetHubContext().Clients;
    return clients[clientId];
}

Lets add a route in the global.asax.cs file for the SendResponse action method.

routes.MapRoute(
"Response", // Route name
"sendresponse/{clientId}", // URL with parameters
new { controller = "Response", action = "SendResponse", clientId = UrlParameter.Optional } // Parameter defaults
);

8.  Create our own client Id.

SignalR will create a client id for each browser session, if the user refreshes their page then a new client id will be created.  This isn't ideal for my purpose, I want to associate a client browser with their unique customer id in SurveyWizz.  We can do this by creating our own connection id generator.  So create a folder called Custom or whatever you want to call it.  In there create a new class.  The code below is returning a guid as the client id, the real method would use the membership provider and return the actual user id.  But for now a random guid is fine.

public class UserIdClientIdFactory : IConnectionIdGenerator
{
    public string GenerateConnectionId(IRequest request)
    {
        return Guid.NewGuid().ToString();
    }
}

We now need to register our new connection generator in the Global.asax.cs.  Notice the last line using dependency injection and registering UserIdClientIdFactory.

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();
    RegisterGlobalFilters(GlobalFilters.Filters);
    RegisterRoutes(RouteTable.Routes);
    GlobalHost.DependencyResolver.Register(typeof(IConnectionIdGenerator), () => new UserIdClientIdFactory());
}

Well, that should be it for now, if we go ahead and build and run the solution you should see something similar to the screen-shot below.

browser1

The long series of numbers and characters is our guid that GenerateConnectionId generated in step 8.  The guid will be used to uniquely identify the browser window when I call the action method SendResponse.  So let’s do that now, open a new window and enter the following url “http://yourlocalhosthere/sendresponse/guidIdgoeshere”.  For example.

URL

Hopefully, you will see the 0 replaced with a random number.  If you open up a new browser window and point it are you local host, in my case http://localhost:2825 and re-fresh the SendResponse url you will see that the 0 is not replaced in the new window.

Try calling SendRresponse again but this time replace the guid with the one from the new window and you can see that the new window is updated but not the first window.

Summary

How easy was that, I now have a project that will update a specific browser with a random number when calling an external action method.  The actual implementation on SurveyWizz will follow the same format though obviously random numbers and guids will be replaced with valid figures and user id’s. 

You can down load the sample project from here.

Thursday, February 14, 2013 1:58:53 AM
Very nice sample. Unfortunately, IConnectionIdGenerator is not more part of the library and it seems not to have a obvious replacement. It can be a real pain to develop with something that will radicaly change after some time. Do you think SignalR is mature enough to adopt in projects?
Friday, February 15, 2013 9:17:12 AM
Hi Zam, Good point, I will update this blog when I upgrade SurveyWizz with the latest SignalR library. Regarding your question asking is SignalR mature enough? Well, I've been in a situation before when using Coolite (very immature product a few years ago) and every release fixed something I needed fixing but broke something else. The documentation and examples where immature and all contributed to delaying the project. However, my personal opinion is that SignalR is pretty straightforward and pretty easy to implement. So when I do upgrade SurveyWizz I can’t imagine it being that difficult. You have probably seen the link on Stackoverflow but may give you an idea on how to implement something similar (I’ve not tried it). http://stackoverflow.com/questions/14725051/signalr-iconnectionidgenerator-not-found-in-signalr-1-0rc2-release Thanks for your comment. Steve
Feel free to leave a comment.
Thanks, will take a few minutes before your comment is reviewed.