Using SignalR and an event driven architecture to delivery timely information to users.

Using SignalR to deliver notifications.

We're building a collaboration assistance application, and part of collaboration is getting timely notifications to people. Our strategy is to have notifications and data change delivered to the user when it's most suitable, so...

Q: How do you go about easily delivering timely information to application users?

A: SignalR

Example scenario

You're in the middle of an activity which has an associated deadline, and your colleague who's responsible for the deadline changes it.

Be it a shorter or longer deadline you would likely want to see that information so you can act accordingly. Deadline extended - allowing you to possibly move onto something more urgent, deadline shortened – put those headphones on and get cracking.

Some functionality to the rescue

To get something like this in your application is not that complicated with the help of a delivery mechanism like SignalR. What makes it even easier is a an event driven messaging architecture.

The fundamentals of the deadline data changing are as simple as another subscriber listening for a DeadlineOnWorkItemUpdatedEvent.

There’s likely many other areas of the system that could be interested in that event taking place. Now that we're introducing SignalR, there’s simply one more; 'the user notification area'.

The user notification logic revolves around determining if a piece of information such as a deadline being updated is a suitable message to deliver to connected clients, i.e. answering the 'worth notifying' question.

Flow Diagram

An example where it would not be relevant is there’s no one impacted by this deadline yet, i.e. no one is associated with the piece of work yet.

But in the case there is then the notification logic would decide to notify the appropriate users. Now we have a user (or several) to notify with a particular message. This is now a new event DeadlineUpdatedNotificationEvent.

The user action of changing/setting a deadline triggers this flow.

public ActionResult UpdateDeadline(SetDeadline sd)
{
    /* ... save this change ... */

    // For this demonstration the event gets published here
    bus.Publish(new DeadlineUpdatedNotificationEvent 
        { 
            TaskId = sd.Id, Deadline = sd.Date 
        });
}

Making use of SignalR.

In this case there’s a set of logic wrapping a SignalR Hub. For more details visit the SignalR wiki on Github

A hub is an RPC framework over PersistentConnection, which saves you from doing your own dispatching. More info

It’s up for debate if the logic for knowing if the user is in the best ‘state’ to receive the notification should exist on the client or the server.

We feel it’s likely that client side (JavaScript in browser) logic would have the most accurate information but that’s your call. Making use of some persistence logic we can maintain server side (web tier) which users are online and what they’re up to (i.e. last requested part of the application.)

So now a ‘DeadlineUpdatedNotificationEvent’ arrives at the server side Hub wrapping logic, and some more work is done on the message or it’s simply routed via SignalR Clients or Groups so it can reach the users browser.

public class NotificationHub : Hub
{
    private SafeCollection<UserInfo> _connectedUsers;

    public NotifyAboutDeadlineChanged(UserId id, DeadlineDetails dd)
    {
        var user = _connectedUsers.Single(c => c.UserId == userId);

        Clients[user.SignalrId].deadlineHasChanged(dd);
    }
}

The code above is highly simplified to get the point across, it’s about:

  1. Locating if the user is online (your own tracking logic),
  2. How to reach them (via the SignalR session identifier)
    • Groups / Clients[]
  3. Calling the client side method on the dynamic collection to send the message

Some examples of what else might need to be considered are:

  • Is the user still online?
    • Maybe you don't worry about this as with SignalR sending to a connected sessionId that doesn't exist has no ill effects.
  • Do they have multiple connections (i.e. multiple tabs/browsers)?
    • In that case have your user tracking and message sending logic operate on collections of users.
  • Pooling notifications so we don't flood users on quick changes?
  • Server side persisted data that indicates a user doesn't want notifications?

On the client side

The JavaScript logic will also be quite simple. The summary of what code below achieves is:

  • The standard SignalR client side setup logic,
  • Wiring up a method to your Hub on the backend
    • In this case that's the deadlineHasChanged method.
    • The rest is supporting logic to handle and display the notification.

Note this is in CoffeeScript so it reads clearer.

class SignlaRNotifications

    setup: =>
        connectionHub = $.connection.hub

        $.connection.notificationHub.deadlineHasChanged = @deadlineHasChanged

        connectionHub.start()
            .done =>
                console.log("SignalR up and connected.")
            .fail =>
                console.log("SignalR connection failure. Turn on logging to see what went wrong.")


    deadlineHasChanged: (details) ->
        if userAcceptingNotificiations()
            @notications.show details.id, details.msg


    notifications: (id, msg) ->
        #your display notification logic

As for the actual way to notify the user that’s anything you like, so long as it’s not an alert box. An example is - pnotify, there are many choices out there.

Conclusion

That's it. It's now simple to get a variety of system events surfaced quickly to those currently making use of the application.

Nick Josevski
Posted by: Nick Josevski  
Last revised: 20 Feb, 2013 12:32 AM
 

Comments

No comments yet. Be the first!

blog comments powered by Disqus