The Practical Client

Calling Web Services with TypeScript

Peter starts integrating a TypeScript client-side object with a server-side Web API service. Along the way, he looks at method overloading (not good), making JSON calls (good), testing asynchronous methods in Visual Studio (mostly good) and being a "TypeScript programmer."

In my last column, Building and Testing a Web API Service, I started building the typical Web API services that client-side code will interact with. This column builds out (and tests) the TypeScript client-side code for calling those Web API services. Before I began this column, I took the opportunity to upgrade all of my NuGet packages, picking up TypeScript 0.9.1 on the way.

What I want in my client is a single TypeScript class that holds both the code to drive my Web page and the code to interact with the Web Services that provide the page's content: a ViewModel, in other words. That ViewModel needs methods that I can bind to events fired by my page's objects and properties to hold the data that my page will display and update.

I begin by giving my ViewModel a method to retrieve all the customers from my Web Service so that I can display them in a dropdown list. I also need a method to retrieve an individual Customer from the Web Service and a method to refresh the current Customer's data.

To support retrieving individual customers, I considered creating two overloaded methods: one version of the method would accept no parameters and would refresh the current Customer; the other version would accept a customer ID and fetch that customer. However, TypeScript doesn't really support method overloading (at least, not yet). TypeScript does let you define two method signatures with the same name but different parameter lists. However, to support those two different signatures, you write a single method and add some tests to the top of the method that check the parameters to see which signature was used to call the method. To my mind, this isn't really method overloading at all, so I simply gave my two methods different names and had one call the other.

The signatures for the three methods (and the code for one) in my ViewModel look like this:

fetchAllCustomers(){}

fetchCurrentCustomer() 
  {this.fetchCustomerById(this.cust.getId());}

fetchCustomerById(id: number) {}

Being a TypeScript Programmer
One of the things important to me as a programmer is to be a programmer in whatever language I'm working in. Every language has its own special way of doing things, and I want to honor that. I'd like to write C# like a C# programmer, and write Visual Basic like a Visual Basic programmer. I want the same to be true of my TypeScript code.

For instance, I first wrote my MVVM's constructor to accept two optional parameters and use those parameters to update two internal fields. One parameter (custs) holds my collection of Customer objects, while the other (cust) holds the current customer object (the Customer object that the page is using). My original code looked like this:

export class CustomerVM implements ICustomerVM 
{
  cust: AdventureWorksEntities.ICustomer;
  custs: AdventureWorksEntities.ICustomer[];

  constructor(cust: AdventureWorksEntities.ICustomer = null,
              custs: AdventureWorksEntities.ICustomer[] = [])
  {
    this.cust = cust;
    this.custs = custs;
  }

However, TypeScript provides a shortcut that does the same thing with less code. If I add a scope declaration (private or public) to my constructor's parameters, TypeScript automatically creates corresponding fields and updates them with the values from the parameters. Rewriting my constructor to look like it was written by a TypeScript programmer gives this:

export class CustomerVM implements ICustomerVM 
{
    constructor(private cust: AdventureWorksEntities.ICustomer = null,
                private custs: AdventureWorksEntities.ICustomer[] = [])
{}

When I start calling my Web Services, I want to do call them the "TypeScript way" also.

Calling a Web Service the TypeScript Way
Outside of TypeScript, when you use jQuery's getJSON method, you pass a callback function that's to be invoked when the Web Service returns a value. For my first method, I want to use that returned value to update my custs field with the list of Customer objects retrieved from the server. Unfortunately, the "this" keyword inside that callback function doesn't refer to the ViewModel. This JavaScript code, for instance, doesn't update my custs field, but instead updates a variable local to the callback function:

$.getJSON("http://localhost:49306/CustomerManagement",
          function (custs) 
 		{this.custs = custs;});

(A side note: If I rewrite this code into TypeScript, the TypeScript compiler doesn't deal with "this" as I'd expect. My expectation is that the custs in this.custs would be flagged as an undeclared variable but, as of TypeScript 9.1, the compiler doesn't do that.)

In JavaScript, the standard way of accessing fields from within the callback function is to capture the "this" reference outside of the function (conventionally, in a variable called "self") and use that variable inside the function. In TypeScript, the equivalent code looks like this:

var self: CustomerVM;
self = this;
$.getJSON("http://localhost:49306/CustomerManagement",
          function (custs) {self.setCustomers(custs);});

However, that's "TypeScript written by a JavaScript programmer." In TypeScript, you can use a lambda expression to accept the result returned by the Web Service and, in the lambda expression, the "this" keyword will refer to my ViewModel. Like the callback function, my lambda expression is passed the value returned from the Web Service, so rewriting my code the "TypeScript way" gives me something like this:

$.getJSON("http://localhost:49306/CustomerManagement",
     cs => {this.custs = cs;});

I'm still not writing like a TypeScript programmer, however: this code isn't type safe because that cs variable isn't datatyped. I'd prefer to datatype that variable as a collection of the Customer objects (that is, after all, what my service is returning). I can't, unfortunately, reference a .NET object from a TypeScript program, so I have to declare a TypeScript interface that mimics my server-side Customer object. That interface, which I declare in a separate TypeScript module, looks like this:

module AdventureWorksEntities 
{
  export interface ICustomer 
  {
    getId(): number;
    getFirstName(): string;
    getLastName(): string;
    ...

With that client-side interface in place, I can rewrite my call to the Web Service to use that interface to type the result as a collection of Customer objects:

fetchAllCustomers()
{
  $.getJSON("http://localhost:49306/CustomerManagement",
            cs => {
                    this.custs = <AdventureWorksEntities.ICustomer[]>cs;
            });
}

Another method on my ViewModel exposes the collection to the outside world:

getCustomers(): AdventureWorksEntities.ICustomer[]
  {return this.custs;}

The corresponding methods for retrieving and exposing an individual customer look like this:

fetchCustomerById(id: number) {            
  $.getJSON("http://localhost:49306/CustomerManagement/" + id.toString(),
            c => {
                    this.cust = <AdventureWorksEntities.ICustomer>c;
            });
}

fetchCurrentCustomer() 
 {this.fetchCustomerById(this.cust.getId());}

getCustomer(): AdventureWorksEntities.ICustomer
  {return this.cust;}
Testing Asynchronous TypeScript in Visual Studio

Now, of course, I want to test this. I've been using QUnit to write my tests, and the Visual Studio extension Chutzpah to run those tests. However, getJSON calls are asynchronous: if I call my fetchAllCustomers method and then immediately check my getCustomers method, I'm going to be disappointed as it's unlikely that the Web Service will have returned the result in the time it takes my test to move from one method to the other.

QUnit provides the asyncTest function to deal with this. The asyncTest accepts three parameters: a name for your test, a count of the number of assertions (ok functions) you'll be making, and the function you write to test your code. Normally you'll also include a call to a stop function, but when working with Chutzpah (and outside of a Web browser), I don't need that. Instead, I just put the code to call my Web Service inside the function I pass to asynTest

After making my call to the Web Service, I do need to call the setTimeout function to cause my test to pause until the Web Service has had a chance to return my call. The setTimeout function takes two parameters: the test function and the number of milliseconds to wait before processing (two seconds seemed to work for me). In my test function, I include my assertions and, following them, a call to the start function. My whole test, with the references to the required libraries, looks like this:

/// <chutzpah_reference path="Scripts/jquery-2.0.2.js" />
/// <reference path="../PJSalesOrder/Scripts/SalesOrderMvvm.ts" />
/// <reference path="Scripts/typings/qunit/qunit.d.ts" />

asyncTest('get all customers async', 
          1, 
          function () 
          {
    var custVM: SalesOrderMvvm.CustomerVM;
            custVM = new SalesOrderMvvm.CustomerVM();
            custVM.fetchAllCustomers();
        
           setTimeout(function () 
           {
             ok(custVM.getCustomers().length > 0, 
                "Not getting all customers");    
             start();
           }, 
           2000);
    })

Before running my tests, I open my Web Services project in another copy of Visual Studio and press F5 to load the services and make them available to be called from my test code. I run my tests from Visual Studio 2012's Test Explorer and (eventually) get all of them to pass -- none of my code ever works the first time. I had some problems, of course (it's still beta code). For some reason, while I could retrieve an individual Customer object, I would time out while reading properties on that Customer object. When I tried the code in a Web page it worked fine, though, so I've just moved on.

Next month, I finally move on to integrating these TypeScript libraries into an actual Web page (at that point, I'll have to remember to use the .js files generated from my TypeScript code). I'll see then if this all makes sense.

About the Author

Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.

comments powered by Disqus

Featured

  • Compare New GitHub Copilot Free Plan for Visual Studio/VS Code to Paid Plans

    The free plan restricts the number of completions, chat requests and access to AI models, being suitable for occasional users and small projects.

  • Diving Deep into .NET MAUI

    Ever since someone figured out that fiddling bits results in source code, developers have sought one codebase for all types of apps on all platforms, with Microsoft's latest attempt to further that effort being .NET MAUI.

  • Copilot AI Boosts Abound in New VS Code v1.96

    Microsoft improved on its new "Copilot Edit" functionality in the latest release of Visual Studio Code, v1.96, its open-source based code editor that has become the most popular in the world according to many surveys.

  • AdaBoost Regression Using C#

    Dr. James McCaffrey from Microsoft Research presents a complete end-to-end demonstration of the AdaBoost.R2 algorithm for regression problems (where the goal is to predict a single numeric value). The implementation follows the original source research paper closely, so you can use it as a guide for customization for specific scenarios.

  • Versioning and Documenting ASP.NET Core Services

    Building an API with ASP.NET Core is only half the job. If your API is going to live more than one release cycle, you're going to need to version it. If you have other people building clients for it, you're going to need to document it.

Subscribe on YouTube