TypeScripted Knockout in ASP.NET MVC

Posted by: Sumit Maitra , on 9/7/2013, in Category ASP.NET MVC
Views: 41891
Abstract: We take a look at how to use TypeScript’s definitions for KnockoutJS to build a small ASP.NET MVC application. Along the way we explore the TypeScript language and gauge how it can be potentially used for build ASP.NET MVC applications that lean on the client side JS Libraries for richness and interactivity

Back in October 1, 2012 when Microsoft Technical Fellow Ander Hejlsberg introduced TypeScript, we had taken a quick look at what is TypeScript and how we could build an MVC 4 template for TypeScript. [That was TypeScript v0.8.x.] 

In //BUILD 2013 Anders presented the latest updates to TypeScript as v 0.9.0. Between October 2012 and the present, TypeScript has made steady progress and now includes advanced constructs like Generics and lambda expressions and so on. There has been improvement in the Tooling support as well. Best part has been the community engagement and uptick in TypeScript definition support for majority of JS libraries and frameworks. There is now a Github project called DefinitelyTyped that is maintaining most Type annotations for TypeScript.

So today, we will explore what is takes to use Knockout JS in an app that Uses TypeScript for development of the client side in an ASP.NET MVC 4 application. We will also take a look at the latest tooling support for TypeScript.

 

Getting Started with TypeScript in Visual Studio 2013 Preview

TypeScript’s tooling support comes in form of an out-of-band extension to Visual Studio that can be downloaded from http://www.microsoft.com/en-us/download/confirmation.aspx?id=34790. The binary, which was at v0.9.1.1 at the time of writing, is approximately a 12 MB in (download) size.

typescript-plugin

Once downloaded, close Visual Studio and run the installer. In my case, I ran it on Visual Studio 2013 Preview but the installer works for VS 2012 as well.

typescript-installation

The installation takes a minute or so to complete, once done, you are good to get started with TypeScript.

Unlike previous tooling, there are no MVC project templates created for TypeScript. The TypeScript installer only installs the ‘HTML Application with TypeScript” project template.”

typescript-html-application

While we can use the above for demoing TypeScript’s capabilities, we want to see a slightly more practical use for it and that is in an ASP.NET MVC Application.

TypeScript in ASP.NET MVC

Turns out you don’t have to do anything special to include TypeScript in ASP.NET MVC. You start off with the ASP.NET Web Application Project Type.

typescript-mvc-new-project

Next I have selected the MVC template to bring in the nice BootStrap styling etc.

typescript-mvc-new-project-template

Once the code generation completes, we select the Scripts folder, right click on it and select New Item. In the search window, we type in ‘TypeScript’ to eke out the TypeScript File item and add ‘typescript-list.ts’ file to the project.

add-new-typescript

Once you click Add, Visual Studio pops up the following dialog for your convenience

mvc-typescript-support

This essentially means it has added the build commands required to build .ts files into .js at compile time and if you click Yes, it springs up the Nuget Package Manager dialog.

definitely-typed-typescript-definitions

This is filtered by the tag:typescript thus giving you a list of already available TypeScript definition files. Of the given list, I added jQuery, Knockout, BootStrap and Knockout.Mapping definitions.

definitely-typed-typescript-definitions-installed

Once you click on Close, you’ll end up with a Folder structure for Script as follows

typescript-definitions-added

The typings folder has one sub-folder for each library that contains the .ds file for the library’s type definition encapsulated.

Finally we install KnockoutJS using Nuget Package Management Console

PM> install-package knockoutjs

With this, we are all set with the required dependencies, let’s now start with our application.

Note: Installing the Type Definitions does not install the original library. For example installing definitions for KnockoutJS didn’t actually install KO. We had to do that separately.

The Task Listing App in TypeScript

We will start with an easy target that is to create a Task listing app in TypeScript. To make things even simpler, we’ll implement the List or Index page only for the moment and pass some hardcoded values from the controller.

The Task Entity

In the Models folder, we add a Task class with the following definition:

public class TaskDetails
{
public int Id { get; set; }
public string Title { get; set; }
public string Details { get; set; }
public DateTime Starts { get; set; }
public DateTime Ends { get; set; }
}

Build the application.

The Tasks List View

To add a View for the Tasks, we right click on the Views\Home folder and select Scaffold. From the available scaffolding options, we select ‘MVC 5 View – List’ as shown below. This generates the markup for a tabular UI.

tasks-view

In the options of adding the view, we select the Model Class and mark it as a partial view.

tasks-view-setup

Once we click Add, a strongly typed view is scaffolded for us.

Updating the Controller to return dummy data for Tasks

Next we add an Action method that returns some dummy data for us:

public ActionResult Tasks()
{
List<TaskDetails> tasks = new List<TaskDetails>();
for (int i = 0; i < 10; i++)
{
  TaskDetails newTask = new TaskDetails
  {
   Id = i,
   Title = "Task " + (i + 1),
   Details = "Task Details " + (i + 1),
   Starts = DateTime.Now,
   Ends = DateTime.Now.AddDays(i + 1)
  };
  tasks.Add(newTask);
}
return View(tasks);
}

As we can see above, the code return 10 Tasks with some sample data generated on the fly. Running the app and navigating to the /Home/Tasks view, we get the following:

tasks-view-server-side

This is our Tasks view rendered on the server side. Lets see what it takes to render it on the client side by building our ViewModel out in TypeScript.

Adding reference to the TypeScript code

In the Tasks.cshtml file, add the following snippet at the bottom of the page

@section Scripts{
<script src="~/Scripts/knockout-2.3.0.js"></script>
<script src="~/Scripts/typescript-list.js"></script>
}

The first script reference adds KnockoutJS to our project. But the second dependency is actually pointing to a JS file that doesn’t really exist. Actually we have typescript-list.ts file instead. Well, the Build task that got added to our Project when we added the first TypeScript file, will ensure that there is a corresponding JS file after a successful build.

Just for confirmation, if you run the application again and navigate to /Home/Tasks; in Visual Studio’s Solution explorer you’ll see a folder structure like the following (note the ts file linked to the generated js file).

typescript-to-js

Setting up our Knockout ViewModel using TypeScript

With our dependencies in place, lets write our first bit of TypeScript.

Referencing other TypeScript Definitions

First thing to do is reference jQuery and Knockout’s definitions. The syntax for that is as follows:

///<reference path="typings/jquery/jquery.d.ts" />
///<reference path="typings/knockout/knockout.d.ts" />

Next we create a Class called Task Details and declare the properties of the requisite types

class TaskDetails {
    id: KnockoutObservable<number>;
    title: KnockoutObservable<string>;
    details: KnockoutObservable<string>;
    starts: KnockoutObservable<string>;
    ends: KnockoutObservable<string>;
// … more to come
}

As we can see, we have declared each property as KnockoutObservable with their respective types. The KnockoutObservable<T> generic Type definition is provided via the knockout.d.ts Type description file. Key thing to note here is that we have only defined the variables NOT instantiated them.

To instantiate them, we use the constructor function as per TypeScript syntax as follows:

class TaskDetails
{
// … variable declarations

constructor(id: number, title: string, details: string,
  starts: string, ends: string) {
  this.id = ko.observable(id);
  this.title = ko.observable(title);
  this.details = ko.observable(details);
  this.starts = ko.observable(starts);
  this.ends = ko.observable(ends);
}
}

Note, while instantiating the properties, we are using base KO types here with values passed in to the constructor. Now our View Model contains an array of TaskDetails, so lets setup the ViewModel class.

class TaskViewModel {
public tasks: KnockoutObservableArray<TaskDetails>;
  constructor() {
   this.tasks = ko.observableArray([]);
}
}

We have declared a TaskViewModel class with one public property task. The type for the property is a KnockoutObservableArray<TaskDetails> that we instantiate in the constructor. Notice how the tasks list is strongly typed so if we try to shove in any random object like we can in JavaScript, we’ll get compile time errors.

Saving Strongly Typed Data as JSON and retrieving it on the Client Side

Traditionally I’ve used an MVC Action Result to return an Empty View and then do an AJAX GET once the document is loaded. This gives us options to load banners, progress bars etc if required. Today I’ll take a different approach and stuff the JSON serialized model data in a Hidden Input field. Then, once the document is loaded, I’ll retrieve it and build a view Model out of it.

To save the data in Hidden Input field, we add the following markup in the Tasks.cshtml

<input type="hidden" id="serverJSON"  
value="@Newtonsoft.Json.JsonConvert.SerializeObject(Model)" />

The reason why this works is, as we know, all Razor syntax is evaluated on the server. So the Server uses Newtonsoft Json to serialize the entire Model into JSON and stick it in the Hidden Input field. This method apparently saves one round trip, but if your ViewModel is large, it can result in a rather large page size for initial load.

Now that the data is with us, let’s see how we can use it.

- We first create a serverData object of type any[]. Declaring it as an array gives us minimum Intellisense.

- Next we use the native JSON.parse to convert the string in the serverJSON field to a JS object array. This is the only ‘weakly typed’ object that we have in our TypeScript code. If we were to use Knockout Mapping we could have skipped this step, but we’ll get to KO Mapping another day.

- Next we declare a local variable vm of type TaskViewModel and instantiate it.

- The for loop essentially loops through the data we got from the server and creates strongly typed TaskDetails objects and adds them to the vm.tasks observable array.

- Once the View Model is ready, we use ko.applyBindings to assign the ViewModel to the View.

The complete TypeScript source for this is as follows:

$(document).ready(function () {
var serverData: any[];
serverData = JSON.parse($("#serverJSON").val());
var vm: TaskViewModel;
vm = new TaskViewModel();
var i: number;
 
for (i = 0; i < serverData.length; i++) {
  var serverTask: any;
  serverTask = serverData[i];
  vm.tasks.push(new TaskDetails(serverTask.Id, serverTask.Title, 
   serverTask.Details, serverTask.Starts, serverTask.Ends));
}
ko.applyBindings(vm);
});

It is worth stressing here that if we were to try and push any object into the vm.tasks array, we would get an Error as follows:

strongly-typed-task

Updating View to use client side bindings

We remove the entire server side markup and replace it with the following:

<table class="table">
<tr>
  <th>Title</th>
  <th>Details</th>
  <th>Starts</th>
  <th>Ends</th>
  <th></th>
</tr>
<tbody data-bind="foreach: tasks">
  <tr>
   <td data-bind="text: title"></td>
   <td data-bind="text: details"></td>
   <td data-bind="text: starts"></td>
   <td data-bind="text: ends"></td>
   <td></td>
  </tr>
</tbody>
</table>

This is standard KO binding and there is nothing new to it. If we run the application again now, we should get the following view at /Home/Tasks

default-client-side-view

Our dates look a little ugly because of the default date to string conversion. We can clean that up with help of moment.js. (On a side note, read about Using jQuery and Moment.js in ASP.NET MVC to do Custom Unobtrusive Date Comparison and Validation).

So we add the Moment JS using Nuget. Note we could potentially add Moment’s TypeScript definitions as well, if we were going to do more complex operations. Since we are going to use it for formatting only, I’ll use Moment directly.

add-moment-js-typescript-defs

Back in the TaskDetails class, we update the constructor to use Moment as follows:

constructor(id: number, title: string, details: string,
starts: string, ends: string) {
this.id = ko.observable(id);
this.title = ko.observable(title);
this.details = ko.observable(details);
this.starts = ko.observable(moment(starts).format("MMM DD, YYYY  h:mm:ss a"));
this.ends = ko.observable(moment(ends).format("MMM DD, YYYY  h:mm:ss a"));
}

Now if we run the application, we’ll see our view comes up nicely:

final-view-using-moment

Summarizing our TypeScript code

Before we can conclude the article, let’s see what was the JavaScript that our TypeScript code compiled down to:

generated-javascript

At runtime we opened the typescript-list.js file and we got the script above. As we can see, this is very close to what we would have written if we were writing the ViewModel ourselves. However I must admit it was much easier to think in terms of a ‘TaskDetails’ entity class and a ‘TaskViewModel’ especially if you are coming from a statically typed language like C# or Java to a more dynamic language like JavaScript. TypeScript provides a nice and easy bridge.

Conclusion

To conclude, we saw the following features of TypeScript in action

- Statically typed Classes

- Generic types

- Typed properties

- Knockout’s TypeScript descriptions

The key reasons for using TypeScript is that it offers better structuring of code while building large-scale JavaScript applications. I must admit that it was easier for me to think in terms of classes than functions which resulted in a clean implementation straight off the bat. However we have barely scratched the surface of TypeScript. In future we’ll look at building much larger applications using TypeScript and leverage more language features like Lambda expressions, Modules etc.

Download the entire source code of this article (Github)

Give a +1 to this article if you think it was well written. Thanks!
Recommended Articles
Sumit is a .NET consultant and has been working on Microsoft Technologies since his college days. He edits, he codes and he manages content when at work. C# is his first love, but he is often seen flirting with Java and Objective C. You can follow him on twitter at @sumitkm or email him at sumitkm [at] gmail


Page copy protected against web site content infringement by Copyscape


User Feedback
Comment posted by Avenall Earle on Saturday, September 7, 2013 10:42 PM
This is how hooked I am to your site until the next article comes up :OO) http://cdn.toonvectors.com/images/35/12926/toonvectors-12926-940.jpg
Comment posted by charles raj on Thursday, September 26, 2013 12:00 PM
I cant able to find MVC5 - view List in Scaffold dialogue box... How can i add it?.. suggestion pls
Comment posted by Pat Tormey on Friday, October 25, 2013 5:44 PM
The RTM is VS 2013 is a  little different.. Select New Scaffold item. Then the list option for scaffolding.. The dialog lets you select the TasksDetail and the context (Application)
Comment posted by Pat To on Friday, October 25, 2013 6:02 PM
Note: TS complied on Build but I did not see the resulting JS file until I showed all files in VS 2013
Comment posted by Pat Tormey on Friday, October 25, 2013 6:54 PM
Note:If you don't use moment typing you'll see a red underline indicating that moment isn't defined. This article actual shows that moment is referenced, but the download code from github does not reference it..
Comment posted by Trong Phan on Tuesday, February 18, 2014 12:20 PM
The source code seem not contain all things you describe in the article.
Can you have the one with ko.mapping stuff.

Thanks,
Comment posted by jitendra on Tuesday, May 27, 2014 1:10 AM
Hi
can you help me i h ave download your project code but when i am running in my local p.c so typeScript is not working so please let me know what i d
please please please very urgent

Post your comment
Name:  
E-mail: (Will not be displayed)
Comment:
Insert Cancel