Client Login Contact Us imi : home

Home

Clients
Services
Industries
Capabilities

News Releases
Articles About imi
Why We're Here
Technology Partners
Join Our Team



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

  1. Declaring a proxy class instance and parameter variables,
  2. Instantiating a proxy class object,
  3. Assigning values to the parameters,
  4. Invoking the Web method, and
  5. 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:

  1. SoapTestHarness.cs
  2. SoapClientGenerator.cs
  3. ServerFileSystemMaintenance.cs
  4. Home.aspx.cs
  5. EnterParams.aspx.cs
  6. DeclarationCode.aspx.cs
  7. PreProcessCode.aspx.cs
  8. ExecuteCode.aspx.cs
  9. 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:

  1. WSDL.exe - Creates a Web Service proxy. (Comes with VS.NET.)
  2. CSC.exe - C# command-line compiler. (Comes with VS.NET.)
  3. mkappdir.vbs - IIS script for creating virtual directory.
  4. 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.