Harnessing SignalR in SharePoint 2013 (Office 365)

Over a year ago I wrote a post on Harnessing SignalR in SharePoint. Back then, SignalR was fairly new and the amount of information out there was limited. Getting SignalR to work with SharePoint posed some challenges, all of which are outlined in that post, however eventually we got it working. Since then I’ve had little to do with the technology however the colleague I worked with on that proof of concept, Elliot Wood, has continued to keep on top of the changes to the library and the implications that may have for SharePoint integration. While i’m posting this to my blog, i’m doing so on behalf of Elliot who is responsible for all of the magic soon to be displayed.

Even though there was a fair amount of excitement in regards to SignalR, and particularly the work we did around getting it working in SharePoint, the number of subsequent posts along the same lines was fairly limited. One person who carried the torch was Maximilian Melcher – his post SignalR in SharePoint 2013 – the real-time web is coming! outlined that integrating SignalR in SharePoint 2013 wasn’t as easy as one might expect – even though we were now on the .NET 4.0 framework which should have made life much easier. Max ended up getting his solution working and subsequently posted a codeplex solution, however the post was enough to pose the challenge to Elliot to get it working as easily as possible.

The solution presented uses an autohosted App for SharePoint 2013 hosted on Office 365. With Microsoft’s direction seemingly steering away from full-trust and sandboxed solutions and straight towards the App model, this can be considered the most viable and future-proof solution to the ‘SharePointR’ dream.

To ensure the focus of this post remains focussed on integrating SignalR with SharePoint i’m going to avoid the specifics about SignalR and how to program with it. I’m also going to avoid specifics on how to create and deploy SharePoint Apps. We’ve come a long way with SignalR and the information out there is much better – I’d recommend reading ASP.NET SignalR Hubs API Guide – Server (C#) if you need an overview of programming with SignalR. For those that need a primer on SharePoint Apps, particularly in relation to provider-hosted Apps on Office 365 (the more ‘production-safe’ way of hosting Apps on Office 365), then head over to Chris O’Brien’s post Deploying SP2013 provider-hosted apps/Remote Event Receivers to Azure Websites (for Office 365 apps) for a quick read.

Now that you’re comfortable with the theory, it’s on to the solution. There are 2 pieces to the autohosted App puzzle – the SharePoint App and the SharePoint App Web.

SharePoint App

The purpose of this project is to define the Remote Event Receiver (RER) and also configure the Installed Event Endpoint for the App. The former essentially sets the types of events that will trigger the remote service and points the App to that service, the latter points to the service which will trigger when the App is installed.

Adding the RER is as simple as right-clicking the project and adding a new Remote Event Receiver and selecting the events you want to listen out for. Everything else will be taken care of for you including creating the Package, Feature and tying the RER to its class which will be hosted in the SharePoint App Web.

Configuring the Installed Event Endpoint requires you to edit the properties of the SharePoint App project and set the Handle App Installed property to true. Once again Visual Studio takes care of the rest.

The final step is to access the Permissions tab in the AppManifest.xml editor and add the necessary permissions to the Web and List to ensure your App has the required levels of access to your site.

That’s all there is to it.

SharePoint App Web

Setting up the SharePoint App Web project is a little more involved than the above however is still relatively simple. The first step is to add a SignalR Hub Class to the project – having this native in Visual Studio is fantastic and greatly simplifies the process of getting up and running with SignalR ensuring all necessary references are added to the solution (note that you must have installed the ASP.NET and Web Tools 2012.2 update before you can add this class natively). Alternatively you can add SignalR via the Package Manager Console.

Elliot has also decided to implement the hub using a Singleton instance for optimum performance. For this you’ll need to add another class and insert the following code:

    public class SharePointR
    {
        // Singleton instance
        private readonly static Lazy<SharePointR> _instance = new Lazy<SharePointR>(() =>
            new SharePointR(GlobalHost.ConnectionManager.GetHubContext<SharePointRHub>().Clients));

        public SharePointR(IHubConnectionContext clients)
        {
            Clients = clients;
        }

        public static SharePointR Instance
        {
            get
            {
                return _instance.Value;
            }
        }

        private IHubConnectionContext Clients
        {
            get;
            set;
        }

        public void NotifyDataChanged(string ListName, string Event)
        {
            Clients.Group(ListName).dataChanged(Event);
        }
    }

Once that’s been created we can edit the Hub we added earlier and insert:

    [HubName("SharePointRHub")]
    public class SharePointRHub : Hub
    {
        private readonly SharePointR _sharePointR;

        public SharePointRHub() : this(SharePointR.Instance) { }

        public SharePointRHub(SharePointR sharePointR)
        {
            _sharePointR = sharePointR;
        }

        public void Subscribe(string ListName)
        {
            Groups.Add(Context.ConnectionId, ListName);
        }

        public void UnSubscribe(string ListName)
        {
            Groups.Remove(Context.ConnectionId, ListName);
        }

        public void NotifyDataChanged(string ListName, string Event)
        {
            _sharePointR.NotifyDataChanged(ListName, Event);
        }
    }

One final piece to the SignalR puzzle is to add a Global.asax file which should include the following:

        protected void Application_Start(object sender, EventArgs e)
        {
            var hubConfiguration = new HubConfiguration();
            hubConfiguration.EnableCrossDomain = true;
            hubConfiguration.EnableDetailedErrors = true;
            RouteTable.Routes.MapHubs("/signalr", hubConfiguration);
        }

Now we have our SignalR plumbing we can set up our event receivers. The first cab off the rank is the AppEventReceiver which will fire when the App is installed. What we want to achieve here is to manually hook up the item event receivers to a list in the host web, for instance an Announcements list.

    public class AppEventReceiver : IRemoteEventService
    {
        private string ReceiverName = "RemoteEventReceiver";
        private List<EventReceiverType> EventType = new List<EventReceiverType>()
        {
            EventReceiverType.ItemAdded,
            EventReceiverType.ItemAdding,
            EventReceiverType.ItemUpdated,
            EventReceiverType.ItemUpdating,
            EventReceiverType.ItemDeleted,
            EventReceiverType.ItemDeleting
        };
        private string ListTitle = "Announcements";

        public SPRemoteEventResult ProcessEvent(SPRemoteEventProperties properties)
        {
            SPRemoteEventResult result = new SPRemoteEventResult();

            using (ClientContext clientContext = TokenHelper.CreateAppEventClientContext(properties, false))
            {
                Web hostWeb = clientContext.Web;
                clientContext.Load(hostWeb);

                List docLib = clientContext.Web.Lists.GetByTitle(ListTitle);

                string opContext = OperationContext.Current.Channel.LocalAddress.Uri.AbsoluteUri.Substring(0,
                   OperationContext.Current.Channel.LocalAddress.Uri.AbsoluteUri.LastIndexOf("/"));
                string remoteUrl = string.Format("{0}/RemoteEventReceiver.svc", opContext);

                if (properties.EventType == SPRemoteEventType.AppInstalled)
                {
                    foreach (var eventType in EventType)
                    {
                        EventReceiverDefinitionCreationInformation newEventReceiver = new EventReceiverDefinitionCreationInformation()
                        {
                            EventType = eventType,
                            ReceiverAssembly = Assembly.GetExecutingAssembly().FullName,
                            ReceiverClass = "SignalRProxyService.Services.RemoteEventReceiver",
                            ReceiverName = ReceiverName + eventType.ToString(),
                            ReceiverUrl = remoteUrl,
                            SequenceNumber = 1000
                        };
                        docLib.EventReceivers.Add(newEventReceiver);
                    }
                    clientContext.ExecuteQuery();
                }
                else if (properties.EventType == SPRemoteEventType.AppUninstalling)
                {
                    IEnumerable<EventReceiverDefinition> receivers = clientContext.LoadQuery(docLib.EventReceivers
                        .Where(e => e.ReceiverName == ReceiverName));

                    foreach (var rec in receivers)
                    {
                        rec.DeleteObject();
                    }
                    clientContext.ExecuteQuery();
                }
            }
            return result;
        }

        public void ProcessOneWayEvent(SPRemoteEventProperties properties)
        {
            // This method is not used by app events
        }
    }

We then want to code up the Remote Event Receiver to handle the events triggered on the site and push a message via SignalR to our page. This is a very simplistic example that shows that the item affected in the list we’ve subscribed to is what is causing the update on the page.

    public class RemoteEventReceiver : IRemoteEventService
    {
        private SharePointRHub client = new SharePointRHub();

        public SPRemoteEventResult ProcessEvent(SPRemoteEventProperties properties)
        {
            client.NotifyDataChanged(properties.ItemEventProperties.ListTitle, properties.EventType.ToString());
            return new SPRemoteEventResult();;
        }

        public void ProcessOneWayEvent(SPRemoteEventProperties properties)
        {
        }
    }

The final step is to code up the page. There are a number of aspects here which should be completed to get the branding right for the App – these are covered in greater detail in the how-to guide listed at the end of this post. For now i’ll highlight the main code needed to hook up SignalR on the page (note that references to scripts not yet mentioned in this post are also covered in that guide).

    <!--Script references. -->
    <script src="//ajax.aspnetcdn.com/ajax/4.0/1/MicrosoftAjax.js"></script>
    <script src="/Scripts/jquery-1.8.2.min.js"></script>
    <script src="/Scripts/json2.js"></script>
    <script src="/Scripts/chromeLoader.js"></script>
    <script src="/Scripts/jquery.signalR-1.0.0.js"></script>
    <script src="/signalr/hubs"></script>

    <!--Add script to update the page and send messages.-->
    <script type="text/javascript">
        $(function () {
            var connection = $.hubConnection();
            proxy = connection.createHubProxy('SharePointRHub')
            proxy.on('dataChanged', function (eventType) {
                $('#MainContent').append('<li>Data changed - ' + eventType + '</li>');
            });
            connection.start()
                .done(function () {
                    //Subscribe to Announcements list
                    proxy.invoke('Subscribe', "Announcements");
                    $('#MainContent').append('<span>Now connected, connection ID=' + connection.id + '</span>');
                })
                .fail(function () {
                    $('#MainContent').append('<span>Could not Connect!</span>');
                });
        });
    </script>

And that’s about it! The image below gives a sneak peak into how an event triggered via a SharePoint list can be reflected on a separate page in real time. It is far more impressive however seeing it in person, so I’d encourage you to follow the guide provided below and get ‘SharePointR’ up and running for yourself – the possibilities this technology opens up is limitless and it will be exciting to see the kinds of solutions designed which leverage SignalR within SharePoint in the near future.

PreviewDownload: Walkthrough-Deploying a SignalR autohosted App to Office 365

6 Responses to Harnessing SignalR in SharePoint 2013 (Office 365)

  1. Elliot Wood says:

    Amazing write-up thanks Matt!
    pingback https://spsignalr.codeplex.com

  2. Max Melcher says:

    Great post you guys!

    Cheers
    Max

  3. Pingback: How to Integrate SignalR 2.0 in SharePoint 2010 | Second Life of a Hungarian SharePoint Geek

  4. Hi Matt,

    thanks for your great post!! Any idea with SPUtility.ValidateFormDigest(); throws an exception in the Hub class? I just needed to add a user in a SP group.

    thanks,
    Tony.

    • Matt says:

      Hey Tony, sorry I don’t really follow your question, hard to guess without a bit more detailed information. It doesn’t sound related to this post? (which is quite old now and I haven’t used SignalR in quite some time!). This example is hosted outside of SharePoint so wouldn’t run full-trust code as in SPUtility, you’d need to use the client side libraries to add your user to a group (which is definitely doable, I’ve done so using JS on a project recently)

  5. ganesanblog says:

    Great Post Matt.

Leave a comment