Using SignalR and ASP.NET MVC’s Hot Towel SPA template to create an Online Collaboration application

Posted by: Suprotim Agarwal , on 5/31/2013, in Category ASP.NET MVC
Views: 120463
Abstract: A mashup demonstrating simultaneous editing of a Data element in a ASP.NET MVC 4 app using Hot Towel SPA template, SignalR and the Diff-Match-Patch JavaScript library.

SignalR started off as an OSS side project by Damien Edwards and David Fowler at Microsoft but soon it snowballed into a popular abstraction for persistent connections in .NET. With the release of Visual Studio Web Update 2012.2, SignalR was formally rolled into a part of ASP.NET and is now an open source component that is fully supported by Microsoft.

In the past, we have explored interesting ways to use SignalR here on DotNetCurry. Our first SignalR post demonstrated how to use SignalR with the excellent Diff-Patch-Match library by Neil Fraser and build a collaborative ‘Review’ functionality where two people or more people can review the same post and view changes made by each other, in real time.

With the release of Windows 8 and Office 2013 came One Note that had similar real-time ‘review’ functionality.

Today we will use SignalR, ASP.NET MVC’s Hot Towel application template and the JavaScript Diff-Patch-Match library to build a single page application that will broadcast changes to a ‘Note’ to all who are watching it.

 

The Concept

SignalR as we know helps us maintain persistent connections between a server and a client. The Server is typically an IIS hosted ASP.NET application, but it could very well be a Katana hosted or Mono Hosted non-IIS server. There is a wide array of clients supported, some official, some community built. The JavaScript client is official. Today we are going to use it with the JavaScript client.

Our application will be a Note taking application where users can share the URL of their note with others who can then watch changes being made in real time.

Hot Towel and Single Page Apps

The Hot Towel application template was created by noted .NET Community member John Papa. If you are a subscriber to the DNC Magazine you would have got a chance to read through Fanie Reynders excellent introduction to the Hot Towel SPA article. If not, we strongly suggest you go through it. We’ll cover only data access here today. We leave navigation and routing for another day.

The Hot Towel Template

This Visual Studio template was created by John Papa to provide all the moving pieces required to build a complete Single Page application in one template. To understand ‘All Pieces’ let’s look at this diagram that I snagged from John Papa’s hour long tutorial at the DotNetConf. You can see the full video on YouTube here.

signalr-hottowel

As you can see, John has highlighted the features of each SPA template currently available. His idea behind Hot Towel was to have something that you can get up and running with quickly without having to ‘delete’ anything in the template. In that respect, all the templates in blue have Sample implementation showing a simple ToDo app. So if you want to use any of those templates, you have to take the code for the ToDo app out first. However the templates in Orange are empty templates with all the ingredients in and ready to start as in you don’t have to delete anything to get started.

So Hot Towel template essentially uses Knockout for Data binding, provides Rich Data support via BreezeJS, has Navigation and History built in and is based off DurandalJS SPA framework by Rob Eisenberg.

The SignalR Application Setup

To keep things simple today, we’ll add a single Note to the Database via SQL query; the Note will store three pieces of information

- Name

- Note Content

- Date Updated

For brevity, we’ll ignore Authentication and Authorization for now. The data will be stored in SQL Server LocalDB and we’ll use Entity Framework for persisting data.

Getting Started

We start off with an ASP.NET MVC 4 Web Application using .NET Framework 4.5 and select the “Hot Towel Single Page Application” project template.

hot-towel-spa

Once the project is setup, let’s spend a few minutes understanding the project layout.

The Project Layout

As seen in the image below, the most apparent ‘new’ folder is the App folder that is not usually associated with a normal MVC Project.

The App Folder

The App folder for an SPA application is basically where your client side logic lies. It’s like a project inside a project. It contains the ViewModel (JavaScript), the Views (html) as well as Services like logging, DataContext and initialization. Note that almost all the ViewModel JS files have a corresponding view. Nav and Footer are two sub-views who use the view model of their parent that is shell.js.

The Durandal subfolder contains its dependencies, composition code, plugins and other bootstrap code that we can use as-is for the moment.

basic-solution-layout

The App_Start Folder

The App_Start folder has the additional BreezeWebApiConfig.cs that uses the MVC WebActivator hooks to initialize a BreezeApi route. The HotTowelConfig.cs Registers all the Bundles whereas the HotTowelRouteConfig registers the HotTowel Route as the default by inserting it before the MVC Route. (The WebActivator kicks in before the Application_Start is invoked).

public static void RegisterHotTowelPreStart() {
// Preempt standard default MVC page routing to go to HotTowel Sample
System.Web.Routing.RouteTable.Routes.MapRoute(
  name: "HotTowelMvc",
  url: "{controller}/{action}/{id}",
  defaults: new
  {
   controller = "HotTowel",
   action = "Index",
   id = UrlParameter.Optional
  }
);
}

The First Run

With this rudimentary idea of what the various project components are, let’s change the App\Views\nav.html’s header from HotTowel SPA to CollaboratR. Run the Application. You should see a view similar to the following:

spa-first-run

Including SignalR

Now that we have our SPA going, let’s include SignalR in the mix and test it out.

Installing SignalR

1. In the Package Manager Console type in the following to install SignalR.

PM> install-package Microsoft.Aspnet.SignalR

2. Once all the dependencies and the SignalR library is installed, add the highlighted code to the beginning of the RegisterHotTowelPreStart method in the App_Start\HotTowelRouteConfig.cs Application_Start in the Global.asax to call

public static void RegisterHotTowelPreStart()
{
System.Web.Routing.RouteTable.Routes.MapHubs();
// Preempt standard default MVC page routing to go to HotTowel Sample
System.Web.Routing.RouteTable.Routes.MapRoute(
  name: "HotTowelMvc",
  url: "{controller}/{action}/{id}",
  defaults: new
  {
   controller = "HotTowel",
   action = "Index",
   id = UrlParameter.Optional
  }
);
}

3. Next we create a bundle for the jquery.signalR script and add it to the bundle collection. Update the code in AppStart\BundleConfig to add the following bundle

bundles.Add(new ScriptBundle("~/scripts/signalr").Include(
"~/Scripts/jquery.signalR-{version}.js"));

4. For ease of use, we’ll add the script references to the index.cshtml but you can put it in any sub-page if you want to. At the bottom of the page add the following (the sequence is important)

@Scripts.Render("~/scripts/signalr")
<script src="~/signalr/hubs"></script>

5. Next we’ll add a SignalR Hub file to the Controllers folder.

image

This adds the following Hub class

public class CollaboratorHub : Hub
{
public void Hello()
{
  Clients.All.hello("Ping " + DateTime.Now.ToString());
}
}

We have updated the message being sent out from “Hello” to “Ping “ + DateTime.Now.ToString() so that we can some easily distinguishable changes on the client when a new message is sent out.

Updating View Model for a test run

To test SignalR out, we will update the home.js and home.html. We need to do make the following changes

- Update the ViewModel with a sendMessageAction property that’s bound to the click event of a button on the view, and a messageRecieved property that’s bound to a label in the view.

var vm = {
    activate: activate,
    title: 'All Notes',
    sendMessageAction: sendMessage,
    messageReceived: ko.observable()
};

- Initialize the SignalR client and implement the sendMessage method

function sendMessage()
{
logger.log("sending message to server");
sig.server.hello();
}

var sig = $.connection.collaboratorHub;

$.connection.hub.start().done(function ()
{
vm.sendMessageAction;
});

- Finally we implement a function to receive the ‘Hello’ ping from the server and update the ViewModel

sig.client.hello = function (what)
{
vm.messageReceived(what);
logger.log('Server says: ' + what, null, 'home', true);
}

return vm;

Updating View for the test run

We add a Button whose click event is bound to the sendMessageAction in the ViewModel and a label to show the ping message from the server.

<div class="container">
<button class="btn" data-bind="click: sendMessageAction">
  Send a Hello to All clients
</button>
<label data-bind="text: messageReceived">
  No Messages Received
</label>
</div>

The Test Run

Now run the application and open it in two browsers. Click on the Send Message… button and see the same message being updated on both clients

signalr-test-run

Cool! SignalR is working along with the Hot Towel framework. Let’s move on to our real requirement.

The Implementation

We start off by creating our Data Entity - Note and the required plumbing to Save and Retrieve it from database. Once that is done, we’ll update the Home.html to show the notes. Here the Text field will be an editor where we’ll do the SignalR magic to show updates on multiple browsers in Real-Time.

The Data Layer

1. In the Models folder we add a new class called Note with the following properties.

public class Note
{
public int Id { get; set; }
public string Name { get; set; }
public string Text { get; set; }
public DateTime Updated { get; set; }
}

2. Next we add another class in the Models folder called CollaboratrDbContext. This is the context class

public class CollaboratrDbContext : DbContext
{
public CollaboratrDbContext()
  : base(nameOrConnectionString: "CollaboratRDbConnection")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
  Database.SetInitializer<CollaboratrDbContext>(
   new DropCreateDatabaseIfModelChanges<CollaboratrDbContext>());
}
public DbSet<Note> Notes { get; set; }
}

3. Finally we add the following connection string in the web.config’s <connectionString> section.

<add name=" CollaboratRDbConnection"
connectionString="Data Source=(LocalDb)\v11.0;Initial Catalog=CollaboratRDb;Integrated
  Security=SSPI;AttachDBFilename=|DataDirectory|\CollaboratRDb.mdf"
providerName="System.Data.SqlClient" />

With this our DataLayer is setup. Now to setup the Services.

The Web API Service

Now that our Data entity and DBContext is setup, time to setup the service that will retrieve all the data as well as save new Data.

The BreezeController

In the Controllers folder, we add a new class called BreezeController as follows and decorate it with an attribute [Breeze.WebApi.BreezeController]. In the class below, we see an EFContextProvider class which is essentially a wrapper for dealing with JSon inputs. Notice the super compact implementation where we have only SaveChanges method for save new or updated Notes. Other two methods are for fetching MetaData or an IQueryable for all the Notes in the system.

[Breeze.WebApi.BreezeController]
public class BreezeController : ApiController
{
readonly EFContextProvider<CollaboratrDbContext> _contextProvider =
  new EFContextProvider<CollaboratrDbContext>();
[HttpGet]
public string Metadata()
{
  return _contextProvider.Metadata();
}
[HttpPost]
public SaveResult SaveChanges(JObject saveBundle)
{
  return _contextProvider.SaveChanges(saveBundle);
}
[HttpGet]
public IQueryable<Note> Notes()
{
  return _contextProvider.Context.Notes;
}
}

This sets up the Server side. Over to the client.

Updating the Client

We start with the Home.js file and update the ViewModel as follows:

1. We declare the vm as an empty object. In the define method, we pass the logger, the router and the app path for reference.

2. Next we declare the service path in the serviceName variable. This corresponds to the BreezeController web API Controller we had defined earlier.

3. We then initialize the ViewModel vm with the notes, title, selectedNote and activate methods.

var vm = {};
define(['services/logger', 'durandal/app', 'durandal/plugins/router'], function (logger)
{
    // service name is route to the Web API controller
    var serviceName = 'api/Breeze';
    // manager is the service gateway and cache holder
    var manager = new breeze.EntityManager(serviceName);
    // define the viewmodel
    vm = {
        notes: ko.observableArray(),
        show: ko.observable(false),
        title: 'My Notes',
        selectedNote: ko.observable(),
        activate: activate       
    };

4. Next we have the activate method which in turn queries the database and gets the data for us

   function activate()
    {
        return getNotes();
    }

    return vm;
    //#region private functions

    // get Notes asynchronously
    // returning a promise to wait for    
    function getNotes()
    {
     logger.log("querying Notes");
     var query = breeze.EntityQuery.from("Notes");

        return manager
            .executeQuery(query)
            .then(querySucceeded)
            .fail(queryFailed);

        // reload vm.notes with the results
        function querySucceeded(data)
        {
            logger.log("queried Notes");
            vm.notes(data.results);
            vm.show(true); // show the view
        }
    };

    function queryFailed(error)
    {
        logger.log("Query failed: " + error.message);
    }
});

This concludes the internal methods. We’ll revisit the home.js to update the SignalR logic, but first we’ll update the UI in home.html

1. The HTML is rather simple. It uses KO’s data binding syntax to bind the list of notes populated in the ViewModel.

<section>
    <h2 class="page-title" data-bind="text: title"></h2>
</section>

<div>
    <ul data-bind="foreach: notes">
        <li style="list-style: none">
            <div style="border: solid 1px gray; padding:10px">
                <h1>
                    <label data-bind="text: Name"></label>
                </h1>
                <label data-bind="text: Updated"></label>
                <br />
                <textarea data-bind="value: Text" class="note-text" rows="5" style="width:90%"></textarea><br />
            </div>
        </li>
    </ul>
</div>

2. Updating client to use SignalR and Diff Match Patch library. We can install the Diff Match Patch library we need from Nuget as follows

PM> Install-Package Google.DiffMatchPatch.Js

3. Once installed, we update the BundleConfig.cs to include the above JS file. Since the SignalR bundle is already included in the Shell, we don’t need to do anything special.

bundles.Add(new ScriptBundle("~/scripts/signalr").Include(
          "~/Scripts/jquery.signalR-{version}.js",
          "~/Scripts/diff_match_patch.js"));

We update the Home.js again as follows:

var sig = $.connection.collaboratorHub;
$.connection.hub.start();

$(document).on('keyup', '.note-text', null, function ()
{
    var note = ko.dataFor(this);
    vm.selectedNote = note;
    sig.server.patch(this.value);
});

sig.client.patch = function (newText)
{
    var dmp = new diff_match_patch();
    dmp.Match_Distance = 1000;
    dmp.Match_Threshold = 0.5;
    dmp.Patch_DeleteThreshold = 0.5;
    var source = vm.notes()[0].Text();
    var patches = dmp.patch_make(source, newText);
    var dest = "";
    var results = dmp.patch_apply(patches, source);
    vm.notes()[0].Text(results[0]);
}

4. Finally we need the corresponding method in the Hub class, so we update the CollaboratoRHub.cs to get the updatedText as an input parameter. It is then sent back to all other connections except for the sender. The ‘Others’ dynamic property on which we are calling the ‘Patch’ ensures that SIgnalR doesn’t call back the client from where the change originated.

public void Patch(string updatedText)
{
Clients.Others.Patch(updatedText);
}

With the Diff and Patch logic in, we are ready to roll. First we insert a row of data in our database

data-row

Next we run the app.

final-run-start

We open a second browser with the same URL. Now as we update one Note, the other one gets updated. Proof that the diff-match-patch is actually patching, is by typing in both the screens to see changes being merged at both ends.

final-run-end

Awesome right? Well that’s a wrap for today.

Conclusion

Today we saw a mashup created using the Hot Towel Single Page App template for ASP.NET MVC4, along with SignalR and a nifty little Diff-Match-Patch library, to get realtime and concurrent updates to a document.

We also saw how to retrieve data from a database in the Hot Towel SPA template. Will anyone of you be interested to see how to do complete CRUD operations and upgrade the simple text editor to a Rich Text Editor. If yes, let me know in the comments section.

Download the entire source code of this article (Github)

Give a +1 to this article if you think it was well written. Thanks!
Recommended Articles
Suprotim Agarwal, ASP.NET Architecture MVP, MCSD, MCAD, MCDBA, MCSE, is the CEO of A2Z Knowledge Visuals Pvt. He primarily works as an Architect Consultant and provides consultancy on how to design and develop .NET centric database solutions.

Suprotim is the founder and primary contributor to DotNetCurry, DNC .NET Magazine, SQLServerCurry and DevCurry. He has also written an EBook 51 Recipes using jQuery with ASP.NET Controls. and authored a new one at The Absolutely Awesome jQuery CookBook.

Follow him on twitter @suprotimagarwal


Page copy protected against web site content infringement by Copyscape


User Feedback
Comment posted by Raju Dutta on Wednesday, June 5, 2013 12:57 PM
just wow!!!
Comment posted by Tess on Tuesday, June 25, 2013 4:18 PM
Excellent article !!
Will anyone of you be interested to see how to do complete CRUD operations and upgrade the simple text editor to a Rich Text Editor -  Yes, Definitely !!
Comment posted by Timbo on Wednesday, June 26, 2013 1:29 AM
Is there any chance of updating this example's nuget references on the github repositry? I have tried and unable to make this work. If i update all references but breeze then it work's but once i update breeze i get a javascript error as seen in this stackoverflow post http://stackoverflow.com/questions/17273993/breeze-1-3-6-cannot-get-metadata
Comment posted by Sembrador on Friday, July 19, 2013 5:13 PM
Superb article!!!
Will anyone of you be interested to see how to do complete CRUD operations and upgrade the simple text editor to a Rich Text Editor -  YES!!!
Comment posted by Sembrador on Saturday, July 20, 2013 10:44 AM
Superb article!!!
Will anyone of you be interested to see how to do complete CRUD operations and upgrade the simple text editor to a Rich Text Editor -  YES!!!
Comment posted by Sembrador on Sunday, July 28, 2013 7:51 AM
Superb article!!!
Will anyone of you be interested to see how to do complete CRUD operations and upgrade the simple text editor to a Rich Text Editor -  YES!!!
Comment posted by Robert on Saturday, August 10, 2013 1:28 AM
CRUD and rich text, yes please.  One interesting note on this SignalR site - when you make changes in one browser/client, you can go to the other one and type CTRL+Z and it will undo them.  So, it is re-creating the action of typing or deleting on the other clients.  Really cool.  I also want to do some collaboration with arranging graphics/icons on a layout.  Thanks for this article!!!
Comment posted by santosh on Thursday, August 22, 2013 6:31 AM
Superb article!!!
yes i want to see how to do complete CRUD operations and upgrade the simple text editor to a Rich Text Editor.
Comment posted by Tole on Wednesday, September 4, 2013 12:01 PM
Excellent article!!

CRUD operations? definetly yes
Comment posted by Shakti on Wednesday, October 9, 2013 4:54 AM
Nice article!!
Yes, I would definitely want to know how tp do complete CRUD operations and upgrade the simple text editor to a Rich Text Editor
Comment posted by Michael Kassa on Friday, October 18, 2013 3:15 PM
Thank you very much, just what I needed!
Comment posted by norb on Wednesday, November 13, 2013 4:31 AM
very nice work indeed. I would also like to see a second part turning it into a full CRUD app
Comment posted by ElmerM on Sunday, December 29, 2013 6:40 PM
Great! There is so much power in these frameworks.
CRUD please.
Comment posted by Steve F on Tuesday, April 1, 2014 2:25 PM
That violates the laws of physics! (Recalling a statement from Stephen Walther regarding ajax in the book ASP.NET.) Very cool stuff Suprotim.

Post your comment
Name:  
E-mail: (Will not be displayed)
Comment:
Insert Cancel