KnockoutJS and Strongly-Typed Helpers in ASP.NET MVC

A few weeks ago, I posted a screencast overview of KnockoutJS, a JavaScript MVVM framework. Shortly after that, I published this post, where I share my take on using Knockout in a more unobtrusive style, eschewing the standard data-bind syntax in favor of a modelBinder object that creates those bindings for me.

In both cases, I presented the markup in a pretty server-framework-agnostic way. Meaning specifically that I didn’t use any ASP.NET MVC HTML helpers. I did this on purpose, and to clearly illustrate that KnockoutJS cares not about your server framework. You can use it with ASP.NET MVC, Rails, PHP, Python, whatever.

I did, however, receive a few comments suggesting that I had left the reader or screencast watcher with the impression that Knockout cannot be used with the strongly-typed HTML helpers provided with ASP.NET MVC. This is not the case, so I wanted to take a moment and append my previous post and screencast with some quick examples of using Knockout alongside the ASP.NET MVC strongly-typed helpers.

For starters, let’s take a look at the Speaker Info fieldset from the Add Speaker page I used both in the original screencast and my alternative binding post.

Note: If you cannot view the embedded Gist above for any reason, click here to view the embedded code.

Modifying this to use my strongly-typed helper methods in ASP.NET MVC is easy. I just need to use the appropriate helper method, and then set my data-bind attributes as properties in the anonymous object parameter MVC uses for HTML attributes.

Note: If you cannot view the embedded Gist above for any reason, click here to view the embedded code.

They key thing to note here is that all of my data-bind properties have been changed to data_bind (underscore instead of dash). Properties in C# cannot contain dashes (-), but underscores are allowed (_), and, by convention, ASP.NET MVC will convert any underscores appearing in the HTML attributes object to dashes.

If I’m using KnockoutJS with the default data-bind syntax—as most are—this is all I need to do to use Knockout and MVC strongly-typed helpers happily together.

If, on the other hand, I’m using Knockout with Unobtrusive bindings, as I detail in my previous post, I need to make a few difference changes. For unobtrusive bindings in Knockout, I’m not specifying data-bind attributes myself, so I can remove those from my helpers, like so:

Note: If you cannot view the embedded Gist above for any reason, click here to view the embedded code.

With unobtrusive binding in Knockout, my JavaScript binding object contains a list of fields and bindings to create when the page executes. When the bindings are created, I use a convention to minimize typing so that when my modelBinder sees an input binding of ‘name’, it knows to look for a HTML field with an id=’name’ and, if found, sets the data-bind attribute to “value: name.”

When I’m using the ASP.NET MVC strongly-typed helpers with unobtrusive bindings, this convention won’t work as previously described, because ASP.NET MVC auto-generates field name and id attributes for me, and it uses the Model property name to do so. Since the model property for “Name” is capitalized, my convention breaks.

There are several ways to fix this. I could capitalize the first letter of all of my ViewModel properties in JavaScript, but that would be too easy. Instead, I decided to modify my modelBinder to support first-letter capitalization differences between my HTML and my ViewModel.

Note: If you cannot view the embedded Gist above for any reason, click here to view the embedded code.

To my existing modelBinder object, I added a new private method called getElement, which will attempt to select an element from the DOM given the provided id. If it cannot find that element, it performs a simple capitalization on the first letter and tries again. Once I modify the setBinding method to call this new method, I’m good to go, and I can now used the MVC-generated id attributes with my existing unobtrusive Knockout bindings.

Page 1 of 2 | Next page