How To Create A Real-Time Notification Area Within Your Episerver Website

This post is a continuation of: Different Ways Of Implementing Notifications Within Episerver and How To Install SignalR Within Episerver .

In today’s guide, I’m going to go over a demo project I’ve created that implements real-time notifications within Episerver. In this demo, notifications can be updated in real-time either through a ‘Content Page Type’ being published within Episerver, or, whenever a scheduled task called ‘Notification Scheduled Task’ runs. When either of these things occurs, the user will instantly be made aware of the notification on the homepage, without them having to refresh the page.

The system will need to have three main functions:

1. Get a list of current notifications
2. Add new notifications and broadcast to all clients when this happens
3. Mark a notification as read from the client side

As there is a lot of code to set this code, I’m only going to cover the more important aspects. The project is available to download from my Github account, here.

Getting Started

The real-time messaging works through a library called SignalR. I won’t cover how to install SignalR here as I’ve previously gone over it (see above). The main class that hangs everything together is the ‘Hub’. A hub class is a special SignalR class you need to inherit from. My Hub looks like this:

public class NotificationHub : Hub
{
private readonly NotificationCentre _notificationCentre;
public NotificationHub() : this(NotificationCentre.Instance) 
{
}
public NotificationHub(NotificationCentre notificationCentre)
{
_notificationCentre = notificationCentre;
}
public IEnumerable<Notification> GetNotifications()
{
return _notificationCentre.GetAllSystemNotifications();
}
public void MarkNotifacationsAsRead()
{
_notificationCentre.MarkNotificationAsRead();
}
public void DeleteAllNotifications()
{
_notificationCentre.DeleteAllNotifications();
}
}

The hub’s main purpose is to manage the messaging. How you architecture this file is up to you. I’ve followed an approach based on an MS tutorial, by implementing a singleton class called ‘NotificationCentre’ to handle the notifications business logic.

The ‘NotificationCentre’ does the actual managing of Notification objects. In this example, it’s all done in-memory, but in the reality, this data may live in a No-SQL, or SQL database.

A singleton is useful in this situation as it stops duplicate actions being performed. For example, if someone had the scheduled jobs running twice, you could possibly end up with two different notifications for the same action… which I definitely don’t want to happen.

The code above is pretty self-explanatory, you inherit from ‘Hub’ and then implement any methods that your custom logic needs.

The NotificationCentre code is a little bit more interesting:

public class NotificationCentre
{
private static readonly Lazy<NotificationCentre> _instance = new Lazy<NotificationCentre>(() => new NotificationCentre(GlobalHost.ConnectionManager.GetHubContext<NotificationHub>().Clients));
private readonly List<Notification> _notifications = new List<Notification>();
private NotificationCentre(IHubConnectionContext<dynamic> clients)
{
Clients = clients;
_notifications = new List<Notification>
{
new Notification()
};
}
public static NotificationCentre Instance
{
get
{
return _instance.Value;
}
}
private IHubConnectionContext<dynamic> Clients { get; set; }
public IEnumerable<Notification> GetAllSystemNotifications()
{
return _notifications;
}
public void AddNewNotification(Notification notification)
{
if (notification == null)
return;
_notifications.Add(notification);
BroacdcastUpdate();
}
public void MarkNotificationAsRead()
{
_notifications.ForEach(x => x.Read = true);
BroacdcastUpdate();
}
public void DeleteAllNotifications()
{
_notifications.Clear();
BroacdcastUpdate();
}
private void BroacdcastUpdate()
{
Clients.All.updateNotifications(_notifications);
}
}

Most of the stuff in here is custom logic that I’ve architected in a style I like. I define a List of Notification objects, this will be used to store the notifications. I’ve implemented a private constructor to ensure it’s a singleton. I’ve added three methods, AddNewNotification, DeleteAllNotifications and GetAllSystemNotifications that does my custom business logic.

The only real SignalR code in this class is BroadcastUpdate. In here I’m passing in my list of Notification objects, into Clients.All.updateNotifications. Clients.All is a signalR thing, updateNotifications is the name of an event that I defined. You can call the methods you define whatever you want to, however, they have to match up with the names you’ll use in the HTML.

In my HTML file (which I’ll cover below), I create a listener that is attached to ‘updateNotifications’. So whenever I call BroadcastUpdate any clients that have registered to that method will get triggered. The HTML to hook this all up, is below:

@model JonDJones.Com.Core.ViewModel.Pages.StartPageViewModel
@{
Layout = "";
}
<h2>Notifications</h2>
To log into Episerver, username: episerver. Password. episerver 🙂
<input type="button" id="markNotificationAsRead" value="Mark Notifications As Read" />
<input type="button" id="deleteAllNotifications" value="Delete All Notifications" />
<ul id="notificationBoard"></ul>
<script src="~/Scripts/jquery-1.6.4.min.js"></script>
<script src="~/Scripts/jquery.signalR-2.2.1.min.js"></script>
<script src="~/signalr/hubs"></script>
<script>
$(function() {
var notificationHub = $.connection.notificationHub;
var notificationBoard = $('#notificationBoard');
notificationHub.client.updateNotifications = function (notifications) {
$('#notificationBoard').empty();
$.each(notifications, function () {
var notification = this;
notificationBoard.append('<li>' + notification.Date + ': <strong>' + notification.Message + '</strong> (Message Read = ' + notification.Read + ')</li>');
});
};
$.connection.hub.start().done(function () {
$('#markNotificationAsRead').click(function () {
notificationHub.server.markNotifacationsAsRead();
});
$('#deleteAllNotifications').click(function () {
notificationHub.server.deleteAllNotifications();
});
notificationHub.server.getNotifications().done(function (notifications) {
$('#notificationBoard').empty();
$.each(notifications, function () {
var notification = this;
notificationBoard.append('<li>' + notification.Date + ': <strong>' + notification.Message + '</strong> (Message Read = ' + notification.Read + ')</li>');
});
});
});
})
</script>

To help make it easier to visualise what the code looks like, see below:

episerver_implementing_a_notifcation_centre_1

The page has two buttons, one to mark a notification as read and the other to delete the message. Pressing either of these buttons will trigger a call to the notification hub class. The notification hub will then call an instance of the singleton NotificationCentre class and perform the relevant action. As mentioned above, each method that will change the List of notification will eventually call BroadcastUpdate() which will trigger the ‘updateNotifications’, perform a system broadcast so the clients can refresh their views accordingly.

All the communication from the HTML to the server is done with the SignalR Javascript library. As you can see in the code above, I’m including jquery, jquery.signalR and ‘~/signalr/hubs’. ~/signalr/hubs is a special route SignalR creates. This route is created in your Startup class with the app.MapSignalR(); call. (See the Installing SignalR tutorial about the Startup class)

The Javascript that registers a listener is shown below:

var notificationHub = $.connection.notificationHub;
notificationHub.client.updateNotifications = function (notifications) {
$('#notificationBoard').empty();
$.each(notifications, function () {
var notification = this;
notificationBoard.append('
<li>' + notification.Date + ': <strong>' + notification.Message + '</strong> (Message Read = ' + notification.Read + ')</li>
');
});
};

On the first line, we create a reference to the hub using .connection. The name of the hub should be the same as the class name you created earlier (it’s the class that inherits from ‘Hub’). Remember, the first letter of the method needs to be lowercase, otherwise the JS will complain it can’t find it!

The stuff within the function is custom code, which updates the HTML when a broadcast occurs. The rough process is delete all the current notifcations out of the notificationBoard div and then loop through each notification pushed from the server and write it to the HTML.

Another thing that is useful to notice is the ‘notificationHub.client’ call. When you use SignalR you have ‘client’ and ‘server’ calls. The client is the way that you set up a listener. Notice how updateNotifications is the same name as the method I make in the NotificationCentre -> BroadcastUpdate() method. Using the same name is the way it hooks the process up. On your server side code, you write a Clients.All. to do the broadcast.

To push to the hub, you use a ‘client’ call, like so:

$('#deleteAllNotifications').click(function () {
notificationHub.server.deleteAllNotifications();
});

This code is a little easier to understand. You’re pushing, so you make a ‘server’ call. The method name on your server call should relate to the method name within your hub class. Again, make sure the method name starts with a lowercase letter, otherwise the JS will complain.

These two different types of call were the main thing that confused me when I started with SignalR. When you want to push to the server, you use the ‘server’ call and the method name of your C# hub code.

When you want to create a listener, you need the SignalR method using the ‘client’ call. In my case this is ‘updateNotifications’. This name not translated to a C# class name, this is simply a method you register with SignalR. You can create as many of these methods as you like and call them whatever you want.

To finish off this tutorial, I’ll quickly add the code examples for the page publishing event listener and the scheduled task code. If this code is unfamiliar with you then I suggest that you read, How To Set-Up A Scheduled Task In Sitecore and How to Hook Into The Episerver CMS Events Pipeline .

Scheduled Task

public class NotificationScheduledTask : JobBase
{
private static readonly ILog Logger = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private static bool MessagesPushed = false;
public NotificationScheduledTask()
{
IsStoppable = true;
}
public override string Execute()
{
var notification = new Notification
{
Message = "Schedule Task Notification"
};
NotificationCentre.Instance.AddNewNotification(notification);
return ToString();
}
public override string ToString()
{
return string.Format(
"Message pushed = {0} ",
MessagesPushed);
}

Page Publishing Pipeline

[InitializableModule]
[ModuleDependency(typeof(EPiServer.Web.InitializationModule))]
public class NotificationGeneratorIntalisationModule : IInitializableModule
{
public void Initialize(InitializationEngine context)
{
context.InitComplete += InitCompleteHandler;
var events = ServiceLocator.Current.GetInstance<IContentEvents>();
events.PublishedContent += EventsPublishedContent;
}
private void EventsPublishedContent(object sender, ContentEventArgs e)
{
var repo = ServiceLocator.Current.GetInstance<IContentRepository>();
var content = repo.Get<IContent>(e.ContentLink);
if (!(content is ContentPage))
return;
var notification = new Notification
{
Message = "Content Page Published Notification"
};
NotificationCentre.Instance.AddNewNotification(notification);
}
public void Preload(string[] parameters)
{
}
public void Uninitialize(InitializationEngine context)
{
}
private void InitCompleteHandler(object sender, EventArgs e)
{
}
}

Real-Time Notification takeaway

Real-time notifcations are pretty easy when you implment SignalR. It comes with a pretty good server-side and client-side framework that takes the complexities of web sockets and managing messaging away from you and allows you to focus on your code

Jon D Jones

Software Architect, Programmer and Technologist Jon Jones is founder and CEO of London-based tech firm Digital Prompt. He has been working in the field for nearly a decade, specializing in new technologies and technical solution research in the web business. A passionate blogger by heart , speaker & consultant from England.. always on the hunt for the next challenge

More Posts

0 replies

Leave a Reply

Want to join the discussion?
Feel free to contribute!

Leave a Reply

Your email address will not be published. Required fields are marked *