SharePoint Realtime Notifications - UI feedback for your server-side solutions

by Patrick Penn, SharePoint Architect, Enthusiast and Entrepreneur.

Have you ever asked yourself how to send notifications, in realtime, from your server-side code (Event Receiver, Workflow etc.) to the users UI? Wouldn't it be great to send the user a message to the UI when his started workflow has been finished or more important, even failed?
This technology fills a gap between user actions and feedback from your solutions.

I hope this post will give you a straightforward solution to realize this.

This doesn't look spectacular, but the shown notification is sent from SharePoint server-side C# code!

What you need

  1. An MVC project in Visual Studio
  2. A Delegate Control in your SharePoint solution
  3. toastr to show notifications, but you can use whatever you want
  4. Postman app for Chrome or something elso you like to manually send POST requests
  5. optionally an Azure account to host your SignalR MVC project

If you don't know what SignalR is, look here. This post assumes that you know the basic functionallity of this technology. At least, you should know what a SignalR hub is. You will find some more information here and here

Setup SignalR and notification

Create a new project using the ASP.NET Web Application template. And give the project a name, SignalR for example.

Now use NuGet to add the required components.

Right click on you project and select "Manage NuGet packages...". Then search for RequireJS and click to install it. Next look for SignalR, select "Microsoft ASP.NET SignalR" and click on install, too.
Now download toastr and put the js and css files into the scripts folder of your project. Create a subfolder, if you like.

Create a new js file to setup the required js libraries and the connection to the SignalR hub. Let's name it notifier.app.js. Paste the following code to this file. Be sure to setup the paths to match the locations of the required files. Below in the iffy, you'll see the connection to the SignalR Hub "myhub". So now we really have to setup SignalR.

var url = "http://<your signalr app url>";

(function () {
    require.config({
        paths: {
            "jquery": url + "/scripts/jquery-1.10.2.min",
            "signalr.core": url + "/scripts/jquery.signalR-2.0.2",
            "signalr.hubs": url + '/signalr/hubs?',
            "toastr": url + "/scripts/toastr/toastr",
            "notifier" : url + "/scripts/notifier/notifier"
        },
        shim: {
            "jquery": {
                exports: "$"
            },
            "toastr": {
                deps: ["jquery"],
                exports: "toastr"
            },
            "signalr.core": {
                deps: ["jquery"],
            },
            "signalr.hubs": {
                deps: ["signalr.core"],
            },
            "notifier": {
                deps: ["toastr"],
                exports: "notifier"
            }
        }
    });

    require(['signalr.hubs', 'notifier'], function (hubs, notifier) {
        var myhub;
        var siteUrl = window.location.protocol + "//" + window.location.host + _spPageContextInfo.siteServerRelativeUrl;
        $.connection.hub.logging = true;
        $.connection.hub.url = url + '/signalr';
        myhub = $.connection.myhub;
        myhub.client.newMessage = notifier.newMessage;

        $.connection.hub.start();
    });

    loadjscssfile(url + "/scripts/toastr/toastr.css", "css");
})();


function loadjscssfile(filename, filetype) {
    if (filetype == "js") { //if filename is a external JavaScript file
        var fileref = document.createElement('script')
        fileref.setAttribute("type", "text/javascript")
        fileref.setAttribute("src", filename)
    }
    else if (filetype == "css") { 
        var fileref = document.createElement("link")
        fileref.setAttribute("rel", "stylesheet")
        fileref.setAttribute("type", "text/css")
        fileref.setAttribute("href", filename)
    }
    if (typeof fileref != "undefined")
        document.getElementsByTagName("head")[0].appendChild(fileref)
}

Add another js file to place the toastr notifications. Name it "notifier.js" and paste the following code into it.

define(['signalr.hubs', 'toastr'], function (hubs, toastr) {
    function Toast(type, css, msg) {
        this.type = type;
        this.css = css;
        this.msg = msg;
    }

    toastr.options = {
        "newestOnTop": true
    }

    var newMessage = function (notification) {
        toastr.info(notification.Message);
    }
    return {
        newMessage: newMessage
    }
});

Before creating the hub we have to do an initial setup.

In the root folder of your newly created solution you will find the Startup.cs class. Open it and replace the existing Configuration with the following code:

public void Configuration(IAppBuilder app)
        {
            ConfigureAuth(app);
            app.Map("/signalr", map =>
            {
                // Setup the CORS middleware to run before SignalR.
                // By default this will allow all origins. You can 
                // configure the set of origins and/or http verbs by
                // providing a cors options with a different policy.
                map.UseCors(CorsOptions.AllowAll);
                var hubConfiguration = new HubConfiguration
                {
                    EnableDetailedErrors = true,
                    EnableJSONP = true,
                    // You can enable JSONP by uncommenting line below.
                    // JSONP requests are insecure but some older browsers (and some
                    // versions of IE) require JSONP to work cross domain
                    // EnableJSONP = true
                };
                // Run the SignalR pipeline. We're not using MapSignalR
                // since this branch already runs under the "/signalr"
                // path.
                map.RunSignalR(hubConfiguration);
            });
            //app.MapSignalR();
        }

Now add a new model to represent your message. Name it "Notification.cs" and paste the following code:

public class Notification
{
    public string Message { get; set; }
}

At this point we have to add the hub. Create a new class into the Hubs folder and name it MyHub.cs. Replace the classes code with the following lines:

    [HubName("myhub")]
    public class MyHub : Hub
    {
        public void SendMessage(Notification notification)
        {
            Clients.All.newMessage(notification);
        }
    }

Next add a new controller and name it NotificationController.cs. Replace the Post method with the following code:

public HttpResponseMessage Post([FromBody]Notification notification)
        {
            var context = GlobalHost.ConnectionManager.GetHubContext<MyHub>();

            try
            {
                context.Clients.All.newMessage(notification);
            }
            catch (Exception)
            {

            }

            return new HttpResponseMessage(HttpStatusCode.Created);
        }

That's it on SignalR side.

Setup the SharePoint Delegate Control

In your empty SharePoint project, add a new Delegate Control. Here's a step-by-step instruction.
In delegate controls .ascx file simply add the following javascript tag and replace your urls to the required js files (the url of your SignalR web application):

<script type="text/javascript" data-main="http://<your SignalR web application url>/scripts/notifier/notifier.app" src="http://<your SignalR web application url>/scripts/require.js"></script>

Send notifications to your hub

Be sure, that you have startet your SignalR application!

No use the Postman app for Chrome to send a POST to your SignalR hub.

Use the following url for your POST:

`http:///api/MyHub``

Put this JSON into the body:
{"Message":"Hello World!"}

Finally click on "Send". Now you should see the toastr notification in your SharePoint UI.

The last step is to implement the POST Request into you server-side SharePoint solution. There are different ways to do this. We decided to use RestSharp.
Here is a sample code snippit, which you can use in your Event Receiver or Workflow or wherever you want:

using RestSharp;

public void SendMessage()
{
        var client = new RestClient();
        client.BaseUrl = new Uri("http://<your SignalR web application url>/api/MyHub");
        var message = @"{""Message"":""Hello World!""}";
        var request = new RestRequest { Resource = Resource, Method = Method.POST, RequestFormat = DataFormat.Json };

        try
        {
            var jsonMessage = JToken.Parse(message);
            request.AddParameter("application/json; charset=utf-8", jsonMessage, ParameterType.RequestBody);
        }
        catch (Exception)
        {
            request.AddParameter("message", message);
        }

        Execute(request);
 }

I hope everything works well and you get the picture. Otherwise feel free to comment below, so we might help to find a solution.
Also do, if you find some errors in the code, because we extracted the snippits from a more complex solution.

Your next steps should involve authentication and authorization for SignalR and sending messages to groups. With these basics you will have a solution with a huge potential for bi-directional communication between client and server, in realtime, within your own SharePoint solutions.