Building a real-time SPA using KnockoutJS, CodeFirst and XSockets.NET

Monday, July 15, 2013 4:34 PM 0

Building Single Page Applications aka. SPA is something that has become more popular this days. We can find a series of astute client-side JavaScript libraries helping us out to solve the most common tasks. KnockoutJS, AngularJS, DurandalJS and BreezJS are frameworks that we would recommend you to take a closer look into.

All of those frameworks delivers a wide range of functionality to solve time consuming issues such as dependency tracking, binding and associating DOM elements to objects and templating.

Compared to using a more straightforward approach using jQuery for such tasks those frameworks brings more standardized way of solving such tasks as mentioned above.

Binding and applying updates from the server to the UI is something that also can be streamlined and more effective.

In this blogpost we show you how you can publish, subscribe updates via a simple RealtimeMVC controller using XSockets.NET. The controller has a dependency to a simple domain model and EntityFramework.

The controller, EF and the simple domain model covers and briefly describes how the client in realtime interacts with backed in real-time and how the UI (SPA ) stays in sync with the data.

Note that the purpose of the post is to show the concept.

So what will we be building?

Shortly, the domain model describes things and the number likes each thing have. The UI will consist of a webform giving the user the possibility to create a “Thing” , A list of things with the number of likes each , and a action that places a like.

When a client places a like or create a new “thing”, the real-time controller and the client side will ensure that all clients are in sync.

Lets start!

We will be using Visual Studio and you will need a Internet connection because we will be adding a few Nuget Packages.

Step 1 - Create the MVC4 Project

Create the solution

  1. Create a new ASP.NET MVC4 Project C#
  2. (Install\Templates\Visual C#\ASP.NET MVC4 Webapplication)
  3. Name the Application e.g “MySimpleRealtimeSPA”
  4. Choose “Empty” when prompted to select project template
  5. Choose Razor as your Viewengine

Step 2 - Adding KnockoutJS and XSockets.NET Realimeframwork for .NET

Add the necessary Nuget packages. We will be relying on Entityframework codefirst, Knockout JS and XSockets.NET therefor we need to add the follwing packages to our solution.

  1. Open the Package Manager Console
  2. (View\Other Windows\Package Manager Console)
  3. Install KnockoutJS using “Install-Package knockoutjs”
  4. Install XSockets.NET using “Install-Package XSockets”

Note: EF is automatically added by the XSockets.NET package, therefore we want be needing to install it.

Step 3.a - Create the UI skeleton

First of all we add a new Razor View even though we are not aiming to use it more that once ( as we will be building a SPA )

  1. Add a new controller by right clicking the Controller folder of your soluion
  2. Name the controller “DefaultController” when prompted to enter the name
  3. You will now find a “DefaultControlle.cs” file in \Controllers
  4. Open the DefaultController.cs file
  5. Create the view by right-click return View() statement
  6. When prompted set the view name to “Index” and click ‘Add’
  7. You will now have a index.chtml file in the \View\Default folder
  8. Its now time to add the skeleton markup for our App - See steb 3.b

Step 3.b - The markup

Paste the following markup into the index.chtml file created.

<div>
    <fieldset>
        <legend>
            Wanna create a thing to like?
        </legend>
        <input type="text" name="caption" id="caption" placeholder="Give the thing a caption!"
        />
        <textarea name="description" id="description" placeholder="Describe what to like...">
        </textarea>
        <button id="createThing">
        </button>
    </fieldset>
</div>
<div>
    <h3>
        Things to like?
    </h3>
    <ul id="things">
        <li>
            <a>
            <span data-bind="text: Caption">The caption of the thing to like</span>&nbsp;
            (<mark>0</mark>)
            </a>
            <p>
                The description of the thing to like
            </p>
        </li>
        </ul>

Step 4 - Add the JavaScript references to the view (UI)

We will be needing a few JavaScript files in our sample. Add the following script tags to your view. we prefer to have them right before the end of the body tag :-)

All of the needed libraries can be found in the /scripts/ folder of your solution.

 

<script src="~/Scripts/jquery-1.9.1.js"></script>
<script src="~/Scripts/jXSockets.2.3.min.js"></script>
<script src="~/Scripts/knockout-2.3.0.debug.js"></script>
<script>

$(function(){
// Your custom JS will come here...
});

</script> 

..

Step 5 - Add a real-time controller

It’s now time for us the add a real-time controller to our solution. A real-time controller is an ordinary class that inherits from XSockets.Core.XSocket.XBaseSocket. For a start we don’t add any actions to the controller, we will get back to those later.

  1. Create a new class name RealtimeController.cs in the Controllers folder of your solution
  2. Make sure that it inherits XSockets.Core.XSocket.XBaseSocket to ensure that the real-time controller is plugged into the server when the server starts.
  3. We will also be needing a few extension methods later on , so lets also add a using as follows: using XSockets.Core.XSocket.Helpers;

The class will now look as follows:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using XSockets.Core.XSocket.Helpers;

namespace MySimpleRealtimeSPA.Controllers
{
    public class RealtimeController: XSockets.Core.XSocket.XBaseSocket
    {
    }
} 

Step 6 - Configure, build

Now its time for us to connect to the real-time controller and verify that our XSockets service is launched properly and that the controller can be reached.

  1. Make sure that the development server of Visual Studio has “Enable edit and continue” option checked under the “Web” tab pane of the propertypage of the web.
  2. Build your solution
  3. Start the web app
  4. Browser will show you a 404 due to the fact that we are missing the Default Route , just type /Default/Index and you the view of yours will be fetched.

You can find a more detaild explanation of this here under - Get rid of the "stop development server" issue

Step 7 - Connect to the Real Time-controller (WebSocket) using the JavaScript API

Connect to the XSockets.Controller of yours (RealtimeController.cs) by adding the following code to a script tag in your index.cshtml ( View )

 

var ws;
$(function () {
    ws = new XSockets.WebSocket("ws://127.0.0.1:4502/RealtimeController", "RealtimeController");
    ws.subscribe(XSockets.Events.open, function (ctx) {
        console.log("ctx", ctx);
    });
});
 
Explanation

When the document is ready (loaded ) we are creating an new connection to the realtime controller using XSockets.WebSocket(url, subprotocol) , futher on we are creating a subscription for the open event that the server will fire when the client is connected, we are then displaying the ctx variable passed in the callback in the console.

To verify that the connection was successfully made openup the development console of (Chrome ) , it’t sholde have a log entry ctx

 

Step 8 - Create the domain model

Our application consist if an very simple domain-model that contains two different types of entities - Thing and Like.

  1. Create a file called domainmodel.cs ( or split up the classes below into seperate files if you desire )
  2. Paste the code found below

 

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace MySimpleRealtimeSPA.Models
{

    public class PersistentEntity
    {
        [Key]
        public int Id
        {
            get;
            set;
        }
        public DateTime Created
        {
            get;
            set;
        }

        public PersistentEntity()
        {
            this.Created = DateTime.Now;
        }
    }

    public class Like: PersistentEntity
    {
        public int Score
        {
            get;
            set;
        }
        public Thing Thing
        {
            get;
            set;
        }
    }

    public class Thing: PersistentEntity
    {
        public string Caption
        {
            get;
            set;
        }
        public string Description
        {
            get;
            set;
        }
        public virtual List <Like> Likes
        {
            get;
            set;
        }
    }
} 

Step 9 - Create the database context for our domain model

As mentioned in the beginning of the blogpost will have a simple domain model that we will be accessing using via our real time controller and EntityFramework.

  1. Add a new folder to your solution named DataBase
  2. Add a new class named AppDbContext.cs to the folder
  3. Add the code find below to your class
using System.Data.Entity;
using MySimpleRealtimeSPA.Models;
namespace MySimpleRealtimeSPA.Database {
    public class AppDbContext: System.Data.Entity.DbContext {
        static AppDbContext() {
            System.Data.Entity.Database.SetInitializer(new DropCreateDatabaseIfModelChanges <DbContext> ());
        }
        public AppDbContext() {}
        public DbSet <Like> Likes {
            get;
            set;
        }
        public DbSet <Thing> Things {
            get;
            set;
        }
    }
}

Step 10 - Create the Knockout view models for our entities

Now it’s time for us to see the magic of KnockoutJS and see how we can combine KnockoutJS and XSockets.NET sofisticated publish and subscribe pattern.

The JavaScript below consist of three different
classes . Two of those represents our domain model and the last one covers our view in general. The thingsModel and likesModel reflects each entity and third appViewModel our application in general ( the list of things etc )

 

// model that represents our "things"
var thingModel = (function () {
    function thingModel(data) {
        this.Id = data.Id;
        this.Caption = data.Caption;
        this.Description = data.Description;
        this.Likes = ko.observableArray(ko.utils.arrayMap(data.Likes, function (like) {
            console.log(new likeModel(like));
            return new likeModel(like);
        }));;
    }
    return thingModel;
})();

// model that represents our Likes
var likeModel = (function () {
    function likeModel(data) {
        this.Id = data.Id;
        this.Score = data.Score;
        this.Created = data.Created;
    }
    return likeModel;
})();

// model that represents our app(view) in general 
var appViewModel = (function () {
    function appViewModel() {
        this.things = ko.observableArray();
        this.thing = ko.observable(new thingModel({}));
    }
    appViewModel.prototype.findById = function (obj) {
        var match = ko.utils.arrayFirst(this.things(), function (item) {

            return item.Id === obj.Id;
        });
        if (!match) return undefined;
        else return match;
    };
    appViewModel.prototype.delegate = function (obj, topic, cb) {
        obj.prototype[topic] = cb;
    };
    return appViewModel;
})();
 

Note: Worth mentioning here is that the appViewModel contains a method named .delegate(obj,topic, cb) . Using this method we can attach an event handler (listener) to the entity and fire a callback that passes the model itself. Within the callback, fired by the user or application we can publish the data to our backend for instance.

 

Step 11 - Add “Action-Methods” to our real-time controller

Now its time for us to add the action methods to our real time controller. Our application will need three different actions to be able to create and retrieve “Things” and “Likes” . Below is a list of Actions and a short explanation of those. We also recommend you to study the JavaScript API for further info regarding our JavaScript API

Things
Will return all “Things” in the database to the client “asking”.

ThingsSaveOrUpdate

Will create a new Thing and distribute it to all clients subscribing to the topic of “ThingsSaveOrUpdate”.

AddLikeToThing

Will register a like on a Thing, and notify all subscribers that a new like is registred.

1. Paste the following three methods to into the RealtimeController.cs file

 

 

public void Things() {
    var ctx = new AppDbContext();
    var things = ctx.Things;
    var all = new List < ThingViewModel > ();

    foreach(var thing in things) {
        all.Add(new ThingViewModel(thing));
    }

    this.Send(all.OrderByDescending(p = > p.Likes.Count), "Things");
    // Send (return) all exisiting things to the client using Topic 'Things'
}

public void ThingSaveOrUpdate(Thing model) {
    var ctx = new DataBase.AppDbContext();
    ctx.Things.Add(model);
    ctx.SaveChanges();
    this.SendToAll(new ThingViewModel(model), "ThingSaveOrUpdate"); // Lets notify all client about the new thing!
}

public void AddLikeToThing(int thingId, int score) {
    var ctx = new DataBase.AppDbContext();
    var thing = ctx.Things.SingleOrDefault(t = > t.Id.Equals(thingId));
    thing.Likes.Add(new Like() {
        Score = score
    });
    ctx.SaveChanges();
    this.SendToAll(new ThingViewModel(thing), "Liked"); // Lets notift all the client about the new like!
}


As you may notice we are calling this.SendTo(..) and this.Send(..) extention methods found in the XSockets.Core.XSocket.Helpers namespace. The diffrent between those are quite clear; SendToAll will send the object (ThingViewModel) as to all clients subscribing to "Liked", and Send will pass it back to the current client ( if subscribing ) only,

We havent yet created all nessessary code on the backend, as it is not possible to serialize the Entities as JSON due to complexity we need to create a viewmodel (non complex ) , in this case you can se above that im using a class called ThingViewModel .

2. Create a new class named ThingViewModel ( just add it to the model folder where you have the domain-model) and make sure you have a using pointing that namespace out in the RealtimeController. Paste the code below

 

using System;
using System.Collections.Generic;
namespace MySimpleRealtimeSPA.Models {
    public class ThingViewModel {
        public int Id {
            get;
            set;
        }
        public string Caption {
            get;
            set;
        }
        public string Description {
            get;
            set;
        }
        public DateTime Created {
            get;
            set;
        }
        public IList < LikeViewModel > Likes {
            get;
            set;
        }
        public ThingViewModel(Thing thing) {
            this.Id = thing.Id;
            this.Caption = thing.Caption;
            this.Description = thing.Description;
            this.Created = thing.Created;
            this.Likes = new List < LikeViewModel > ();
            foreach(var like in thing.Likes) {
                this.Likes.Add(new LikeViewModel(like));
            }
        }
    }
    public class LikeViewModel {
        public int Id {
            get;
            set;
        }
        public int Score {
            get;
            set;
        }
        public DateTime Created {
            get;
            set;
        }
        public LikeViewModel(Like like) {
            this.Id = like.Id;
            this.Score = like.Score;
            this.Created = like.Created;
        }
    }
} 

 

Step 12 - Wrapping things up on the client side (JavaScripts)

Its now time for us to wrap things up on the JavaScript on the client side, this is where we se the actual power of KnockoutJS and XSockets.NET . By just adding the following code to the document ready event ( where we ensured that we got the connection earlier ) we will tie the backend and the client’s view and model together. We placed a few comments inside the code that explains what we are doing ( hopefully you will be able to understand )

 

 
$(function () {
 
  // Create a new Connection to our Realtime controller
    ws = new XSockets.WebSocket("ws://127.0.0.1:4502/RealtimeController", "Realtimecontroller");
    ws.subscribe(XSockets.Events.open, function (connection) {
        ws.trigger("Things"); // Get all the "things"
    });
    // Create a new instance of our application viewmodel
    vm = new appViewModel();
    // attach a delagate for the "action" -> saveOrUpdate on our thingModel,
    vm.delegate(thingModel, "saveOrUpdate", function (model, event) {
        // Send a mesasge to the server using the topic, ThingSaveOrUpdate. We pass the model as a JSON literal
        ws.publish("ThingSaveOrUpdate", ko.toJS(model)); // Note, we are passing the complete model as XSockets.NET supports modelbinding (serverside)
    });
    // attach a delegate for the "action" -> like on our thingModel,
    vm.delegate(thingModel, "like", function (model, event) {
        // Send a message and add a like to the 'thing'
        ws.publish("AddLikeToThing", {
            thingId: model.Id,
            score: 5
        });
    });
    // Lets listen for inbound messages with the topic of 'ThingSaveOrUpdate' 
    ws.subscribe("ThingSaveOrUpdate", function (t) {
        vm.things.push(new thingModel(t)); // Add the thing as a thingModel to our observable array
    });
    // Someone liked a thing, find the thuing, and replace
    ws.subscribe("Liked", function (l) {
        vm.things.replace(vm.findById(l), new thingModel(l));
    });
    // Listen to all things , this will anly fire once ( after connect , as we below trigger Things )
    ws.subscribe("Things", function (allThings) {
        allThings.forEach(function (a, b) {
            vm.things.push(new thingModel(a));
        });
    });
    ko.applyBindings(vm);
});

Note: Above you can see how we use the .delagate method of our appViewModel to "subscribe" to the saveOrUpdate event for instance. When the callback fires we are doing a publish on the WebSocket (ws) , saying the topic is "ThingSaveOrUpdate" , this will pass the model and later on the ActionMethod of our RealtimeController will be invoked.

Step 13 - Apply UI binding to the UI using Knockout

In the beginning of the blogpost we created a skeleton of the UI , now its time for us to use Knockout to bind our ViewModels to the UI as well as we will attach “click” events that will fire our callbacks ( delegates ) and further on passthe model to the real-time controller. Knockout will also ensure that the UI is in sync.

To just replace the skeleton with this complete markup, and study the data-bind attributes on the markup.

 

    <div>
        <fieldset data-bind="with: thing">
            <legend>Wanna create a thing to like?</legend>
            <input type="text" name="caption" id="caption" placeholder="Give the thing a caption!" data-bind="value: Caption, valueUpdate: 'afterkeydown'" />
            <textarea name="description" id="description" placeholder="Describe what to like..." data-bind="value: Description, valueUpdate: 'afterkeydown'"></textarea>
            <button id="createThing"
                data-bind="click: saveOrUpdate">
                Create!</button>
        </fieldset>
    </div>
    <div>
        <h3>Things to like?</h3>
        <ul id="things" data-bind="foreach: things">
            <li>
                <a data-bind="click: like">
                    <span data-bind="text: Caption"></span>&nbsp;
                (<mark data-bind="text: Likes().length"></mark>)

                </a>
                <p data-bind="text: Description"></p>
            </li>
        </ul>
</div> 

Yes, you have after this 13 steps come to the end...

So what to do?

Just recompile and start the webapplication and navigate to /default/index -> open several browser windows to simulate two users or more.

Summary

As you can see KnockoutJS gives us many nice features for developing Rich Internet Applications (SPA) using JavaScript. Its easy to make our markup based interface responsive to changes in the underlying model. By just using the simple delegate shown above we are able to publish the entity to the realtime-controller by a few lines of code.

The client can also easy subscribe to changes by just establishing telling the controller its interests

This few pieces of code gives us an application where all the clients are in sync (ui and backend in sync also )

As and end of this summary i must point out a good resource when it comes to KnockoutJS - Have a look at Ryan Niemeyers blog/site http://knockmeout.net  , tons onf great stuff can be find there.

We also made this example available on GitHub in un furbished version  https://github.com/MagnusThor/XSockets.ILike.HTML5.App.Using.KnockoutJS.EF/ 

 

 

Comments