Introduction
In this article a simple code generator for generating web proxy classes targeting JavaScript/VBScript is described. The code generator is essentially a single XSLT document.
Background
Whilst working with a web application that made heavy use of VBScript I wanted a simple way to access web services (written in C#). As I couldn't find a web service generation tool for VBScript I wrote my own, using XSLT. Generating equivalent JavaScript was not difficult, so I included generation to JavaScript as well. This may be useful for programmers who wish to call web services from JavaScript but do not want to use a full-blown AJAX framework. In fact, the approach I adopted is almost anti-AJAX - it's synchronous, and it uses string handling routines to create/parse the web request/response.
Using the code
To generate VBScript web proxy code, an XSLT transformation needs to be performed. This can be achieved in a few lines of code:
XslCompiledTransform xsltransform = new XslCompiledTransform();
try
{
string xsltFileName = "WSDLtoVBScript.xslt";
string wsdlSourceFileName = "ServiceX.wsdl";
string vbscriptProxyFileName = "ServiceX.vbs";
xsltransform.Load(xsltFileName);
xsltransform.Transform(wsdlSourceFileName, vbscriptProxyFileName);
}
catch (System.Xml.XmlException theException)
{
}
An HTML file which contains VBScript client code to access the web service would look as follows:
<html>
<head>
<script language="vbscript" src="./Scripts/String.vbs"></script>
<script language="vbscript" src="./Scripts/ServiceXProxy.vbs"></script>
<script language="vbscript">
Dim ServiceXProxy
Sub window_onload
Set ServiceXWS = New ServiceX
ServiceXWS.serviceAddress = "htpp://myhost.com/ServiceX.asmx"
End Sub
Sub Test
window.alert(ServiceX.Test1)
End Sub
</script>
</head>
<body>
...
<input type=button value="Test" id="TestBtn" onclick="vbscript:Test()" />
...
</body>
</html>
The above example assumes that the web service ServiceX
has a method called Test1
which returns a string
value. Also, the file String.vbs needs to be included as it contains string handling routines required by the web proxy class.
XSLT Stylesheet
The XSLT will generate a class with the same name as the web service (as specified by the name
attribute of the service
WSDL element). Within the class a function is defined for each method (i.e. each operation
WSDL element). Prior to this, new classes are defined for each new type defined by the web service, and for each new type a SOAPResponseTo_
(name of type) function is generated which parses a soap response and returns a VBScript instance of the type. Thus the structure of the WSDLtoVBScript.xslt document is as follows:
- Generate class definitions of complex types (/wsdl:definitions/wsdl:types/s:schema/s
:complexType[not(contains(@name,'ArrayOf'))]
)
- Generate SOAP response parsing methods - for simple types and complex types (wsdl:definitions/wsdl:types/s:schema/s:complexType)
- Generate the web service class (/wsdl:definitions/wsdl:service)
- Generated the function definitions (/wsdl:operation)
This is described in more detail below.
Set output type
As the output will be VBScript/JavaScript code the output type is text:
<xsl:output method="text" />
Declare global variables
Some commonly used element values are given variable names for quick access in later parts of the script, for instance, the serviceName
variable is defined as follows:
<xsl:variable name="serviceName"
select="/wsdl:definitions/wsdl:service/@name"/>
Select root node
The root node is selected to begin the transformation:
<xsl:template match="/">
Class definitions
A VBScript class is created for each complex type (which isn't an array), using the xsl:for-each
element to loop through all the applicable complex types:
<xsl:for-each select="/wsdl:definitions/wsdl:types/s:schema/s:
complexType[not(contains(@name,'ArrayOf'))]">
...
</xsl:for-each>
The class definition also includes a toString
function, which uses a common XSLT 'pattern' to generate a comma separated list of values:
str = str + "<xsl:if test="position()!=1">, </xsl:if>...
SOAP response parsing methods
No actual transformation is required for the parsing of simple SOAP response types, so the methods have been included as plain text (they could be taken out and moved to an include script). Eg. for integer types:
Function SOAPResponseTo_int(soapRespText)
SOAPResponseTo_int = CInt(soapRespText)
End Function
For complex types the same for-each
loop is used as for the initial class definitions for complex types. A check is made using the xsl:choose
element to see whether the complex type is an array or not:
<xsl:for-each select="wsdl:definitions/wsdl:types/s:schema/s:complexType">
<xsl:variable name="baseClass"
select="substring-after(s:complexContent/s:extension/@base,':')"/>
Function SOAPResponseTo_<xsl:value-of select="@name"/>(soapRespText)
<xsl:choose>
<xsl:when test="contains(@name,'ArrayOf')">
...
</xsl:when>
<xsl:otherwise>
...
</xsl:otherwise>
</xsl:choose>
End Function
</xsl:for-each>
Generate class
The web service proxy class is generated as follows:
Class <xsl:value-of select="$serviceName"/>
Public serviceAddress
Public serviceNamespace
Private Sub Class_Initialize
me.serviceAddress
= "<xsl:value-of select=_
"/wsdl:definitions/wsdl:service/wsdl:port/soap:address/@location" />"
me.serviceNamespace
= "<xsl:value-of select="$serviceNamespace"/>"
End Sub
<xsl:apply-templates select=_
"wsdl:definitions/wsdl:binding[soap:binding]/wsdl:operation" />
End Class
This is the last text inside the root xsl:template
node. The xsl:apply-templates
element will result in the wsdl:operation
elements being added to the stack of nodes for the XSLT processor to process.
Generate functions
Finally the functions are generated by template matches on the wsdl:operation
elements:
<xsl:template match="wsdl:operation">
<xsl:variable name="methodName" select="@name" />
<xsl:variable name="resultType" select=... />
' Method: <xsl:value-of select="$methodName"/>
(for operation '<xsl:value-of select="@name"/>')
' ...
Public Function <xsl:value-of select="$methodName"/>(
<xsl:call-template
name="CallParameterList">
<xsl:with-param name="methodName" select="$methodName"/>
</xsl:call-template>)
' Create the XML HTTP request object
Set req = CreateObject("Microsoft.XMLHTTP")
' Construct the SOAP request
Dim soapReq
soapReq = ...
' Send the SOAP request (synchronously)
req.open "POST", me.serviceAddress, false
req.setRequestHeader "Content-Type", "text/xml; charset=utf-8"
req.setRequestHeader "SOAPAction", me.serviceNamespace +
"<xsl:value-of select="$methodName"/>"
req.send soapReq
' Construct the result
<xsl:choose>
-->
<xsl:when test="($resultType = 's:int') or
($resultType = 's:string') or ...">
<xsl:value-of select="$methodName"/>
= SOAPResponseTo_<xsl:value-of select=
"substring-after($resultType,':')"/>(...)
</xsl:when>
...
</xsl:choose>
End Function
</xsl:template>
The signature of the function is generated by making a call to an XSLT named template (like a procedure - see below) via the xsl:call-template
element. The SOAP request is constructed, then posted to the web service address using the Microsoft.XMLHTTP
object. The SOAP response is then deserialised by making a call to the appropriate SOAP_ResponseTo_
... function.
Named templates (XSLT procedures)
The function generated XSLT above makes much use of calls to named templates to break up the code better. An example is the CallParameterList
named template:
<xsl:template name="CallParameterList">
<xsl:param name="methodName"/>
<xsl:for-each
select="/wsdl:definitions/wsdl:types/s:schema/s:element
[@name=$methodName]/s:complexType/s:sequence/s:element">
<xsl:if test="position()!=1">, </xsl:if><xsl:value-of select="@name" />
</xsl:for-each>
</xsl:template>
JavaScript
For JavaScript the process is much the same. The file String.js needs to be included in place of String.vbs. Also, the prototype feature of JavaScript is used instead of the Class construct in VBScript.
History
- 27 Jun 2007 - Posted
- 04 Jul 2007 - Described XSLT Stylesheet in more detail