Introduction

Recently, I came across a great JavaScript framework KnockoutJS. It simplifies development of user interfaces by implementing data binding functionality. In this article, I want to briefly describe it and talk about problems in its usage in ASP.NET applications.

Prerequisites

To work with the sample from this article, you should download the latest version of KnockoutJS. Also, I have used Visual Studio 2010 to write my code. But I'm sure that everything will work with Visual Studio 2008.

Introduction to KnockoutJS

Let's start with a very simple example of KnockoutJS usage. Here is the code of the Web page:

<scripttype="text/javascript"src="Scripts/knockout-1.1.2.js"></script><tableborder="1"><tbody><tr><td>
Change visibility of next row:
<inputtype="checkbox"data-bind="checked: isRowVisible"/></td></tr><trdata-bind="visible: isRowVisible"><td>
This is a row which visibility can be changed.
</td></tr></tbody></table><scripttype="text/javascript">var viewModel = { isRowVisible: ko.observable(false) };
ko.applyBindings(viewModel);</script>

There are some things I should mention here:

First of all, you should attach the knockout-1.1.2.js file.

Then you should make bindings by applying data-bind attributes to any HTML element you like. I believe the syntax is clear. data-bind="visible: isRowVisible" means that this element is visible if the isRowVisible property is true. What is the isRowVisible property for? Wait a moment.

And at last, there is the heart of KnockoutJS. In a script block, you should create the viewmodel. viewmodel is a simple JavaScript object with properties. These properties will be used in bindings. isRowVisible is a property of the viewmodel. As you can see, the values of these properties are assigned with the help of the ko.observable function. This function allows the system to track changes in the properties and send them into the bound HTML elements.

And the last thing is the call to ko.applyBindings(viewModel). It makes all the magic work.

These are the basics of the usage of KnockoutJS. You may refer to the official documentation on the site to know more. Now I'd like to go to the usage of KnockoutJS in ASP.NET.

Binding to ASP.NET controls

In the previous example, I used the usual HTML input element. It is time to test with an ASP.NET control. Let's try to use it with asp:Checkbox. It appears the code:

Persisting the View Model between postbacks

It is usual for an ASP.NET page to make postbacks to the server. If we make a postback on the page from our sample, then it will appear that after the postback, all our changes are lost. This is a usual problem with JavaScript-made changes. Our View Model will be loaded again from scratch and the initial state will be set again.

But of course, we'd like to save all our changes. The approach I suggest is very similar to the way ViewState is persisted in ASP.NET. We will store our View Model in a hidden field. Let's add a hidden field to our page:

<inputtype="hidden"runat="server"id="hViewStateStorage"/>

Now we should write our View Model into this field. In most cases, the initial state of controls is defined on the server side. We'll use this technique too. On the server side, we'll create an object of the View Model, serialize it in JSON format, and place the JSON string in the hidden field:

As you can see, I have used an anonymous class here. It means that you don't even need to create a separate class for the View Model on the server side.

Another tempting possibility here is in changing the View Model while postback. As it is stored in a hidden field, you can extract the View Model from there, deserialize it, analyze and change its properties, serialize it, and put it in the hidden field again. In this case, you'll need to create a separate class for the View Model to deserialize the Model to.

Now we have to extract the View Model from the hidden field in the JavaScript code. Here is how you do it:

What is the reason? It is in the function ko.observable. In fact it returns the function, not an ordinal value. It means that all the properties of our View Model are functions now. So they are not serialized in JSON format. To return all properties to their "non-functional" state, we must use the ko.utils.unwrapObservable function:

Using KnockoutJS in independent ASP.NET controls and pages

So far everything is great. But let's consider the following scenario. I'd like to use KnockoutJS in an ASP.NET control. And this control could be inserted into a page which uses KnockoutJS as well. Furthermore, I'd like to place several instances of this control into one page. And also, I may place on this page other controls which use KnockoutJS. I believe you can already see the problem. How can different View Models with probably the same names of properties work on one page?

Well, KnockoutJS has a mechanism to work with several View Models on the same page. Do you remember the ko.applyBindings function? It may accept the second parameter, so call the context:

ko.applyBindings(viewModel, document.getElementById('someDivId'));

This context is a DOM element. In this case, elements will be bound to the View Model only inside this DOM element. So you can make several divs on the page and assign different View Models to each of them. The problem is that these divs can't be nested. In my opinion, it is a big limitation preventing free usage of KnockoutJS in ASP.NET. We may not be sure that one View Model will not interfere with another. Of course, we can design our pages and controls very carefully, trying to avoid nesting of controls with KnockoutJS, but in any case, this is a source of possible errors, I think.

The method I suggest is merging all View Models on the page into a single View Model. Something like this:

How to create unique names of submodels inside the main View Model for each page\control?

How to merge all submodels into the main View Model?

How to implement persisting of submodels?

Let's solve these problems. First of all, we already have unique names in ASP.NET. I'm talking about the ClientID property of each page\control. I suggest all our submodels will have names of ClientID of the corresponding page\control. In this case, we can reference the properties of our submodles like this:

<trdata-bind="visible: <%= this.ClientID %>.isRowVisible">

Unfortunately, this approach does not work with server-side controls (controls with the attribute runat="server"). For these controls, you should set the data-bind attribute in the code-behind file:

Considering that sometimes we must use InputAttributes instead of Attributes, this approach is unavoidable in any case.

Now let's look at system merging all our Models into a single one. If we want to have only one instance of something in our code, we must use Singleton pattern. But for the sake of simplicity, I'll just create a global variable in a JavaScript file which I'll attach to all our pages\controls:

Let's take a closer look at this code. g_KnockoutRegulator is a JavaScript object with a ViewModel property. This property is our main View Model which is bound to all controls. The only function LoadViewModel does all the magic. It gets the ClientID of the current page\control (clientId) and the ClientID of the hidden storage field (storageFieldId). Inside, it extracts the local View Model from the storage field and loads it into the main View Model as one of its properties (the first for loop). Then it subscribes for the Submit event where it does the opposite operation, storing the local View Model into the storage field. And at last, it binds the main View Model to the controls.

<%@PageLanguage="C#"AutoEventWireup="true"CodeBehind="KnockoutJsSample.aspx.cs"Inherits="KnockoutJsTest.KnockoutJsSample"%><%@RegisterSrc="~/UserControls/TestControl.ascx"TagPrefix="uc"TagName="TestControl"%><!DOCTYPEhtmlPUBLIC"-//W3C//DTDXHTML1.0Transitional//EN""http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><htmlxmlns="http://www.w3.org/1999/xhtml"><headrunat="server"><title></title></head><body><formid="form1"runat="server"><scriptsrc="Scripts/knockout-1.1.2.js"type="text/javascript"></script><scriptsrc="Scripts/jquery-1.4.1.min.js"type="text/javascript"></script><scriptsrc="Scripts/KnockoutSupport.js"type="text/javascript"></script><inputtype="hidden"runat="server"id="hViewStateStorage"/><uc:TestControlrunat="server"/><tableborder="1"><tr><td>
Change visibility of next row:
<asp:CheckBoxID="chkChangeVisibility"runat="server"/></td></tr><trdata-bind="visible: <%= this.ClientID %>.isRowVisible"><td>
This is a row which visibility can be changed.
</td></tr></table><inputrunat="server"type="submit"/><scripttype="text/javascript">
g_KnockoutRegulator.LoadViewModel('<%= this.ClientID %>',
'<%=hViewStateStorage.ClientID %>');
</script></form></body></html>

TestControl.ascx

<%@ControlLanguage="C#"AutoEventWireup="true"CodeBehind="TestControl.ascx.cs"Inherits="KnockoutJsTest.UserControls.TestControl"%><scriptsrc="Scripts/knockout-1.1.2.js"type="text/javascript"></script><scriptsrc="Scripts/jquery-1.4.1.min.js"type="text/javascript"></script><scriptsrc="Scripts/KnockoutSupport.js"type="text/javascript"></script><inputtype="hidden"runat="server"id="hViewStateStorage"/><tableborder="1"><tr><td>
Change visibility of next row:
<asp:CheckBoxID="chkChangeVisibility"runat="server"/></td></tr><trdata-bind="visible: <%= this.ClientID %>.isRowVisible"><td>
This is a row which visibility can be changed.
</td></tr></table><scripttype="text/javascript">
g_KnockoutRegulator.LoadViewModel('<%= this.ClientID %>',
'<%=hViewStateStorage.ClientID %>');
</script>

As you can see, both the page and control use the same names of properties of their View Models. But they do not interfere with each other. This is exactly what we want.

Some optimization

We have created the main framework to work with KnockoutJS in ASP.NET. The only thing I don't like here is the call to ko.applyBindings. It is called for every control using KnockoutJS. But it is obvious that one call would be enough. The only thing to consider is that this call must be made after all controls have loaded their local View Models into the main one. How can we achieve this? I'll modify the KnockoutSupport.js file like this:

Here, the field NumberOfLocalViewModels must be set to the correct number of pages\controls loading the local View Models. How do we get this number? I'll do it using the RegisterClientScriptBlock and RegisterStartupScript methods of the ClientScriptManager object. All code registered using RegisterClientScriptBlock is executed before all code registered using RegisterStartupScript. So here is my helper class to register the necessary JavaScript code:

Using this class, we don't need references to .js files or a manual call to the g_KnockoutRegulator object. The last two lines do the main work. The first line registers the script, increasing the number of local View Models to be loaded. The last line does the actual loading. Here is an example of a page with KnockoutJS using this KnockoutJsHelper class:

KnockoutJsSample.aspx:

<%@PageLanguage="C#"AutoEventWireup="true"CodeBehind="KnockoutJsSample.aspx.cs"Inherits="KnockoutJsTest.KnockoutJsSample"%><%@RegisterSrc="~/UserControls/TestControl.ascx"TagPrefix="uc"TagName="TestControl"%><!DOCTYPEhtmlPUBLIC"-//W3C//DTDXHTML1.0Transitional//EN""http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><htmlxmlns="http://www.w3.org/1999/xhtml"><headrunat="server"><title></title></head><body><formid="form1"runat="server"><inputtype="hidden"runat="server"id="hViewStateStorage"/><uc:TestControlrunat="server"/><tableborder="1"><tr><td>
Change visibility of next row:
<asp:CheckBoxID="chkChangeVisibility"runat="server"/></td></tr><trdata-bind="visible: <%= this.ClientID %>.isRowVisible"><td>
This is a row which visibility can be changed.
</td></tr></table><inputrunat="server"type="submit"/></form></body></html>

I have a linkbutton on my page, but the __doPostBack function created by ASP.NET doesn't 'submit' the form, i.e. the function subscribed to the submit event is never executed. You can find lots of people asking this question on the internet (one example on StackOverflow).

Hi, thanks for the article, very interesting indeed. Given all the code required I do wonder whether it's a good way to go, or whether JQuery/Ajax would be easier to use without extra frameworks? Would be interested in your thoughts!

Thank you for your appreciation. Even it requires so much of code to use Knockout safely with ASP.NET I still think that this framework is great. Using jQuery/AJAX for data binding can work well in simple cases, but in more complicated situations Knockout rules. Knockout requires less code to implement the same things and also this code is more simple and understandable. So IMHO Knockout is the best solution for client-side data binding.