This article uses the WebSphere SDK for Web Services V5.1 (WSDK 5.1) for the server side of the story. For the Windows client application, the article uses Visual Basic .Net, although other client languages can be used in a similar way, such as Visual C. In the simple example presented here, we use the Sample1 Web Service shipped with the WSDK 5.1.
The .Net clients shown in this article were developed using Microsoft Visual Basic .Net Development Environment 2003 (Version 7.1.3088) together with Microsoft .Net Framework 1.1 (Version 1.1.4322).
This article is not intended as a lesson in Visual Basic .Net itself, but concentrates on how you can develop a web services client, in this case using Visual Basic .Net, which is able to invoke a Java-based web service developed in WSDK.
Note that both the web service examples utilize the Wrapped Doc/Literal style of WSDL. This is an important point, in that other styles of WSDL (for example, RPC style) were not acceptable to the versions of the Microsoft tools used.
Prerequisites for this article
If you would like to follow this article by building the applications for yourself, you will need a Windows system on which you have installed Microsoft Visual Basic .Net (or Microsoft Visual Studio .Net). You will also need a system (Windows or Linux) on which you have installed WebSphere SDK for Web Services V5.1.
You can install the Windows version of WSDK 5.1 and the Microsoft tools onto the same Windows system, if the system is big enough in terms of RAM and disk storage. Alternatively, WSDK 5.1 can be installed on a separate system from the Microsoft tools, with the systems connected over a network running TCP/IP.
Create a new Windows Application project in Microsoft Visual Basic .Net -- we gave it the name Sample1WebService.
Create a new form and add three text boxes with labels and two buttons to the form so that it looks something similar to Figure 1.
Figure 1. Sample Visual Basic Application form
A couple of points to note about the form:
- The Web host text box is given a default value of 'localhost'. This is done assuming that WSDK is installed on the same machine as Visual Basic .Net. Including this text box as a variable allows the user the chance to easily run the client application in an installation where the machine hosting the web service is running on a different machine, by supplying the network name of the machine. We'll show later where this value is included in the call to the web service.
- We've included some code that validates the text in the 'Enter your name...’ text box, so that the web service cannot be invoked if this is blank. This code is included in the code which handles the click event for the Invoke Service button and its purpose is to avoid a null pointer exception when the web service is invoked with an empty string. The text box validation is shown in Listing 1.
Listing 1. Text box validation
If TextBox1.Text <> "" Then
... Web Service invocation code goes here ...
Else
MessageBox.Show("You must enter a name...", "User Input Error",
MessageBoxButtons.OK, MessageBoxIcon.Stop)
TextBox1.Focus()
End If
|
Considering the overhead of invoking a web service, it makes sense to perform basic input validation prior to making any such call.
Now we have the basic client GUI for invoking the Sample1WebService. We now need to include the code to invoke the web service and handle the returned result.
Referencing and invoking the web service
We assume that the WSDK run time server is started and that the WSDK Sample1WebService is running and enabled (refer to the WSDK documentation for detail about performing these tasks).
In the Solution Explorer Window of Microsoft Development Environment, right click the Sample1WebService project name and select Add Web Reference... and, in the dialog you are presented with, complete the URL field with the address of the WSDL file for the WSDK Sample1WebService, which is:
http://localhost:6080/Sample1WebService/services/Sample1?wsdl |
as shown in Figure 2.
Figure 2. Panel to add a new Web Reference
Once you enter the URL of the WSDL document and press the Go button, the IDE reads the document and is able to work out that this web service exposes one method, getGreeting() and that this method takes a string as a parameter and returns a string. Click the Add Reference button to confirm this reference for our client application project.
The next step we need to make is to declare a variable that will invoke the Sample1WebService when a user clicks Button1 (the one labeled Invoke Service !). In the Microsoft Development Environment, change to the Class View, which now contains a tree description of the Sample1WebService.
Expand the class Sample1WebService that you will find in this view, followed by {}Sample1WebService, followed by {}localhost and {}Sample1InterfaceService so that you end up with a fully expanded tree as shown in Figure 3.
Figure 3. Class tree view of Sample1WebService
The declaration can be typed into the code window for the Invoke Service button click event as shown in Listing 2.
Listing 2. Invoke Servicebutton click event
Dim WS As New Sample1WebService.localhost.Sample1InterfaceService(HostTextBox.Text) |
Or, by typing the code shown in Listing 3 and then clicking and dragging the Sample1InterfaceService icon from the Class View window to the above line in the code window. Then add HostTextBox.Text parameter which is the reference to the host machine name which comes from the first text box. This defaults to localhost or it can be edited by the user to refer to another machine as appropriate.
Listing 3. Alternative code
Dim WS As New |
Then invoke the call to the web service within a try/catch block as shown in Listing 4. This code invokes the web service using the content of the Text Box labeled EnterYour name.
Listing 4. Try/catch block
Try
TextBox2.Text = WS.getGreeting(TextBox1.Text)
Button1.Text = "&Clear"
Catch ex As Exception
MessageBox.Show("A problem occurred executing the call to the Web Service",
"Application Server Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
End Try
|
You may recall from the panel design that we allow the user to specify a different host other than the default, localhost. To get this to work, go to the Class View of the Microsoft Development Environment (see Figure 3), expand Sample1WebService, then localhost and Sample1InterfaceService. Double click the icon by New(ByVal String) method. This opens the code editor with the cursor in the relevant New() method. Then, below the existing call to MyBase.New(), add the code in Listing 5 which will allow the web service to be initialized on the host machine entered by the user.
Listing 5. Initialize web service
REM Allow the user to specify a remote host for the Web Service Dim lhost As String If host = "" Or host = "localhost" Then lhost = "localhost" Else lhost = host End If Me.Url = "http://" + lhost + ":6080/Sample1WebService/services/Sample1" |
Finally, to aid usability, once the web service has been invoked, we change the text on Button 1 to show Clear. Then at the start of the Button 1 click handler, we check the text displayed and, if it is Clear, we clear all the existing text in the text boxes and set the button text to Invoke Service.
The full code for the Button 1 Click Handler is shown in Listing 6.
Listing 6. Full code for Button 1 Click Handler
If Button1.Text = "&Invoke Service" Then
REM Avoid null pointer exception by ensuring user has input a value
If TextBox1.Text <> "" Then
Dim WS As New Sample1WebService.localhost.Sample1InterfaceService(HostTextBox.Text)
Try
TextBox2.Text = WS.getGreeting(TextBox1.Text)
Button1.Text = "&Clear"
Catch ex As Exception
MessageBox.Show("A problem occurred executing the call to the Web Service",
"Application Server Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
End Try
Else
MessageBox.Show("You must enter a name...", "User Input Error",
MessageBoxButtons.OK, MessageBoxIcon.Stop)
TextBox1.Focus()
End If
ElseIf Button1.Text = "&Clear" Then
TextBox1.Text = ""
TextBox2.Text = ""
Button1.Text = "&Invoke Service"
TextBox1.Focus()
End If
|
And when the client application is saved and then started, it will look like Figure 4.
Figure 4. Client Application running
Developing a more complex web service client
This part of the article extends the principles learned in the above scenario. This client will invoke a web service that accepts a string parameter and searches an address book, returning a complex type called AddressBook if a match is found against the parameter string (clearly in a real world implementation, additional business logic and/or parameters would be necessary, however, for the purpose of this article, a single string keeps things simple).
First, let’s look at the Java platform web service which will run on the WSDK server. It consists of the main service class AddressBookBean (Listing 7), which has a single method getAddressFromName. The method uses a complex data type Address (Listing 8) which in turn uses the data types Phone (Listing 9) and StateType (Listing 10).
Listing 7. AddressBookBean class
package com.ibm.wsdk.demos.addr;
import java.util.Hashtable;
import java.util.Map;
public class AddressBookBean {
private Map addresses = new Hashtable();
public AddressBookBean()
{
// populate the AddressBook
// Andy
Address addrAndy = new Address();
Phone phoneNumberAndy = new Phone();
addrAndy.setStreetNum(57);
addrAndy.setStreetName("Mount Pleasant Street");
addrAndy.setCity("Metropolis");
addrAndy.setState(StateType.IN);
addrAndy.setZip(47907);
phoneNumberAndy.setAreaCode(765);
phoneNumberAndy.setExchange("555");
phoneNumberAndy.setNumber("4901");
addrAndy.setPhoneNumber(phoneNumberAndy);
addresses.put("andy", addrAndy);
// Nick
Address addrNick = new Address();
Phone phoneNumberNick = new Phone();
addrNick.setStreetNum(64);
addrNick.setStreetName("Zoo Lane");
addrNick.setCity("BigSmoke");
addrNick.setState(StateType.OH);
addrNick.setZip(67559);
phoneNumberNick.setAreaCode(881);
phoneNumberNick.setExchange("560");
phoneNumberNick.setNumber("9675");
addrNick.setPhoneNumber(phoneNumberNick);
addresses.put("nick", addrNick);
/**
* Retrieve an entry from the AddressBook.
*
*@param name the name of the entry to look up.
*@return the AddressBook entry matching name or null if none.
*/
public Address getAddressFromName(java.lang.String name)
{
return(Address) this.addresses.get(name.toLowerCase());
}
}
|
Listing 8. Address class
package com.ibm.wsdk.demos.addr;
public class Address implements java.io.Serializable {
private int streetNum;
private java.lang.String streetName;
private java.lang.String city;
private StateType state;
private int zip;
private Phone phoneNumber;
public Address() {
}
public Address(int streetNum,
java.lang.String streetName,
java.lang.String city,
StateType state,
int zip,
Phone phoneNumber) {
this.streetNum = streetNum;
this.streetName = streetName;
this.city = city;
this.state = state;
this.zip = zip;
this.phoneNumber = phoneNumber;
}
public int getStreetNum() { return streetNum; }
public void setStreetNum(int streetNum) {
this.streetNum = streetNum;
}
public java.lang.String getStreetName() { return streetName; }
public void setStreetName(java.lang.String streetName) {
this.streetName = streetName;
}
public java.lang.String getCity() { return city; }
public void setCity(java.lang.String city) {
this.city = city;
}
public StateType getState() { return state; }
public void setState(StateType state) {
this.state = state;
}
public int getZip() { return zip; }
public void setZip(int zip) {
this.zip = zip;
}
public Phone getPhoneNumber() { return phoneNumber; }
public void setPhoneNumber(Phone phoneNumber) {
this.phoneNumber = phoneNumber;
}
}
|
Listing 9. Phone class
package com.ibm.wsdk.demos.addr;
public class Phone implements java.io.Serializable {
private int areaCode;
private java.lang.String exchange;
private java.lang.String number;
public Phone() {
}
public Phone(int areaCode, java.lang.String exchange, java.lang.String number) {
this.areaCode = areaCode;
this.exchange = exchange;
this.number = number;
}
public int getAreaCode() { return areaCode; }
public void setAreaCode(int areaCode) {
this.areaCode = areaCode;
}
public java.lang.String getExchange() { return exchange; }
public void setExchange(java.lang.String exchange) {
this.exchange = exchange;
}
public java.lang.String getNumber() { return number; }
public void setNumber(java.lang.String number) {
this.number = number;
}
}
|
Listing 10. StateType class
package com.ibm.wsdk.demos.addr;
public class StateType implements java.io.Serializable {
private java.lang.String _value_;
private static java.util.HashMap _table_ = new java.util.HashMap();
// Constructor
protected StateType(java.lang.String value) {
_value_ = value;
_table_.put(_value_,this);
};
public static final java.lang.String _TX = "TX";
public static final java.lang.String _IN = "IN";
public static final java.lang.String _OH = "OH";
public static final StateType TX = new StateType(_TX);
public static final StateType IN = new StateType(_IN);
public static final StateType OH = new StateType(_OH);
public java.lang.String getValue() { return _value_;}
public static StateType fromValue(java.lang.String value)
throws java.lang.IllegalStateException {
StateType enum = (StateType)
_table_.get(value);
if (enum==null) throw new java.lang.IllegalStateException();
return enum;
}
public static StateType fromString(String value)
throws java.lang.IllegalStateException {
return fromValue(value);
}
public boolean equals(Object obj) {return (obj == this);}
public int hashCode() { return toString().hashCode();}
public String toString() { return _value_;}
}
|
To get the AddressBookBean web service running on your WSDK server, place the four Java classes into a suitable directory for WSDK -- note that the code uses the package name com.ibm.wsdk.demos.addr so you will need to use a directory name ending in com\ibm\wsdk\demos\addr. Compile the code with javac and then run the WSDK Bean2WebService tool for the AddressBookBean class, specifying the deploy option to deploy the web service to the runtime server.
The WSDL for the AddressBookBean web service will appear as shown in Listing 11.
Listing 11. WSDL for AddressBookBean web service
<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions targetNamespace="http://addr.demos.wsdk.ibm.com"
xmlns="http://schemas.xmlsoap.org/wsdl/"
xmlns:apachesoap="http://xml.apache.org/xml-soap"
xmlns:impl="http://addr.demos.wsdk.ibm.com"
xmlns:intf="http://addr.demos.wsdk.ibm.com"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:wsdlsoap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<wsdl:types>
<schema elementFormDefault="qualified" targetNamespace=
"http://addr.demos.wsdk.ibm.com"
xmlns="http://www.w3.org/2001/XMLSchema">
<element name="getAddressFromName">
<complexType>
<sequence>
<element name="name" nillable="true" type="xsd:string"/>
</sequence>
</complexType>
</element>
<complexType name="Address">
<sequence>
<element name="streetNum" type="xsd:int"/>
<element name="streetName" nillable="true" type="xsd:string"/>
<element name="city" nillable="true" type="xsd:string"/>
<element name="state" nillable="true" type="impl:StateType"/>
<element name="zip" type="xsd:int"/>
<element name="phoneNumber" nillable="true" type="impl:Phone"/>
</sequence>
</complexType>
<simpleType name="StateType">
<restriction base="xsd:string">
<enumeration value="TX"/>
<enumeration value="IN"/>
<enumeration value="OH"/>
</restriction>
</simpleType>
<complexType name="Phone">
<sequence>
<element name="areaCode" type="xsd:int"/>
<element name="exchange" nillable="true" type="xsd:string"/>
<element name="number" nillable="true" type="xsd:string"/>
</sequence>
</complexType>
<element name="getAddressFromNameResponse">
<complexType>
<sequence>
<element name="getAddressFromNameReturn" nillable="true" type="impl:Address"/>
</sequence>
</complexType>
</element>
</schema>
</wsdl:types>
<wsdl:message name="getAddressFromNameRequest">
<wsdl:part element="intf:getAddressFromName" name="parameters"/>
</wsdl:message>
<wsdl:message name="getAddressFromNameResponse">
<wsdl:part element="intf:getAddressFromNameResponse" name="parameters"/>
</wsdl:message>
<wsdl:portType name="AddressBookBean">
<wsdl:operation name="getAddressFromName">
<wsdl:input message="intf:getAddressFromNameRequest" name=
"getAddressFromNameRequest"/>
<wsdl:output message="intf:getAddressFromNameResponse" name=
"getAddressFromNameResponse"/>
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name="AddressBookBeanSoapBinding" type=
"intf:AddressBookBean">
<wsdlsoap:binding style="document" transport=
"http://schemas.xmlsoap.org/soap/http"/>
<wsdl:operation name="getAddressFromName">
<wsdlsoap:operation soapAction=""/>
<wsdl:input name="getAddressFromNameRequest">
<wsdlsoap:body use="literal"/>
</wsdl:input>
<wsdl:output name="getAddressFromNameResponse">
<wsdlsoap:body use="literal"/>
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
<wsdl:service name="AddressBookBeanService">
<wsdl:port binding="intf:AddressBookBeanSoapBinding" name=
"AddressBookBean">
<wsdlsoap:address location=
"http://localhost:6080/AddressBook/services/AddressBookBean"/>
</wsdl:port>
</wsdl:service>
</wsdl:definitions>
|
The WSDL will be accessible for the Microsoft .Net tools at the URL:
http://localhost:6080/AddressBook/services/AddressBookBean |
In the Microsoft Visual Basic .Net environment, you should create a new project for the client application for the AddressBookBean. Use the Solution Explorer Window to add a Web Reference to the AddressBookBean web service, using the WSDL URL for the AddressBookBean web service. This will create a Class View for the AddressBookBean web service as shown in Figure 5.
Figure 5. Class View for AddressBookBean web service
In the Class View, you will find not only method calls for the web service, including getAddressfromName(...), but also definitions for the complex types Address, Phone, and StateType. Note that these complex types are equivalent to the types in the Java platform web service code -- but that Microsoft tools built the .Net versions from the WSDL file, not by knowing anything directly about the Java code.
For the Microsoft .Net client, you should develop a client application window with similar text boxes and buttons to those shown in Figure 6. At the top is a Text Box where the user enters a name to search in the phone book as a String. The lower part of the window contains a set of fields which will display the Address Book details as returned by the AddressBookBean web service.
Figure 6. Client for Complex web service
The initial logic behind the Search button is very similar to the first example: we check that the text box doesn't contain an empty string prior to invoking the web service.
You need to declare and initialize a variable for the web service, as shown in Listing 12. You can use the Class View window to help with this.
Listing 12. Declare and initialize variable for web service
Dim AddressService As New WebService.localhost.AddressBookBeanService |
In this example, we also need to declare and initialize a variable to handle the Address complex type returned by the Web Service, shown in Listing 13.
Listing 13. Declare and initialize variable to handle Address
Dim Address1 As WebService.localhost.Address |
We then invoke the Address Book web service and populate the Address variable with the complex type data returned upon a match, shown in Listing 14.
Listing 14. Invoke web service
REM Call the web service and get the details if there's a match Address1 = AddressService.getAddressFromName(TextBox1.Text) |
All that then remains is to populate the fields in the client window, by setting their Text values with the data returned from the web service, shown in Listing 15.
Listing 15. Populate fields
TextBox2.Text = Address1.streetNum TextBox3.Text = Address1.streetName TextBox4.Text = Address1.city TextBox5.Text = Address1.state.ToString TextBox6.Text = Address1.zip REM Now handle the telephone number as complex type TextBox7.Text = Address1.phoneNumber.exchange TextBox8.Text = Address1.phoneNumber.areaCode TextBox9.Text = Address1.phoneNumber.number |
The relevant point to note is the way in which the state property is de-referenced using the ToString method. Also notice the way in which the elements of the phoneNumber property are de-referenced.
For completeness, the full code for our implementation of the Button 1 Click Handler for the client application is shown in Listing 16.
Listing 16. Full code
REM Depending on the current text displayed on the button, take appropriate action
If Button1.Text = "&Search" Then
REM Avoid a null pointer exception by not searching for an empty string
If TextBox1.Text <> "" Then
Try
Dim AddressService As New WebService.localhost.AddressBookBeanService
Dim Address1 As WebService.localhost.Address
REM Call the web service and get the details if there's a match
Address1 = AddressService.getAddressFromName(TextBox1.Text)
REM Populate the address fields on the form
TextBox2.Text = Address1.streetNum
TextBox3.Text = Address1.streetName
TextBox4.Text = Address1.city
TextBox5.Text = Address1.state.ToString
TextBox6.Text = Address1.zip
REM Now handle the telephone number as complex type
TextBox7.Text = Address1.phoneNumber.exchange
TextBox8.Text = Address1.phoneNumber.areaCode
TextBox9.Text = Address1.phoneNumber.number
REM Change the button caption to the next task
Button1.Text = "&Clear"
Catch ex As Exception
MessageBox.Show("An error occurred executing the Web Service",
"Application Server Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
End Try
Else
MessageBox.Show("You must enter a name to search for", "No Name Supplied",
MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
TextBox1.Focus()
End If
ElseIf Button1.Text = "&Clear" Then
REM Do the obvious!
TextBox1.Text = ""
TextBox2.Text = ""
TextBox3.Text = ""
TextBox4.Text = ""
TextBox5.Text = ""
TextBox6.Text = ""
TextBox7.Text = ""
TextBox8.Text = ""
TextBox9.Text = ""
Button1.Text = "&Search"
TextBox1.Focus()
End If
|
The result of invoking our Address Book web service with our second .Net client application is shown in Figure 7.
Figure 7. Client Application after calling web service
In this article, we built a .Net client for a simple Hello World web service that accepts a string as a parameter and returns another string to the client. We examined how to use an existing web service, running on the application server provided within the WSDK, from within the Microsoft Development Environment. We then developed and tested a .Net client invoking the web service and displaying the results in the GUI application.
We then built a .Net client for a web service which involves a complex type as the return data from a second web service. We saw how to de-reference the properties of the complex type which included another class (PhoneNumber) and an enumerated type (StateType).
Both the examples in this tutorial utilized Wrapped Doc/Lit WSDL style to ensure interoperability between the .Net client application and the WebSphere-based web service.
- You can find more information about WebSphere SDK for Web Services, including the download package.
- You can also find more information about WebSphere Application Server.
- Information about the Web Services Interoperability Organization, plus the definition of the Basic Profile 1.0, can be found on the WS-I web site.
Andy Clarke is the Development Team Lead for the WSDK in Hursley, England, a role he has fulfilled through the release of WSDK versions 5.0.1 and 5.1. Andy graduated from the University of Central Lancashire in 2000 with a BSc in Computing (1st Class Hons.) and joined the Java Development Team at IBM in May 2001. You can contact Andy at ClarkeAJ at uk.ibm.com.
Dr. Mike Edwards is a Strategic Planner in the IBM Java Technology Center in Hursley, England. He is responsible for technical planning for future IBM products including the IBM Java SDKs and for Web services related products. Before working on WSDK, Mike was involved in the planning of Java SDK 1.4.0 and was a member of the Expert Group for JSR 059 which defined the specification for J2SE 1.4.0. Mike is also a member of the Expert Group for JSR 166, Concurrency Utilities, which will be part of J2SE 1.5.0. Mike received his Ph.D. in Elementary Particle Physics from Birmingham University. You can contact Mike at mike_edwards at uk.ibm.com.




