Using KnockoutJS with SignalR in ASP.NET MVC

KnockoutJS is a MVVM implementation for JavaScript written by Steve Sanderson, in my opinion the author of the best ASP.NET MVC textbooks available. Simply put it lets you bind a JavaScript object model to your HTML UI using a simple binding format, and when the underlying model is updated the UI is automatically updated to reflect the change.

SignalR is a library from Microsoft utilising HTML5′s WebSockets allowing server side code to call JavaScript functions on the client to create real-time functionality.

To me these two libraries seem like a match made in heaven. SignalR calling a JavaScript function to alter data, and Knockout automatically updating the UI, awesome!

In this post I’ll show how you can use KnockoutJS and SignalR together to create real-time web application with little effort. I’m going to create a very basic example of a web page showing a list of exchange rates hooked up using KnockoutJS. I’ll then create a server side page to update the data and use SignalR to call a JavaScript function to update the JavaScript model.

Source code

First off I’ve created an empty ASP.NET MVC web application and added the KnockoutJS and SignalR libraries via NuGet. I’ve also added Twitter Bootstrap for some styling.

I have created a basic layout page and added some references to the libraries I need. The layout page looks like this.

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>@ViewBag.Title</title>

    <link rel="stylesheet" type="text/css" href="~/Content/bootstrap.css">

    <script src="~/Scripts/jquery-1.6.4.js" type="text/javascript"></script>
    <script src="~/Scripts/jquery.signalR-1.0.0-rc2.js" type="text/javascript"></script>
    <script src="~/Scripts/knockout-2.2.1.debug.js" type="text/javascript"></script>
    <script src="~/Scripts/knockout.mapping-latest.debug.js" type="text/javascript"></script>
    <script src="/signalr/hubs" type="text/javascript"></script>

</head>
<body>
    <div class="container">
        @RenderBody()
    </div>

    @RenderSection("scripts", false)
</body>
</html>

You can see I’ve included references for SignalR, jQuery, Knockout and Knockout Mapping (I’ll explain what this is and how to use it later). You’ll also see a reference to signalr/hubs. This gets generated on the fly by SignalR to manage the client connection, but you still need to create a reference to it.

Now I want to create a view to display my currencies with Knockout. I have created a HomeController with an Index view that looks like this:

<h2>Current Exchange Rates</h2>

<table class="table table-bordered">
    <thead>
        <tr>
            <th></th>
            <th>USD</th>
            <th>EUR</th>
            <th>GBP</th>
            <th>INR</th>
            <th>AUD</th>
            <th>CAD</th>
            <th>ZAR</th>
            <th>NZD</th>
            <th>JPY</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <th>USD</th>
            <!-- ko foreach: currencies -->
            <td data-bind="text: price"></td>
            <!-- /ko -->
        </tr>
    </tbody>
</table>

It’s just a basic table with some bindings for Knockout. Knockout supports several types of bindings and other functions; if you haven’t already run through the live examples it’s worth doing. Above I’m using the for foreach binding to iterate though a collection on the model called currencies. Bindings can be applied to a HTML element, for example to the tbody element to make each item create a table row. In my example I only want one row and I’m manually adding the text USD to the first column, so I’m using the containerless syntax using comment tags. For each item in my collection it will render a TD tag and bind the price property of the object as the text.

That’s my template all set up and ready to go. Now for adding the JavaScript to bind my model to my UI.

var currentCurrencies = [
                { code: "USD", price: ko.observable(1.00000) },
                { code: "EUR", price: ko.observable(0.75103) },
                { code: "GBP", price: ko.observable(0.63163) },
                { code: "INR", price: ko.observable(53.8253) },
                { code: "AUD", price: ko.observable(0.95095) },
                { code: "CAD", price: ko.observable(1.00020) },
                { code: "ZAR", price: ko.observable(9.04965) },
                { code: "NZD", price: ko.observable(1.18616) },
                { code: "JPY", price: ko.observable(89.3339) }
            ];

            var currencyModel = function (currencies) {
                this.currencies = currencies;
            };

            ko.applyBindings(currencyModel(currentCurrencies));

First I have created an array of currency objects. For each price I have used the ko.observable function which is required for knockout to track the property and automatically update the UI. I have created a simple model with a property called currencies, and then used ko.applyBinding passing the model which binds to the template. I get a basic table that looks like this:

Knockout

At this point if I were to use JavaScript to update the price of one of the objects in the currencies collection the UI would automatically update to reflect that change. I’m now going to do that, but using SignalR to call the JavaScript function.

I’m going to create a page that allows me to update the currencies server side, so I have created a C# model called CurrencyViewModel which looks like this:

public class CurrencyViewModel
{
    public class Currency
    {
        [JsonProperty(PropertyName = "code")]
        public string Code { get; set; }
        [JsonProperty(PropertyName = "price")]
        public decimal Price { get; set; }
    }

    public List Currencies { get; set; }
}

You’ll notice the JsonProperty attribute for each property in the Currency object. Later when I call the JavaScript function the Currency object will be serialised as Json and passed into the method, so I added the attribute to make the property names lowercase to fit with JavaScript coding standards.

Next I’ve created an AdminController with an Index method to manage the currencies:

[HttpGet]
public ActionResult Index()
{
    var viewModel = new CurrencyViewModel()
    {
        Currencies = CurrencyService.GetCurrencies()
    };

    return View(viewModel);
}

CurrencyService is just an object returning a static list of currency objects. This could be coming from a database or file etc. Later I’ll show how you can also use SignalR to call server side code, and I’ll call this GetCurrencies method to populate the initial exchange rates instead of using the JavaScript array. The CurrencyService object looks like this.

public class CurrencyService
{
    public static List GetCurrencies()
    {
        return new List()
                    {
                        new CurrencyViewModel.Currency() {Code = "USD", Price = 1.00000M},
                        new CurrencyViewModel.Currency() {Code = "EUR", Price = 0.75103M},
                        new CurrencyViewModel.Currency() {Code = "GBP", Price = 0.63163M},
                        new CurrencyViewModel.Currency() {Code = "INR", Price = 53.8253M},
                        new CurrencyViewModel.Currency() {Code = "AUD", Price = 0.95095M},
                        new CurrencyViewModel.Currency() {Code = "CAD", Price = 1.00020M},
                        new CurrencyViewModel.Currency() {Code = "ZAR", Price = 9.04965M},
                        new CurrencyViewModel.Currency() {Code = "NZD", Price = 1.18616M},
                        new CurrencyViewModel.Currency() {Code = "JPY", Price = 89.3339M}
                    };
    }
}

Now I have created a couple of EditorTemplate to allow me to update the currencies. I’ll omit the markup from this post but you can see them in the provided source code. This gives me a form to edit my rates:

Knockout

Okay, so now it’s time to use SignalR. The first step is to register the necessary routes in the Global.asax Application_Start method:

RouteTable.Routes.MapHubs();

Next I need to create a hub. A hub is a C# class containing methods that the client can call. When you run the application, SignalR will create the hubs JavaScript file I mentioned earlier based on the C# hubs in your application. This is essentially a proxy allowing the client to connect to the hub and for messages to flow from server to client and vice versa.

All hubs must inherit from the abstract Hub class. I have a CurrencyHub which looks like this:

public class CurrencyHub : Hub
{
    public void GetCurrencies()
    {
        Clients.Caller.setCurrencies(CurrencyService.GetCurrencies());
    }
}

Methods in the hub are available to the client. As I mentioned earlier I will later get the currencies for the initial load from the currency service, so that is what this GetCurrencies method is for. You do not need to add methods to your hub to call functions on the client.

I’m now going to implement the post action for the Index method on my AdminController. This is where I will call the client side JavaScript to update the exchange rates directly to the UI:

[HttpPost]
public ActionResult Index(CurrencyViewModel viewModel)
{
    var context = GlobalHost.ConnectionManager.GetHubContext<CurrencyHub>();
    viewModel.Currencies.ForEach(c => context.Clients.All.updateCurrency(c.Code, c.Price));
    return View(viewModel);
}

I am using the GlobalHost helper method to get the current context for my CurrencyHub. Once I have this I can use Clients.All to call a JavaScript function on all clients connected to the hub. All is dynamic so you simply add a method matching that of the JavaScript function with the same signature. Here that function is called updateCurrency and code and price are passed as arguments.

My action method is looping through each currency object in the model’s collection and calling updateCurrency for each. Not the most efficient way to do it in the real world, but I’m trying to keep this example simple.

Now for the JavaScript to hook it all up. Knockout isn’t dependent on any other library, but SignalR uses jQuery so I’ve now wrapped everything in a ready function. First off I’ve added an updateCurrency method to my model which finds the given currency and updates the price. This is using Knockout’s arrayFirst utilitymethod. The remaining changes set up SignalR. First I create a reference to my currency hub then start the connection to the hub. Next I set up the updateCurrency function on hub.client which is the function that will be called from the server. From this function I’m just calling the function on my model to update the correct price.

$(function () {
    var currentCurrencies = [
        { code: "USD", price: ko.observable(1.00000) },
        { code: "EUR", price: ko.observable(0.75103) },
        { code: "GBP", price: ko.observable(0.63163) },
        { code: "INR", price: ko.observable(53.8253) },
        { code: "AUD", price: ko.observable(0.95095) },
        { code: "CAD", price: ko.observable(1.00020) },
        { code: "ZAR", price: ko.observable(9.04965) },
        { code: "NZD", price: ko.observable(1.18616) },
        { code: "JPY", price: ko.observable(89.3339) }
    ];

    var currencyModel = function (currencies) {
        this.currencies = currencies;

        this.updateCurrency = function (code, price) {
            var currency = ko.utils.arrayFirst(this.currencies, function (currency) {
                return currency.code == code;
            });

            currency.price(price);
        };
    };

    ko.applyBindings(currencyModel(currentCurrencies));

    var hub = $.connection.currencyHub;

    $.connection.hub.start();

    hub.client.updateCurrency = function (code, price) {
        updateCurrency(code, price);
    };
});

And that’s all there is to it. My server side code calls hub.client.updateCurrency which calls the method of the same name in the model. That method finds the correct currency and updates the price. As price is observable Knockout then automatically updates the UI.

The final thing I’m going to do is load the currencies from the server when opening the page. This could be done using an ajax request, but you can also do it with SignalR. I’ll be calling the method in my CurrencyHub which I briefly mentioned earlier in the post. Here it as again:

public void GetCurrencies()
{
    Clients.Caller.setCurrencies(CurrencyService.GetCurrencies());
}

The method doesn’t accept any arguments, and when called, it calls a JavaScript function on the client that made the call to the server. This is through Clients.Caller, rather than Clients.All which I used earlier. The JavaScript can now be amended as below to first load the data from the server using SignalR.

$(function () {
    var currencyModel = function (currencies) {
        this.currencies = ko.mapping.fromJS(currencies);

        this.updateCurrency = function (code, price) {
            var currency = ko.utils.arrayFirst(this.currencies(), function (currency) {
                return currency.code() == code;
            });

            currency.price(price);
        };
    };

    var hub = $.connection.currencyHub;

    $.connection.hub.start().done(function () {
        hub.server.getCurrencies();
    });

    hub.client.updateCurrency = function (code, price) {
        updateCurrency(code, price);
    };

    hub.client.setCurrencies = function (currentCurrencies) {
        ko.applyBindings(currencyModel(currentCurrencies));
    };
});

The first obvious change is that I’m using the done function on hub.start so that once the connection has started it calls the getCurrencies function on the server. The function on the server was in pascal case, but when calling via JavaScript you need to use camel case.

The next change is that I’ve moved the ko.applyBindings call inside the setCurrencies function. This is the function called by the server which passes the collection of Currency objects. These objects are serialised as Json, so currentCurrencies here is an array of JavaScript objects that represent the currencies. This is that passed into my model when doing the binding.

As this is just an array of plain objects, none of the properties are observable, so the UI won’t be updated if they change. You could easily manually parse the array and re-create the objects as observable, but here I’m using the Knockout Mapping plugin. The plugin takes a JavaScript object and returns an object with observable properties. It has overrides that allow you to take control of some of the mapping, but here I’m just using the default implementation with ko.mapping.fromJS. As I’m passing in an array, it will return an observable array, where each property of each object in the array is also observable.

Due to this there are a couple of other changes required. One line 6 this.currencies becomes this.currencies(). Any observable object requires parenthesis to access as it’s converted to a method. Similarly on line 7 this.code becomes this.code() for the same reason. Previously I didn’t make code observable, so I didn’t need parenthesis, but as this time I used the mapping plugin it’s now observable.

I hope this simple example shows how Knockout and SignalR compliment each other allowing developers to create elegant real-time web applications.

Source code

Posted on by Joe in ASP.NET, C#, MVC

Add a Comment