Section 10.4. Consuming External Web Services


10.4. Consuming External Web Services

As mentioned earlier, browser security forbids the XMLHttpRequest object from connecting to any domain but the one on which the current web page resides. Therefore, if you need data from a remote web service (one that is on a different server), there is only one solution: create a server proxy on your server and then call this proxy from your JavaScript code.

The good news: Atlas comes with built-in support for proxying such web services calls, namely a technology referred to as a web service bridge. In the following sections, we will create pages that get data from two of the most popular commercial web services: the Google search web service and the Amazon e-commerce web service. The techniques shown here can easily be adapted to any other SOAP web service.

The secret behind this lies in a new file extension that the Atlas installer prompted you to register with IIS (see Chapter 1): .asbx. Files with this extension can contain XML markup that provides information about a local (server-based) proxy class for a web service. The web page's JavaScript code just connects with the .asbx file, which then takes care of communication with the remote service. Figure 10-5 shows this mechanism.

Figure 10-5. The client page calls the server bridge, which then calls the remote web service


Manual .asbx Registration

If you cannot run the .msi Atlas installer and register the .asbx extension with your IIS web server, run the IIS management console and map the .asbx file extension to the aspnet_isapi.dll file, allowing the HTTP verbs GET, POST, and HEAD. Also, add the following markup to the web.config file so that the bridge files are recognized:

 <configSections>   <sectionGroup name="microsoft.web" type="Microsoft.Web.Configuration.MicrosoftWebSectionGroup">     ...     <section name="webServices" type="Microsoft.Web.Configuration.WebServicesSection" requirePermission="false"/>   </sectionGroup> </configSection> ... <compilation>   <buildProviders>   ...     <add extension=".asbx" type="Microsoft.Web.Services.BridgeBuildProvider"/>   </buildProviders> </compilation> ... <httpHandlers>   ...   <add verb="*" path="*.asbx" type="Microsoft.Web.Services.ScriptHandlerFactory" validate="false"/> </httpHandlers> ... <httpModules>   ...   <add name="BridgeModule"        type="Microsoft.Web.Services.BridgeModule"/> </httpModules> 

In case you cannot change IIS metabase settings via the IIS console, http://atlas.asp.net/docs/atlas/doc/bridge/tunnel.aspx describes a workaround for that. The following entry in the web.config file redirects all requests to xxxBridge.axd to xxx.asbxfor instance, a call to AmazonBridge.axd would be passed to Amazon.asbx on the web server.

 <httpModules>   <add name="BridgeModule"        type="Microsoft.Web.Services.BridgeModule"/> </httpModules> 

This workaround might be removed in future Atlas versions. However, until then, it is an excellent alternative to get the Atlas web service bridge running on servers to which you do not have administrative access.


10.4.1. Using the Google Web Service

The Google web service provides convenient access to the search engine, using both a SOAP and a REST interface. For our example, we will use the SOAP interface for the Atlas web service bridge.

Using the Google web service requires you to register with Google. To make the request, go to http://www.google.com/apis. Google will send you a 32-byte license key, which you will need to send with every search request to the service.

Of course, it would be a terrible idea to store this (secret!) license key in JavaScript code in the page. Putting the key in the ASP.NET server code is also not recommended. But you can put the license key in the <appSettings> section of the web.config file, like this:

 <appSettings>   <add key="GoogleLicenseKey" value="***" /> </appSettings> 

Obviously, the web.config file available as part of the source code downloads for this chapter does not contain this license key yet; you will have to fill in your own key. You could also use the encryption feature of web.config entries to encrypt your secret API key.

On the Google API web site, the Google Web API Developer's Kit is available for download. It also contains a WSDL description file named GoogleSearch.wsdl that describes the web service interface. The tool wsdl.exe (part of the .NET Framework SDK) can use this WSDL information to generate a proxy class.

After you have downloaded the GoogleSearch.wsdl file or extracted it as part of the Google API SDK, open a Command window and run the following command:

 wsdl.exe /namespace:Google GoogleSearch.wsdl 

To run the wsdl.exe command at the command line, you might have to set a PATH variable to the folder containing the .NET SDK utilities. By default, the utilities are in the folder %windir%\Program Files\Microsoft Visual Studio 8\SDK\v2.0\Bin.


As Figure 10-6 shows, you will get some warnings, but you can safely ignore them for this web service. Also, notice that we provide a namespace for the class to prevent a potential name collision with other classes in our web application.

Figure 10-6. Creating the web service proxy for the Google web service


Put the generated class file, GoogleSearchService.cs, in the App_Code folder of the web application. (If the web site doesn't already have an App_Code folder, create one.) This enables you to use the class without any manual compilation.

In the next step, you have to create a wrapper in server code for the web service proxy, one that calls the search method. Going into great detail about the Google web service API is beyond the scope of this book. But the most important information is that the web service exposes a doGoogleSearch() method, which accepts two parameters: the Google license key and the search string. The wrapper just calls this method and returns the results, as Example 10-5 shows. Create a class file named GoogleSearchServiceWrapper.cs in the App_Code folder, delete any code already in the file, and copy the code from Example 10-5 into it.

Example 10-5. A Google web service wrapper class

 GoogleSearchServiceWrapper.cs using Google; public class GoogleSearchServiceWrapper {   public GoogleSearchResult Search(string licenseKey, string query)   {     GoogleSearchService gss = new GoogleSearchService();     return gss.doGoogleSearch(       licenseKey,       query,       0, // offset of the first result       10, // maximum number of results       false, // whether to filter similar results       "", // subset of Google to restrict search to       false, // whether to filter adult content       "", // language to restrict search to       "", // ignored, as is the next parameter       "");   } } 

Now we can use the Atlas web service bridge. To activate the web service bridge, you have to provide all relevant web service information in an .asbx file. Create an XML file named Google.asbx in the root of your web site.

In the .asbx file, you provide the name of your (custom) namespace where the bridge will reside (namespace attribute) and the name of the class you want to implement with the bridge (className attribute).

 <bridge namespace="OReilly.Atlas" className="Google" > 

The <proxy> element holds the name of the wrapper class and where to find it:

 <proxy type="GoogleSearchServiceWrapper, App_Code" /> 

Then all methods in the web service are listed, including the names of the parameters. All the parameters specified here can be used in JavaScript calls later. However, remember that the required license key is stored in the web.config file. The parameter for the Google license key therefore cannot be set using JavaScript. Instead, you can use the following syntax to load the key at runtime from the <appSettings> section:

 <parameter name="licenseKey" value="% appsettings : GoogleLicenseKey %" serverOnly="true" /> 

The serverOnly="true" syntax makes the licenseKey parameter unavailable for the JavaScript code, so the value for it is always taken from web.config.

That wraps it up. Example 10-6 contains the complete code for the bridge file.

Example 10-6. The web service bridge for the Google web service

 Google.asbx  <?xml version="1.0" encoding="utf-8" ?> <bridge namespace="OReilly.Atlas" className="Google" >   <proxy type="GoogleSearchServiceWrapper, App_Code" />   <method name="Search">     <input>       <parameter name="licenseKey"                  value="% appsettings : GoogleLicenseKey %"                  serverOnly="true" />       <parameter name="query" />     </input>   </method> </bridge> 

Now all that is left to do is to write the Atlas-powered .aspx page. Our page contains a text box for the search query, a button to run the query, and some placeholders to display the results.

The markup might look like the following:

 <div>   <input type="text"  />   <input type="button" value="Search" onclick="Search();" /> </div> <div>   <p>Approx. <span >0</span> results.</p>   <ul >   </ul> </div> 

Of course, the page must contain a ScriptManager control. In its <Services> subelement, the web service is referencednaturally, as an .asbx file!

 <atlas:ScriptManager  runat="server">   <Services>     <atlas:ServiceReference Path="~/Google.asbx" />   </Services> </atlas:ScriptManager> 

This loads the bridge and exposes our OReilly.Atlas namespace to JavaScript. You can then call the Search() method from the web service wrapper as you would call any local web service. Notice how you provide the parametersyou use an array with the parameter names as the indexes:

 OReilly.Atlas.Google.Search(   { "query": query },   callComplete, callTimeout, callError ); 

The return data from the web service is a JavaScript representation of the SOAP objects returned by the server. For a Google search, the return data has a property (or subelement) named resultElements, which contains an array of all individual URLs found by this search. Each of these URLs has, among other things, title and URL properties that we will display in the page.

The complete code in Example 10-7 contains some other nice JavaScript effects: when the results from the web service arrive, they are dynamically added to the selection list (a <ul> HTML element). The clearList() helper function clears that list when a new search is executed. Figure 10-7 shows the result; the search results from Google are visible on the local page, thanks to the Atlas web service bridge (see Figure 10-7).

Example 10-7. Calling the Google web service

 Google.aspx <%@ Page Language="C#" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR /xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server">   <title>Atlas</title>   <script language="JavaScript" type="text/javascript">   function clearList() {     var list = document.getElementById("Results");     while (list.firstChild != null) {       list.removeChild(list.firstChild);     }   }   function Search() {     var query = new Sys.UI.TextBox($('Query'))     document.getElementById("Button").disabled = true;     clearList();     OReilly.Atlas.Google.Search(       { "query": query.get_text() },       callComplete, callTimeout, callError     );     new Sys.UI.Label($('Count')).set_text("...");   }   function callComplete(result)  {     new Sys.UI.Label($('Count')).set_text(result.estimatedTotalResultsCount);     if (result.resultElements != null) {       for (var i = 0; i < result.resultElements.length; i++) {         var page = result.resultElements[i];         var li = document.createElement("li");         var a = document.createElement("a");         a.setAttribute("href", page.URL);         a.innerHTML = page.title;         li.appendChild(a);         document.getElementById("Results").appendChild(li);       }     }     document.getElementById("Button").disabled = false;   }   function callTimeout(result) {     window.alert("Error! " + result.get_message());     new Sys.UI.Label($('Count')).set_text("0");     document.getElementById("Button").disabled = false;   }   function callError(result) {     window.alert("Error! " + result.get_message());     new Sys.UI.Label($('Count')).set_text("0");     document.getElementById("Button").disabled = false;   }   </script> </head> <body>   <form  runat="server">     <atlas:ScriptManager  runat="server">       <Services>         <atlas:ServiceReference Path="~/Google.asbx" />       </Services>     </atlas:ScriptManager>     <div>       <input type="text"  />       <input type="button"  value="Search" onclick="Search();" />     </div>     <div>       <p>Approx. <span >0</span> results.</p>       <ul >       </ul>     </div>   </form> </body> </html> 

Figure 10-7. Searching with the Google API and an Atlas web bridge


10.4.2. Using the Amazon Web Service

The preceding section showed you how to use the Google web service, a rather trivial service with no custom types as parameters and just a simple method that does it all. In this section, we will cover the Amazon web service, which is more complex. It supports several types that together make up a search request. Again, the implementation details of the Amazon web service are of no particular interest, but the way Atlas can use this data is.

Once again you will need a license key (Amazon calls it an access key). As with the Google web service, this requires registration; the URL of the Amazon web service documentation site is http://www.amazon.com/gp/aws/landing.html. As with the Google key, you must put the access key (in the case of Amazon, 20 bytes long) in the <appSettings> section of the web.config file.

The sample file you can download for this book does not contain this key, so you have to put yours in:

 <appSettings>   <add key="AmazonAccessKey" value="***" /> </appSettings> 

The next step is similar to the Google example: use the wsdl.exe tool to create a proxy class from the WSDL description of the Amazon web service. You can get the Amazon WSDL file at http://webservices.amazon.com/AWSECommerceService/AWSECommerceService.wsdl.

Use the following command in a Command window to generate the proxy class AWSECommerceService.cs:

 wsdl.exe /namespace:Amazon http://webservices.amazon.com/AWSECommerceService /AWSECommerceService.wsdl 

Copy the resulting .cs file to your application's App_Code folder.

Implementing the wrapper class is a bit more difficult this time, since the web service uses some custom objects. Create a class file named AWSECommerceServiceWrapper.cs in the site's App_Code folder. In the class, you must instantiate an ItemSearchRequest object where you provide the search term (what to search), the search index (where to search), and the response group (how much data to return):

 public Amazon.Items Search(string accessKey, string query) {   ItemSearchRequest searchRequest = new ItemSearchRequest();   searchRequest.Keywords = query;   searchRequest.ResponseGroup = new string[] { "Small" };   searchRequest.SearchIndex = "Books"; 

The next step is to instantiate an ItemSearch object, providing the Amazon access key and the previously created ItemSearchRequest object:

   ItemSearch search = new ItemSearch();   search.AWSAccessKeyId = accessKey;   search.Request = new ItemSearchRequest[1] { searchRequest }; 

Finally, you instantiate the main class, AWSECommerceService, and call the ItemSearch() method, providing the ItemSearch object as a parameter. The return data is an array of the responses of all search queries sent (it is possible to send multiple queries in one call).

Since we were sending only one query, we expect only one result:

   AWSECommerceService awse = new AWSECommerceService();   ItemSearchResponse searchResponse = awse.ItemSearch(search);   return searchResponse.Items[0]; } 

Example 10-8 has the complete code for AWSECommerceServiceWrapper.cs wrapper class.

Example 10-8. The Amazon web service wrapper class

 AWSECommerceServiceWrapper.cs using Amazon; public class AWSECommerceServiceWrapper {   public Amazon.Items Search(string accessKey, string query)   {     ItemSearchRequest searchRequest = new ItemSearchRequest();     searchRequest.Keywords = query;     searchRequest.ResponseGroup = new string[] { "Small" };     searchRequest.SearchIndex = "Books";     ItemSearch search = new ItemSearch();     search.AWSAccessKeyId = accessKey;     search.Request = new ItemSearchRequest[1] { searchRequest };     AWSECommerceService awse = new AWSECommerceService();     ItemSearchResponse searchResponse = awse.ItemSearch(search);     return searchResponse.Items[0];   } } 

The rest of this Amazon demo application is more or less the same as the Google example. An Amazon.asbx file serves as the bridge to the external web service. The accessKey data is taken from web.config, and the query parameter will come from the client application. Example 10-9 shows you the XML for the Amazon.asbx file.

Example 10-9. The web service bridge for the Amazon web service

 Amazon.asbx <?xml version="1.0" encoding="utf-8" ?> <bridge namespace="OReilly.Atlas" className="Amazon" >   <proxy type="AWSECommerceServiceWrapper, App_Code"  />   <method name="Search">     <input>       <parameter name="accessKey"                  value="% appsettings : AmazonAccessKey %"                  serverOnly="true" />       <parameter name="query" />     </input>   </method> </bridge> 

Not only is sending data to the Amazon web service complicated, getting the data out of it is also complex. The wrapper's return data (which is an array of type Amazon.Item) contains a list of books. Most of the interesting data in this array is put in the ItemAttributes property, another custom object.

Example 10-10 shows an ASP.NET page that contains code to extract the author(s) of all found books along with the book title, and then put the results in a <ul> element. Figure 10-8 shows the result.

Example 10-10. Calling the Amazon web service

 Amazon.aspx <%@ Page Language="C#" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server">   <title>Atlas</title>   <script language="JavaScript" type="text/javascript">   function clearList() {     var list = document.getElementById("Results");     while (list.firstChild != null) {       list.removeChild(list.firstChild);     }   }   function Search() {     var query = new Sys.UI.TextBox($('Query'))     document.getElementById("Button").disabled = true;     clearList();     OReilly.Atlas.Amazon.Search(       { "query": query.get_text() },       callComplete, callTimeout, callError     );     new Sys.UI.Label($('Count')).set_text("...");   }   function callComplete(result)  {     new Sys.UI.Label($('Count')).set_text(result.TotalResults);     if (result.Item != null) {     for (var i = 0; i < result.Item.length; i++) {     var article = result.Item[i];     var author = (article.ItemAttributes.Author != null ?     join(article.ItemAttributes.Author) + ": " : "");     var title = article.ItemAttributes.Title;     var li = document.createElement("li");     var liText = document.createTextNode(author + title);     li.appendChild(liText);     document.getElementById("Results").appendChild(li);     }     }     document.getElementById("Button").disabled = false;   }   function callTimeout(result) {     window.alert("Error! " + result.get_message());     new Sys.UI.Label($('Count')).set_text("0");     document.getElementById("Button").disabled = false;   }   function callError(result) {     window.alert("Error! " + result.get_message());     new Sys.UI.Label($('Count')).set_text("0");     document.getElementById("Button").disabled = false;   }   function join(a) {     var s = "";     for (var i=0; i < a.length - 1; i++) {       s += a[i] + "/";     }     s += a[a.length - 1];     return s;   }   </script> </head> <body>   <form  runat="server">     <atlas:ScriptManager  runat="server">       <Services>         <atlas:ServiceReference Path="~/Amazon.asbx" />       </Services>     </atlas:ScriptManager>     <div>       <input type="text"  />       <input type="button"  value="Search" onclick="Search();" />     </div>     <div>       <p>         <span >0</span> results.</p>       <ul >       </ul>     </div>   </form> </body> </html> 

Figure 10-8. Searching the Amazon catalog using an Atlas bridge


An interesting side note: both Google and Amazon offer a SOAP and a REST interface to their services; both interfaces provide the same functionality. The REST usage numbers are much higher in both cases than the SOAP numbers. One reason is certainly the increased complexity of using SOAP. However, with the Atlas web service bridge, most of that complexity is taken care of for you.


10.4.3. Transforming a Web Service Result with XSLT

The data returned from a web service is generally XML (at least if SOAP or REST is used). This XML is represented in your Atlas page as a JavaScript object, from which you can extract what you need and display it using HTML elements.

Another way to convert the web service data from XML to HTML output, though, is to use XSLT; that is, an XSL transformation. Explaining the use of XSLT is beyond the scope of this book, but I have cited some excellent sources of information at the end of this chapter (see "For Further Reading"). Modern web browsers (Mozilla, Internet Explorer, Opera 9) support XSLT via JavaScript, but very inconsistently. Therefore, a better approach is to perform the transformation in server code. This is possible using custom .NET code, or by letting Atlas components do all the work. In this section, we'll transform the return data from the Google search service into an HTML fragment, which is then shown on the web page.

The Atlas web service bridge supports two built-in transformers that can convert objects into another format. The Microsoft.Web.Services.XmlBridgeTransformer class converts an object into XML, and the Microsoft.Web.Services.XsltBridgeTransformer class performs an XSL conversion of XML data into any output format (usually, HTML).

As before, we will have a bridge, a wrapper class, and JavaScript code in the page that sets the search in motion. The JavaScript code will send the search request from the page to the bridge, which will call the wrapper, which performs the search. The results come back to the wrapper as an object (as we saw earlier, we can work with the object as an array). The wrapper sends this to the bridge. However, this time, the bridge does not send the results back down to the page as is. Instead, the bridge performs two transforms on the results. The first transform turns the result object into XML. The second transform applies an XSLT transformation to the XML and produces HTML; in fact, it produces the HTML that we want to use to display the result list. The bridge sends this HTML to the page, where a single line of JavaScript can just insert the finished HTML into a convenient container.

We will use variations on the three files that we created for the earlier Google search example. However, we need one additional item: an XSL transformation (XSLT) created as an .xsl file. This is the transformation that will be called by the bridge to convert the XML to HTML.

In the root folder of your web application, add a new XSLT file named Google.xsl. This file will hold the XSLT instructions for transforming Google search results into HTML.

As in the previous Google example, we want to display the search results as an HTML <ul> list. Therefore, the XSLT must iterate over all the matches returned by the search as XML, which we can do with an XSL for each loop. There is one small hurdle here: although every search result resides in a <resultElement> element, the Atlas XML transformer converts this into <ResultElement>. XSL is lowercase, therefore accessing resultElement will not work, we have to use ResultElement instead:

 <xsl:for-each select="//resultElements/ResultElement"> ... </xsl:for-each> 

For each search result item, an <li> element is created. The text of the <li> element is a link (an <a> element) pointing to the web page for that result. XSLT processors escape HTML entities, but the Google web service returns the page's title as HTML (since the search terms are highlighted in bold). Therefore, we will need to use the disable-output-encoding attribute for the title.

One other point: since we want to create an HTML fragment, in the XSLT's <xsl:output> element we need to include the omit-xml-declaration attribute to prevent the transformation from creating a result that starts with <?xml ?>. Example 10-11 shows the complete XSLT file.

Example 10-11. The XSL transformation file for the Google web service

 Google.xsl <?xml version="1.0" encoding="utf-8"?> <xsl:stylesheet version="1.0"     xmlns:xsl="http://www.w3.org/1999/XSL/Transform">   <xsl:output method="html" encoding="utf-8" omit-xml-declaration="yes" />   <xsl:template match="/">     <p>       Approx. <xsl:value-of select="//estimatedTotalResultsCount" /> matches!       <ul>         <xsl:for-each select="//resultElements/ResultElement">     <li>     <a>     <xsl:attribute name="href">     <xsl:value-of select="URL" />     </xsl:attribute>     <xsl:value-of select="title" disable-output-escaping="yes" />     </a>     </li>         </xsl:for-each>       </ul>     </p>   </xsl:template> </xsl:stylesheet> 

For the next step, create a new bridge file named GoogleXSLT.asbx by making a copy of the existing Google.asbx file.

In the new bridge file, we need a new entry for the XSL transformation. This will set up the object transformation to XML (via XmlBridgeTransformer) and the XML transformation into HTML (via XsltBridgeTransformer). Usually you would create a new method in the web service wrapper for the search that generates the results for the object transformation, but for this example, there is no new business logic to implement.

Instead, in the bridge, the serverName attribute of the <method> element can be used to redirect requests to the wrapper method:

 <method name="SearchXslt" serverName="Search"> ... </method> 

This exposes a method called SearchXslt() that is accessible in JavaScript, but it just executes the existing Search() method in the wrapper.

In the <method> element, the <input> element stays the same, since the parameters do not change. However a new <transforms> element is introduced, which specifies the two transformers. For the XSLT transformer, you must provide the XSL file to use, obviously. Example 10-12 shows the XML for a bridge file named GoogleXSLT.asbx.

Example 10-12. The XSLT web service bridge for the Google web service

 GoogleXSLT.asbx <?xml version="1.0" encoding="utf-8" ?> <bridge namespace="OReilly.Atlas" className="Google" >   <proxy type="GoogleSearchServiceWrapper, App_Code" />   <method name="SearchXslt" serverName="Search">     <input>       <parameter name="licenseKey"                  value="% appsettings : GoogleLicenseKey %"                  serverOnly="true" />       <parameter name="query" />     </input>     <transforms>       <transform type="Microsoft.Web.Services.XmlBridgeTransformer" />       <transform type="Microsoft.Web.Services.XsltBridgeTransformer">         <data>           <attribute name="stylesheetFile" value="~/Google.xsl" />         </data>       </transform>     </transforms>   </method> </bridge> 

All that's left to do is to call this bridge. Since it returns an HTML fragment, the result from the web service call can just be assigned to the innerHTML property of a <div> container. This simplifies the JavaScript code quite a lot.

Example 10-13 shows a complete ASP.NET page with markup and JavaScript code. The output of this page is identical to the one from Example 10-7.

Example 10-13. Calling the Google web service with XSLT

 GoogleXSLT.aspx <%@ Page Language="C#" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server">   <title>Atlas</title>   <script language="JavaScript" type="text/javascript">   function clearList() {     document.getElementById("Results").innerHTML = "";   }   function Search() {     var query = new Sys.UI.TextBox($('Query'))     document.getElementById("Button").disabled = true;     clearList();     OReilly.Atlas.Google.SearchXslt(     { "query": query.get_text() },     callComplete, callTimeout, callError     );   }   function callComplete(result)  {     document.getElementById("Results").innerHTML = result;     document.getElementById("Button").disabled = false;   }   function callTimeout(result) {     window.alert("Error! " + result.get_message());     document.getElementById("Button").disabled = false;   }   function callError(result) {     window.alert("Error! " + result.get_message());     document.getElementById("Button").disabled = false;   }   </script> </head> <body>   <form  runat="server">     <atlas:ScriptManager  runat="server">       <Services>         <atlas:ServiceReference Path="~/GoogleXSLT.asbx" />       </Services>     </atlas:ScriptManager>     <div>       <input type="text"  />       <input type="button"  value="Search" onclick="Search();" />     </div>     <div >     </div>   </form> </body> </html> 




Programming Atlas
Programming Atlas
ISBN: 0596526725
EAN: 2147483647
Year: 2006
Pages: 146

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net