The Practical Client

Managing Client-Side Objects Using TypeScript: It's a Breeze

Once you've delivered server-side objects to your client, you're going to need to manage them. Here's how to integrate a powerful client-side (and server-side) object manager into your application using TypeScript.

In the last few columns, I've been looking at creating an Ajax-enabled application using TypeScript. I've been doing that both as a way of demonstrating how to write TypeScript code and how to use TypeScript in a real-world application. For instance, as part of building this application, I want to create a ViewModel in the client both to simplify my client-side code and to allow me to test my code outside of the browser. That client-side ViewModel holds all the methods for the page it's used in and exposes data as a series of properties. The tool I like for creating the ViewModel is the Knockout JavaScript library, which raises another issue I want to address in these columns: How well does TypeScript work with standard JavaScript libraries?

The code in my ViewModel retrieves data as JSON objects from a Web API controller and exposes those objects in a Knockout array. Here's the TypeScript code that sets up my Knockout array (named customers) in the ViewModel constructors and then, in a method called fetchAllCustomers, retrieves the objects and stores them in that array:

export class CustomerVM 
{       
  constructor(public customers: 
            KnockoutObservableArray<AdventureWorksEntities.ICustomer> 
                                      = ko.observableArray([])) {}
  fetchAllCustomers()
  {
    $.getJSON("http://localhost:49306/CustomerManagement",
            cs => {
                    this.customers = ko.observableArray(this.custs);                    
                  });                  
  }

In my Web page, I have a select tag that's bound to that Knockout array in order to display a dropdown list of the retrieved items:

<select id='CustomerList' 
        data-bind="options: customers, optionsValue: 'Id', 
                  optionsText: function (cust) {
                                return cust.FirstName() + ' ' + cust.LastName()}"/>

And here's where things get interesting: Once the user selects a specific customer from the dropdown list, I want to display all of that customer's information in the page. I could write a function that re-fetches the selected customer from the server, but that would be grossly inefficient; I shouldn't be making another trip to my server (and, from there, on to the database) to get an object that I've already brought down to the client.

I could check the collection for the object, but I don't have a great tool for doing that in JavaScript, other than writing a loop (though I could use jQuery's grep implementation). If I do search the array, I'll have to add some code that checks to see if the array is empty before searching it. If the array is empty or if I don't find the Customer object I'm looking for, I should then fetch the single customer I want from the server and add it to the array.

This is an interesting problem already, but there's more to it. If I'm busy fetching Customers from the server, it's probably because I'm making changes to those objects at the client. After retrieving single Customer objects, I call the fetchAllCustomers method; then, when I fill the array, I want (somehow) to merge the newly fetched Customers with the Customers already added so that I don't lose the changes I've made.

If I then call the fetchAllCustomers method to fill the array, I want (somehow) to merge the newly fetched Customers with the Customers I've made changes to.

Breezing Through the Problem
What I want is a client-side cache manager that will integrate my TypeScript ViewModel, Knockout array and Web API services. I went looking for one and I found Breeze. Breeze will fetch objects from the server into a cache in the browser, based on LINQ-like queries. Breeze also provides a LINQ-like querying tool for checking to see if an item is in the client-side cache. And Breeze tracks changes to objects and manages getting the changes back to the server for processing. Breeze should be perfect...but I have two issues.

First, Breeze expects you to build your server-side controllers a specific way. That's not actually the end of the world, because Breeze's demands aren't that different from the way I'd prefer to build my Web API services. More awkward is Breeze's integration with Entity Framework (EF): It's very, very good. That may not sound like a complaint, but it is for me.

Breeze expects me to retrieve EF entity objects from my services. That means, for example, if I want to display a sales order (which includes data from Customer, Sales Order Header, Sales Order Detail and Product entities), I'm going to have to retrieve many different entities from my server. EF's navigation properties may help here (ideally, I'll only need one trip to the server), but I'd prefer to send a DataTransfer Object with all -- and only -- the data that the client needs.

Breeze's integration with EF also means that I can't test my client-side code without accessing real data. I prefer to test my services by creating dummy, code-first entity objects on the server. You can do that with Breeze (look at its NoDb sample solution), but only in the same sense as "If you're trapped alone at the South Pole, you can remove your own appendix." Among other issues, you have to create your own equivalent of the EF DataContext object using a combination of boiler-plate code and code specific to each object you intend to return.

There are competing products to Breeze (JayData , for example, looks very good). In the end, however, I decided that I could live with what I didn't like about Breeze. And, who knows, maybe I just don't fully understand how to use Breeze (yet).

Getting Started with Breeze
The first step in using Breeze is to add it to your project using Visual Studio's NuGet Package Manager: Search for "Breeze Client and Server," which includes everything you need to use Breeze with Web API services and EF. Since I'm building my client-side code in TypeScript, I also downloaded Breeze's DefinitelyTyped library from NuGet; and because Breeze depends upon Q (a library supporting promise-based asynchronous programming), the DefinitelyTyped library for Q, as well. Note that Q itself is added with the Breeze NuGet package.

I need to add references to the Breeze and Q libraries to the top of the TypeScript file holding my VIEWMODEL. That's easy -- I just drag the DefinitelyTyped files from Solution Explorer and drop them in the editor window for my TypeScript file.

The results look like this:

/// <reference path="../typings/q/Q.d.ts" />
/// <reference path="../typings/breeze/breeze-1.2.d.ts" />

I'm now ready to start writing some Breeze-related client-side code. In the method that I use to get my Customers from the service, I create a Breeze EntityManager, passing the route to my ASP.NET Web API controller. Breeze creates routes to its controllers that begin with the text "breeze," so my code looks like this:

function FetchAllCustomer()
{
  var em: breeze.EntityManager;
  em = new breeze.EntityManager("http://localhost:49862/breeze/customermanagement");

Asking for all my Customer objects is a two-step process. First, I create a Breeze EntityQuery (which looks an awful lot like a LINQ query). This EntityQuery specifies that I want to use the Customers method on my server, sorting the results by the returned objects' LastName property:

var eq: breeze.EntityQuery;
eq = breeze.EntityQuery
     .from("Customers")
     .orderBy("LastName");

To send the query to the server, I use the EntityManager's executeQuery function, passing my EntityQuery (Breeze will convert my query into an OData query before sending it to the server). I use Breeze's then function to specify what to do after the results come back. Because I'm using TypeScript, I can pass a lambda expression to the then function.

A Breeze service returns an object with a results property that holds my entity objects. The following example takes that results property and passes it to the Knockout collection specified earlier:

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

Finally, I write some code to run when my page finishes loading to instantiate my client-side class, call the method that retrieves my objects and tell Knockout to bind everything it can find to the HTML tags on the page:

var custVM: SalesOrderMvvm.CustomerVM;    
$(function () {
    custVM = new SalesOrderMvvm.CustomerVM();
    custVM.fetchAllCustomers();
    ko.applyBindings(custVM);
});

That's all I need on the client. It's now time to look at my Breeze-related server-side code.

Server-Side Breeze
As I mentioned, Breeze expects the ASP.NET Web API controllers to be built a specific way. Breeze disables some of the standard pipeline for processing requests and adds its own handlers in their place (one of the reasons that Breeze segregates its controllers onto their own route). That's easy to implement, fortunately; all you have to do is decorate your controller class with the BreezeController attributes, as I've done here:

 [BreezeController]
public class CustomerManagementController : ApiController
{

Breeze also requires a wrapper class (called EfContextProvider) for any EF DataContext or ObjectContext you're using. This code sets up the variable that will hold that object, specifying that it's to wrap the EF DataContext object I'm using to manage my data (I called my DataContext object SalesOrderContext). I initialize that variable in my controller's constructor:

EFContextProvider<SalesOrderContext> cm;

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

Next, Breeze expects me to add a method (called MetaData) that Breeze calls automatically to retrieve data about my objects. That data is generated by Breeze's EFContextProvider so the method looks like this:

[HttpGet]
public string Metadata()
{
  return cm.Metadata();
}

Finally, I write the method that returns my Customers collection through Breeze's wrapper object. To support OData, the method has to return an IQueryable collection of Customer objects:

[HttpGet]
public IQueryable<Customer> Customers()
{
  return cm.Context.Customers;
}

And, with my client-side and server-side code written, I'm done. Sure enough, when I press <F5>, I get a dropdown list displaying all the customers from my database.

And, if you're wondering why I picked Breeze despite the issues I listed earlier, let me point something out: Because Breeze turns its LINQ-like queries into OData requests, and EF then turns those OData requests into precisely-targeted SQL statements, it's possible that the Customers method in my Web API controller may be the only server-side method I write for retrieving customer data. I find that a compelling argument for using Breeze.

I should point out that I'm still working with the beta version of TypeScript, and I do keep getting reminded of that. At several points, for example, I found that I had six or seven copies of the TypeScript compiler (tsc.exe) running in background and soaking up CPU cycles. But this is why we have Task Manager and the End Process button.

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