With the advent of Microsoft's Windows Communication Foundation (WCF) technology,
creating REST services on the .NET platform has become a very attractive prospect for many developers. WCF's native support for
JSON serialization combined with URI templates for creating resource-based URLs makes creating RESTful web services even more attractive for developers looking to use WCF to build Ajax-based web applications. In fact, the Nimbus project demonstrated at Blackbaud's 2008 Conference for Nonprofits is largely built on WCF web services, along with a JavaScript library that makes it easy to communicate with those web services.
One major hurdle for many developers writing WCF-based Ajax applications (or even writing JavaScript in general) is the lack of familiar tools with which to build these applications. Until recently, Microsoft Visual Studio has not supported JavaScript with conveniences like IntelliSense which .NET developers have become accustomed to. Fortunately Microsoft has done a lot in the way of adding IntelliSense support with
Visual Studio 2008 SP1, and popular JavaScript libraries like
jQuery are
jumping on board to support IntelliSense in Visual Studio.
If you're not familiar with how JavaScript IntelliSense works in Visual Studio, here's a quick overview. JavaScript functions are documented using XML comments, much like XML code comments for Visual Basic.NET and C#. These comments include a summary of the function, the parameters and their types, and a description of what the function returns. JavaScript constructors may contain more information, such as "instance" fields that are accessible off of objects created from the constructor. At design time, Visual Studio reads these comments from within the JavaScript functions and builds the IntelliSense for those functions. Full documentation for how JavaScript XML code comments work can be found
here, which is suggested reading before delving into the rest of this article.
So what does this have to do with WCF? WCF web services work by taking an object on the server and serializing that object to either JSON or XML (we'll only be discussing JSON in this article as it is the format that most easily lends itself to JavaScript programming), then sending that payload down to the client. WCF knows how to turn these objects into JSON by looking at the DataContract and DataMember attributes used to mark up the class. Once a JavaScript method has received a JSON payload, it can be turned into a JavaScript object, either by calling eval() on it or by using any of the popular
JSON parsers available on the web.
When using certain techniques in ASP.NET, such as using an ASP ScriptManager control and ScriptMethods, Visual Studio can generate IntelliSense for the data contract classes. But what if you are building a re-usable library like Nimbus which is designed to be used within any web application, including applications not created with ASP.NET or even hosted on a Windows server? Not only would it be ideal to get IntelliSense on your data contracts while developing your library, but it would also be beneficial to other application developers using your library to get that same experience when building their own applications.
To achieve this, I've created a utility that will generate a JavaScript IntelliSense file from data contract classes. Given a .NET assembly, the utility will find all classes with the DataContract attribute, look at its DataMember properties and generate corresponding JavaScript file, complete with XML comments that include the data type of each property and a description if specified. Here's how it works:
First, make sure your data contract classes are marked up properly. Your data contract class should have a DataContract attribute, and each property to be serialized should have a DataMember attribute. You should also add a System.ComponentModel.Description attribute to the classes and properties so the utility can use the description when creating the JavaScript IntelliSense file.
In this example, I have three data contract classes: Individual, Address, and PhoneNumber. Each Individual has an ID, FirstName, LastName, Gender, MailingAddress, and a collection of PhoneNumbers. Here's an example of how the PhoneNumber class is marked up (Individual and Address have similar attributes):
Imports System.ComponentModel
Imports System.Runtime.Serialization
<DataContract(Name:="phoneNumber")> _
<Description("An individual's phone number.")> _
Public Class PhoneNumber
Private _number As String
<DataMember(Name:="number")> _
<Description("The formatted phone number.")> _
Public Property Number() As String
Get
Return _number
End Get
Set(ByVal value As String)
_number = value
End Set
End Property
End Class
Run the Utility
Once you've written your data contract classes and have compiled your project, it's time to run the utility. Download and run DataContractJSGenerator.exe or compile and run the included source code. You should see this window:

First, browse to the assembly containing your data contracts. Next, choose a JavaScript namespace that will contain your data contract objects (I'll be using BB.contracts for this example). Choosing a namespace will keep your data contract objects from clashing with other global variables. The "Include 'IntelliSense-only' warnings in generated code" option will include a warning in the summary of each object telling the user not to instantiate them, since this file will only be used for Visual Studio IntelliSense and will not be included in the application at runtime. Finally, you may choose to save the file to disk once the JavaScript is generated. Once you've set all your options, click Go. You will see the generated JavaScript in the preview pane, and if you chose to save the file, a file with the .js extension will be created on disk. The JavaScript generated from my data contract classes looks like this:
if (typeof BB === "undefined") {BB = {};}
if (typeof BB.contracts === "undefined") {BB.contracts = {};}
BB.contracts.Address = function () { /// <summary>***WARNING*** For IntellSense only. You should never create an instance of this object from code. ***WARNING***</summary>
/// <field name="line1" type="String">The first line of the individual's address.</field>
/// <field name="line2" type="String">The second line of the individual's address.</field>
/// <field name="city" type="String">The city where the individual resides.</field>
/// <field name="state" type="String">The state where the individual resides.</field>
/// <field name="zipCode" type="String">The ZIP code where the individual resides.</field>
};
BB.contracts.Individual = function () {
/// <summary>***WARNING*** For IntellSense only. You should never create an instance of this object from code. ***WARNING***</summary>
/// <field name="id" type="Object">The ID of the individual's record.</field>
/// <field name="firstName" type="String" mayBeNull="true">The individual's first name.</field>
/// <field name="lastName" type="String" mayBeNull="true">The individual's last name.</field>
/// <field name="gender" type="Number">The individual's gender. Possible values: 0 (Female), 1 (Male), 2 (Unknown).</field>
/// <field name="mailingAddress" type="BB.contracts.Address" mayBeNull="true">The address where the individual should receive all postal mail correspondence.</field>
/// <field name="phoneNumbers" type="Array" elementType="BB.contracts.PhoneNumber">A list of the individual's phone numbers.</field>
};
BB.contracts.PhoneNumber = function () {
/// <summary>***WARNING*** For IntellSense only. You should never create an instance of this object from code. ***WARNING***</summary>
/// <field name="number" type="String">The formatted phone number.</field>
};
Note that there are no fields or methods on these objects; this is because Visual Studio only needs the comments to provide IntelliSense. Also note the data types in some of the field comments. Many are intrinsic JavaScript data types like String and Number, but others actually point to other types created by the utility. The utility can detect when a data member points to another type within the same assembly and will reference that type appropriately in the XML comments. Also note the gender field on the Individual object. The Gender data member in our data contract is an enum type, so the field's description has been built to indicate the epxected values for this field.
Reference the Generated File
In my example web application, I have two JavaScript files: service.js and myapp.js. The first file, service.js, is the file I've written for my library and contains methods for accessing my WCF web service. The second file, myapp.js, is where my application-specific logic resides. Here's what service.js looks like:
if (typeof BB === "undefined") {BB = {};}
BB.Service = function () {
/// <summary>Provides interaction with the client and the web service.</summary>
};
BB.Service.prototype.getIndividual = function (id) {
/// <summary>Returns an individual object for the given ID.</summary>
/// <param name="id" type="Number" integer="true">The ID of the individual's record.</param>
// Send a synchronous request to the web server.
var req = new XMLHttpRequest();
req.open("GET", "http://www.mysite.com/myservice.svc/individuals/" + id.toString() + "/result.json", false);
req.setRequestHeader("Content-type", "application/json");
req.send();
// Parse the response text using the JSON utility and return it.
return JSON.parse(req.responseText);
};
Note that the file contains a type called Service with one function on its prototype called getIndividual(). The getIndividual() function takes an ID and returns an Individual object from the server. I've already added XML comments to indicate everything but the return type, which up until now I couldn't do. Now that I've generated a JavaScript file from my data contracts, however, I can now specify a return type from that generated file:
/// <reference path="contracts.js" />
if (typeof BB === "undefined") {BB = {};}
BB.Service = function () {
/// <summary>Provides interaction with the client and the web service.</summary>
};
BB.Service.prototype.getIndividual = function (id) {
/// <summary>Returns an individual object for the given ID.</summary>
/// <param name="id" type="Number" integer="true">The ID of the individual's record.</param>
/// <returns type="BB.contracts.Individual">The Individual object.</returns>
// Send a synchronous request to the web server.
var req = new XMLHttpRequest();
req.open("GET", "http://www.mysite.com/myservice.svc/individuals/" + id.toString() + "/result.json", false);
req.setRequestHeader("Content-type", "application/json");
req.send();
// Parse the response text using the JSON utility and return it.
return JSON.parse(req.responseText);
};
Here I've added a "returns" XML comment to the getIndividual() function. I've also added a "reference" comment to the top of the file; this is how Visual Studio knows to look for BB.contracts.Individual in my contracts.js file.
Now in myapp.js I have some JavaScript to call the getIndividual method in service.js. Here's what that code looks like:
/// <reference path="service.js" />
var showMailingAddress = function () {
// Create the service.
var svc = new BB.Service();
// Get the individual's record.
var ind = svc.getIndividual(96);
};
In this file, I only need to add a reference XML comment to point to service.js; since service.js references contracts.js, Visual Studio will pick up contracts.js as well and show IntelliSense for that file. In the showMailingAddress() function, the variable "ind" represents an instance of the BB.contracts.Individual type, and since the getIndividual() function indicates this as its return type, I now get IntelliSense on my "ind" variable:
Since the "mailingAddress" property in contracts.js points to BB.contracts.Address as its type, I also get IntelliSense on that field's properties:
Here are a couple of tips that have helped me work with JavaScript IntelliSense:
- Turn on the status bar in Visual Studio. When JavaScript IntelliSense is updated for a file, the status bar shows the status of the update and will notify you if there was a problem generating the IntelliSense. To turn on the status bar, go to Tools > Options, and under the Environment > General section, check on the "Show status bar" option. This will show the status bar at the bottom of the Visual Studio window.
- Visual Studio updates JavaScript IntelliSense when Visual Studio is idle. If you need to force Visual Studio to update the IntelliSense immediately, for instance after you've updated the XML comments in your file, press Ctrl-Alt-J or select the "Update JScript IntelliSense" menu item from the Edit > IntelliSense menu.
- Visual Studio only provides IntelliSense from XML comments when the function or field is referenced from another file, meaning if you mark up a function or field in a JavaScript file, you will not see IntelliSense from those XML comments elsewhere in the same document. I've been told that Visual Studio 2010 will address this shortcoming, but for now I've gotten around it by factoring my code into separate JavaScript files and combining them when I build my project (which is a good practice anyway).
There's a lot to learn about JavaScript IntelliSense in Visual Studio, so be sure to read the links mentioned at the beginning of this article. The utility, its source code, and the example data contracts and JavaScript files are available for download in the Related Downloads section at the bottom of this page.