

by Craig Probus
Introduction
Web Services are an integral part of a new technology and set of standards
and protocols (including SOAP and XSD) designed to enhance and simplify
the interaction of heterogeneous and remote software components across the
Internet and within intranets. Microsoft has embraced these new standards
and has made them an integral part of its .NET strategy both in its Visual
Studio.NET (VS.NET) development environment and the .NET Framework in order
to facilitate the use of Web Services.
My project team is currently developing a distributed .NET application
using Web Services as a basis for a component model. Because of this intensive
use of Web services, we needed to facilitate the testing process of our
components for both component consumers and component developers. The
SOAP Test Harness was created for this end. Although there is a test harness
included in VS.NET, it uses an HTTP "Get" to invoke the methods
of a Web Service and not an HTTP Post using the SOAP standard. The Simple
Object Access Protocol (SOAP) standard defines an eXtensible Markup Language
(XML) structure for a message (the "SOAP:Envelope") and the
contents of the message (the "SOAP:Body"). Due to this limitation,
we could not verify that even if the method worked properly with the HTTP
Get invocation, it would work with a SOAP message. A testing program could
have been developed for each Web Service by individual programmers, but
for the size of this project, maintenance, and other reasons, it was deemed
beneficial to develop a generic program to test the various Web Services.
In addition, the lessons learned during the development of the test harness
were useful for the ongoing development of our Web Services.
The SOAP Test Harness is an ASP.NET application created in VS.NET that
allows technical and functional personnel to test Web Services. The SOAP
Test Harness takes advantage of many of the .NET Framework classes to
effectively use XML, XPath, and the operating system to access the directory
structure. XPath is an XSL language for addressing parts of an XML document.
XPath was designed to be used by eXtensible Stylesheet Language Transformations
(XSLT), but can now be used to make queries against an XML document. This
article will present examples beyond XML and XSL, such as creating files
and accessing external applications.
I also wanted to ensure that programmers developing remotely could access
the Web Services Description Language (WSDL --pronounced whiz-dull) document
without error. The WSDL document acts as a contract for a Web Service
and describes the interfaces, how to access the methods, and the parameters
for the methods. The WSDL document is an XML document that uses XML Schema
(XSD) to define primitive and object data types. XSD Schema describes
the data structures exposed by Web Services. This allows the developer
to define complex data types using XSD Schemas, supporting a richer set
of data types. If the Web Service is created using VS.NET, then the WSDL
document is automatically generated. For clarification, an explanation
of the most important tags of the WSDL document follows:
- <definitions>: the root element of the WSDL document.
-
<service>: a child of the <definitions> element, it has an
attribute "name" that defines the name of the Web Service.
-
<soap:address>: a grandchild of the <service> element, it
has an attribute "location" that specifies the URL of the Web
Service.
-
<message>: describes the request and response messages. Two are
needed for each method that will be exposed through the Web Service. It
is a child node of the <definitions> element.
-
<part>: a child of the <message> element, it describes the
parameters or return values in the SOAP call.
-
<types>: describes the data types and number of parameters or return
values for a method call. It is a child element of the <definitions>
element. XSD data types describe the parameters and return values.
-
<portType>: Describes all the methods exposed through the Web Service
for a specified transfer protocol (such as SOAP, HTTPGet, or SMTP).
-
<operation>: A child of the <portType> element, it specifies
which message is the input and which message is the output.
-
<soap:binding>: indicates the transport protocol. It is a child
of the <binding> element.
-
<soap:operation>: a grandchild of the <binding> element, it
has an attribute "soapAction" that states the SOAPAction HTTP
header.
Description
A SOAP Test Harness user enters the URL of the WSDL document and then
selects the specific Web method that needs to be tested. The user then
enters values for the method parameters. Code is generated for
- Declaring a proxy class instance and parameter variables,
- Instantiating a proxy class object,
- Assigning values to the parameters,
- Invoking the Web method, and
- Displaying the output.
The generated output is then sent to either a label or a DataGrid depending
on the return type. The user has the opportunity to change the generated
code for individual cases. The generated output will be a single ASP.NET
file written in C# (See Figure 1 below).
Components
Technologies:
- C#
- ASP.NET
- Internet Information Server 5.0 (IIS)
- .NET Framework SDK
- Visual Studio.NET (VS.NET)
The Soap Test Harness is an ASP.NET application consisting of six ASP.NET
pages and three C# class definitions. These are:
- SoapTestHarness.cs
- SoapClientGenerator.cs
- ServerFileSystemMaintenance.cs
- Home.aspx.cs
- EnterParams.aspx.cs
- DeclarationCode.aspx.cs
- PreProcessCode.aspx.cs
- ExecuteCode.aspx.cs
- OutputCode.aspx.cs
The ASP.NET pages are written in C# and implement the "Codebehind"
attribute of the "Page" directive.
Four external items are used to support the application:
- WSDL.exe - Creates a Web Service proxy. (Comes with VS.NET.)
- CSC.exe - C# command-line compiler. (Comes with VS.NET.)
- mkappdir.vbs - IIS script for creating virtual directory.
- chaccess.vbs - IIS script for changing access permissions. (Comes with
IIS 5.0.)
Figure 1: Page Flow
Class Definitions
The parent class is SoapTestHarness (see Figure 2), which has two children
-- SoapClientGenerator and ServerFileSystemMaintenance. The SoapTestHarness
class stores the namespace, the root directory of the generated client
pages, and the locations of the external applications and scripts.
Figure 2: SoapTestHarness.cs
using System;
namespace imiTestHarness_v2
{
public class SoapTestHarness
{
public static string HOME_DIRECTORY =
"C:\\Inetpub\\wwwroot\\STHClients\\";
public static string NAMESPACE =
"STHClients";
public static string WSDL_EXE =
"C:\\Program Files\\Microsoft.Net\\FrameworkSDK\\Bin\\wsdl.exe";
public static string CSC_EXE =
"C:\\WINNT\\Microsoft.NET\\Framework\\v1.0.2914\\csc.exe";
public static string MKAPPDIR_VBS =
"C:\\Inetpub\\AdminScripts\\mkappdir.vbs";
public static string CHACCESS_VBS =
"C:\\Inetpub\\AdminScripts\\chaccess.vbs";
public SoapTestHarness()
{
}
}
}
The SoapClientGenerator class stores data about the Web Service, retrieves
data from the WSDL document, and creates the tags for the client ASP.NET
page. A structure made up of three ArrayList objects holds the parameter
names, data types, and values. This class uses objects from the System.Xml,
System.Xml.XPath, System.Xml.Xsl, and the System.Collections Namespace
of the .NET Framework.
The fillXmlIterator() method (see Figure 3) is a commonly used method
by the SOAP Test Harness. It allows the various ASP.NET pages to conveniently
retrieve data from the WSDL file. The client passes in the XPath query
and the URL of the WSDL document and receives an XPathNodeIterator that
it can treat similar to a recordset.
- The XPathDocument holds the XML data and creates an XPathNavigator that
allows us to query the document and compile an XPath query.
- The XmlNamespaceManager is needed so that we can link element prefixes
with a given namespace.
- The XPathExpression object holds the compiled XPath query, and it is used
by the XPathNavigator to carry out the query.
- The XPathNodeIterator holds the results of the XPath query and allows
us to loop through the results.
Figure 3: fillXmlIterator() method
public XPathNodeIterator fillXmlIterator(string xPathQuery, string xmlUrl)
{
try
{
XPathDocument xmlDoc = new XPathDocument(xmlUrl);
XPathNavigator xmlNav = xmlDoc.CreateNavigator();
XmlNamespaceManager nsMgr = new XmlNamespaceManager(xmlNav.NameTable);
XPathExpression xPathExp;
XPathNodeIterator iterator;
nsMgr.AddNamespace("ab", "http://schemas.xmlsoap.org/wsdl/");
nsMgr.AddNamespace("s", "http://www.w3.org/2001/XMLSchema");
nsMgr.AddNamespace("soap", "http://schemas.xmlsoap.org/wsdl/soap/");
xPathExp = xmlNav.Compile(xPathQuery);
xPathExp.SetContext(nsMgr);
iterator = xmlNav.Select(xPathExp);
return iterator;
}
catch(Exception ex)
{
return null;
}
}
The ServerFileSystemMaintenance class is needed in order to create directories,
generate files, and invoke external applications and scripts. A physical
directory and a virtual directory are created that store the generated
ASPX file. The virtual directory's Execute Permissions property is updated,
allowing users access to the generated ASP.NET page. A proxy file must
be created to act as an interface between the generated client and the
Web Service so the test harness invokes external applications to create
the proxy class and then to compile it.
In addition, we must create our ASP.NET Web Service client file. We will
go over a few methods that demonstrate creating a directory, changing
a virtual directory's access permission, and invoking an external application
or script.
The createInstanceDirectory() method (see Figure 4) is called whenever
a Web Service client is generated.
- The System.IO.DirectoryInfo object is used to create the directory. The
CreateSubDirectory() method creates the "bin" directory that
holds the DLL file from the compiled proxy class.
- A persistent attribute, "timeStamp", distinguishes one instance
from another.
- HOME_DIRECTORY is a constant attribute declared in the SoapTestHarness
class.
Figure 4: createInstanceDirectory() method
private bool createInstanceDirectory()
{
try
{
DirectoryInfo baseDir = new DirectoryInfo(@HOME_DIRECTORY);
string instanceDir = this.timeStamp + "\\bin";
baseDir.CreateSubdirectory(@instanceDir);
return true;
}
catch(Exception ex)
{
return false;
}
}
The updateVirDirectoryAccess() method (see Figure 5) updates an existing
virtual directory, allowing the user access to the generated ASP.NET page.
- Here, the "chaccess.vbs" script bundled with IIS 5.0 is used
to modify the access permission.
- If an exception occurs, a false value is returned and the application
acts accordingly.
Figure 5: updateVirDirectoryAccess() method
private bool updateVirDirectoryAccess()
{
try
{
string chaccessArgs = "-a W3svc/1/Root/" + NAMESPACE + "/"
+ this.getTimeStamp()
+ " " + "+execute";
runExternalApp(CHACCESS_VBS, chaccessArgs);
return true;
}
catch(Exception ex)
{
return false;
}
}
The method runExternalApp(string fileName, string args) (see Figure 6)
uses the .NET class System.Diagnostics.Process to invoke external applications.
* The name of the script or application file is passed to the FileName
property and command-line arguments are passed in to the Arguments property.
* "WaitForExit()" will accept an int value instructing the thread
to wait n number of milliseconds before it resumes execution. This method
is used to ensure the proper synchronization of events.
Figure 6: runExternalApp(string fileName, string args) method
private bool runExternalApp(string fileName, string args)
{
try
{
Process process = new Process();
process.StartInfo.FileName = fileName;
process.StartInfo.Arguments = args;
process.Start();
process.WaitForExit(14000);
process.Dispose();
return true;
}
catch(Exception ex)
{
return false;
}
}
ASP.NET Classes
The Home.aspx file is the entry point to the Soap Test Harness. The user
will enter the URL of the WSDL document that VS.NET creates. The methods
exposed by the Web Service are listed for the tester to select. Once a
method is selected, the user clicks the "Next" link and is forwarded
to the parameters page. Home.aspx uses the SoapClientGenerator to retrieve
information from the WSDL document and store that data. Let us review
one of the methods that use the SoapClientGenerator for retrieving XML
data from the WSDL file. The getClassName() method (see Figure 7) uses
the fillXmlIterator() method to easily read selected XML nodes from an
XML document. An XPath query to retrieve the Web Service name is created
and then passed to the fillXmlIterator() method that was discussed earlier.
The XPath query accesses the "service" element and selects the
"name" attribute. We should recall that the "ab:"
prefix was assigned to the "http://schemas.xmlsoap.org/wsdl/"
namespace, and the "service" element is defined in that namespace.
The "iterator" is treated similarly to a recordset by invoking
the MoveNext() method to move through nodes.
Figure 7: getClassName() method
private string getClassName()
{
try
{
if(!validateWsdlUrlTextbox())
return null;
string className = null;
string wsdlUrl = txtWsdlUrl.Text.Trim();
string xpathquery = "//ab:service/@name";
XPathNodeIterator iterator = this.scg.fillXmlIterator(xpathquery, wsdlUrl);
while(iterator.MoveNext())
className = iterator.Current.Value;
return className;
}
catch(Exception ex)
{
return null;
}
}
The EnterParams.aspx page allows the user to enter values for the method
parameters, as well as view the returned data type. This page also uses
the SoapClientGenerator and the fillXmlIterator() method to get parameter
data from the WSDL document. The parameter names, data types, and corresponding
text boxes are displayed to the user. The displayParameters() method (see
Figure 8) uses Label[] and TextBox[] arrays to hold the WebControls objects
as the Soap Harness loops through each to modify their visible property.
Figure 8: displayParameters() method
protected void displayParameters()
{
try
{
ArrayList parameters = (ArrayList) this.ip.parameters;
ArrayList dataTypes = (ArrayList) this.ip.dataTypes;
Label[] dataTypeLabels = {lblDataType1, lblDataType2, lblDataType3,
lblDataType4, lblDataType5, lblDataType6,
lblDataType7, lblDataType8, lblDataType9,
lblDataType10};
Label[] paramNameLabels = {paramName1, paramName2, paramName3,
paramName4, paramName5, paramName6,
paramName7, paramName8, paramName9,
paramName10};
TextBox[] txtParams = {txtParam1, txtParam2, txtParam3,
txtParam4, txtParam5, txtParam6,
txtParam7, txtParam8, txtParam9,
txtParam10};
if(this.ip.parameters.Count == 0)
{
this.lblNoParams.Visible = true;
this.lblParam.Visible = false;
this.lblDataType.Visible = false;
this.lblValue.Visible = false;
}
else
{
for(int i = 0; i < this.ip.parameters.Count; i++)
{
this.lblParam.Visible = true;
this.lblDataType.Visible = true;
this.lblValue.Visible = true;
if(i == 9)
{
displayErrorMessage("Only 10 parameters are allowed!");
return;
}
dataTypeLabels[i].Visible = true;
dataTypeLabels[i].Text = dataTypes[i].ToString().Replace("s:",
"");
paramNameLabels[i].Visible = true;
paramNameLabels[i].Text = (string) parameters[i];
txtParams[i].Visible = true;
}
}
}
catch(Exception ex)
{
displayErrorMessage(ex.Message);
}
}
The DeclarationCode.aspx, PreProcessCode.aspx, ExecuteCode.aspx, and OutputCode.aspx
pages all have the same basic functionality. Each page will invoke a method
on the SoapClientGenerator to generate code and then display the code
to the user. The user will be able to customize the code and then move
on to the next page. The code is stored within the SoapClientGenerator
object and that object is passed throughout the application in the Session
variable.
The OutputCode.aspx page uses the ServerFileSystemMaintenance class, which
creates the generated ASP.NET page and the corresponding directory, virtual
directory, proxy class, and proxy DLL file. The testSoapClient() method
(see Figure 9) administers the tasks that create all necessary support
files and directories. The ServerFileSystemMaintenance (sfsm) and SoapClientGenerator
(scg) objects are used in conjunction with the displayErrorMessage() method.
If no exceptions are thrown, then the user is forwarded to the generated
ASP.NET page.
Figure 9: testSoapClient() method
public void testSoapClient()
{
try
{
if(!this.sfsm.makeDirectory())
{
displayErrorMessage("Error creating host directory for testing");
return;
}
if(!this.sfsm.makeProxyFile(this.scg.getWsdlUrl()))
{
displayErrorMessage("Error creating proxy file");
return;
}
this.scg.generateAspxCode();
if(!this.sfsm.makeAspxFile(this.scg.getAspxCode()))
{
displayErrorMessage("Error creating .aspx file");
return;
}
string soapClientURL = "http://localhost/STHClients/" +
this.sfsm.getTimeStamp() + "/" +
this.sfsm.getTimeStamp() + ".aspx";
Response.Redirect(soapClientURL, true);
}
catch(Exception ex)
{
displayErrorMessage(ex.Message);
}
}
Screen Shots
1. Home.aspx
2. EnterParams.aspx
3. DeclarationCode.aspx
4. PreProcessCode.aspx
5. ExecuteCode.aspx
6a. OutputCode.aspx (bool)
7a. Generated ASP.NET page (bool)
6b. OutputCode.aspx (DataSet)
7b. Generated ASP.NET page (DataSet) (Note: Data from SQL Server "pubs"
sample database.)
Pending Issues and Enhancements
The original plan for interfacing the client with the Web Service was
to use the .NET class System.Web.Services.SoapHttpClientProtocol. This
is a simple class to use; all we have to do is assign the URL of the Web
Service to the "Url" attribute and then call the "invoke()"
method, passing in the name of the specific web method as a string. So
why was this class not used instead of having to use the WSDL.exe and
CSC.exe to create an external proxy? Well, the SoapHttpClientProtocol
requires the use of preprocessor attributes that contain literal values
of an individual Web method location. Since this program needed to be
generic and there is no way to alter the preprocessor attribute at runtime,
a workaround needed to be implemented. Note that I could use the SoapMessage
class in the System.Runtime.Serialization.Formatters namespace to construct
the SOAP envelope, but it uses the "RPC" format instead of the
"Document" format that we need. In the future, a System.Xml.XmlTextWriter
object will be used to build the SOAP message and a System.Net.WebRequest
object will submit the message. This will eliminate the need to dynamically
generate temporary proxy classes.
Summary
The need to test Web Services for component consumers and developers
is a common requirement. The SOAP Test Harness eliminates the needed for
developers to write their own testing code. In addition, The SOAP Test
Harness facilitates a uniform and generic process for testing Web Services.
The Microsoft .NET framework allows developers to rapidly develop applications
using XML, XSL, operating systems, and Web Services. The SOAP Test Harness
is a testament to how simple and quickly an ASP.NET application can be
created using the .NET Framework and Visual Studio.NET.
About the Author
Craig Probus is a consultant for Information Methodologies, Inc. (imi), higher education's leading enterprise web integrators. His areas of expertise are Web infrastructure integration and development. He is a Sun-certified programmer for the Java 2 platform and an IBM-certified XML developer.
This article originally published at 15
Seconds.
|