Learn how to use Knockout in an ASP.NET MVC 3 web application to handle real-time UX updates.

Posted on July 4th, 2011

Knockout provides a great way to implement a MVVM (Mode-View-View Model) pattern in JavaScript on the client side and is easy to integrate with an ASP.NET MVC 3 application. Using a prototype application as an example, we will explore how to create a block of notification data and continuously update it from data received via a controller action. The application markup will display the inventory levels of a product and will include a message when the product has reached a reorder level. It will have a single controller responsible for rendering a single view and providing an action method for getting the current inventory data state.

First things first: get Knockout

Knockout is available via NuGet. You can search for "knockout" in the Manage NuGet Packages dialog or you can use the Package Manager Console and run the command Install-Package knockoutjs. After adding the package we can include a <script> tag in the _Layout.cshtml view and have all we need to start using Knockout (version 1.2.1 at the time of this writing).

<script src="@Url.Content("~/Scripts/knockout-1.2.1.js")" type="text/javascript"></script>

The View

The first step to working with Knockout is to craft your view model(s) for your UX. Assuming we have a controller with an Index.cshtml view set up, within a <script> tag in the view file we create a JavaScript object and define fields on it. For fields that you want Knockout to monitor, you set their value to ko.observable(). The view model for the prototype application needs fields for "InStock" and "Reserve". The JavaScript for defining this view model looks like:

var inventoryDataViewModel = {
    InStock: ko.observable(0),
    Reserve: ko.observable(0)
};

This sets up the client side storage structure for the inventory data. The data also needs a field that represents the total available. This can be determined by getting the difference of the "InStock" amount and the "Reserve" amount. Knockout can handle calculating this via a "dependent observable". A new field named "Available" is created after the initialization of the inventoryDataViewModel object and is assigned a dependent observable that is passed a function to handle the calculation and the view model to act on (more info on dependent observable can be found in the Knockout documentation).

inventoryDataViewModel.Available = ko.dependentObservable(function () {
    return this.InStock() - this.Reserve();
}, inventoryDataViewModel);

It is important to note that the () are required after the field names in the function. This tells Knockout to evaluate the current values of each.

We can also add a field that is a dependent observable to keep track of a "low stock" state and use that field to change the look of the UX.

inventoryDataViewModel.StockIsLow = ko.dependentObservable(function () {
    return this.Available() < 5;
}, inventoryDataViewModel);

This will encapsulate some logic to indicate if the "Available" value has hit a given threshold (in this case, lower than 5 units). With that in place the markup can check that value and react accordingly.

The last step to working with the JavaScript view model is to tell Knockout to apply bindings. Typically this is just a matter of calling the ko.applyBindings() method and passing in the instance of the view model. However, it is important to note that this will have Knockout observe the entire DOM. Knockout is capable of working with multiple view models, but to do so you need to pass in a second argument to the ko.applyBindings() method defining what DOM element to monitor. We will use this approach in our prototype because it is certainly possible that we may want to reuse this notification block across multiple pages (a product details page, a category page that has that product, etc). The JavaScript code to apply the bindings looks like:

ko.applyBindings(inventoryDataViewModel, $("#InventorySummary")[0]);

The second argument is using jQuery to select a DOM element with an id attribute set to "InventorySummary". The HTML for the inventory summary:

<div id="InventorySummary">
    <ul>
        <li>In Stock: <span data-bind="text:InStock"></span></li>
        <li>Reserve: <span data-bind="text:Reserve"></span></li>
        <li>Total Available: 
            <span data-bind="text:Available, css:{ ImportantMessage: StockIsLow }"></span>
        </li>
    </ul>
    <div class="Hidden ImportantMessage" data-bind="css: { Hidden: !StockIsLow() }">
        Order more widgets!
    </div>
</div>

The list items contain span tags that will display the data from the Knockout view model. The data-bind attribute on the "In Stock", "Reserve" and "Available" elements tell Knockout to set the "text" in the element to the value of the view model InStock, Reserve and Available fields. The call to ko.applyBindings() triggered Knockout to wire up the bindings and thus understand that the keys "InStock", "Reserve" and "Available" are referring to the fields on the inventoryDataViewModel object.

Multiple bindings can be added to the data-bind attribute by separating each with a comma. For the "Available" display we are including the "css" binding as well. If the inventory level has reached a "low stock" state then we tell Knockout to apply the ImportantMessage css class to the element (more info can be found in the css binding section of the Knockout documentation).

The div element that has the "Order more widgets!" message implements a similar data binding, but it is checking the opposite value of the StockIsLow field and adding/removing a css class named Hidden (that contains a css definition display:none;) accordingly. In order to do a logic check in the data-bind attribute you need to include the () on the view model field name in order to let Knockout know that it needs to evaluate the current value.

Our prototype needs to handle updating this data in "real-time". This can be accomplished by using the JavaScript setTimeout method to continuously poll the data from a MVC controller action. A function can be created to make a jQuery call to the $.get() method and upon completion it can update the Knockout view model and call the setTimeout method.

function PollInventoryData() {
    $.get('/Home/InventoryData', function (data) {
        inventoryDataViewModel.InStock(data.InStock);
        inventoryDataViewModel.Reserve(data.Reserve);
        setTimeout("PollInventoryData()", 5000);
    });
}

The setTimeout method tells the client to call the given method (in this case, itself) after a period of X milliseconds. The logic in this method will make the asynchronous call to the controller action, and when it is completed it will "queue" up another call to itself. Thus, while the user remains on this page, the inventory data will continuously get polled and our UX will get updated each time. The final thing we need to do in the View code is to make the initial call to the PollInventoryData() method. We will do this once the document is ready via the jQuery check:

$(function () {
    PollInventoryData();
});

The Index.cshtml view file:

@{ ViewBag.Title = "Product: Widget"; }
<h2>Product: Widget</h2>
<div id="InventorySummary">
    <ul>
        <li>In Stock: <span data-bind="text:InStock"></span></li>
        <li>Reserve: <span data-bind="text:Reserve"></span></li>
        <li>Total Available: 
            <span data-bind="text:Available, css:{ ImportantMessage: StockIsLow }"></span>
        </li>
    </ul>
    <div class="Hidden ImportantMessage" data-bind="css: { Hidden: !StockIsLow() }">
        Order more widgets!
    </div>
</div>
<script type="text/javascript">
    var inventoryDataViewModel = {
        InStock: ko.observable(0),
        Reserve: ko.observable(0)
    };

    inventoryDataViewModel.Available = ko.dependentObservable(function () {
        return this.InStock() - this.Reserve();
    }, inventoryDataViewModel);

    inventoryDataViewModel.StockIsLow = ko.dependentObservable(function () {
        return this.Available() < 5;
    }, inventoryDataViewModel);

    ko.applyBindings(inventoryDataViewModel, $("#InventorySummary")[0]);

    $(function () {
        PollInventoryData();
    });

    function PollInventoryData() {
        $.get('/Home/InventoryData', function (data) {
            inventoryDataViewModel.InStock(data.InStock);
            inventoryDataViewModel.Reserve(data.Reserve);
            setTimeout("PollInventoryData()", 5000);
        });
    }
</script>

The Site.css file:

.Hidden { display:none; }
.ImportantMessage { color:#ff0000; }

The Model Classes

A basic model class named InventoryData is created in MVC to handle the inventory data structure. It only needs to contain properties for the "In Stock" and "Reserve" values. All other data is handled via the Knockout code we put in the View.

namespace Website.Models
{
    public class InventoryData
    {
        public int InStock { get; set; }
        public int Reserve { get; set; }
    }
}

To simulate a data management layer we will use a similar method that was used in an earlier post, "Build a dialog form using jQuery UI in MVC 3". We can create a class named InventoryManager with a method to get inventory data and create initial data. This class will store data in the runtime cache to give us a rudimentary way of simulating data storage and persistence in our prototype. You would want to refactor this to work with whatever data storage means you are currently or planning to implement.

using System;
using System.Web;

namespace Website.Models
{
    public class InventoryManager
    {
        public InventoryData GetInventoryData()
        {
            if(HttpRuntime.Cache["InventoryData"] == null)
                return this.CreateInitialData();
            var item = (InventoryData)HttpRuntime.Cache["InventoryData"];
            item.Reserve += this.generateRandomReserveAmount();
            return item;
        }

        public InventoryData CreateInitialData()
        {
            var item = new InventoryData { InStock = 20, Reserve = 0 };
            HttpRuntime.Cache["InventoryData"] = item;
            return item;
        }

        private int generateRandomReserveAmount()
        {
            var random = new Random();
            return random.Next(1, 6);
        }
    }
}

The private generateRandomReserveAmount is used to simulate someone placing an order for the product, thus putting X amount in "Reserve". This code is called in the GetInventoryData method so that each call to that method will trigger the simulation. Again, the code in this class is purely there to get our UX functioning in a sample without the overhead of creating a full blown data layer.

The Controller

Using a general HomeController for the prototype, we can add an action method for rendering the view and one for getting the inventory data. The Index method will just return the view. The InventoryData method will instantiate an instance of an InventoryManager class, call the GetInventoryData method to get an instance of an InventoryData model, and return the model instance as JSON.

using System.Web.Mvc;
using Website.Models;

namespace Website.Controllers
{
    public class HomeController : Controller
    {
        public static int CurrentInning;

        public ActionResult Index()
        {
            return View();
        }

        [OutputCache(Duration = 0)]
        public JsonResult InventoryData()
        {
            var manager = new InventoryManager();
            var model = manager.GetInventoryData();
            return Json(model, JsonRequestBehavior.AllowGet);
        }
    }
}

Since the jQuery call will cache the call to the content in Internet Explorer by default (a setting in jQuery), we need to decorate the InventoryData action method with the OutputCache attribute and a duration of 0. This will send the no-cache flag back in the header of the content to the browser and jQuery will pick that up and not cache the call, thus keeping our data fresh each time the controller method is polled for data.

Lights, camera, action

Everything is in place and the code is ready to run. Doing an F5 will result in the inventory levels getting rendered and the "order more" message hidden. Approximately every 5 seconds the InventoryData controller action will get hit and the UX will get updated with new "Reserve" and "Available" amounts. Once the "Available" amount falls below 5 the font color of the "Available" will change to red and the "order more" message becomes visible. If you don't feel like waiting it out you can refresh the page over and over, which will trigger the initial call to the controller action and move you through the simulation faster.

Extra credit

Lets take a look at another feature of Knockout and in the process provide us a means of resetting our data (instead of having to either kill Cassini or IIS Express to clear the cache from our data storage simulation code). We can create a button to reset levels and use Knockout to bind a click event to it. Start by adding a button element within the "InventorySummary" element and set the data-bind attribute to use the "click" binding:

<div id="InventorySummary">
    <ul>
        <li>In Stock: <span data-bind="text:InStock"></span></li>
        <li>Reserve: <span data-bind="text:Reserve"></span></li>
        <li>Total Available: 
            <span data-bind="text:Available, css:{ ImportantMessage: StockIsLow }"></span>
        </li>
    </ul>
    <div class="Hidden ImportantMessage" data-bind="css: { Hidden: !StockIsLow() }">Order more widgets!</div>
    <button data-bind="click: ResetLevels">Reset Levels</button>
</div>

This tells Knockout to monitor the click event of the element and call the function that is bound to the ResetLevels field of the view model. To add that logic, all we need to do is define a new field in the JavaScript view model instantiation and set its value to a function:

    var inventoryDataViewModel = {
        InStock: ko.observable(0),
        Reserve: ko.observable(0),
        ResetLevels: function () {
            $.post('/Home/ResetInventory', function (data) {
                inventoryDataViewModel.InStock(data.InStock);
                inventoryDataViewModel.Reserve(data.Reserve);
            });
        }
    };

The function will call the jQuery $.post() method to hit a controller action named ResetInventory and upon completion it will update the view model fields. By updating the view model fields here we can keep the UX data current without having to interrupt the current polling that is taking place via our existing logic.

The only other thing we need to do is add the controller action method to the HomeController:

public JsonResult ResetInventory()
{
    var manager = new InventoryManager();
    var model = manager.CreateInitialData();
    return Json(model);
}

Since the InventoryManager class already has the method CreateInitialData, we simply call that and use the returned value as the model for the JsonResult. Now we have a way to manually reset the data while the page is up.

Knockout has a ton of potential uses on the client side for gaining control over your application code structure and maintainability. It also helps reduce the amount of code needed to handle changes in data and user events which is a welcome relief, especially as your application grows and you realize how much jQuery you have been writing to wire up and pull strings to make your interface dance!

Download the code
Sample Solution Zip File (KnockoutRealTimeUXMvc3.zip)

Discussion

Simon
Simon
08 Jul, 2011 05:54 AM

This is a very well written article and I enjoyed it. However this isn't actually "real time" it is just close to real time. If you coupled this with a comet implementation then you could eliminate the need to do polling of the server.

08 Jul, 2011 02:43 PM

Simon
Thanks for the comment! Do you have an example or link of how to implement a Comet solution? Looks like the Wikipedia entry gives a good summary of the concept.

Michael Flynn
Michael Flynn
08 Jul, 2011 03:43 PM

I just did what you with knockout.js did but actually implemented long polling as the solution instead of setTimeout. Look into AsyncController to accomplish this. You can view the long polling on grassrootshoops.com and Firebug.

Simon Timms
Simon Timms
11 Jul, 2011 03:34 AM

Take a look at https://github.com/nmosafi/aspcomet which is a pretty good solution for .net. We put out state updates via nservicebus and have the asp.net site act as a listener for these messages. They they filter the messags and send them on to interested clients. If web sockets ever gain better acceptance, and they will, then that is a way better solution. As it stands our site defaults to long polling which isn't ideal.

Felix
Felix
11 Jul, 2011 05:23 AM

Justin, Excellent article. I have a few comments, but those are just minor gripes :)

You have css:{ ImportantMessage: StockIsLow } (in two places). I think it's just a typo and should be css:{ ImportantMessage: StockIsLow() }, as is in another place.

Also, you have second parameter in applyBindings - $("#InventorySummary")[0], but you never describe why you need it there. After all, it works just fine without it, and examples on knockout page don't have second param.

Last, you simplified your example by putting JavaScript at the end of the file. Nowadays we typically put all of JS at the top and wrap it into $function({ ... }). However, if I do that, there is a problem with setTimeout. I would recommend to put PollInventoryData inside JQuery block and then change seTimeout as follows:

function PollInventoryData() {
    $.get('/Home/InventoryData', function (data) {
        inventoryDataViewModel.InStock(data.InStock);
        inventoryDataViewModel.Reserve(data.Reserve);
        setTimeout(PollInventoryData, 5000);
    });
}
Felix
Felix
11 Jul, 2011 05:59 AM

Sorry, two more. You run setTimeout(), but never clear it - in fact never assign it to a variable, that would allow you to clear it. So, even when stock is low and the page shows "order more widgets", the code pings the server for update, and stock keeps going down...

And really minor. It would be more intuitive to show InStock going down, rather than Reserve for some reason going up. At least, that's what my retailer wife tells me ;)

11 Jul, 2011 03:40 PM

Felix
Thanks for the feedback (and contribution) as usual!

  1. The css:{ ImportantMessage: StockIsLow } for the total available doesn't need the () because the data in StockIsLow is a bool. The css: { Hidden: !StockIsLow() } in the message uses the () because there is an expression there (the check for not "stock is low"). These two are different to illustrate how you handle each case in Knockout. If you have a variable that resolves to a true/false then you don't need the (). Otherwise you need to call the function on that variable if you want to use it in an equation in the data-bind attribute. Knockout docs section for more info.
  2. The paragraph right above the ko.applyBindings(inventoryDataViewModel, $("#InventorySummary")[0]); in the article explains the second param.
  3. Removing the string for the function name (and using the reference to the function instead) in the call to setTimeout is a better approach. Good call.
11 Jul, 2011 03:53 PM

Felix

  • If you wanted halt the polling then you would need to add some logic within the PollInventoryData function to make that decision, then not call the setTimeout method. The recursive call to PollInventoryData should be the thing that is causing the repeat polling. The setTimeout should simply be delaying the call to it for a brief moment.
  • My hypothetical app is for inventory managers to monitor their stock levels and get to work ordering more from their vendors when the demand gets close to exceeding stock. :) So they need to know that they still have 20 on the shelves but there are orders out there placed for 17 and if they don't get their butt in gear and order more before the end of the day then the sales team is going to have to tell customers they are out of stock! But you could totally make your sample app do whatever you (or your wife) need it to do ;)
11 Jul, 2011 04:00 PM

Simon Timms
Thank you for sharing the reference to the "comet" implementation in .NET!
https://github.com/nmosafi/aspcomet

11 Jul, 2011 04:13 PM

Michael Flynn
Thanks for the suggestion on AsyncController. I found a summary of it on the MSDN docs site but it looks like it was written for MVC 2. Not sure if much has changed on it in MVC 3 or not. Probably still worth the read. I also found the post How to Use Asynchronous Controllers in ASP.NET MVC2 & MVC3 that does a good job expanding on the MSDN doc.

Felix
Felix
13 Jul, 2011 05:15 AM
  1. Hmm... you are right - StockIsLow works with or without parenthesis; however !StockIsLow requires parens. Well, it is kinda counter-intuitive. As my English teacher used to say, "you can't explain it - you should memorize it".
  2. Sorry, I missed the stuff before the code; was looking only at the statement after it :(

  3. Got to go with what my wife says... :D

13 Jul, 2011 02:57 PM

Felix
Always go with what the wife says :)

Anne
Anne
04 Jan, 2012 05:56 PM

Nice work Justin.

Billy Braga
Billy Braga
17 Feb, 2012 07:07 PM

This ain't realtime !!! Like the first guy said...

06 Mar, 2012 07:59 PM

Thank you for your tutorial :)

No new comments are allowed on this post.