Calling Web Services with MS AJAX using Code Vs. Declaratively
I've been playing around with MS AJAX more these days and had some sample code working fine when using MS AJAX (formerly Atlas) controls combined with JavaScript that calls a Web Service. Since I'm quite familiar with JavaScript I initially wrote most of the functionality using code. For example, the following JavaScript routine calls a Web Service that returns customers in a given country and specifies the callback function that should process the returned data:
function GetCustomerByCountry() {
var country = document.getElementById("txtCountry").value;
XmlForAsp.MSAjax.CustomersService.GetCustomersByCountry(country, OnWSRequestComplete);
}
function OnWSRequestComplete(result) {
//debug.dump(result,"Returned Data");
if (result != null) {
var searchResults = $("searchResults");
searchResults.control.set_data(result);
GetMap(result[0].Country);
}
}
While experimenting with the declarative XML Script approach to calling the Web Service (as opposed to using JavaScript for everything) I ran into a problem. I had the following XML Script code defined to handle calling the Web Service and then handling the data within a JavaScript callback function named OnWSRequestComplete (shown above). The code uses the <serviceMethod> element to handle the Web Service call.
<page xmlns:script="http://schemas.microsoft.com/xml-script/2005">
<components>
<textBox id="txtCountry" />
<serviceMethod id="smCustomersByCountry" url="CustomersService.asmx"
methodName="GetCustomersByCountry" completed="OnWSRequestComplete">
<bindings>
<binding dataContext="txtCountry" dataPath="text"
property="parameters" propertyKey="country" />
</bindings>
</serviceMethod>
<button id="btnSubmit">
<click>
<invokeMethod target="smCustomersByCountry" method="invoke" />
</click>
</button>
<listView id="searchResults" cssClass="listView" itemTemplateParentElementId="searchResults_itemTemplateParent">
<layoutTemplate>
<template layoutElement="searchResults_layoutTemplate" />
</layoutTemplate>
<itemTemplate>
<template layoutElement="searchResults_itemTemplate">
<label cssClass="lbl" id="searchResults_layoutTemplate_CustomerID">
<bindings>
<binding dataPath="CustomerID" property="text" />
</bindings>
</label>
<label cssClass="lbl" id="searchResults_layoutTemplate_ContactName">
<bindings>
<binding dataPath="ContactName" property="text" />
</bindings>
</label>
<label cssClass="lbl" id="searchResults_layoutTemplate_CompanyName">
<bindings>
<binding dataPath="CompanyName" property="text" />
</bindings>
</label>
<label cssClass="lbl" id="searchResults_layoutTemplate_Country">
<bindings>
<binding dataPath="Country" property="text" />
</bindings>
</label>
</template>
</itemTemplate>
<emptyTemplate>
<template layoutElement="divNoData"/>
</emptyTemplate>
</listView>
</components>
</page>
When a button is clicked within the page (named btnSubmit), <serviceMethod> is called. When doing this I was able to see that the Web Service was called correctly and the country param was passed as expected using the debugger. However, I got an error on the set_data line within OnWSRequestComplete() that stated "Object does not support this property or method". Looking at the line in the Atlas JavaScript file it appears to be the following section of code causing the error:
if (_data && Sys.INotifyCollectionChanged.isImplementedBy(_data)) {
_data.collectionChanged.add(_dataChangedDelegate); //Line 9641
}
So....I was stumped. To the point that I considered throwing my laptop out the window after struggling with it for awhile (I wouldn't do it....but the thought did enter my mind). The same exact callback function code worked fine when I call the Web Service directly with JavaScript code instead of using the <serviceMethod> element. When I call the service with pure JavaScript (vs. declaratively) the databinding occurs correctly.
Fortunately, Ryan Trudelle-Schwarz (Atlas guru) gave me the answer for this one. It turns out that when <serviceMethod> is used a different callback signature is needed for the JavaScript function than when a Web Service JavaScript proxy is used to call the Web Service. All I had to do was the following to get it working. First the signature had to be changed, and second I had to call get_result() on the data returned from the Web Service. Super easy.......once you know the trick.
function OnWSRequestComplete(sender,eventArgs)
{
var data = sender.get_result();
if (data != null) {
var searchResults = $("searchResults");
searchResults.control.set_data(data);
GetMap(data[0].Country);
}
}
Once I get the samples finished I'll post them here.