Practical TypeScript

Integrating the Client and the Server with TypeScript

Peter walks through a simple Web page that retrieves and updates data on the server to summarize his best practices for creating the client-side portion of an ASP.NET application.

As I write this, the TypeScript team has published its roadmap on how it will get from the current version of TypeScript to version 1.0. Presumably, that means a stable, production-ready release of TypeScript is in sight. It seems like a good time, therefore, to sum up building client-side applications using TypeScript.

When I started this column back in April 2013, I wanted to ensure that I could, in TypeScript, do all the necessary things required by client-side applications, including leveraging a variety of JavaScript libraries. I wanted to answer the question, "Do I want to use TypeScript?"

Well, I do. Even in beta, I like the IntelliSense support, and I like having my code checked at compile time rather than run time. In Visual Studio 2013, I like that I can debug TypeScript code rather than having to switch to the generated JavaScript code (and I also like the way Visual Studio 2013 integrates retrieving TypeScript definition files for JavaScript libraries).

I'm still waiting for Visual Studio to include a test-driven development framework for testing TypeScript outside the browser. While I wait on that integration, Chutzpah and Qunit provide a testing environment for much of what I want to build. I wish that the TypeScript support in the applications I build for my clients who want me to program in Visual Basic was as slick at it is in my C# projects (that will come with version 1.0, I assume). I also wish that I had a girlfriend named "Lola." But, in the meantime, I'm happy enough with what I've got.

On the Server
With Entity Framework (EF) 5, the data-access support for my application consists of an entity class that maps to a table in my database, and a DbContext object to handle the conversion from rows to objects:

 public class Customer
   public int Id {get; set;}
   public string FirstName {get; set;}
   public string LastName { get; set; }
   public string IsActive { get; set; }
   public int CustomerType { get; set; }

public partial class SalesOrderContext:DbContext
  public DbSet<Customer> Customers {get; set;}

This is boring, repetitious code to write, especially for the entity class, and the EF team has traditionally supplied a tool for generating this code (and, where that tool isn't available, you can just use EF's database-first designer to do the same thing). Typically, I end up modifying some of this tool-generated code to create more complex relations than "one entity = one table," so the tool's output isn't necessarily my final code. It's still a great starting point.

I've also started using the Breeze JavaScript library for managing my client-side objects, and will be integrating Breeze into future applications (and for all of you reading this and already using Breeze…well, yeah, I'm late to the party). To use Breeze, I create a Web API controller that makes my DbContext available to Breeze. I need to add three methods to the controller: one method to return my Customer entity objects, another method to handle updates and a third method to return the metadata that Breeze needs. I also need a constructor that wraps my DbContext object in Breeze's provider.

Listing 1 shows a sample controller with all of that code. In the method that returns Customer objects, I've added a restriction that limits the results to active customers and sorts the results by Customer name. Any query that Breeze makes to this method will be merged with this server-side query, which means, for example, that my sorting will be done by the database engine (almost always the best tool for that job).

Listing 1: All the Server-Side Code You'll Need for REST
public class CustomerManagementController : ApiController
 EFContextProvider<SalesOrderContext> cm;

 public CustomerManagementController()
   cm = new EFContextProvider<SalesOrderContext>();

 public string Metadata()
   return cm.Metadata();

 public IQueryable<Customer> Customers()
    return from cust in cm.Context.Customers
           where cust.IsActive == "T"
           orderby cust.LastName, cust.FirstName
           select cust; 

 public SaveResult SaveChanges(JObject saveBundle)
   return cm.SaveChanges(saveBundle);

Adding Breeze to my project also adds a routing rule (beginning with the string "breeze") that directs clients to my Breeze controller.

I'm not a completely RESTful kind of guy, so I'll no doubt be adding transaction-oriented operations to this Web API service. But, out of the box, I can do a lot with this service.

ViewModel on the Client
On the client, I need to define a TypeScript interface to use with my server-side entity objects. This is the one for my Customer entity (I put it in a file called SalesOrderEntities.ts):

module SalesOrderEntities 
  export interface ICustomer 
    Id: number;
    FirstName: string;
    LastName: string;
    IsActive: string;
    CustomerType: number;

I'm currently shopping around for a Visual Studio add-in that, when aimed at an entity class (or classes), will generate a TypeScript interface for me.

I use Knockout to create a class that exposes two things: methods that retrieve objects from my server, and properties to hold those objects once I retrieve them -- a ViewModel, in other words.

At the start of my ViewModel code (in a file called SalesOrderVM.ts), I put the reference tags that give my TypeScript code access to the definition files for Breeze and Knockout, along with a reference to the file holding my interfaces (these aren't required in Visual Studio 2013). To save some typing, I use TypeScript's import statement to provide me with shorthand versions for Breeze's namespace and the namespace that I declared my Customer interface in:

/// <reference path="../typings/breeze/breeze-1.2.d.ts" />
/// <reference path="../typings/knockout/knockout.d.ts" />
/// <reference path="SalesOrderEntities.ts" />

module SalesOrderMvvm
  import b = breeze;
  import ent = SalesOrderEntities;

I need two properties on my ViewModel, both defined as KnockoutObservables: one to hold the collection of Customer objects (I called that property "customers") and one to hold the current Customer object (a property I called "cust"). Thanks to TypeScript, I can specify data types for all of these items and initialize them just by setting them up as optional parameters to my ViewModel's constructor. My constructor also initializes a variable to hold the Breeze client-side EntityManager that keeps track of my client-side objects and handles retrieving Customer objects from my Web API service:

export class CustomerVM 
     public customers: KnockoutObservableArray<ent.ICustomer> =
     public customer: KnockoutObservable<ent.ICustomer> = 
      private em: b.EntityManager = 
            new b.EntityManager("http://localhost:49306/breeze/customermanagement"))
        {    }

I could set up additional properties on my ViewModel for each of the properties on my Customer object. So far, I haven't needed to, and I'm hoping to continue to avoid that. As it is, I have to keep my client-side interface synchronized with my server-side entity class as I add and remove columns from the Customer table; I don't want to have to keep my ViewModel in sync, also.

Now I need a method to fetch Customer objects from my service. In a previous column, I constructed a Breeze query and then passed it to the Breeze EntityManager for processing. Breeze's fluent API includes a method called "using" that lets me pass the EntityManager to the query in the same statement that creates the query. Taking advantage of that method, the code that retrieves all the Customer objects from the server and then stuffs them into my ViewModel's "customers" property looks like this:

.then(dt => { this.customers(dt.results); });      

The method for saving changes to the objects under Breeze's control is even simpler, since all I have to do is call the saveChanges method on Breeze's EntityManager (Breeze tracks changes to the objects it retrieves):


My final step is to make my ViewModel known to Knockout. To do that, I add one more TypeScript file to the project (UIIntegration.ts) with code to instantiate my ViewModel, and pass it to Knockout when the page starts up. That's one line of code in a jQuery ready function (along with the necessary references to TypeScript definition files and my code):

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

$(function () {
    ko.applyBindings(new SalesOrderMvvm.CustomerVM());   

Tying to the UI
Of course, for this code to work in the browser, my HTML page needs script references to the JavaScript libraries I'm using and the JavaScript files generated from my TypeScript code:

<script src="Scripts/jquery-2.0.3.js"></script>
<script src="Scripts/knockout-2.3.0.js"></script>
<script src="Scripts/q.min.js"></script>
<script src="Scripts/breeze.min.js"></script>
<script src="Scripts/Application/SalesOrderEntities.js"></script>
<script src="Scripts/Application/SalesOrderMvvm.js"></script>
<script src="Scripts/Application/UIIntegration.js"></script>

Knockout lets me declaratively bind parts of my ViewModel to HTML elements in my page by adding an attribute called data-bind to the elements. To bind a ViewModel method to an event fired by an element in the page, I use Knockout's event binding, passing two things: the name of the element's event and the name of a method on my ViewModel. This example tells Knockout to bind the click event on two buttons to my ViewModel's fetchAllCustomers and saveChanges methods:

<input type="button" value="Get Customers"
       data-bind="event: { click: fetchAllCustomers }"/>
<input type="button" value="Save Changes"
       data-bind="event: { click: saveChanges }"/>

Knockout supports more complex declarative bindings and allows you to mix in procedural code. The following example, for instance, does many things to a dropdown list:

<select id='CustomerList' 
        data-bind="options: customers,
                   value: customer,
                   optionsCaption: 'Select a Customer',
                      function (cust) { return cust.FirstName() + ' ' + cust.LastName() }"/>
  • Generates entries in the dropdown list from my ViewModel's customers property (the options binding).
  • Specifies that the currently selected item should be bound to my ViewModel's customer property (the value binding).
  • Sets a caption to be displayed in the list when nothing is selected (optionsCaption binding).
  • Specifies that the text to be displayed in the list is to be generated by a function (optionsText binding).

One reason I like Knockout is that it's primarily a declarative system: Specify what binding you want, set the parameters and it all works -- or should. There are problems with declarative systems, of course. You're limited to what the framework allows you to declare (as opposed to procedural code, where you can probably program around any problem, eventually). Debugging is often impossible in declarative programming. If you get the syntax right, everything works; get the syntax wrong and nothing works (compile time error messages are also often non-existent). And, of course, every declarative system has its own special syntax that you need to become familiar with. Still, I'm coming to value declarative programming, and Knockout is a great example of how it works. (For more about Knockout, see Kelly Adams and Mark Michaelis' two-part introduction.) I need another binding to display data from the currently selected Customer and let the user update it. To bind a textbox, I use Knockout's value binding, this time passing one thing: the name of the ViewModel property holding the currently selected Customer object with the property on that Customer object that I want to bind to. However, since there will be times when no Customer is selected and that property is set to null, I actually pass Knockout a conditional statement that checks to see if there's anything in my customer property, and displays a message if there isn't:

<input id='CustId' type="text" 
       data-bind="value: customer() ? customer().LastName : 'Not selected'"/>

And, with that, the user can now retrieve server-side entities with a button click, select a Customer from a dropdown list and update the Customer's last name from a textbox.

It's Still in Beta
If you've been following along in this series, I made some changes to my environment for this column, though I still continue to work in Visual Studio 2012. I updated all my NuGet packages, because Visual Studio started whining about my project using incompatible packages (I also applied Update 3 for Visual Studio 2012). Along the way, NuGet wanted to upgrade me to EF 6, but at the time, no compatible version of Breeze existed for EF 6 (there is a compatible version now). In the meantime, I stuck with EF 5 for this sample. Even with the upgrades, I still had TypeScript teething problems. My computer's memory would gradually fill up with instances of tsc*32.exe (the TypeScript compiler, I assume) and something called conhost.exe. I'd eventually have to terminate those processes in Task Manager or reboot my machine. TypeScript 0.9.5 seems to have addressed these issues.

While poking around in the definition file for Breeze, I noticed that it contained a reference to the TypeScript definition file for Q (Q is a JavaScript library that Breeze depends on for promise-based, asynchronous programming). That reference assumed that the Q definition file was in the same folder as the Breeze definition file. That's not the case in my project (the Q definition file is in the folder where NuGet put it), so I updated the Breeze reference to point to the Q definition file's actual location in my project. That change meant that I no longer needed a separate reference to the Q definition file in my code; other than that, nothing changed.

This application is trivially simple, so in my next columns, I'll do something more interesting: implement support for adding and deleting customers and then expand it to a master-detail page that displays all the sales orders for a selected customer.

comments powered by Disqus
Upcoming Events

.NET Insight

Sign up for our newsletter.

I agree to this site's Privacy Policy.