One of the good reasons to create a single page application is for the user experience it provides. This is not only due to the desktop like smooth behavior of the application but often also thanks to the fast response time. The way to keep a low response time is to pass small amount of data between the server and the client and to take advantage of the local cache. But what happens if we have data that is changing frequently? The local cache must change too often or false data will be displayed to the user. We can solve that problem by having real-time communication between the client and the server and force updates on only the entities we know for a fact have changed. SignalR is an ASP .NET library which provides real-time web functionality for web applications. It has been created as an open source project by David Fowler and Damian Edwards and is now part of the ASP .NET family. Here’s what we’re going to cover in this article.
- The hub
- Enabling SignalR
- Connecting to the hub on the client side
- Wrapping it all in a Durandal module
- Using the module
The techniques demonstrated in this article are included in a sample package at the end of it:
The hub
To enable communication between the client and the server first we create a hub. The hub is an integration point between the client and the server. Methods defined in the hub are available for the client to call. In the same time we can make calls to dynamic methods on the hub – we can perceive those as messages being sent to the clients. Messages can be broadcasted to one or more clients. For example a client can tell the server that an edition has begun on a restaurant instance by calling the StartEditRestaurant (line 3) method on the hub. The server can then broadcast the restaurantEditing message to all the other clients (line 5). Note that we haven’t defined restaurantEditing anywhere.
public class RestaurantsHub : Hub { public void StartEditRestaurant(int restaurantId) { Clients.AllExcept(new string[] { Context.ConnectionId }).restaurantEditing(restaurantId); } public static void RestaurantUpdated(Restaurant restaurant) { var context = GlobalHost.ConnectionManager.GetHubContext<RestaurantsHub>(); context.Clients.All().restaurantUpdated(restaurant.Id); } }
Enabling SignalR
Connecting to the hub on the client requires just a few lines of code. SignalR generates dynamically an interface based on what we have defined on the hub. This means the StartEditRestaurant function is available for use on the client side immediately after we compile our project. To use it however we have to register the SignalR route and then add a script reference to it. The default route is /signalr/hubs. We register the route by adding a class in the application Pre_Start directory. Make sure the SignalR is the first route to be registered (just after the bundles registration), otherwise it may not be available (the order of the route registrations in ASP .NET routing table matters!).
[assembly: WebActivator.PreApplicationStartMethod( typeof(spa.App_Start.SignalRConfig), "RegisterSignalRPreStart", Order = 1)] namespace spa.App_Start { public static class SignalRConfig { public static void RegisterSignalRPreStart() { System.Web.Routing.RouteTable.Routes.MapHubs(); } } }
Connecting to the hub on the client side
To connect to the hub we must simply register the signalr javascript file, a script reference pointing to /signalr/hubs route and make a few calls. We call the start method on the hub and subscribe to the restaurantUpdated and restaurantEditing messages.
<script type="text/javascript" src="~/signalr/hubs"></script> <script type="text/javascript"> // Declare a proxy to reference the hub. var hub = $.connection.restaurantsHub; //subscribe to messages from the hub hub.client.restaurantUpdated = function(){ //do something when the client receives a restaurantUpdated call }; hub.client.restaurantEditing = function(){ //do something when the client receives a restaurantEditing call }; //start the connection and bind functions to send messages to the hub $.connection.hub.start().done(function () { //do something when the hub starts }); </script>
Wrapping it all in a Durandal module
The communication interface SignalR provides us with is good but I would like to wrap it and have something I can reuse and maybe even distribute. I can do that by creating a Durandal module. How do we do that? When we created viewmodels in the previous article of the series we actually created Durandal modules. We’re going to do the same for the signalr-restaurant module, but we’re going to put the javascript file in the app/durandal/plugins folder this time.
define(function (require) { var hub; function connect(onEdit, onUpdate, onStarted) { // Declare a proxy to reference the hub. hub = $.connection.restaurantsHub; //subscribe to messages from the hub hub.client.restaurantUpdated = onUpdate; hub.client.restaurantEditing = onEdit; //start the connection and bind functions to send messages to the hub $.connection.hub.start().done(function () { onStarted(); }); } function startEditingRestaurant(id) { hub.server.startEditRestaurant(id); } return signalr = { connect: connect, startEditingRestaurant: startEditingRestaurant }; });
As you can see our model only has two functions, one to connect to the hub and one to call later when an edition begins. On the connect method we have to pass 3 functions to handle events on the hub. That makes our module event driven.
Using the module
We include this module in our editRestaurant.js viewmodel. When the viewmodel is activated it will connect to the hub and register the event handlers. One when the user starts editing, one when a restaurant update occurs and one that will execute after the connection has started.
function activate(context) { restaurantId = context.id; signalrRestaurants.connect(onRestaurantEditing, onRestaurantUpdated, onRestaurantSignalRStarted); return getRestaurant(context.id); } function onRestaurantEditing(editedRestaurantId) { if(editedRestaurantId == restaurantId) { toastr.warning("Someone else is editing this restaurant!"); } } function onRestaurantUpdated(updatedRestaurantId) { if (updatedRestaurantId == restaurantId) { getRestaurant(updatedRestaurantId); toastr.warning("Restaurant updated!"); } } function onRestaurantSignalRStarted() { toastr.success("Connected to hub."); signalrRestaurants.startEditingRestaurant(restaurantId); }
We reuse the module in our details and list views to have the restaurant information updated in real time. Since we use the Breeze EntityManager to update the data the updated data is preserved in the local cache and it is up to date for what concerns the data on the current page.
Download
Other articles in this series
- Building a SPA with the HotTowel Template – Part One
- Building a SPA with the HotTowel Template – Part Two
- Building a SPA with the HotTowel Template – Part Three – Validation
- Building a SPA with the HotTowel Template – Part Four – Real-Time Communication
- Building a SPA with the HotTowel Template – Part Five – GRID
The post Build a Single Page Application with the HotTowel SPA Template – Part Four – Real-Time Communication appeared first on Georgi Stavrev.