|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionFrom the languages and programming environments like C, the .NET CLR and Java we know the proxy generation mechanisms that are based on IDL and RPC for a long time. These generated classes and files enable the programmer to call a server-side method by calling a local method with the same name. The implementation of network transfer is taken off from your application code. If you want to implement a communication from JavaScript to web services using SOAP, it is very important to use an approach that needs only a small amount of code. Complex and long scripts tend to be buggy. This proxy generator can be used on its own but is also a part of an AJAX framework available through my blog site that is still under development. Some AJAX implementations use their own way to transport information between the client and the server. This implementation uses the standard SOAP protocol and works on Internet Explorer and the Firefox browser. How it works - in shortWeb services can be described by using the formal description standard for web services called WSDL (Web Service Description Language). Everything we need to know for calling a web service is available in this XML formatted information and all we need to do is transform this information into a JavaScript source code syntax that can be directly executed by using an XSLT based translation. A common include file is used for bringing the core implementations of the SOAP protocol. Using the proxyTo make these proxy functions work, a common JavaScript include (ajax.js) file and a file that generates the web service specific code must be included: <script type="text/javascript" src="ajax.js"></script>
<script type="text/javascript"
src="GetJavaScriptProxy.aspx?service=CalcService.asmx">
</script>
The implementation of real communication details are implemented in the ajax.js file. A variable named The second script include now retrieves the WSDL description of the web service and generates the specific JavaScript for this service containing local proxy methods that can be called to execute the corresponding method on the server. Asynchronous callsCalling a server-side method may look like this: proxies.CalcService.CalcPrimeFactors.func =
displayFactors; // hook up a method that
// gets the response
proxies.CalcService.CalcPrimeFactors(12); // now call the server
// The return value is passed to this function as a parameter
function displayFactors (retVal) {
document.getElementById("outputField").value = retVal;
} // displayFactors
Here you seen an asynchronous call. The function Synchronous callsThere is also a synchronous version that can be used. In this case, the function attribute must remain unset or proxies.CalcService.func = null; // no hook up function !
var f = proxies.CalcService.CalcPrimeFactors(12);
// call the server and return the result.
Implementation detailsHere is a sample extract of the code that is generated for the client to show how the mechanism works. The include file ajax.js generates the global object named var proxies = new Object();
Per WebService, an object named like the WebService is attached to the // JavaScript proxy for webservices
// A WebService for the calculation of prime factors.
proxies.CalcService = {
url: "http://localhost:1049/CalcFactors/CalcService.asmx",
ns: "http://www.mathertel.de/CalcFactorsService/"
} // proxies.CalcService
For each web service method, a function on the client is created that mirrors the method on the server. The information we need to build up the full SOAP message is attached to the function object as attributes: // Add 2 numbers.
proxies.CalcService.AddInteger =
function () { return(proxies.callSoap(arguments)); }
proxies.CalcService.AddInteger.fname = "AddInteger";
proxies.CalcService.AddInteger.service = proxies.CalcService;
proxies.CalcService.AddInteger.action =
"http://www.mathertel.de/CalcFactors/AddInteger";
proxies.CalcService.AddInteger.params =
["number1:int","number2:int"];
proxies.CalcService.AddInteger.rtype = ["AddIntegerResult:int"];
CachingThe proxy implementation also offers a client-side caching feature. An approach that leads to less traffic on the net because repeating the same calls can be prevented. The HTTP caching features, instrumented by using HTTP headers, do not help in these situations because the request is not an HTTP-GET request and there is always a payload in the HTTP body. Caching must therefore be realized by some scripting on the client. The caching feature in the JavaScript web service proxy implementation can be enabled by calling the method proxies.EnableCache(proxies.CalcService.CalcPrimeFactors)
By calling this method, a JavaScript object is added that stores all the results and is used to prevent a call to the server if an entry for the parameter already exists inside this object. This is not a perfect solution, but it works only under the following circumstances:
ReferenceThis is the map of the objects and properties that are used for the proxy functions to work:
Supported data typesWith version 2.0 there is now more support for different data types. Simple data typesTill now only those methods were supported that were converting the parameters and the result values were not necessary. This applies to strings and numbers. With this version, the data types defined on the server and the WSDL are passed to the client so that the data types can be converted using JavaScript at runtime. In the generated proxy code, the listing of the names of the parameters is now extended by an optional specification of the data type. Without these the values are treated as strings. In the HTML object model, the JavaScript data types are not well supported. The value that is displayed inside an HTML input field is always a string, even if it contains only digits. So, when calling the proxy functions, all the parameters are also accepted as JavaScript strings and converted (if possible) to the right types. XML dataPassing XML documents was implemented to make it possible to pass complex data. In the supported browser clients, the A method has to be is declared in C# like this: [WebMethod()]
public XmlDocument Calc(XmlDocument xDoc) {
...
return (xDoc);
} // Calc
The proxy functions also accept the XML document as a string type. In this case, the contents of the passed string is passed directly to the server and it must for this reason contain a valid XML document without the declarations and without any "XML processing instructions" like <? ... ?>. With this data type it is possible to pass complex data directly to the server. And there is no need to define a method with many parameters if we use this data type. If the data scheme is extended with new fields, it will not be necessary to give a new signature to the web service. The disadvantage of this approach is that the content of the XML document cannot be validated by the web service infrastructure because there is no schema for this part of the conversation available. Data type mappings
The implementation of the callThe transmission of the SOAP/XML messages can be implemented using the appropriate ///<summary>
///Get a browser specific implementation of the
///XMLHTTP object.
///</summary>
function getXMLHTTP() {
var obj = null;
try {
obj = new ActiveXObject("Msxml2.XMLHTTP");
} catch (e) { }
if (obj == null) {
try {
obj = new ActiveXObject("Microsoft.XMLHTTP");
} catch (e) { }
} // if
if ((obj == null) &&
(typeof XMLHttpRequest != "undefined"))
obj = new XMLHttpRequest();
return(obj);
} // getXMLHTTP
This object is implemented in different technologies, depending on the available technologies in the browsers. It was first developed by Microsoft in the Internet Explorer as an ActiveX control and the Mozilla developers re-implemented it by providing the same methods and properties. A call can be done by using the following sequence of methods: x.open("POST", p.service.url, true); // async call
x.setRequestHeader("SOAPAction", p.action);
x.setRequestHeader("Content-Type",
"text/xml; charset=utf-8");
// hook up a method for the result processing
x.onreadystatechange = p.corefunc;
x.send(soap); // send a soap request
More details and some more internal description can be found in the ajax.js include file. A proxy generator for JavaScriptRetrieving a WSDL description is very easy when implemented in ASP.NET. You navigate to the URL of the web service and use the link that is available on this page. You can also attach a WSDL parameter. The proxy generator retrieves this XML document by using an The complex part lies in writing the right transformations. Inside the wsdl.xslt file, you can find the templates of the JavaScript code that defines these proxy objects. Instead of generating another XML document this transformation produces plain text that is valid JavaScript code. The source code of GetJavaScriptProxy.aspx<%@ Page Language="C#" Debug="true" %>
<%@ Import Namespace="System.IO" %>
<%@ Import Namespace="System.Net" %>
<%@ Import Namespace="System.Xml" %>
<%@ Import Namespace="System.Xml.Xsl" %>
<!--
/// 19.07.2005 white space removed
/// 20.07.2005 more datatypes and XML Documents
/// 04.09.2005 XslCompiledTransform
-->
<script runat="server">
private string FetchWsdl(string url) {
Uri uri = new Uri(Request.Url, url + "?WSDL");
HttpWebRequest req =
(HttpWebRequest)WebRequest.Create(uri);
req.Credentials = CredentialCache.DefaultCredentials;
// running on the same server !
// req.Proxy = WebRequest.DefaultWebProxy;
req.Timeout = 6 * 1000; // 6 seconds
WebResponse res = req.GetResponse();
#if DOTNET11
XmlDocument data = new XmlDocument();
data.Load(res.GetResponseStream());
XslTransform xsl = new XslTransform();
xsl.Load(Server.MapPath("~/ajaxcore/wsdl.xslt"));
System.IO.StringWriter sOut =
new System.IO.StringWriter();
xsl.Transform(data, null, sOut, null);
#else
XmlReader data =
XmlReader.Create(res.GetResponseStream());
XslCompiledTransform xsl = new XslCompiledTransform();
xsl.Load(Server.MapPath("~/ajaxcore/wsdl.xslt"));
System.IO.StringWriter sOut =
new System.IO.StringWriter();
xsl.Transform(data, null, sOut);
#endif
return (sOut.ToString());
} // FetchWsdl
</script>
<%
string asText = Request.QueryString["html"];
Response.Clear();
if (asText != null) {
Response.ContentType = "text/html";
Response.Write("<pre>");
} else {
Response.ContentType = "text/text";
} // if
string fileName = Request.QueryString["service"];
if (fileName == null)
fileName = "CalcService";
// get good filenames only (local folder)
if ((fileName.IndexOf('$') >= 0) || (Regex.IsMatch(fileName,
@"\b(COM\d|LPT\d|CON|PRN|AUX|NUL)\b",
RegexOptions.IgnoreCase)))
throw new ApplicationException("Error in filename.");
if (! Server.MapPath(fileName).StartsWith(
Request.PhysicalApplicationPath,
StringComparison.InvariantCultureIgnoreCase))
throw new ApplicationException("Can show local files only.");
string ret = FetchWsdl(fileName);
ret = Regex.Replace(ret, @"\n *", "\n");
ret = Regex.Replace(ret, @"\r\n *""", "\"");
ret = Regex.Replace(ret, @"\r\n, *""", ",\"");
ret = Regex.Replace(ret, @"\r\n\]", "]");
ret = Regex.Replace(ret, @"\r\n; *", ";");
Response.Write(ret);
%>
The source code of wsdl.xslt<?xml version="1.0" ?>
<xsl:stylesheet version='1.0'
xmlns:xsl='http://www.w3.org/1999/XSL/Transform'
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:fmt="urn:p2plusfmt-xsltformats"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:s="http://www.w3.org/2001/XMLSchema"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/">
<!--
/// 19.07.2005 optional documentation
/// 20.07.2005 more datatypes and XML Documents
/// 20.07.2005 more datatypes and XML Documents fixed
-->
<xsl:strip-space elements="*" />
<xsl:output method="text" version="4.0" />
<xsl:param name="alias">
<xsl:value-of select="wsdl:definitions/wsdl:service/@name" />
</xsl:param>
<xsl:template match="/">
// javascript proxy for webservices
// by Matthias Hertel
/*<xsl:value-of select="wsdl:definitions/wsdl:documentation"/>*/
<xsl:for-each select=
"/wsdl:definitions/wsdl:service/wsdl:port[soap:address]">
<xsl:call-template name="soapport" />
</xsl:for-each>
</xsl:template>
<xsl:template name="soapport">
proxies.<xsl:value-of select="$alias" /> = {
url: "<xsl:value-of select="soap:address/@location" />",
ns: "<xsl:value-of
select=
"/wsdl:definitions/wsdl:types/s:schema/@targetNamespace"/>"
} // proxies.<xsl:value-of select="$alias" />
<xsl:text>
</xsl:text>
<xsl:for-each select="/wsdl:definitions/wsdl:binding[@name =
substring-after(current()/@binding, ':')]">
<xsl:call-template name="soapbinding11" />
</xsl:for-each>
</xsl:template>
<xsl:template name="soapbinding11">
<xsl:variable name="portTypeName"
select="substring-after(current()/@type, ':')" />
<xsl:for-each select="wsdl:operation">
<xsl:variable name="inputMessageName"
select="substring-after(/wsdl:definitions/wsdl:portType[@name =
$portTypeName]/wsdl:operation[@name =
current()/@name]/wsdl:input/@message, ':')" />
<xsl:variable name="outputMessageName"
select="substring-after(/wsdl:definitions/wsdl:portType[@name =
$portTypeName]/wsdl:operation[@name = current()/@name]
/wsdl:output/@message, ':')" />
<xsl:for-each select="/wsdl:definitions/wsdl:portType[@name =
$portTypeName]/wsdl:operation[@name =
current()/@name]/wsdl:documentation">
/** <xsl:value-of select="." /> */
</xsl:for-each>
proxies.<xsl:value-of
select="$alias" />.<xsl:value-of select="@name" />
= function () { return(proxies.callSoap(arguments)); }
proxies.<xsl:value-of
select="$alias" />.<xsl:value-of select="@name" />.fname
= "<xsl:value-of select="@name" />";
proxies.<xsl:value-of
select="$alias" />.<xsl:value-of select="@name" />.service
= proxies.<xsl:value-of select="$alias" />;
proxies.<xsl:value-of
select="$alias" />.<xsl:value-of select="@name" />.action
= "<xsl:value-of select="soap:operation/@soapAction" />";
proxies.<xsl:value-of
select="$alias" />.<xsl:value-of select="@name" />.params
= [<xsl:for-each select="/wsdl:definitions/wsdl:message[@name
= $inputMessageName]">
<xsl:call-template name="soapMessage" />
</xsl:for-each>];
proxies.<xsl:value-of select="$alias" />.
<xsl:value-of select="@name" />.rtype
= [<xsl:for-each
select="/wsdl:definitions/wsdl:message[@name =
$outputMessageName]">
<xsl:call-template name="soapMessage" />
</xsl:for-each>];
</xsl:for-each>
</xsl:template>
<xsl:template name="soapMessage">
<xsl:variable name="inputElementName"
select="substring-after(wsdl:part/@element, ':')" />
<xsl:for-each select="/wsdl:definitions/wsdl:types/s:schema/s:element
[@name=$inputElementName]//s:element">
<xsl:choose>
<xsl:when test="@type='s:string'">
"<xsl:value-of select="@name" />"
</xsl:when>
<xsl:when test="@type='s:int'
or @type='s:unsignedInt' or @type='s:short'
or @type='s:unsignedShort' or @type='s:unsignedLong'
or @type='s:long'">
"<xsl:value-of select="@name" />:int"
</xsl:when>
<xsl:when test="@type='s:double' or @type='s:float'">
"<xsl:value-of select="@name" />:float"
</xsl:when>
<xsl:when test="@type='s:dateTime'">
"<xsl:value-of select="@name" />:date"
</xsl:when>
<xsl:when test="./s:complexType/s:sequence/s:any">
"<xsl:value-of select="@name" />:x"
</xsl:when>
<xsl:otherwise>
"<xsl:value-of select="@name" />"
</xsl:otherwise>
</xsl:choose>
<xsl:if test="position()!=last()">,</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
History
The files for implementation and more samples are available in the sample website of my AJAX project.
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||