Using jQuery and JSON to Interact with a WCF Service via AJAX

Yesterday's post reviewed how to setup a WCF web service to work with JSON from top to bottom. Today I am going to review what needs to be done to use jQuery to work with those WCF end points. If you have not already done so please download the example code so you can really follow along. This demonstration shows a simple web site with a contact form, an admin type page to display a list of contacts and a full contact upon selection. It also contains a page to demonstrate how exceptions can be handled. The exception page will be explained in a future post.

Lets get right to it by opening the ContactUs.js file. At the very top there is a ContactInfo object defined. The members of this object match the members of the corresponding ContactInfo class in the class library we reviewed yesterday. The default value for each member is also define to an empty string.

var

ContactInfo = {
FirstName:

''

,
LastName:

''

,
Address1:

''

,
Address2:

''

,
City:

''

,
State:

''

,
PostalCode:

''

,
Business:

''

,
PhoneNumber:

''

,
EMail:

''

,
Comment:

''


}

The reason I defined this object at the top and on a 'global' scope is because it will be used in multiple functions later and I do not want to repeat the code over and over.

Before I go any further in the file I want to point out the dependant files for this script to actually function. First I decided to use the latest jQuery 1.4 library. It was just released and I felt this would be a great opportunity to test it out. All the code should work with the previous release to my knowledge. I am not doing anything in this script that has not worked with v 1.3.x in the past.

The next important dependency is the JSON2.js library created by Douglas Crockford and modified by Rick Strahl. It makes working with JSON objects a breeze in JavaScript. A quick side-bar here; Internet Explorer 8 and the latest version of FireFox have native JSON serialization built into their JavaScript engines. Unfortunately I do not like to be locked into the browser specific things like that so I rely on the JSON2 library. In the future, once all the relevant browsers have this ability we will probably change, but I bet we will need an abstraction layer even then.

Rick updated the original JSON library create by Crockford to handle odd date formatting issues created by MS AJAX library.

Other jQuery plugins include the BlockIU, to display Please Wait messages, and the validation and masked input. I have review the latter two last year.

I am going to skip around the ContactUs.js file for now, so bear with me. First the Ready function, it defines some basic setup functionality like input masks, table striping and default buttons etc. Below that it defines some behavior to display some stock messages for global jQuery AJAX events.

The ajaxError event handler gives you the ability to 'hook' into the pipeline to define your own actions to take when an exception is thrown by the server or any other instance where there is an Http Status code of 500 generated by the server or a request timeout occurs. For this demonstration I have simply created a DIV to hold these error messages.

$(

"#AJAXError"

).ajaxError(

function

(

event

, request, settings) {
$(

this

).append(

"Error requesting page "

+ (settings.url) ?
settings.url :

''

+

"<br/>http Status Code: "

+
(request.status) ? request.status :

''

+

"<br/>http Status: "


+ (request.statusText) ? request.statusText :

''

);
});

var

ajaxLog = $(

"#ajaxLog"

);

ajaxLog.ajaxStart(

function

(evt, request, settings) {

var

dt =

new

Date();
$(

this

).append(

"<br/><hr/>Starting request... "

+ dt.toLocaleString());
});

ajaxLog.ajaxComplete(

function

() {

var

dt =

new

Date();
$(

this

).append(

'<br/>Triggered ajaxComplete handler.'

+ dt.toLocaleString());
});
ajaxLog.ajaxSuccess(

function

() {

var

dt =

new

Date();
$(

this

).append(

'<br/>Triggered ajaxSuccess handler.'

+ dt.toLocaleString());
});

I also added an 'ajaxlog' DIV to the site's master page. The code above builds a log of when an AJAX request is initiated, completed and successful. This is done with a simple timestamp.

<div id=

'container'

>
<div id=

"AJAXError"

class

=

"Error-Message-Area"

>
</div>
<asp:ContentPlaceHolder ID=

"ContentPlaceHolder1"

runat=

"server"

>
</asp:ContentPlaceHolder>
<br />
<hr />
<div id=

"ajaxLog"

>
</div>
</div>

Now scroll down almost to the bottom of the ContactUs.js file till you find the object definition for serviceProxy. I am going to defer a detailed explanation of how this object functions to Rick Strahl since he has already written about the serviceProxy helper object. I did enhance it a little by allowing you to pass in a set of options and defaults to define how an AJAX call is made.

// *** Call a wrapped object


this

.invoke =

function

(options) {

// Default settings


var

settings = {
serviceMethod:

''

,
data:

null

,
callback:

null

,
error:

null

,
type:

"POST"

,
processData:

false

,
contentType:

"application/json"

,
timeout: 10,

//Not Preferable, but needed for now.


dataType:

"text"

,
bare:

false


};

if

(options) {
$.extend(settings, options);
}

This makes the invoke method more flexible by letting you define custom settings depending on your needs. In my case I realized the application I was working on had some non-optimized methods that were causing timeouts the first time they were run. Instead of wasting time chasing these issues on the client end I decided to extend the timeout setting to 2 minutes (I know super long, but it was needed). Normally I would set this value to 10 seconds or less. So as I add new method calls I set the value appropriately.

Before I go on you may be wondering why Rick chose to create a serviceProxy object instead of just calling the jQuery.ajax method directly. Well by creating an object you only have to create one instance of the object and it can retain common variables during the lifetime of the page. This especially comes in handy when you are calling more than one service on a page.

There are really two members of the serviceProxy object you need to be aware of; the ServiceURL value and the invoke function. The ServiceURL value is the URL to the WCF service you are calling.

var

ContactServiceURL =

"ContactUs.svc/"

;

var

ContactServiceProxy =

new

serviceProxy(ContactServiceURL);

The invoke method, which I showed the top part above, actually makes the AJAX call based on the options you passed to the method. The $.ajax() success option is a callback function that is executed when the service responds to the client. In the case of serviceProxy a function is executed. It does some error checking before it executes the actual callback method you may have defined in your options. It calls the JSON2 parse function to deserialize a JavaScript object and passes the object to your custom callback.

success:

function

(res) {

if

(!settings.callback) {

return

; }

// *** Use json library so we can fix up MS AJAX dates


var

result = JSON2.parse(res);

if

(result.ExceptionDetail) {
OnPageError(result.Message);

return

;
}

// *** Bare message IS result


if

(settings.bare)
{ settings.callback(result);

return

; }

// *** Wrapped message contains top level object node


// *** strip it off


for

(

var

property

in

result) {
settings.callback(result[property]);

break

;
}
}

Below is a code snippet from the example code where the serviceProxy invoke method is called to retrieve a ContactInfo object. Three parameters are set in the call, serviceMethod, data and callback. If you do not need to pass an object to the service, for example GetActiveContacts the data does not need to be set. The serviceMethod value should be set because it is the name of the method being called, otherwise you will not get the response you expect.

ContactServiceProxy.invoke({ serviceMethod: 

"GetContact"

,
data: { request: request },
callback:

function

(response) {
...

//Do Work Here


}

The next key thing I need to discuss is how to deal with the data once you have received it on the client. This is where John Resig's  parseTemplate function comes in real handy. I found it looking around for templating solutions and sort of fell into a chain of three Blog posts by Stephen Walther '> Rick Strahl '> John Resig.
 
http://weblogs.asp.net/dwahlin/archive/2009/04/17/minimize-code-by-using-jquery-and-data-templates.aspx
http://www.west-wind.com/Weblog/posts/509108.aspx
http://ejohn.org/blog/javascript-micro-templating/

The parseTemplate plugin/function accepts one parameter, data. this is your deserialized object. It magically merges the object into a template you define.

dContactDlg.empty().html($(

"#ContactInfoDlg"

).parseTemplate({ contact: response.ContactInfo }));

Where does this template reside? For this demonstration I decided to create script objects in the HTML.  This is a little trick I learned from the above posts. You can even place loops and other scripting conventions in the templates, which is really cook for tabular data (see GetActiveContacts). You just need to place a merge tag for your object and property like this <#=contact.FirstName#> in your template and the parseTemplate function will merge the value into the template and create the string of markup with your values in place.
 

<

script

id

="ContactInfoDlg"

type

="text/html"

>

1:

 

2:

<ol>

3:

<li>

4:

<label id=

"lblFirstName"

>

5:

First Name :

6:

</label>

7:

<#=contact.FirstName#></li>

8:

...

9:

<li>

10:

<label id=

"lblComment"

>

11:

Comment or Question :

12:

</label>

13:

<#=contact.Comment#>

14:

</li>

15:

</ol>

</

script

>



The last thing I want to touch on is error handling. Even though I placed a global $ajaxError handler in the .ready function, I use a custom error handler for each AJAX call. The error setting of the .ajax function is a function that receives three parameters. I followed the example set by Rick again and have an OnPageError event handler function, but often I need to place some clean up code in the handler. In the following example I close the 'Processing'' message I display using the BlockUI plugin. Otherwise it would remain on the page. I can't just assume it was displayed either and close it in the general handler, so I need to deal with it for each call.
 
error: 

function

(xhr, errorMsg, thrown) {
$.unblockUI();
OnPageError(xhr, errorMsg, thrown);
}

I will go into more detail on dealing with AJAX exceptions in my next post. So keep an eye out for that post. I also have posts planned for AJAX debugging techniques and unit testing examples.

Share This Article With Your Friends!