The transfer to the hosting facility was not easy and one of the biggest problems turned out to be our Web Services (for more information see articles on Software as a Service, Saas). There were all kinds of configuration errors as well as testing problems. About 10 days after the deployment my boss sends me this innocuous email:
Please create a new folder in the web site called “test”
Create one new webpage that reference your web services.
One for CreateJob.
The page should merely have all of the required fields on an input form and invoke the web service call and display the XML response.
People other than you have to be able to test this functionality.
For those who might ever be in a position to manage geeks with creation complexes don't write blank checks like this, it leads to the following.
I spent my entire day Tuesday writing a complete, automatically generated, test bed system for testing webservices. The idea behind it was to request the WSDL (Web Service Description Language) for the web service, generate the list of available services, use the reflection library of C# (System.Reflection - super cool) to enumerate the methods and parameters of all available services and then execute a test call to the method.
There were some snags, but I completed the entire Research-Design-Develop-Test-Deploy cycle in one day for some very cool (in my own mind) code.
I wound up with two pages:
- ServiceList.aspx
- TestOperation.aspx
Markup
<div class="Form">
<h3>Web Service Operations - <asp:Label ID="lblServicesURL" runat="Server" /></h3>
<table class="ListView_Table" border="0" cellpadding="0" cellspacing="0" >
<tr class="HeaderRow">
<th>Name</th>
<th>Description</th>
</tr>
<asp:PlaceHolder ID="OperationsList" runat="server" />
</table>
</div>
Code Behind
using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Net;
using System.Reflection;
using System.Text;
/// Web service references
using localhost;
public partial class test_servicelist : System.Web.UI.Page
{
struct WSDLOperation
{
public string operation;
public string description;
}
protected void Page_Load(object sender, EventArgs e)
{
// Request the WSDL from the services site
System.Collections.ArrayList wsdlOperations = new System.Collections.ArrayList();
Services services = new Services();
System.Collections.ArrayList wsdlOperationNames = ParseWSDL(services.Url + "?WSDL", ref wsdlOperations);
lblServicesURL.Text = services.Url;
int i = 0;
foreach (WSDLOperation op in wsdlOperations)
{
HtmlTableRow tr = new HtmlTableRow();
HtmlTableCell td = new HtmlTableCell();
HtmlAnchor link = new HtmlAnchor();
link.InnerText = op.operation;
link.HRef = "testOperation.aspx?op=" + HttpUtility.HtmlEncode(op.operation);
td.Attributes.Add("style", "padding-left: 10px;");
td.Controls.Add(link);
tr.Cells.Add(td);
td = new HtmlTableCell();
td.InnerText = op.description;
tr.Cells.Add(td);
if (i % 2 == 0)
tr.Attributes.Add("class", "EvenRow");
else
tr.Attributes.Add("class", "OddRow");
i++;
OperationsList.Controls.Add(tr);
}
}
private static System.Collections.ArrayList ParseWSDL(string url, ref System.Collections.ArrayList wsdlOperations)
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
System.Text.StringBuilder html = new StringBuilder();
// read in and store the XML
using (System.IO.StreamReader stream = new System.IO.StreamReader(response.GetResponseStream()))
{
string line = "";
while ((line = stream.ReadLine()) != null)
{
html.AppendLine(line);
}
}
response.Close();
//System.Console.WriteLine(html.ToString());
// Open an array list for use in storing the available operations.
System.Collections.ArrayList wsdlOperationNames = new System.Collections.ArrayList();
int start, end;
string wsdl = html.ToString();
// skip down to the list of operations in the WSDL listing.
// we don't need to worry about the types so we can skip them for now.
start = wsdl.IndexOf(">wsdl:porttype"); end="wsdl.IndexOf("</wsdl:portType">", start);
wsdl = wsdl.Substring(start, end - start);
while (true)
{
start = wsdl.IndexOf(">wsdl:operation"); if="" start=""> end =" wsdl.IndexOf("<");
// single out an operation
string operation = wsdl.Substring(start, end - start);
// and remove it from the operation listing
wsdl = wsdl.Substring(end + ">/wsdl:operation");<".Length);
WSDLOperation op = new WSDLOperation();
// parse out the name of the operation
start = operation.IndexOf("name=\"") + "name=\"".Length;
end = operation.IndexOf("\"<", start);
op.operation = operation.Substring(start, end - start);
// parse out the description of the operation
start = operation.IndexOf(">wsdl:documentation"); if="" start=""> 0)
{
start = operation.IndexOf(">", start) + 1;
end = operation.IndexOf("</wsdl:documentation");>", start);
op.description = operation.Substring(start, end - start);
if (op.description.Length > 80)
op.description = op.description.Substring(0, 80);
}
else
op.description = "No description from the WSDL is available";
wsdlOperations.Add(op);
wsdlOperationNames.Add(op.operation);
}
// print out the names of all the available operations
return wsdlOperationNames;
}
}
Test Operation used the reflection library for executing the method call to the remote service:Markup
<div class="Form">
<h3><asp:Label ID="lblOperation" runat="Server" /></h3>
<table class="ListView_Table" border="0" cellpadding="0" cellspacing="0" >
<tr class="HeaderRow">
<th>Parameter Name</th>
<th>Type</th>
<th></th>
</tr>
<asp:PlaceHolder ID="Input" runat="server" />
<tr>
<td colspan="3" style="text-align: right;">
<asp:Button ID="btnSubmit" runat="server" Text="Test Operation" OnClick="btnSubmit_Click" />
</td>
</tr>
</table>
<p />
<h3>Response</h3>
<table class="ListView_Table">
<tr class="HeaderRow">
<th>Return Parameter</th>
<th>Value</th>
</tr>
<ajax:UpdatePanel ID="ResponsePanel" runat="server">
<Triggers>
<ajax:PostBackTrigger ControlID="btnSubmit" />
</Triggers>
<ContentTemplate>
<asp:PlaceHolder ID="PHResponse" runat="Server" />
</table>
</ContentTemplate>
</ajax:UpdatePanel>
</table>
</div>
Code Behind
using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Reflection;
/// Web service references
//using PBTMM;
using localhost;
public partial class test_testOperation : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (Request["op"] == null || Request["op"].Length == 0)
Response.Redirect("servicelist.aspx");
Services services = new Services();
lblOperation.Text = Request["op"] + " from " + services.Url;
// Use the reflection library to get the associated data for the operations from the
// web reference DLL
Type serviceType = typeof(Services);
MethodInfo method = serviceType.GetMethod(Request["op"]);
int i = 0;
foreach (ParameterInfo parameterInfo in method.GetParameters())
{
// Print the Parameter Type and the Parameter Name
HtmlTableRow tr = new HtmlTableRow();
HtmlTableCell td = new HtmlTableCell();
td.InnerText = parameterInfo.Name;
tr.Cells.Add(td);
td = new HtmlTableCell();
td.InnerText = parameterInfo.ParameterType.ToString();
tr.Cells.Add(td);
td = new HtmlTableCell();
TextBox txt = new TextBox();
txt.ID = "txt" + parameterInfo.Name;
txt.CssClass = "Required";
txt.Width = Unit.Pixel(200);
td.Controls.Add(txt);
tr.Cells.Add(td);
if (i % 2 == 0)
tr.Attributes.Add("class", "EvenRow");
else
tr.Attributes.Add("class", "OddRow");
i++;
Input.Controls.Add(tr);
}
}
protected void btnSubmit_Click(object sender, EventArgs e)
{
Type serviceType = typeof(Services);
object iBaseType = Activator.CreateInstance(serviceType);
int parmCount = 0;
MethodInfo method = serviceType.GetMethod(Request["op"]);
parmCount = method.GetParameters().Length;
object[] arguments = new object[parmCount];
int i = 0;
foreach (ParameterInfo parameterInfo in method.GetParameters())
{
TextBox txt = (TextBox)Input.FindControl("txt" + parameterInfo.Name);
if (parameterInfo.ParameterType == typeof(String))
arguments[i] = txt.Text;
// We need some sort of sanity check here
else if (parameterInfo.ParameterType == typeof(Int32))
arguments[i] = System.Convert.ToInt32(txt.Text);
i++;
}
object response = serviceType.InvokeMember(Request["op"], BindingFlags.InvokeMethod | BindingFlags.Default, null, iBaseType, arguments);
Type responseType = response.GetType();
i = 0;
foreach (PropertyInfo pi in responseType.GetProperties())
{
if (pi.CanRead)
{
//pi.GetValue(responseType, null);
HtmlTableRow tr = new HtmlTableRow();
HtmlTableCell td = new HtmlTableCell();
td.InnerText = pi.Name;
tr.Cells.Add(td);
td = new HtmlTableCell();
object o = pi.GetValue(response, null);
if (o != null)
{
td.InnerText = o.ToString();
}
else
td.InnerText = "(null)";
tr.Cells.Add(td);
if (i % 2 == 0)
tr.Attributes.Add("class", "EvenRow");
else
tr.Attributes.Add("class", "OddRow");
i++;
PHResponse.Controls.Add(tr);
}
}
}
}

No comments:
Post a Comment