10. Web Services
10.1. Introduction
In the previous chapter, we presented several TCP/IP client-server applications. Since clients and the server exchange lines of text, they can be written in any language. The client simply needs to know the communication protocol expected by the server. Web services are TCP/IP server applications with the following characteristics:
- They are hosted by web servers, and the client-server communication protocol is therefore HTTP (HyperText Transfer Protocol), a protocol running over TCP/IP.
- The Web service has a standard communication protocol regardless of the service provided. A Web service offers various services S1, S2, .., Sn. Each of them expects parameters provided by the client and returns a result to the client. For each service, the client needs to know:
- the exact name of the service Si
- the list of parameters to be provided and their types
- the type of result returned by the service
Once these elements are known, the client-server interaction follows the same format regardless of the web service being queried. Client code is thus standardized.
- For security reasons related to attacks originating from the Internet, many organizations maintain private networks and open only certain ports on their servers to the Internet: primarily port 80 for the web service. All other ports are locked. Consequently, client-server applications such as those presented in the previous chapter are built within the private network (intranet) and are generally not accessible from the outside. Hosting a service on a web server makes it accessible to the entire Internet community.
- The web service can be modeled as a remote object. The services offered then become methods of this object. A client can access this remote object as if it were local. This hides the entire network communication layer and allows for the development of a client independent of that layer. If the layer changes, the client does not need to be modified. This is a huge advantage and likely the main benefit of web services.
- As with the TCP/IP client-server applications presented in the previous chapter, the client and server can be written in any language. They exchange lines of text. These consist of two parts:
- the headers required by the HTTP protocol
- the message body. For a server response to the client, the body is in XML (eXtensible Markup Language) format. For a client request to the server, the message body can take several forms, including XML. The client’s XML request may use a specific format called SOAP (Simple Object Access Protocol). In this case, the server’s response also follows the SOAP format.
10.2. Browsers and XML
Web services send XML to their clients. Browsers may react differently when receiving this XML stream. Internet Explorer has a predefined style sheet that allows it to display the XML. Netscape Communicator, however, does not have this style sheet and does not display the received XML code. You must view the source code of the received page to access the XML. Here is an example. For the following XML code:
<?xml version="1.0" encoding="utf-8"?>
<string xmlns="st.istia.univ-angers.fr">hello again!</string>
Internet Explorer will display the following page:

while Netscape Navigator will display:

If we view the source code of the page received by Netscape, we get:

Netscape did indeed receive the same content as Internet Explorer, but it displayed it differently. From here on, we will use Internet Explorer for the screenshots.
10.3. A First Web Service
We will explore web services through a very simple example available in three versions.
10.3.1. Version 1
For this first version, we’ll use VS.NET, which has the advantage of being able to generate a web service skeleton that’s immediately operational. Once we understand this architecture, we’ll be able to start working independently. That will be the focus of the following versions.
Using VS.NET, let’s create a new project via the [File/New/Project] option:

Note the following points:
- the project type is Visual Basic (left pane)
- the project template is ASP.NET Web Service (right pane)
- the location is flexible. Here, the web service will be hosted by a local IIS web server. Its URL will therefore be http://localhost/[path] where [path] is to be defined. Here, we choose the path http://localhost/polyvbnet/demo. VS.NET will then create a folder for this project. Where? The IIS server has a root directory for the web document tree it serves. Let’s call this root <IISroot>. It corresponds to the URL http://localhost. We can deduce that the URL http://localhost/polyvbnet/demo will be associated with the folder <IISroot>/polyvbnet/demo. <IISroot> is normally the \inetpub\wwwroot folder on the drive where IIS was installed. In our example, this is drive E. The folder created by VS.NET is therefore the e:\inetpub\wwwroot\polyvbnet\demo folder:

As always, there is an abundance of folders created. They are not always useful. We will only explain the ones we need at a given time. VS.NET has created a project:

We find some of the files present in the project’s physical folder. The most interesting one for us is the file with the .asmx extension. This is the extension for web services. A web service is managed by VS.NET as a Windows application, i.e., an application that has a graphical user interface and code to manage it. That is why we have a design window:

A web service normally does not have a graphical user interface. It represents an object that can be called remotely. It has methods, and applications call these methods. We will therefore view it as a classic object with the unique feature that it can be instantiated remotely over the network. Therefore, we will not use the design window provided by VS.NET. Instead, let’s focus on the service’s code using the View/Code option:

Several points are worth noting:
- The file is named Service1.asmx.vb, not Service1.asmx. We’ll return to the contents of the Service1.asmx file a little later.
- We see a code window similar to the one we had when building Windows applications with VS.NET
The code generated by VS.NET is as follows:
Imports System.Web.Services
<System.Web.Services.WebService(Namespace := "http://tempuri.org/demo/Service1")> _
Public Class Service1
Inherits System.Web.Services.WebService
#Region "Code generated by the Web Services Designer"
Public Sub New()
MyBase.New()
'This call is required by the Web Services Designer.
InitializeComponent()
'Add your initialization code after the InitializeComponent() call
End Sub
'Required by the Web Services Designer
Private components As System.ComponentModel.IContainer
'NOTE: The following procedure is required by the Web Services Designer
'It can be modified using the Web Services Designer.
'Do not modify it using the code editor.
<System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
components = New System.ComponentModel.Container()
End Sub
Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
'CODEGEN: This procedure is required by the Web Services Designer
'Do not modify it using the code editor.
If disposing Then
If Not (components Is Nothing) Then
components.Dispose()
End If
End If
MyBase.Dispose(disposing)
End Sub
#End Region
' WEB SERVICE EXAMPLE
' The HelloWorld() service example returns the string "Hello World".
' To build, do not comment out the following lines, then save and build the project.
' To test this web service, make sure the .asmx file is the start page
' and press F5.
'
'<WebMethod()> Public Function HelloWorld() As String
' HelloWorld = "Hello World"
' End Function
End Class
First, note that we have a class here, the Service1 class, which derives from the WebService class:
This leads us to import the System.Web.Services namespace:
Imports System.Web.Services
The class declaration is preceded by a compilation attribute:
<System.Web.Services.WebService(Namespace := "http://tempuri.org/demo/Service1")> _
Public Class Service1
Inherits System.Web.Services.WebService
The System.Web.Services.WebService() attribute indicates that the following class is a web service. This attribute accepts various parameters, including one called NameSpace. It is used to place the web service in a namespace. Indeed, one can imagine that there are several web services named "weather" in the world. We need a way to differentiate them. The namespace makes this possible. One could be named [namespace1].weather and another [namespace2].weather. This is a concept analogous to class namespaces. VS.NET automatically generated code and placed it in a region of the source:
#Region "Code generated by the Web Services Designer"
If we look at this code, it’s the same code the designer generated when we built Windows applications. This is code we can simply delete if we don’t have a graphical user interface, which will be the case for web services.
The class concludes with an example of what a web service might look like:
#End Region
' EXAMPLE OF A WEB SERVICE
' The HelloWorld() service example returns the string "Hello World".
' To generate, do not comment out the following lines, then save and build the project.
' To test this web service, make sure the .asmx file is the start page
' and press F5.
'
'<WebMethod()> Public Function HelloWorld() As String
' HelloWorld = "Hello World"
' End Function
Based on what has just been said, we clean up the code so that it becomes the following:
Imports System.Web.Services
<System.Web.Services.WebService(Namespace:="st.istia.univ-angers.fr")> _
Public Class Hello
Inherits System.Web.Services.WebService
<WebMethod()> Public Function Hello() As String
Return "Hello!"
End Function
End Class
Now we have a clearer picture.
- A web service is a class derived from the WebService class
- The class is qualified by the attribute <System.Web.Services.WebService(Namespace:="st.istia.univ-angers.fr")>. We therefore place our service in the st.istia.univ-angers.fr namespace.
- The class’s methods are qualified by a <WebMethod()> attribute indicating that we are dealing with a method that can be called remotely over the network
The class providing our web service is therefore called Bonjour and has a single method, also named Bonjour, which returns a string. We are ready for an initial test.
- Start the IIS web server if you haven't already
- Use the Debug/Run Without Debugging option. VS.NET
VS.NET will then compile the entire application, launch a browser (often Internet Explorer if it is available), and display the URL http://localhost/polyvbnet/demo/Service1.asmx:

Why the URL http://localhost/polyvbnet/demo/Service1.asmx? Because it was the only .asmx file in the project:

If there had been multiple .asmx files, we would have had to specify which one should be executed first. This is done by right-clicking on the relevant .asmx file and selecting the [Set as Start Page] option.

You might be interested in knowing what the service1.asmx file contains. In fact, with VS.NET, we worked on the service1.asmx.vb file and not on the service1.asmx file. This file is located in the project folder:

Let’s open it with a text editor (Notepad or another). We get the following content:
The file contains a simple directive for the IIS server indicating:
- that this is a web service (WebService keyword)
- that the language of this service's class is Visual Basic (Language="vb")
- that the source code for this class is located in the file Service1.asmx.vb (Codebehind="Service1.asmx.vb")
- that the class implementing the service is called demo.Bonjour (Class="demo.Bonjour"). Note that VS.NET has placed the Bonjour class in the demo namespace, which is also the name of the project.
Let’s return to the page accessed at the URL http://localhost/polyvbnet/demo/Service1.asmx:

Who wrote the HTML code for the page above? Not us—we know that. It’s IIS, which presents web services in a standard way. This page offers us two links. Let’s follow the first one [Service Description]:

Oops... that’s some pretty obscure XML. Note, however, the URL
http://localhost/polyvbnet/demo/Service1.asmx?WSDL. Open a browser and type this URL directly. You’ll get the same result as before. So, remember that the URL http://serviceweb?WSDL provides access to the XML description of the web service. Let’s go back to the home page and click the [Hello] link. Remember that Hello is a method of the web service. If there had been multiple methods, they would all have been listed here. We get the following new page:

We have intentionally truncated the resulting page to keep our demonstration concise. Note the URL again:
If we type this URL directly into a browser, we’ll get the same result as above. We’re prompted to use the [Call] button. Let’s do that. We get a new page:

This is XML again. It contains two pieces of information that were present in our web service:
- the namespace st.istia.univ-angers.fr of our service
<System.Web.Services.WebService(Namespace:="st.istia.univ-angers.fr")>
- the value returned by the Bonjour method:
Return "hello!"
What have we learned?
- how to write an S web service
- how to call it
We will now look at writing a web service without using VS.NET.
10.3.2. Version 2
In the previous example, VS.NET did a lot of things on its own. Is it possible to build a web service without this tool? The answer is yes, and we’ll show you how now. Using a text editor, we’ll build the following web service:
Imports System.Web.Services
<System.Web.Services.WebService(Namespace:="st.istia.univ-angers.fr")> _
Public Class Hello2
Inherits System.Web.Services.WebService
<WebMethod()> Public Function getBonjour() As String
Return "hello again!"
End Function
End Class
The class is called Bonjour2 and has a method called getBonjour. It is located in the demo2.vb file, which is itself located in the IIS server directory structure in the folder E:\Inetpub\wwwroot\polyvbnet\demo2. It is a standard VB.NET class that can therefore be compiled:
dos>vbc /out:demo2 /t:library /r:system.dll /r:system.web.services.dll demo2.vb
dos>dir
03/02/2004 18:04 286 demo2.vb
03/02/2004 18:10 77 demo2.asmx
03/02/2004 18:12 3,072 demo2.dll
We place the demo2.dll assembly in a bin folder (this name is required):
We now create the demo2.asmx file. This is the file that will be called by web clients. Its contents are as follows:
We have already encountered this directive. It indicates that:
- the web service class is called Bonjour2 and is located in the demo2.dll assembly. IIS will look for this assembly in various locations, including the web service’s bin folder. That is why we placed the demo2.dll assembly there.
Now we can perform various tests. We make sure IIS is running and request the URL http://localhost/polyvbnet/demo2/demo2.asmx in a browser:

Then the URL http://localhost/polyvbnet/demo2/demo2.asmx?WSDL

Then the URL http://localhost/polyvbnet/demo2/demo2.asmx?op=getBonjour, where getBonjour is the name of the only method in our web service:

We use the [Call] button above:

We successfully obtain the result of the call to the web service’s getBonjour method. We now know how to build a web service without Visual Studio .NET. From here on, we will ignore the specifics of how the web service is built and focus solely on the fundamental files.
10.3.3. Version 3
The two previous versions of the [Hello] web service used two files:
- an .asmx file, the web service's entry point
- a .vb file, the web service source code
Here, we show that a single .asmx file is sufficient. The code for the demo3.asmx service is as follows:
We can see that the service's source code is now directly in the source file demo3.asmx. The directive
no longer references a class in an external assembly, but a class located in the same source file. Let’s place this file in the <IISroot>\polyvbnet\demo3 folder:

Let’s start IIS and request the URL http://localhost/polyvbnet/demo3/demo3.asmx:

We notice a significant difference from the previous version: we did not have to compile the service’s VB code. IIS performed this compilation itself using the VB.NET compiler installed on the same machine. It then delivered the page. If there is a compilation error, IIS will report it:

10.3.4. Version 4
Here we focus on the IIS server configuration. Until now, we have always placed our web services in the <IISroot> root directory of the IIS server, here [e:\inetpub\wwwroot]. We demonstrate here that we can place the web service anywhere. This is done using IIS virtual directories. Let’s place our service in the following directory:

The folder [D:\data\devel\vbnet\poly\chap9\demo3] is not located in the IIS server directory tree. We must specify this to IIS by creating a virtual IIS folder. Let’s launch IIS and select the [Advanced] option below:

A list of virtual directories is displayed. We won’t dwell on this list. We create a new virtual directory using the [Add] button above:

Using the [Browse] button, we select the physical folder containing the web service, in this case the folder [D:\data\devel\vbnet\poly\chap9\demo3]. We give this folder a logical (virtual) name: [virdemo3]. This means that the documents inside the physical folder [D:\data\devel\vbnet\poly\chap9\demo3] will be accessible on the network via the URL [http://<machine>/virdemo3]. The dialog box above contains other settings that we leave as is. We click OK. The new virtual folder appears in the list of virtual folders in IIS:

Now, we open a browser and request the URL [http://localhost/virdemo3/demo3.asmx]. We get the same result as before:

10.3.5. Conclusion
We have demonstrated several ways to create a web service. Moving forward, we will use the method from version 3 to create the service and method 4 for its deployment. This way, we will not need VS.NET. Nevertheless, it is worth noting the benefits of using VS.NET for the debugging assistance it provides. There are free tools available for developing web applications, notably the WebMatrix product sponsored by Microsoft, which can be found at the URL [http://www.asp.net/webmatrix]. It is an excellent tool for getting started with web programming without any upfront investment.
10.4. A web service for operations
Consider a web service that offers five functions:
- add(a,b), which returns a+b
- subtract(a,b), which returns a-b
- multiply(a,b), which returns a*b
- divide(a,b), which returns a/b
- doAll(a,b), which returns the array [a+b, a-b, a*b, a/b]
The VB.NET code for this service is as follows:
<%@ WebService language="VB" class=operations %>
imports system.web.services
<WebService(Namespace:="st.istia.univ-angers.fr")> _
Public Class operations
Inherits WebService
<WebMethod> _
Function add(a As Double, b As Double) As Double
Return a + b
End Function
<WebMethod> _
Function subtract(a As Double, b As Double) As Double
Return a - b
End Function
<WebMethod> _
Function multiply(a As Double, b As Double) As Double
Return a * b
End Function
<WebMethod> _
Function divide(a As Double, b As Double) As Double
Return a / b
End Function
<WebMethod> _
Function doAll(a As Double, b As Double) As Double()
Return New Double() {a + b, a - b, a * b, a / b}
End Function
End Class
We are repeating some explanations here that have already been given but are worth revisiting or expanding upon. The operations class resembles a VB.NET class, with a few points to note:
- methods are preceded by a <WebMethod()> attribute that tells the compiler which methods should be "published," i.e., made available to the client. A method not preceded by this attribute would be invisible to remote clients. This could be an internal method used by other methods but not intended for publication.
- The class derives from the WebService class defined in the System.Web.Services namespace. This inheritance is not always mandatory. In this example, in particular, we could do without it.
- The class itself is preceded by a <WebService(Namespace="st.istia.univ-angers.fr")> attribute intended to provide a namespace for the web service. A class vendor assigns a namespace to its classes to give them a unique name and thus avoid conflicts with classes from other vendors that might have the same name. The same applies to web services. Each web service must be identifiable by a unique name, in this case st.istia.univ-angers.fr.
- We have not defined a constructor. Therefore, the constructor of the parent class will be used implicitly.
The source code above is not intended directly for the VB.NET compiler but for the IIS web server. It must have the .asmx extension and be saved in the web server directory structure. Here, we save it as operations.asmx in the <IISroot>\polyvbnet\operations folder:

We associate the IIS virtual directory [operations] with this physical directory:
![]() | ![]() |
Let’s access the service using a browser. The URL to request is [http://localhost/operations/operations.asmx]:

We get a web document with a link for each of the methods defined in the operations web service. Let’s follow the add link:

The page that appears invites us to test the add method by providing the two arguments a and b that it requires. Recall the definition of the *add* method:
Note that the page has used the argument names a and b from the method definition. Click the Call button, and the following response appears in a separate browser window:

If you select [View/Source] above, you get the following code:

Let’s repeat the process for the [toutfaire] method:

We get the following page:

Let’s use the [Call] button above:

In all cases, the server’s response has the following format:
- the response is in XML format
- Line 1 is standard and is always present in the response
- The following lines depend on the result type (double, ArrayOfDouble), the number of results, and the web service namespace (st.istia.univ-angers.fr in this case).
There are several methods for querying a web service and obtaining its response. Let’s return to the service’s URL:

and follow the [Add] link. On the page that appears, two methods for querying the [Add] function of the web service are presented:



These two methods for accessing a web service’s functions are called, respectively: HTTP-POST and SOAP. We will now examine them one by one.
Note: In early versions of VS.NET, there was a third method called HTTP-GET. As of the date of this document (March 2004), this method no longer appears to be available. This means that the web service generated by VS.NET does not accept GET requests. This does not mean that you cannot write web services that accept GET requests, particularly using tools other than VS.NET or simply by hand.
10.5. An HTTP-POST client
We’ll follow the method proposed by the web service:

Let’s break down what’s written. First, the web client must send the following HTTP headers:
The web client makes a POST request to the URL /operations/operations.asmx/add according to the HTTP version 1.1 protocol | |
We specify the target machine for the request. Here, localhost. This header was made mandatory by version 1.1 of the HTTP protocol | |
This specifies that after the HTTP headers, additional parameters will be sent in urlencoded format. This format replaces certain characters with their hexadecimal codes. | |
This is the character count of the parameter string that will be sent after the HTTP headers. |
The HTTP headers are followed by a blank line, then by the POST parameter string of [Content-Length] characters in the form a=XX&b=YY, where XX and YY are the "URL-encoded" strings of the values of parameters a and b. We know enough to reproduce the above with our generic TCP client already used in the chapter on TCP/IP programming:
- We launch IIS
- the service is available at the URL [http://localhost/operations/operations.asmx]
- we use the generic TCP client in a DOS window
dos>generic-tcp-client localhost 80
Commands:
POST /operations/operations.asmx/add HTTP/1.1
HOST: localhost
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-length: 7
<-- HTTP/1.1 100 Continue
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 13:55:17 GMT
<-- X-Powered-By: ASP.NET
<--
a=2&b=3
<-- HTTP/1.1 200 OK
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, Mar 3, 2004 1:55:26 PM GMT
<-- X-Powered-By: ASP.NET
<-- Connection: close
<-- X-AspNet-Version: 1.1.4322
<-- Cache-Control: private, max-age=0
<-- Content-Type: text/xml; charset=utf-8
<-- Content-Length: 90
<--
<-- <?xml version="1.0" encoding="utf-8"?>
<-- <double xmlns="st.istia.univ-angers.fr">5</double>
[end of thread reading server responses]
end
[end of the thread sending commands to the server]
First, note that we have added the [Connection: close] header to instruct the server to close the connection after sending the response. This is necessary here. If we do not specify this, by default the server will keep the connection open. However, its response is a sequence of text lines, the last of which is not terminated by a line-end character. It turns out that our generic TCP client reads text lines terminated by an end-of-line character using the ReadLine method. If the server does not close the connection after sending the last line, the client gets blocked because it is waiting for an end-of-line character that never arrives. If the server closes the connection, the client’s ReadLine method completes, and the client does not get blocked.
Immediately after receiving the empty line signaling the end of the HTTP headers, the IIS server sends an initial response:
<-- HTTP/1.1 100 Continue
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 13:55:17 GMT
<-- X-Powered-By: ASP.NET
<--
This response, consisting solely of HTTP headers, tells the client that it can send the 7 characters it said it wanted to send. What we do:
Note that our TCP client sends more than 7 characters here, since it sends them with a line-end marker (WriteLine). This does not interfere with the server, which will only take the first 7 characters from those received, and because the connection is then closed (Connection: close). These extra characters would have been problematic if the connection had remained open, as they would then have been interpreted as coming from the client’s next command. Once the parameters are received, the server sends its response:
<-- HTTP/1.1 200 OK
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 13:55:26 GMT
<-- X-Powered-By: ASP.NET
<-- Connection: close
<-- X-AspNet-Version: 1.1.4322
<-- Cache-Control: private, max-age=0
<-- Content-Type: text/xml; charset=utf-8
<-- Content-Length: 90
<--
<-- <?xml version="1.0" encoding="utf-8"?>
<-- <double xmlns="st.istia.univ-angers.fr">5</double>
We now have the elements to write a client program for our web service. It will be a console client called httpPost2 and used as follows:
dos>httpPost2 http://localhost/operations/operations.asmx
Type your commands in the following format: [add|subtract|multiply|divide] a b
add 6 7
--> POST /operations/operations.asmx/add HTTP/1.1
--> Host: localhost:80
--> Content-Type: application/x-www-form-urlencoded
--> Content-Length: 7
--> Connection: Keep-Alive
-->
<-- HTTP/1.1 100 Continue
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 14:56:38 GMT
<-- X-Powered-By: ASP.NET
<--
--> a=6&b=7
<-- HTTP/1.1 200 OK
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, Mar 3, 2004 2:56:38 PM GMT
<-- X-Powered-By: ASP.NET
<-- X-AspNet-Version: 1.1.4322
<-- Cache-Control: private, max-age=0
<-- Content-Type: text/xml; charset=utf-8
<-- Content-Length: 91
<--
<-- <?xml version="1.0" encoding="utf-8"?>
<-- <double xmlns="st.istia.univ-angers.fr">13</double>
[result=13]
subtract 8 9
--> POST /operations/operations.asmx/subtract HTTP/1.1
--> Host: localhost:80
--> Content-Type: application/x-www-form-urlencoded
--> Content-Length: 7
--> Connection: Keep-Alive
-->
<-- HTTP/1.1 100 Continue
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, Mar 3, 2004 2:56:47 PM GMT
<-- X-Powered-By: ASP.NET
<--
--> a=8&b=9
<-- HTTP/1.1 200 OK
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, Mar 3, 2004 2:56:47 PM GMT
<-- X-Powered-By: ASP.NET
<-- X-AspNet-Version: 1.1.4322
<-- Cache-Control: private, max-age=0
<-- Content-Type: text/xml; charset=utf-8
<-- Content-Length: 91
<--
<-- <?xml version="1.0" encoding="utf-8"?>
<-- <double xmlns="st.istia.univ-angers.fr">-1</double>
[result=-1]
end
end>
The client is called by passing it the URL of the web service:
Next, the client reads the commands typed on the keyboard and executes them. These are in the format:
where function is the web service function being called (add, subtract, multiply, divide) and a and b are the values on which this function will operate. For example:
From there, the client will send the necessary HTTP request to the web server and receive a response. The client-server exchanges are displayed on the screen to help you better understand the process:
add 6 7
--> POST /operations/operations.asmx/add HTTP/1.1
--> Host: localhost:80
--> Content-Type: application/x-www-form-urlencoded
--> Content-Length: 7
--> Connection: Keep-Alive
-->
<-- HTTP/1.1 100 Continue
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 14:56:38 GMT
<-- X-Powered-By: ASP.NET
<--
--> a=6&b=7
<-- HTTP/1.1 200 OK
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, Mar 3, 2004 2:56:38 PM GMT
<-- X-Powered-By: ASP.NET
<-- X-AspNet-Version: 1.1.4322
<-- Cache-Control: private, max-age=0
<-- Content-Type: text/xml; charset=utf-8
<-- Content-Length: 91
<--
<-- <?xml version="1.0" encoding="utf-8"?>
<-- <double xmlns="st.istia.univ-angers.fr">13</double>
[result=13]
The exchange shown above is the same as the one we saw with the generic TCP client, with one difference: the HTTP header **Connection: Keep-Alive instructs the server not to close the connection. The connection therefore remains open for the client’s next operation, so the client does not need to reconnect to the server. However, this requires the client to use a method other than ReadLine() to read the server’s response, since we know that the response consists of a sequence of lines, the last of which is not terminated by a newline character. Once the entire server response has been received, the client parses it to find the result of the requested operation and display it:
Let’s examine our client’s code:
' namespaces
Imports System
Imports System.Net.Sockets
Imports System.IO
Imports System.Text.RegularExpressions
Imports System.Collections
Imports Microsoft.VisualBasic
Imports System.Web
' Web service client operations
Public Module clientPOST
Public Sub Main(ByVal args() As String)
' syntax
Const syntax As String = "pg URI"
Dim functions As String() = {"add", "subtract", "multiply", "divide"}
' number of arguments
If args.Length <> 1 Then
error(syntax, 1)
End If
' note the requested URI
Dim URIstring As String = args(0)
' we connect to the server
Dim uri As Uri = Nothing ' the web service URI
Dim client As TcpClient = Nothing ' The client's TCP connection to the server
Dim [IN] As StreamReader = Nothing ' the client's read stream
Dim OUT As StreamWriter = Nothing ' the client's write stream
Try
' connection to the server
uri = New Uri(URIstring)
client = New TcpClient(uri.Host, uri.Port)
' Create the TCP client's input and output streams
[IN] = New StreamReader(client.GetStream())
OUT = New StreamWriter(client.GetStream())
OUT.AutoFlush = True
Catch ex As Exception
' Incorrect URI or other problem
error("The following error occurred: " + ex.Message, 2)
End Try
' Create a dictionary of web service functions
Dim functionDictionary As New Hashtable
Dim i As Integer
For i = 0 To functions.Length - 1
FunctionDictionary.Add(functions(i), True)
Next i
' User input is typed on the keyboard
' in the form function a b
' they end with the command "end"
Dim command As String = Nothing ' command typed on the keyboard
Dim fields As String() = Nothing ' fields of a command line
Dim function As String = Nothing ' name of a web service function
Dim a, b As String ' arguments for web service functions
' prompt to the user
Console.Out.WriteLine("Enter your commands in the format: [add|subtract|multiply|divide] a b")
' error handling
Dim commandError As Boolean
Try
' loop for capturing keyboard commands
While True
' no error at the start
commandError = False
' read command
command = Console.In.ReadLine().Trim().ToLower()
' finished?
If command Is Nothing Or command = "end" Then
Exit While
End If
' Split the command into fields
fields = Regex.Split(command, "\s+")
Try
' three fields are required
If fields.Length <> 3 Then
Throw New Exception
End If
' Field 0 must be a recognized function
function = fields(0)
If Not functionDictionary.ContainsKey(function) Then
Throw New Exception
End If
' Field 1 must be a valid number
a = fields(1)
Double.Parse(a)
' Field 2 must be a valid number
b = fields(2)
Double.Parse(b)
Catch
' invalid command
Console.Out.WriteLine("syntax: [add|subtract|multiply|divide] a b")
commandError = True
End Try
' Send the request to the web service
If Not errorCommand Then executeFunction([IN], OUT, uri, function, a, b)
End While
Catch e As Exception
Console.Out.WriteLine(("The following error occurred: " + e.Message))
End Try
' end client-server connection
Try
[IN].Close()
OUT.Close()
client.Close()
Catch
End Try
End Sub
...........
' display errors
Public Sub error(ByVal msg As String, ByVal exitCode As Integer)
' display error
System.Console.Error.WriteLine(msg)
' exit with error
Environment.Exit(exitCode)
End Sub
End Module
We’ve seen these elements several times before, and they don’t require any special comments. Let’s now examine the code for the executeFonction method, where the new elements are located:
' executeFonction
Public Sub executeFonction(ByVal [IN] As StreamReader, ByVal OUT As StreamWriter, ByVal uri As Uri, ByVal function As String, ByVal a As String, ByVal b As String)
' executes function(a,b) on the web service at URI uri
' client-server communication occurs via the IN and OUT streams
' the result of the function is in the line
' <double xmlns="st.istia.univ-angers.fr">double</double>
' sent by the server
' constructing the query string
Dim request As String = "a=" + HttpUtility.UrlEncode(a) + "&b=" + HttpUtility.UrlEncode(b)
Dim nbChars As Integer = query.Length
' constructing the array of HTTP headers to send
Dim headers(5) As String
headers(0) = "POST " + uri.AbsolutePath + "/" + function + " HTTP/1.1"
headers(1) = "Host: " & uri.Host & ":" & uri.Port
headers(2) = "Content-Type: application/x-www-form-urlencoded"
headers(3) = "Content-Length: " & nbChars
headers(4) = "Connection: Keep-Alive"
headers(5) = ""
' send the HTTP headers to the server
Dim i As Integer
For i = 0 To headers.Length - 1
' send to the server
OUT.WriteLine(headers(i))
' console output
Console.Out.WriteLine(("--> " + headers(i)))
Next i
' read the first response from the HTTP/1.1 100 web server
Dim line As String = Nothing
' a line from the read stream
line = [IN].ReadLine()
While line <> ""
'echo
Console.Out.WriteLine(("<-- " + line))
' next line
line = [IN].ReadLine()
End While
'print last line
Console.Out.WriteLine(("<-- " + line))
' send request parameters
OUT.Write(request)
' echo
Console.Out.WriteLine(("--> " + request))
' build the regular expression to retrieve the size of the XML response
' in the web server response stream
Dim patternLength As String = "^Content-Length: (.+?)\s*$"
Dim RegexLength As New Regex(patternLength) '
Dim MatchLength As Match = Nothing
Dim length As Integer = 0
' Read the second response from the web server after sending the request
' store the value of the Content-Length line
line = [IN].ReadLine()
While line <> ""
' display on screen
Console.Out.WriteLine(("<-- " + line))
' Content-Length?
MatchLength = RegexLength.Match(line)
If MatchLength.Success Then
length = Integer.Parse(MatchLength.Groups(1).Value)
End If
' next line
line = [IN].ReadLine()
End While
' echo last line
Console.Out.WriteLine("<--")
' build the regular expression to find the result
' in the web server response stream
Dim pattern As String = "<double xmlns=""st.istia.univ-angers.fr"">(.+?)</double>"
Dim ResultPattern As New Regex(pattern)
Dim ResultMatch As Match = Nothing
' read the rest of the web server's response
Dim chrResponse(length) As Char
[IN].Read(chrResponse, 0, length)
Dim strResponse As String = New [String](chrResponse)
' split the response into lines of text
Dim lines As String() = Regex.Split(strResponse, ControlChars.Lf)
' iterate through the text lines to find the result
Dim strResult As String = "?" ' result of the function
For i = 0 To lines.Length - 1
' tracking
Console.Out.WriteLine(("<-- " + lines(i)))
' compare current line to template
MatchResult = TemplateResult.Match(lines(i))
' Was a match found?
If MatchResult.Success Then
' record the result
strResult = MatchResult.Groups(1).Value
End If
Next i
' display the result
Console.Out.WriteLine(("[result=" + strResult + "]" + ControlChars.Lf))
End Sub
First, the HTTP-POST client sends its request in POST format:
' constructing the request string
Dim request As String = "a=" + HttpUtility.UrlEncode(a) + "&b=" + HttpUtility.UrlEncode(b)
Dim nbChars As Integer = request.Length
' Constructing the HTTP header array to be sent
Dim headers(5) As String
headers(0) = "POST " + uri.AbsolutePath + "/" + function + " HTTP/1.1"
headers(1) = "Host: " & uri.Host & ":" & uri.Port
headers(2) = "Content-Type: application/x-www-form-urlencoded"
headers(3) = "Content-Length: " & nbChars
headers(4) = "Connection: Keep-Alive"
headers(5) = ""
' send the HTTP headers to the server
Dim i As Integer
For i = 0 To headers.Length - 1
' send to the server
OUT.WriteLine(headers(i))
' console output
Console.Out.WriteLine(("--> " + headers(i)))
Next i
In the header
we must specify the size of the parameters that will be sent by the client behind the HTTP headers:
To do this, use the following code:
' constructing the query string
Dim request As String = "a=" + HttpUtility.UrlEncode(a) + "&b=" + HttpUtility.UrlEncode(b)
Dim nbChars As Integer = query.Length
The HttpUtility.UrlEncode(string string) method converts certain characters in the string to %n1n2, where n1n2 is the ASCII code of the converted character. The characters targeted by this conversion are all characters that have a specific meaning in a POST request (space, =, &, etc.). Here, the HttpUtility.UrlEncode method is normally unnecessary since a and b are numbers that do not contain any of these special characters. It is used here as an example. It requires the System.Web namespace. Once the client has sent its HTTP headers:
--> POST /operations/operations.asmx/add HTTP/1.1
--> Host: localhost:80
--> Content-Type: application/x-www-form-urlencoded
--> Content-Length: 7
--> Connection: Keep-Alive
-->
The server responds with the HTTP 100 Continue header:
<-- HTTP/1.1 100 Continue
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 14:56:47 GMT
<-- X-Powered-By: ASP.NET
<--
The code simply reads and displays this first response on the screen:
' reading the first response from the HTTP/1.1 100 web server
Dim line As String = Nothing
' a line from the read stream
line = [IN].ReadLine()
While line <> ""
'echo
Console.Out.WriteLine(("<-- " + line))
' next line
line = [IN].ReadLine()
End While
'print last line
Console.Out.WriteLine(("<-- " + line))
Once this initial response has been read, the client must send its parameters:
It does so with the following code:
' send request parameters
OUT.Write(request)
' echo
Console.Out.WriteLine(("--> " + request))
The server will then send its response. This response consists of two parts:
- HTTP headers followed by a blank line
- the response in XML format
<-- HTTP/1.1 200 OK
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 14:56:38 GMT
<-- X-Powered-By: ASP.NET
<-- X-AspNet-Version: 1.1.4322
<-- Cache-Control: private, max-age=0
<-- Content-Type: text/xml; charset=utf-8
<-- Content-Length: 91
<--
<-- <?xml version="1.0" encoding="utf-8"?>
<-- <double xmlns="st.istia.univ-angers.fr">13</double>
First, the client reads the HTTP headers to find the Content-Length line and retrieve the size of the XML response (here, 90). This is retrieved using a regular expression. We could have done this differently and likely more efficiently.
' construction of the regular expression used to determine the size of the XML response
' in the web server response stream
Dim templateLength As String = "^Content-Length: (.+?)\s*$"
Dim RegexLength As New Regex(modelLength) '
Dim MatchLength As Match = Nothing
Dim length As Integer = 0
' Read the second response from the web server after sending the request
' store the value of the Content-Length line
line = [IN].ReadLine()
While line <> ""
' display on screen
Console.Out.WriteLine(("<-- " + line))
' Content-Length?
MatchLength = RegexLength.Match(line)
If MatchLength.Success Then
length = Integer.Parse(MatchLength.Groups(1).Value)
End If
' next line
line = [IN].ReadLine()
End While
' print last line
Console.Out.WriteLine("<--")
Once we have the length N of the XML response, we simply need to read N characters from the IN stream of the server's response. This string of N characters is broken down into lines of text for the purposes of screen monitoring. Among these lines, we look for the line containing the result:
again using a regular expression. Once the result is found, it is displayed.
The end of the client code is as follows:
' construction of the regular expression used to find the result
' in the web server's response stream
Dim template As String = "<double xmlns=""st.istia.univ-angers.fr"">(.+?)</double>"
Dim ResultTemplate As New Regex(template)
Dim ResultMatch As Match = Nothing
' read the rest of the response from the web server
Dim chrResponse(length) As Char
[IN].Read(chrResponse, 0, length)
Dim strResponse As String = New [String](chrResponse)
' split the response into lines of text
Dim lines As String() = Regex.Split(strResponse, ControlChars.Lf)
' iterate through the text lines to find the result
Dim strResult As String = "?" ' result of the function
For i = 0 To lines.Length - 1
' tracking
Console.Out.WriteLine(("<-- " + lines(i)))
' compare current line to template
MatchResult = TemplateResult.Match(lines(i))
' Was a match found?
If MatchResult.Success Then
' record the result
strResult = MatchResult.Groups(1).Value
End If
Next i
' display the result
Console.Out.WriteLine(("[result=" + strResult + "]" + ControlChars.Lf))
End Sub
10.6. A SOAP client
Here we examine a second client that will use a SOAP (Simple Object Access Protocol) client-server dialogue. An example of such a dialogue is presented for the add function:


The client's request is a POST request. We will therefore see some of the same mechanisms as in the previous client. The main difference is that while the HTTP-POST client sent the parameters a and b in the form
the SOAP client sends them in a more complex XML format:
POST /operations/operations.asmx HTTP/1.1
Host: localhost
Content-Type: text/xml; charset=utf-8
Content-Length: length
SOAPAction: "st.istia.univ-angers.fr/add"
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<add xmlns="st.istia.univ-angers.fr">
<a>double</a>
<b>double</b>
</add>
</soap:Body>
</soap:Envelope>
It receives an XML response in return that is also more complex than the responses seen previously:
HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
Content-Length: length
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<addResponse xmlns="st.istia.univ-angers.fr">
<addResult>double</addResult>
</addResponse>
</soap:Body>
</soap:Envelope>
Even though the request and response are more complex, this is indeed the same HTTP mechanism as for the HTTP-POST client. The SOAP client code can therefore be modeled after that of the HTTP-POST client. Here is an example of execution:
dos>clientsoap1 http://localhost/operations/operations.asmx
Type your commands in the following format: [add|subtract|multiply|divide] a b
add 3 4
--> POST /operations/operations.asmx HTTP/1.1
--> Host: localhost:80
--> Content-Type: text/xml; charset=utf-8
--> Content-Length: 321
--> Connection: Keep-Alive
--> SOAPAction: "st.istia.univ-angers.fr/add"
-->
<-- HTTP/1.1 100 Continue
<-- Server: Microsoft-IIS/5.0
<-- Date: Thu, 04 Mar 2004 07:28:29 GMT
<-- X-Powered-By: ASP.NET
<--
--> <?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<add xmlns="st.istia.univ-angers.fr">
<a>3</a>
<b>4</b>
</add>
</soap:Body>
</soap:Envelope>
<-- HTTP/1.1 200 OK
<-- Server: Microsoft-IIS/5.0
<-- Date: Thu, 04 Mar 2004 07:28:33 GMT
<-- X-Powered-By: ASP.NET
<-- X-AspNet-Version: 1.1.4322
<-- Cache-Control: private, max-age=0
<-- Content-Type: text/xml; charset=utf-8
<-- Content-Length: 345
<--
<-- <?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-inst
ance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><ajouterResponse xmlns="st.istia.univ-angers.fr"><ajouterResult>7</ajouterResult></ajouterResponse
></soap:Body></soap:Envelope>
[result=7]
Only the executeFonction method changes. The SOAP client sends the HTTP headers for its request. They are simply a bit more complex than those of HTTP-POST:
add 3 4
--> POST /operations/operations.asmx HTTP/1.1
--> Host: localhost:80
--> Content-Type: text/xml; charset=utf-8
--> Content-Length: 321
--> Connection: Keep-Alive
--> SOAPAction: "st.istia.univ-angers.fr/add"
-->
The code that generates them:
' executeFonction
Public Sub executeFonction(ByVal [IN] As StreamReader, ByVal OUT As StreamWriter, ByVal uri As Uri, ByVal fonction As String, ByVal a As String, ByVal b As String)
' executes function(a,b) on the web service at URI uri
' client-server communication occurs via the IN and OUT streams
' the result of the function is in the line
' <double xmlns="st.istia.univ-angers.fr">double</double>
' sent by the server
' construction of the SOAP request string
Dim requestSOAP As String = "<?xml version=" + """1.0"" encoding=""utf-8""?>" + ControlChars.Lf
SOAPRequest += "<soap:Envelope xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xmlns:xsd=""http://www.w3.org/2001/XMLSchema"" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">" + ControlChars.Lf
SOAPRequest += "<soap:Body>" + ControlChars.Lf
SOAPRequest += "<" + function + " xmlns=""st.istia.univ-angers.fr"">" + ControlChars.Lf
SOAPRequest += "<a>" + a + "</a>" + ControlChars.Lf
SOAPRequest += "<b>" + b + "</b>" + ControlChars.Lf
SOAPRequest += "</" + function + ">" + ControlChars.Lf
SOAPRequest += "</soap:Body>" + ControlChars.Lf
SOAPRequest += "</soap:Envelope>"
Dim nbCharsSOAP As Integer = SOAPRequest.Length
' constructing the array of HTTP headers to send
Dim headers(6) As String
headers(0) = "POST " + uri.AbsolutePath + " HTTP/1.1"
headers(1) = "Host: " & uri.Host & ":" & uri.Port
headers(2) = "Content-Type: text/xml; charset=utf-8"
headers(3) = "Content-Length: " & nbCharsSOAP
headers(4) = "Connection: Keep-Alive"
headers(5) = "SOAPAction: ""st.istia.univ-angers.fr/" + function + """"
headers(6) = ""
' send the HTTP headers to the server
Dim i As Integer
For i = 0 To headers.Length - 1
' send to the server
OUT.WriteLine(headers(i))
' console output
Console.Out.WriteLine(("--> " + headers(i)))
Next i
Upon receiving this request, the server sends its first response, which the client displays:
<-- HTTP/1.1 100 Continue
<-- Server: Microsoft-IIS/5.0
<-- Date: Thu, 04 Mar 2004 07:28:29 GMT
<-- X-Powered-By: ASP.NET
<--
The code for reading this first response is as follows:
' read the first response from the HTTP/1.1 100 web server
Dim line As String = Nothing
' a line from the read stream
line = [IN].ReadLine()
While line <> ""
'echo
Console.Out.WriteLine(("<-- " + line))
' next line
line = [IN].ReadLine()
End While 'while
'echo last line
Console.Out.WriteLine(("<-- " + line))
The client will now send its parameters in XML format in what is called a SOAP envelope:
--> <?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<add xmlns="st.istia.univ-angers.fr">
<a>3</a>
<b>4</b>
</add>
</soap:Body>
</soap:Envelope>
The code:
' send request parameters
OUT.Write(soapRequest)
' echo
Console.Out.WriteLine(("--> " + SOAPRequest))
The server will then send its final response:
<-- HTTP/1.1 200 OK
<-- Server: Microsoft-IIS/5.0
<-- Date: Thu, 04 Mar 2004 07:28:33 GMT
<-- X-Powered-By: ASP.NET
<-- X-AspNet-Version: 1.1.4322
<-- Cache-Control: private, max-age=0
<-- Content-Type: text/xml; charset=utf-8
<-- Content-Length: 345
<--
<-- <?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-inst
ance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><ajouterResponse xmlns="st.istia.univ-angers.fr"><ajouterResult>7</ajouterResult></ajouterResponse
></soap:Body></soap:Envelope>
The client displays the received HTTP headers on the screen while searching for the Content-Length line:
' constructing the regular expression to retrieve the size of the XML response
' in the web server's response stream
Dim templateLength As String = "^Content-Length: (.+?)\s*$"
Dim RegexLength As New Regex(modelLength) '
Dim MatchLength As Match = Nothing
Dim length As Integer = 0
' Read the second response from the web server after sending the request
' store the value of the Content-Length line
line = [IN].ReadLine()
While line <> ""
' print to screen
Console.Out.WriteLine(("<-- " + line))
' Content-Length?
MatchLength = RegexLength.Match(line)
If MatchLength.Success Then
length = Integer.Parse(MatchLength.Groups(1).Value)
End If
' next line
line = [IN].ReadLine()
End While 'while
' print last line
Console.Out.WriteLine("<--")
Once the size N of the XML response is known, the client reads N characters from the server's response stream, splits the retrieved string into lines of text to display them on the screen, and searches for the XML tag of the result: <ajouterResult>7</ajouterResult> and displays it:
' constructing the regular expression to find the result
' in the web server response stream
Dim pattern As String = "<" + function + "Result>(.+?)</" + function + "Result>"
Dim ResultPattern As New Regex(pattern)
Dim ResultMatch As Match = Nothing
' read the rest of the response from the web server
Dim chrResponse(length) As Char
[IN].Read(responseChar, 0, length)
Dim strResponse As String = New [String](chrResponse)
' split the response into lines of text
Dim lines As String() = Regex.Split(strResponse, ControlChars.Lf)
' iterate through the text lines to find the result
Dim strResult As String = "?" ' result of the function
For i = 0 To lines.Length - 1
' tracking
Console.Out.WriteLine(("<-- " + lines(i)))
' compare current line to template
MatchResult = TemplateResult.Match(lines(i))
' Was a match found?
If MatchResult.Success Then
' record the result
strResult = MatchResult.Groups(1).Value
End If
'next line
Next i
' display the result
Console.Out.WriteLine(("[result=" + strResult + "]" + ControlChars.Lf))
End Sub
10.7. Encapsulation of client-server communication
Let’s imagine that our web service operations is used by various applications. It would be useful to provide these applications with a class that acts as an interface between the client application and the web service, hiding most of the network communication, which is not trivial for most developers. This would result in the following architecture:
![]() |
The client application would address the client-server interface to make its requests to the web service. The interface would handle all necessary network communication with the server and return the result to the client application. The client application would no longer have to deal with communication with the server, which would greatly simplify its development.
10.7.1. The Encapsulation Class
Based on what we’ve covered in the previous sections, we now have a good understanding of the network communication between the client and the server. We’ve even looked at three methods. We’ve chosen to encapsulate the SOAP method. The class is as follows:
' namespaces
Imports System
Imports System.Net.Sockets
Imports System.IO
Imports System.Text.RegularExpressions
Imports System.Collections
Imports System.Web
Imports Microsoft.VisualBasic
' SOAP client for the Web service operations
Public Class SOAPClient
' instance variables
Private uri As uri = Nothing ' the Web service URI
Private TcpClient As TcpClient = Nothing ' the client's TCP connection to the server
Private [IN] As StreamReader = Nothing ' the client's read stream
Private OUT As StreamWriter = Nothing ' the client's write stream
' function dictionary
Private functionDictionary As New Hashtable
' list of functions
Private functions As String() = {"add", "subtract", "multiply", "divide"}
' verbose
Private verbose As Boolean = False ' When true, displays client-server exchanges on the screen
' constructor
Public Sub New(ByVal uriString As String, ByVal verbose As Boolean)
' Set verbose
Me.verbose = verbose
' Connect to the server
uri = New Uri(uriString)
client = New TcpClient(uri.Host, uri.Port)
' Create the TCP client's input and output streams
[IN] = New StreamReader(client.GetStream())
OUT = New StreamWriter(client.GetStream())
OUT.AutoFlush = True
' Create the dictionary of web service functions
Dim i As Integer
For i = 0 To functions.Length - 1
dicoFonctions.Add(fonctions(i), True)
Next i
End Sub
' Close the connection to the server
Public Sub Close()
' End client-server connection
[IN].Close()
OUT.Close()
client.Close()
End Sub
' executeFunction
Public Function executeFunction(ByVal function As String, ByVal a As String, ByVal b As String) As String
' executes function(a,b) on the web service at URI uri
' client-server communication occurs via the IN and OUT streams
' The result of the function is in the line
' <double xmlns="st.istia.univ-angers.fr">double</double>
' sent by the server
' Is the function valid?
function = function.Trim().ToLower()
If Not functionDictionary.ContainsKey(function) Then
Return "[function [" + function + "] unavailable: (add, subtract, multiply, divide)]"
End If
' Are arguments a and b valid?
Dim doubleA As Double = 0
Try
doubleA = Double.Parse(a)
Catch
Return "[argument [" + a + "] is incorrect (double)]"
End Try
Dim doubleB As Double = 0
Try
doubleB = Double.Parse(b)
Catch
Return "[argument [" + b + "] is incorrect (double)]"
End Try
' division by zero?
If function = "divide" And doubleB = 0 Then
Return "[division by zero]"
End If
' constructing the SOAP request string
Dim SOAPRequest As String = "<?xml version=" + """1.0"" encoding=""utf-8""?>" + ControlChars.Lf
SOAPRequest += "<soap:Envelope xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xmlns:xsd=""http://www.w3.org/2001/XMLSchema"" xmlns:soap=""http://schemas.xmlsoap.org/soap/envelope/"">" + ControlChars.Lf
SOAPRequest += "<soap:Body>" + ControlChars.Lf
SOAPRequest += "<" + function + " xmlns=""st.istia.univ-angers.fr"">" + ControlChars.Lf
SOAPRequest += "<a>" + a + "</a>" + ControlChars.Lf
SOAPRequest += "<b>" + b + "</b>" + ControlChars.Lf
SOAPRequest += "</" + function + ">" + ControlChars.Lf
SOAPRequest += "</soap:Body>" + ControlChars.Lf
SOAPRequest += "</soap:Envelope>"
Dim nbCharsSOAP As Integer = SOAPRequest.Length
' constructing the array of HTTP headers to send
Dim headers(6) As String
headers(0) = "POST " + uri.AbsolutePath + " HTTP/1.1"
headers(1) = "Host: " + uri.Host + ":" + uri.Port.ToString
headers(2) = "Content-Type: text/xml; charset=utf-8"
headers(3) = "Content-Length: " + nbCharsSOAP.ToString
headers(4) = "Connection: Keep-Alive"
headers(5) = "SOAPAction: ""st.istia.univ-angers.fr/" + function + """"
headers(6) = ""
' send the HTTP headers to the server
Dim i As Integer
For i = 0 To headers.Length - 1
' send to the server
OUT.WriteLine(headers(i))
' Screen echo
If verbose Then
Console.Out.WriteLine(("--> " + headers(i)))
End If
Next i
' Read the first response from the HTTP/1.1 100 web server
Dim line As String = Nothing
' a line from the read stream
line = [IN].ReadLine()
While line <> ""
'echo
If verbose Then
Console.Out.WriteLine(("<-- " + line))
End If
' next line
line = [IN].ReadLine()
End While
'echo last line
If verbose Then
Console.Out.WriteLine(("<-- " + line))
End If
' send request parameters
OUT.Write(SOAPRequest)
' echo
If verbose Then
Console.Out.WriteLine(("--> " + SOAPRequest))
End If
' constructing the regular expression to retrieve the size of the XML response
' in the web server response stream
Dim patternLength As String = "^Content-Length: (.+?)\s*$"
Dim RegexLength As New Regex(patternLength) '
Dim MatchLength As Match = Nothing
Dim length As Integer = 0
' Read the second response from the web server after sending the request
' store the value of the Content-Length line
line = [IN].ReadLine()
While line <> ""
' print to screen
If verbose Then
Console.Out.WriteLine(("<-- " + line))
End If
' Content-Length?
MatchLength = RegexLength.Match(line)
If MatchLength.Success Then
length = Integer.Parse(MatchLength.Groups(1).Value)
End If
' next line
line = [IN].ReadLine()
End While
' echo last line
If verbose Then
Console.Out.WriteLine("<--")
End If
' Construct the regular expression to extract the result
' in the web server response stream
Dim pattern As String = "<" + function + "Result>(.+?)</" + function + "Result>"
Dim ResultPattern As New Regex(pattern)
Dim ResultMatch As Match = Nothing
' read the rest of the web server's response
Dim chrResponse(length) As Char
[IN].Read(responseChar, 0, length)
Dim strResponse As String = New [String](chrResponse)
' split the response into lines of text
Dim lines As String() = Regex.Split(strResponse, ControlChars.Lf)
' iterate through the text lines to find the result
Dim resultStr As String = "?" ' result of the function
For i = 0 To lines.Length - 1
' tracking
If verbose Then
Console.Out.WriteLine(("<-- " + lines(i)))
End If ' compare current line to template
MatchResult = TemplateResult.Match(lines(i))
' Was a match found?
If MatchResult.Success Then
' record the result
strResult = MatchResult.Groups(1).Value
End If
Next i
' return the result
Return strResult
End Function
End Class
There is nothing new here compared to what we have already seen. We simply took the code from the SOAP client we studied and rearranged it slightly to turn it into a class. This class has a constructor and two methods:
' constructor
Public Sub New(ByVal uriString As String, ByVal verbose As Boolean)
' executeFunction
Public Function executeFunction(ByVal function As String, ByVal a As String, ByVal b As String) As String
' Close the connection to the server
Public Sub Close()
and has the following attributes:
' instance variables
Private uri As Uri = Nothing ' the web service URI
Private client As TcpClient = Nothing ' the client's TCP connection to the server
Private [IN] As StreamReader = Nothing ' the client's read stream
Private OUT As StreamWriter = Nothing ' the client's write stream
' function dictionary
Private functionDictionary As New Hashtable
' list of functions
Private functions As String() = {"add", "subtract", "multiply", "divide"}
' verbose
Private verbose As Boolean = False ' When true, displays client-server exchanges on the screen
We pass two parameters to the constructor:
- the URI of the web service it must connect to
- a boolean verbose parameter which, when true, requests that network exchanges be displayed on the screen; otherwise, they will not be displayed.
During construction, the IN stream for network reading, the OUT stream for network writing, and the dictionary of functions managed by the service are created. Once the object is constructed, the client-server connection is open and its IN and OUT streams are ready for use.
The Close method closes the connection to the server.
The ExecuteFonction method is the one we wrote for the SOAP client we studied, with a few minor differences:
- The parameters `uri`, `IN`, and `OUT`, which were previously passed as parameters to the method, no longer need to be passed, since they are now instance attributes accessible to all methods of the instance
- The ExecuteFonction method, which previously returned a void type and displayed the function's result on the screen, now returns that result—and thus a string type.
Typically, a client will use the clientSOAP class as follows:
- creating a clientSOAP object that will establish the connection to the web service
- repeatedly calling the executeFonction method
- Closing the connection to the web service using the Close method.
Let’s examine a first client.
10.7.2. A console client
Here we revisit the SOAP client we studied when the clientSOAP class did not yet exist, and we redesign it so that it now uses this class:
' namespaces
Imports System
Imports System.IO
Imports System.Text.RegularExpressions
Imports Microsoft.VisualBasic
Public Module testClientSoap
' requests the URI of the operations web service
' interactively executes commands typed on the keyboard
Public Sub Main(ByVal args() As String)
' syntax
Const syntax As String = "pg URI [verbose]"
' number of arguments
If args.Length <> 1 And args.Length <> 2 Then
error(syntax, 1)
End If
' verbose?
Dim verbose As Boolean = False
If args.Length = 2 Then
verbose = args(1).ToLower() = "verbose"
End If
' Connect to the web service
Dim client As clientSOAP = Nothing
Try
client = New clientSOAP(args(0), verbose)
Catch ex As Exception
' connection error
error("The following error occurred while connecting to the web service: " + ex.Message, 2)
End Try
' user requests are typed on the keyboard
' in the form function a b - they end with the end command
Dim command As String = Nothing ' command typed on the keyboard
Dim fields As String() = Nothing ' fields of a command line
' prompt to the user
Console.Out.WriteLine("Enter your commands in the format: [add|subtract|multiply|divide] a b" + ControlChars.Lf)
' error handling
Dim errorCommand As Boolean
Try
' loop for entering commands typed on the keyboard
While True
' initially no error
commandError = False
' read command
command = Console.In.ReadLine().Trim().ToLower()
' finished?
If command Is Nothing Or command = "end" Then
Exit While
End If
' Split the command into fields
fields = Regex.Split(command, "\s+")
' there must be three fields
If fields.Length <> 3 Then
Console.Out.WriteLine("syntax: [add|subtract|multiply|divide] a b")
' log the error
commandError = True
End If
' send the request to the web service
If Not commandError Then Console.Out.WriteLine(("result=" + client.executeFunction(fields(0).Trim().ToLower(), fields(1).Trim(), fields(2).Trim())))
' next request
End While
Catch e As Exception
Console.Out.WriteLine(("The following error occurred: " + e.Message))
End Try
' end client-server connection
Try
client.Close()
Catch
End Try
End Sub
' display errors
Public Sub error(ByVal msg As String, ByVal exitCode As Integer)
' display error
System.Console.Error.WriteLine(msg)
' Exit with error
Environment.Exit(exitCode)
End Sub
End Module
The client is now considerably simpler and contains no network communication. The client accepts two parameters:
- the URI of the web service operations
- the optional verbose keyword. If present, network exchanges will be displayed on the screen.
These two parameters are used to construct a clientSOAP object that will handle communication with the web service.
' Connect to the web service
Dim client As clientSOAP = Nothing
Try
client = New clientSOAP(args(0), verbose)
Catch ex As Exception
' connection error
error("The following error occurred while connecting to the web service: " + ex.Message, 2)
End Try
Once the connection to the web service is established, the client can send its requests. These are typed on the keyboard, parsed, and then sent to the server by calling the executeFonction method of the clientSOAP object.
' Make the request to the web service
If Not commandError Then Console.Out.WriteLine(("result=" + client.executeFunction(fields(0).Trim().ToLower(), fields(1).Trim(), fields(2).Trim())))
The clientSOAP class is compiled into an "assembly":
dos>vbc /r:clientSOAP.dll testClientSOAP.vb
dos>dir
03/04/2004 08:46 6 913 clientSOAP.vb
04/03/2004 09:07 7,168 clientSOAP.dll
The testClientSoap client application is then compiled using:
dos>vbc /r:clientSOAP.dll /r:system.dll testClientSOAP.vb
dos>dir
03/04/2004 09:08 2,711 testClientSOAP.vb
03/04/2004 09:08 4,608 testClientSOAP.exe
Here is an example of a non-verbose execution:
dos>testclientsoap http://localhost/st/operations/operations.asmx
Type your commands in the following format: [add|subtract|multiply|divide] a b
add 1 3
result=4
subtract 6 7
result=-1
multiply 4 5
result=20
divide 1 by 2
result=0.5
x
syntax: [add|subtract|multiply|divide] a b
x 1 2
result=[function [x] not available: (add, subtract, multiply, divide)]
add a b
result=[incorrect argument [a] (duplicate)]
add 1 b
result=[invalid argument [b] (duplicate)]
divide 1 by 0
result=[division by zero]
end
You can monitor network traffic by requesting a "verbose" execution:
dos>testClientSOAP http://localhost/operations/operations.asmx verbose
Type your commands in the following format: [add|subtract|multiply|divide] a b
add 4 8
--> POST /operations/operations.asmx HTTP/1.1
--> Host: localhost:80
--> Content-Type: text/xml; charset=utf-8
--> Content-Length: 321
--> Connection: Keep-Alive
--> SOAPAction: "st.istia.univ-angers.fr/add"
-->
<-- HTTP/1.1 100 Continue
<-- Server: Microsoft-IIS/5.0
<-- Date: Thu, 04 Mar 2004 08:15:25 GMT
<-- X-Powered-By: ASP.NET
<--
--> <?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<add xmlns="st.istia.univ-angers.fr">
<a>4</a>
<b>8</b>
</add>
</soap:Body>
</soap:Envelope>
<-- HTTP/1.1 200 OK
<-- Server: Microsoft-IIS/5.0
<-- Date: Thu, 04 Mar 2004 08:15:25 GMT
<-- X-Powered-By: ASP.NET
<-- X-AspNet-Version: 1.1.4322
<-- Cache-Control: private, max-age=0
<-- Content-Type: text/xml; charset=utf-8
<-- Content-Length: 346
<--
<-- <?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-inst
ance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><ajouterResponse xmlns="st.istia.univ-angers.fr"><ajouterResult>12</ajouterResult></ajouterResponse></soap:Body></soap:Envelope>
result=12
end
Now let's build a graphical client.
10.7.3. A Windows graphical client
We will now query our web service using a graphical client that will also use the clientSOAP class. The graphical interface will be as follows:
![]() |
The controls are as follows:
No. | Type | name | role |
TextBox | txtURI | the URI of the web service operations | |
Button | btnOpen | opens the connection to the web service | |
Button | btnClose | closes the connection to the web service | |
ComboBox | cmbFunctions | the list of functions (add, subtract, multiply, divide) | |
TextBox | txtA | the argument for functions | |
TextBox | txtB | argument b of the functions | |
TextBox | txtResult | the result of function(a,b) | |
Button | btnCalculate | starts the calculation of function(a,b) | |
TextBox | txtError | displays a message about the connection status |
There are a few operational constraints:
- The btnOpen button is only active if the txtURI field is not empty and a connection is not already open
- The btnClose button is only active when a connection to the web service has been opened
- The btnCalculate button is active only when a connection is open and the txtA and txtB fields are not empty
- The txtResult and txtError fields have the ReadOnly attribute set to true
The client begins by opening the connection to the web service using the [Open] button:

Next, the user can select a function and values for a and b:





The application code follows. We have omitted the form code, which is not relevant here.
'namespaces
Imports System
Imports System.Windows.Forms
' the form class
Public Class FormClientSOAP
Inherits System.Windows.Forms.Form
' instance attributes
Dim client As clientSOAP ' SOAP client for the operations web service
#Region " Code generated by Windows Form Designer "
Public Sub New()
MyBase.New()
'This call is required by the Windows Form Designer.
InitializeComponent()
' other initializations
myInit()
End Sub
'The form's overridden Dispose method to clean up the component list.
Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
....
End Sub
...
Private Sub InitializeComponent()
....
End Sub
#End Region
Private Sub myInit()
' initialize form
cmbFunctions.SelectedIndex = 0
btnOpen.Enabled = False
btnClose.Enabled = True
btnCalculate.Enabled = False
End Sub
Private Sub txtURI_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles txtURI.TextChanged
' The content of the text box has changed - set the state of the Open button
btnOpen.Enabled = txtURI.Text.Trim <> ""
End Sub
Private Sub btnOpen_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnOpen.Click
' Request to open a connection to the web service
Try
' Create a [clientSOAP] object
client = New clientSOAP(txtURI.Text.Trim, False)
' button states
btnOpen.Enabled = False
btnClose.Enabled = True
'The URI can no longer be modified
txtURI.ReadOnly = True
' client status
txtError.Text = "Connection to the web service open"
Catch ex As Exception
' An error occurred - display it
txtError.Text = ex.Message
End Try
End Sub
Private Sub btnClose_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnClose.Click
' Close the connection to the web service
client.Close()
' button states
btnOpen.Enabled = True
btnClose.Enabled = False
' URI
txtURI.ReadOnly = False
' client status
txtError.Text = "Connection to the web service closed"
End Sub
Private Sub btnCalculate_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnCalculate.Click
' Calculate a function f(a,b)
' Clear the previous result
txtResult.Text = ""
Try
txtResult.Text = client.executeFunction(cmbFunctions.Text, txtA.Text.Trim, txtB.Text.Trim)
Catch ex As Exception
' A network error occurred
txtError.Text = ex.Message
' closing the connection
btnClose_Click(Nothing, Nothing)
End Try
End Sub
Private Sub txtA_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles txtA.TextChanged
' Change the value of A
btnCalculate.Enabled = txtA.Text.Trim <> "" And txtB.Text.Trim <> ""
End Sub
Private Sub txtB_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles txtB.TextChanged
' Change in the value of B
txtA_TextChanged(Nothing, Nothing)
End Sub
' main method
Public Shared Sub main()
Application.Run(New FormClientSOAP)
End Sub
End Class
Once again, the clientSOAP class hides all network-related aspects of the application. The application was built as follows:
- The clientSOAP.dll assembly containing the clientSOAP class was placed in the project folder
- The clientsoapgui.vb GUI was built with VS.NET and then compiled in a DOS window:
dos>vbc /r:system.dll /r:system.windows.forms.dll /r:system.drawing.dll /r:clientSOAP.dll clientsoapgui.vb
dos>dir
03/04/2004 09:13 7 168 clientSOAP.dll
03/04/2004 16:44 9,866 clientsoapgui.vb
03/04/2004 16:44 11,264 clientsoapgui.exe
The graphical interface was then launched by:
10.8. A proxy client
Let’s recap what we’ve just done. We created an intermediate class that encapsulates network exchanges between a client and a web service according to the diagram below:
![]() |
The .NET platform takes this logic a step further. Once we know the Web service to access, we can automatically generate the class that will act as an intermediary to access the Web service’s functions and hide the entire network layer. This class is called a proxy for the Web service for which it was generated.
How do you generate a web service proxy class? A web service is always accompanied by a description file in XML format. If the URI of our web service operations is http://localhost/operations/operations.asmx, its description file is available at the URL http://localhost/operations/operations.asmx?wsdl, as shown in the following screenshot:

This is an XML file that precisely describes all the functions of the web service, including for each one the type and number of parameters, and the type of the result. This file is called the service’s WSDL file because it uses the WSDL (Web Services Description Language). From this file, a proxy class can be generated using the wsdl tool:
dos>wsdl http://localhost/operations/operations.asmx?wsdl /language=vb
Microsoft (R) Web Services Description Language Utility
[Microsoft (R) .NET Framework, Version 1.1.4322.573]
Copyright (C) Microsoft Corporation 1998-2002. All rights reserved.
Writing file 'D:\data\devel\vbnet\poly\chap9\clientproxy\operations.vb'.
dos>dir
03/04/2004 17:17 6,663 operations.vb
The wsdl tool generates a VB.NET source file (option /language=vb) named after the class implementing the web service, in this case operations. Let's examine part of the generated code:
'------------------------------------------------------------------------------
' <autogenerated>
' This code was generated by a tool.
' Runtime Version: 1.1.4322.573
'
' Changes to this file may cause incorrect behavior and will be lost if
' the code is regenerated.
' </autogenerated>
'------------------------------------------------------------------------------
Option Strict Off
Option Explicit On
Imports System
Imports System.ComponentModel
Imports System.Diagnostics
Imports System.Web.Services
Imports System.Web.Services.Protocols
Imports System.Xml.Serialization
'
'This source code was automatically generated by wsdl, Version=1.1.4322.573.
'
'<remarks/>
<System.Diagnostics.DebuggerStepThroughAttribute(), _
System.ComponentModel.DesignerCategoryAttribute("code"), _
System.Web.Services.WebServiceBindingAttribute(Name:="operationsSoap", [Namespace]:="st.istia.univ-angers.fr")> _
Public Class operations
Inherits System.Web.Services.Protocols.SoapHttpClientProtocol
'<remarks/>
Public Sub New()
MyBase.New
Me.Url = "http://localhost/operations/operations.asmx"
End Sub
'<remarks/>
<System.Web.Services.Protocols.SoapDocumentMethodAttribute("st.istia.univ-angers.fr/ajouter", RequestNamespace:="st.istia.univ-angers.fr", ResponseNamespace:="st.istia.univ-angers.fr", Use:=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle:=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)> _
Public Function add(ByVal a As Double, ByVal b As Double) As Double
Dim results() As Object = Me.Invoke("add", New Object() {a, b})
Return CType(results(0), Double)
End Function
'<remarks/>
Public Function BeginAdd(ByVal a As Double, ByVal b As Double, ByVal callback As System.AsyncCallback, ByVal asyncState As Object) As System.IAsyncResult
Return Me.BeginInvoke("add", New Object() {a, b}, callback, asyncState)
End Function
'<remarks/>
Public Function EndAdd(ByVal asyncResult As System.IAsyncResult) As Double
Dim results() As Object = Me.EndInvoke(asyncResult)
Return CType(results(0), Double)
End Function
....
This code may seem a bit complex at first glance. We don’t need to understand the details to be able to use it. Let’s first examine the class declaration:
The class bears the name operations of the web service for which it was built. It derives from the SoapHttpClientProtocol class:

Our proxy class has a single constructor:
The constructor assigns the URL of the web service associated with the proxy to the url attribute. The operations class above does not define the url attribute itself. It is inherited from the class from which the proxy derives: System.Web.Services.Protocols.SoapHttpClientProtocol. Let’s now examine what relates to the add method:
Public Function add(ByVal a As Double, ByVal b As Double) As Double
Dim results() As Object = Me.Invoke("add", New Object() {a, b})
Return CType(results(0), Double)
End Function
We can see that it has the same signature as in the operations Web service, where it was defined as follows:
The way this class communicates with the web service is not shown here. This communication is entirely handled by the parent class System.Web.Services.Protocols.SoapHttpClientProtocol. The proxy contains only what distinguishes it from other proxies:
- the URL of the associated web service
- the definition of the associated service's methods.
To use the methods of the operations web service, a client only needs the operations proxy class generated earlier. Let’s compile this class into an assembly file:
Now let's write a console client. It is called without parameters and executes the requests typed on the keyboard:
dos>testclientproxy
Enter your commands in the following format: [add|subtract|multiply|divide|doall] a b
add 4 5
result=9
subtract 9 8
result=1
multiply 10 4
result=40
divide 6 by 7
result=0.857142857142857
do-it-all 10 20
results=[30,-10,200,0,5]
divide 5 0
result=+Infinity
end
The client's code is as follows:
' namespaces
Imports System
Imports System.IO
Imports System.Text.RegularExpressions
Imports System.Collections
Imports Microsoft.VisualBasic
Public Module testClientProxy
' interactively executes commands typed on the keyboard
' and sends them to the operations web service
Public Sub Main()
' there are no more arguments—the web service URL is hardcoded in the proxy
' creates a dictionary of web service functions
Dim functions As String() = {"add", "subtract", "multiply", "divide", "doall"}
Dim functionDictionary As New Hashtable
Dim i As Integer
For i = 0 To functions.Length - 1
dicoFonctions.Add(functions(i), True)
Next i
' Create a proxy object named operations
Dim myOperations As operations = Nothing
Try
myOperations = New operations
Catch ex As Exception
' connection error
error("The following error occurred while connecting to the web service proxy: " + ex.Message, 2)
End Try
' user requests are typed on the keyboard
' in the form function a b - they end with the end command
Dim command As String = Nothing ' command typed on the keyboard
Dim fields As String() = Nothing ' fields of a command line
' prompt to the user
Console.Out.WriteLine("Enter your commands in the following format: [add|subtract|multiply|divide|doall] a b" + ControlChars.Lf)
' some local data
Dim commandError As Boolean
Dim function As String
Dim a, b As Double
' loop for capturing keyboard commands
While True
' initially no error
commandError = False
' read command
command = Console.In.ReadLine().Trim().ToLower()
' finished?
If command Is Nothing Or command = "end" Then
Exit While
End If
' Split the command into fields
fields = Regex.Split(command, "\s+")
Try
' three fields are required
If fields.Length <> 3 Then
Throw New Exception
End If
' Field 0 must be a recognized function
function = fields(0)
If Not functionDict.ContainsKey(function) Then
Throw New Exception
End If
' Field 1 must be a valid number
a = Double.Parse(fields(1))
' Field 2 must be a valid number
b = Double.Parse(fields(2))
Catch
' invalid command
Console.Out.WriteLine("syntax: [add|subtract|multiply|divide] a b")
commandError = True
End Try
' Send the request to the web service
If Not commandError Then
Try
Dim result As Double
Dim results() As Double
If function = "add" Then
result = myOperations.add(a, b)
Console.Out.WriteLine(("result=" + result.ToString))
End If
If function = "subtract" Then
result = myOperations.subtract(a, b)
Console.Out.WriteLine(("result=" + result.ToString))
End If
If function = "multiply" Then
result = myOperations.multiply(a, b)
Console.Out.WriteLine(("result=" + result.ToString))
End If
If function = "divide" Then
result = myOperations.divide(a, b)
Console.Out.WriteLine(("result=" + result.ToString))
End If
If function = "do-it-all" Then
results = myOperations.doAll(a, b)
Console.Out.WriteLine(("results=[" + results(0).ToString + "," + results(1).ToString + "," + _
results(2).ToString + "," + results(3).ToString + "]"))
End If
Catch e As Exception
Console.Out.WriteLine(("The following error occurred: " + e.Message))
End Try
End If
End While
End Sub
' Display errors
Public Sub error(ByVal msg As String, ByVal exitCode As Integer)
' display error
System.Console.Error.WriteLine(msg)
' Exit with error
Environment.Exit(exitCode)
End Sub
End Module
We are only examining the code specific to using the proxy class. First, a proxy operations object is created:
' create a proxy operations object
Dim myOperations As operations = Nothing
Try
myOperations = New operations
Catch ex As Exception
' connection error
error("The following error occurred while connecting to the web service proxy: " + ex.Message, 2)
End Try
Lines a and b are typed on the keyboard. Based on this information, the appropriate proxy methods are called:
' make the request to the web service
If Not commandError Then
Try
Dim result As Double
Dim results() As Double
If function = "add" Then
result = myOperations.add(a, b)
Console.Out.WriteLine(("result=" + result.ToString))
End If
If function = "subtract" Then
result = myOperations.subtract(a, b)
Console.Out.WriteLine(("result=" + result.ToString))
End If
If function = "multiply" Then
result = myOperations.multiply(a, b)
Console.Out.WriteLine(("result=" + result.ToString))
End If
If function = "divide" Then
result = myOperations.divide(a, b)
Console.Out.WriteLine(("result=" + result.ToString))
End If
If function = "do-it-all" Then
results = myOperations.doAll(a, b)
Console.Out.WriteLine(("results=[" + results(0).ToString + "," + results(1).ToString + "," + _
results(2).ToString + "," + results(3).ToString + "]"))
End If
Catch e As Exception
Console.Out.WriteLine(("The following error occurred: " + e.Message))
End Try
Here, we are dealing for the first time with the all-in-one operation that performs all four operations. It had been ignored until now because it returns an array of numbers encapsulated in an XML wrapper that is more complicated to handle than the simple XML responses from the other functions, which return only a single result. Here, with the proxy class, we see that using the all-in-one method is no more complicated than using the other methods. The application was compiled in a DOS window as follows:
dos>vbc /r:operations.dll /r:system.dll /r:system.web.services.dll testClientProxy.vb
dos>dir
03/04/2004 17:17 6,663 operations.vb
03/04/2004 17:24 7,680 operations.dll
03/04/2004 17:41 4,099 testClientProxy.vb
03/04/2004 5:41 PM 5,632 testClientProxy.exe
10.9. Configuring a Web Service
A web service may require configuration information to initialize correctly. With IIS, this information can be placed in a file called web.config located in the same folder as the web service. Suppose we want to create a web service that requires two pieces of information to initialize: a name and an age. These two pieces of information can be placed in the web.config file in the following format:
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<appSettings>
<add key="name" value="tintin"/>
<add key="age" value="27"/>
</appSettings>
</configuration>
The initialization settings are placed in an XML container:
An initialization parameter named P with the value V will be declared with the line:
<add key="P" value="V"/>
How does the web service retrieve this information? When IIS loads a web service, it checks to see if there is a web.config file in the same folder. If so, it reads it. The value V of a parameter P is obtained using the statement:
where ConfigurationSettings is a class in the System.Configuration namespace.
Let's test this technique on the following web service:
<%@ WebService language="VB" class=personne %>
Imports System.Web.Services
imports System.Configuration
<WebService([Namespace] := "st.istia.univ-angers.fr")> _
Public Class person
Inherits WebService
' attributes
Private name As String
Private age As Integer
' constructor
Public Sub New()
' Initialize attributes
name = ConfigurationSettings.AppSettings("name")
age = Integer.Parse(ConfigurationSettings.AppSettings("age"))
End Sub
<WebMethod> _
Function id() As String
Return "[" + name + "," + age.ToString + "]"
End Function
End Class
The Person web service has two attributes, name and age, which are initialized in its parameterless constructor using values read from the Person service's web.config configuration file. This file is as follows:
<configuration>
<appSettings>
<add key="name" value="tintin"/>
<add key="age" value="27"/>
</appSettings>
</configuration>
The web service also has a <WebMethod> with no parameters that simply returns the name and age attributes. The service is registered in the source file personne.asmx, which is located along with its configuration file in the folder c:\inetpub\wwwroot\st\personne:
Let’s associate a virtual IIS folder /config with the physical folder above. Start IIS, then use a browser to request the URL http://localhost/config/personne.asmx for the person service:

Follow the link for the single id method:

The id method has no parameters. Let’s use the Call button:

We have successfully retrieved the information stored in the service’s web.config file.
10.10. The tax calculation Web Service
We’ll revisit the now-familiar IMPOTS application. The last time we worked with it, we turned it into a remote server that could be accessed over the internet. We’ll now turn it into a web service.
10.10.1. The Web Service
We’ll start with the impôt class created in the chapter on databases, which is built from information contained in an ODBC database:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Imports System.Data
Imports Microsoft.Data.Odbc
Imports System.Collections
Public Class tax
' The data required to calculate the tax
' comes from an external source
Private limits(), coeffR(), coeffN() As Decimal
' constructor
Public Sub New(ByVal LIMITES() As Decimal, ByVal COEFFR() As Decimal, ByVal COEFFN() As Decimal)
' Check that the three arrays are the same size
Dim OK As Boolean = LIMITS.Length = COEFFR.Length And LIMITS.Length = COEFFN.Length
If Not OK Then
Throw New Exception("The three arrays provided do not have the same size(" & LIMITES.Length & "," & COEFFR.Length & "," & COEFFN.Length & ")")
End If
' All good
Me.limites = LIMITES
Me.coeffR = COEFFR
Me.coeffN = COEFFN
End Sub
' constructor 2
Public Sub New(ByVal DSNimpots As String, ByVal Timpots As String, ByVal colLimites As String, ByVal colCoeffR As String, ByVal colCoeffN As String)
' initializes the three limit arrays, coeffR, and coeffN based on
' the contents of the Timpots table in the ODBC database DSNimpots
' colLimits, colCoeffR, and colCoeffN are the three columns of this table
' may throw an exception
Dim connectString As String = "DSN=" + DSNimpots + ";" ' connection string to the database
Dim impotsConn As OdbcConnection = Nothing ' the connection
Dim sqlCommand As OdbcCommand = Nothing ' the SQL command
' the SELECT query
Dim selectCommand As String = "select " + colLimites + "," + colCoeffR + "," + colCoeffN + " from " + Timpots
' arrays to retrieve the data
Dim tLimits As New ArrayList
Dim tCoeffR As New ArrayList
Dim tCoeffN As New ArrayList
' attempt to access the database
impotsConn = New OdbcConnection(connectString)
impotsConn.Open()
' Create a command object
sqlCommand = New OdbcCommand(selectCommand, impotsConn)
' execute the query
Dim myReader As OdbcDataReader = sqlCommand.ExecuteReader()
' Process the retrieved table
While myReader.Read()
' the data from the current row is placed in the arrays
tLimites.Add(myReader(colLimites))
tCoeffR.Add(myReader(colCoeffR))
tCoeffN.Add(myReader(colCoeffN))
End While
' Release resources
myReader.Close()
taxConn.Close()
' dynamic arrays are converted to static arrays
Me.limits = New Decimal(tLimits.Count) {}
Me.coeffR = New Decimal(tLimites.Count) {}
Me.coeffN = New Decimal(tLimites.Count) {}
Dim i As Integer
For i = 0 To tLimits.Count - 1
limits(i) = Decimal.Parse(tLimits(i).ToString())
coeffR(i) = Decimal.Parse(tCoeffR(i).ToString())
coeffN(i) = Decimal.Parse(tCoeffN(i).ToString())
Next i
End Sub
' tax calculation
Public Function calculate(ByVal married As Boolean, ByVal numberOfChildren As Integer, ByVal salary As Integer) As Long
' Calculate the number of shares
Dim nbShares As Decimal
If married Then
nbParts = CDec(nbChildren) / 2 + 2
Else
nbParts = CDec(nbChildren) / 2 + 1
End If
If nbChildren >= 3 Then
nbParts += 0.5D
End If
' Calculate taxable income & Family Quotient
Dim income As Decimal = 0.72D * salary
Dim QF As Decimal = income / nbParts
' Calculate tax
limits((limits.Length - 1)) = QF + 1
Dim i As Integer = 0
While QF > limits(i)
i += 1
End While
' return result
Return CLng(revenue * coeffR(i) - nbParts * coeffN(i))
End Function
End Class
In the web service, only a parameterless constructor can be used. Therefore, the class constructor will become the following:
' constructor
Public Sub New()
' initializes the three arrays limits, coeffR, and coeffN based on
' the contents of the Timpots table in the ODBC DSNimpots database
' colLimites, colCoeffR, colCoeffN are the three columns of this table
' may throw an exception
' retrieves the service configuration settings
Dim DSNimpots As String = ConfigurationSettings.AppSettings("DSN")
Dim Timpots As String = ConfigurationSettings.AppSettings("TABLE")
Dim colLimits As String = ConfigurationSettings.AppSettings("COL_LIMITS")
Dim colCoeffR As String = ConfigurationSettings.AppSettings("COL_COEFFR")
Dim colCoeffN As String = ConfigurationSettings.AppSettings("COL_COEFFN")
' query the database
Dim connectString As String = "DSN=" + DSNimpots + ";" ' database connection string
The five parameters of the constructor from the previous class are now read from the service's web.config file. The code in the impots.asmx source file is as follows. It includes most of the previous code. We have simply wrapped the portions of code specific to the web service:
<%@ WebService language="VB" class=impots %>
' creation of an impots web service
Imports System
Imports System.Data
Imports Microsoft.Data.Odbc
Imports System.Collections
Imports System.Configuration
Imports System.Web.Services
<WebService([Namespace]:="st.istia.univ-angers.fr")> _
Public Class tax
Inherits WebService
' The data required to calculate the tax
' comes from an external source
Private limits(), coeffR(), coeffN() As Decimal
Private OK As Boolean = False
Private errMessage As String = ""
' constructor
Public Sub New()
' initializes the three arrays limits, coeffR, and coeffN based on
' the contents of the Timpots table in the ODBC DSNimpots database
' colLimites, colCoeffR, colCoeffN are the three columns of this table
' may throw an exception
' retrieves the service configuration settings
Dim DSNimpots As String = ConfigurationSettings.AppSettings("DSN")
Dim Timpots As String = ConfigurationSettings.AppSettings("TABLE")
Dim colLimits As String = ConfigurationSettings.AppSettings("COL_LIMITS")
Dim colCoeffR As String = ConfigurationSettings.AppSettings("COL_COEFFR")
Dim colCoeffN As String = ConfigurationSettings.AppSettings("COL_COEFFN")
' query the database
Dim connectString As String = "DSN=" + DSNimpots + ";" ' database connection string
Dim impotsConn As OdbcConnection = Nothing ' the connection
Dim sqlCommand As OdbcCommand = Nothing ' the SQL command
Dim myReader As OdbcDataReader ' ODBC data reader
' SELECT query
Dim selectCommand As String = "select " + colLimites + "," + colCoeffR + "," + colCoeffN + " from " + Timpots
' arrays to retrieve the data
Dim tLimits As New ArrayList
Dim tCoeffR As New ArrayList
Dim tCoeffN As New ArrayList
' attempt to access the database
Try
impotsConn = New OdbcConnection(connectString)
impotsConn.Open()
' create a command object
sqlCommand = New OdbcCommand(selectCommand, impotsConn)
' execute the query
myReader = sqlCommand.ExecuteReader()
' Working with the retrieved table
While myReader.Read()
' the data from the current row is placed in the arrays
tLimites.Add(myReader(colLimites))
tCoeffR.Add(myReader(colCoeffR))
tCoeffN.Add(myReader(colCoeffN))
End While
' release resources
myReader.Close()
taxConn.Close()
' dynamic arrays are converted to static arrays
Me.limits = New Decimal(tLimits.Count) {}
Me.coeffR = New Decimal(tLimites.Count) {}
Me.coeffN = New Decimal(tLimites.Count) {}
Dim i As Integer
For i = 0 To tLimits.Count - 1
limits(i) = Decimal.Parse(tLimits(i).ToString())
coeffR(i) = Decimal.Parse(tCoeffR(i).ToString())
coeffN(i) = Decimal.Parse(tCoeffN(i).ToString())
Next i
' OK
OK = True
errMessage = ""
Catch ex As Exception
' error
OK = False
errMessage += "[" + ex.Message + "]"
End Try
End Sub
' Calculate tax
<WebMethod()> _
Function calculate(ByVal married As Boolean, ByVal numberOfChildren As Integer, ByVal salary As Integer) As Long
' Calculate the number of shares
Dim nbShares As Decimal
If married Then
nbParts = CDec(nbChildren) / 2 + 2
Else
nbParts = CDec(nbChildren) / 2 + 1
End If
If nbChildren >= 3 Then
nbParts += 0.5D
End If
' Calculate taxable income & Family Quotient
Dim income As Decimal = 0.72D * salary
Dim QF As Decimal = income / nbParts
' calculate tax
limits((limits.Length - 1)) = QF + 1
Dim i As Integer = 0
While QF > limits(i)
i += 1
End While
' return result
Return CLng(revenue * coeffR(i) - nbParts * coeffN(i))
End Function
' id
<WebMethod()> _
Function id() As String
' to check if everything is OK
Return "[" + OK + "," + errMessage + "]"
End Function
End Class
Let's explain the few changes made to the impots class beyond those necessary to turn it into a web service:
- Reading the database in the constructor may fail. So we have added two attributes to our class and a method:
- the boolean OK is true if the database could be read, false otherwise
- The string `errMessage` contains an error message if the database could not be read.
- The parameterless id method retrieves the values of these two attributes.
- To handle any potential database access errors, the portion of the constructor’s code related to this access has been enclosed in a try-catch block.
The web.config file for the service configuration is as follows:
<configuration>
<appSettings>
<add key="DSN" value="mysql-impots" />
<add key="TABLE" value="timpots" />
<add key="COL_LIMITES" value="limites" />
<add key="COL_COEFFR" value="coeffr" />
<add key="COL_COEFFN" value="coeffn" />
</appSettings>
</configuration>
When first attempting to load the impots service, the compiler reported that it could not find the Microsoft.Data.Odbc namespace used in the directive:
After consulting the documentation
- a compilation directive was added to web.config to specify that the Microsoft.Data.odbc assembly should be used
- a copy of the microsoft.data.odbc.dll file was placed in the project’s bin folder. This folder is systematically searched by the web service compiler when it looks for an “assembly.”
Other solutions seem possible but have not been explored here. The configuration file has therefore become:
<configuration>
<appSettings>
<add key="DSN" value="mysql-impots" />
<add key="TABLE" value="timpots" />
<add key="COL_LIMITES" value="limites" />
<add key="COL_COEFFR" value="coeffr" />
<add key="COL_COEFFN" value="coeffn" />
</appSettings>
<system.web>
<compilation>
<assemblies>
<add assembly="Microsoft.Data.Odbc" />
</assemblies>
</compilation>
</system.web>
</configuration>
The contents of the impots\bin folder:
The service and its configuration file have been placed in impots:
The physical folder for the web service has been mapped to the virtual folder /impots in IIS. The service page is then as follows:

If you follow the id link:

If you use the Call button:

The previous result displays the values of the OK (true) and errMessage ("") attributes. In this example, the database was loaded successfully. This hasn't always been the case, which is why we added the id method to access the error message. The error was that the database DSN name had been defined as a user DSN when it should have been defined as a system DSN. This distinction is made in the 32-bit ODBC Source Manager:
![]() |
Let’s go back to the service page:

Let’s follow the “Calculate” link:

We define the call parameters and execute the call:

The result is correct.
10.10.2. Generate the proxy for the impots service
Now that we have a working impots web service, we can generate its proxy class. Remember that this will be used by client applications to access the impots web service transparently. First, we use the wsdl utility to generate the source file for the proxy class, which is then compiled into a DLL.
dos>wsdl /language=vb http://localhost/impots/impots.asmx
Microsoft® Web Services Description Language Utility
[Microsoft (R) .NET Framework, Version 1.1.4322.573]
Copyright (C) Microsoft Corporation 1998-2002. All rights reserved.
Writing file 'D:\data\serge\devel\vbnet\poly\chap9\impots\impots.vb'.
D:\data\serge\devel\vbnet\poly\chap9\impots>dir
03/09/2004 10:20 <REP> bin
03/09/2004 10:58 4,651 impots.asmx
03/09/2004 11:05 3,364 impots.vb
03/09/2004 10:19 431 web.config
dos>vbc /t:library /r:system.dll /r:system.web.services.dll /r:system.xml.dll impots.vb
Microsoft® Visual Basic .NET Compiler version 7.10.3052.4
for Microsoft® .NET Framework version 1.1.4322.573
Copyright (C) Microsoft Corporation 1987-2002. All rights reserved.
dos>dir
03/09/2004 10:20 <REP> bin
03/09/2004 10:58 4,651 impots.asmx
03/09/2004 11:09 5,120 impots.dll
03/09/2004 11:05 3,364 impots.vb
03/09/2004 10:19 431 web.config
10.10.3. Using the proxy with a client
In the chapter on databases, we created a console application to calculate taxes:
dos>dir
02/27/2004 16:56 5 120 impots.dll
02/27/2004 5:12 PM 3,586 impots.vb
02/27/2004 5:08 PM 6,144 testimpots.exe
02/27/2004 5:18 PM 3,328 testimpots.vb
dos>testimpots
pg DSNimpots tabImpots colLimits colCoeffR colCoeffN
dos>testimpots odbc-mysql-dbimpots taxes limits coeffr coeffn
Tax calculation parameters in married format nbChildren salary or nothing to stop :o 2 200000
tax=22504 F
The testimpots program then used the standard tax class contained in the impots.dll file. The code for the testimpots.vb program was as follows:
Option Explicit On
Option Strict On
' namespaces
Imports System
Imports Microsoft.VisualBasic
' test page
Module testimports
Sub Main(ByVal arguments() As String)
' Interactive tax calculation program
' The user enters three pieces of information via the keyboard: marital status, number of children, salary
' the program then displays the tax due
Const syntax1 As String = "pg DSNimpots tabImpots colLimits colCoeffR colCoeffN"
Const syntax2 As String = "syntax: married noChildren salary" + ControlChars.Lf + "married: o for married, n for unmarried" + ControlChars.Lf + "noChildren: number of children" + ControlChars.Lf + "salary: annual salary in F"
' Checking program parameters
If arguments.Length <> 5 Then
' error message
Console.Error.WriteLine(syntax1)
' end
Environment.Exit(1)
End If 'if
' retrieve the arguments
Dim DSNimpots As String = arguments(0)
Dim taxTab As String = arguments(1)
Dim colLimits As String = arguments(2)
Dim colCoeffR As String = arguments(3)
Dim colCoeffN As String = arguments(4)
' Create a tax object
Dim taxObj As Tax = Nothing
Try
taxObj = New Tax(TaxDSN, TaxTab, LimitCol, CoeffRCol, CoeffNCol)
Catch ex As Exception
Console.Error.WriteLine(("The following error occurred: " + ex.Message))
Environment.Exit(2)
End Try
' infinite loop
While True
' initially no errors
Dim error As Boolean = False
' Requesting tax calculation parameters
Console.Out.Write("Tax calculation parameters in the format: married, number of children, salary, or 'nothing' to exit:")
Dim parameters As String = Console.In.ReadLine().Trim()
' anything to do?
If parameters Is Nothing Or parameters = "" Then
Exit While
End If
' Check the number of arguments in the entered line
Dim args As String() = parameters.Split(Nothing)
Dim nbParameters As Integer = args.Length
If nbParameters <> 3 Then
Console.Error.WriteLine(syntax2)
error = True
End If
Dim husband As String
Dim nbChildren As Integer
Dim salary As Integer
If Not error Then
' Checking the validity of the
' married
married = args(0).ToLower()
If married <> "o" And married <> "n" Then
Console.Error.WriteLine((syntax2 + ControlChars.Lf + "Invalid 'married' argument: enter 'y' or 'n'")
error = True
End If
' nbChildren
nbChildren = 0
Try
nbChildren = Integer.Parse(args(1))
If nbChildren < 0 Then
Throw New Exception
End If
Catch
Console.Error.WriteLine(syntax2 + "\nInvalid nbChildren argument: enter a positive integer or zero")
error = True
End Try
' salary
salary = 0
Try
salary = Integer.Parse(args(2))
If salary < 0 Then
Throw New Exception
End If
Catch
Console.Error.WriteLine(syntax2 + "\nInvalid salary argument: enter a positive integer or zero")
error = True
End Try
End If
If Not error Then
' parameters are correct - calculate the tax
Console.Out.WriteLine(("tax=" & objTax.calculate(married = "o", numChildren, salary).ToString + " F"))
End If
End While
End Sub
End Module
We’ll use the same program to now have it use the impots web service through the impots proxy class created earlier. We have to modify the code slightly:
- whereas the original tax class had a constructor with five arguments, the tax proxy class has a constructor with no parameters. As we have seen, the five parameters are now set in the web service configuration file.
- therefore, there is no longer any need to pass these five parameters as arguments to the test program
The new code is as follows:
Imports System
Imports Microsoft.VisualBasic
' test page
Module testimpots
Public Sub Main(ByVal arguments() As String)
' Interactive tax calculation program
' the user enters three pieces of data via the keyboard: married, numberOfChildren, salary
' the program then displays the tax due
Const syntax2 As String = "syntax: married numberOfChildren salary" + ControlChars.Lf + "married: o for married, n for unmarried" + ControlChars.Lf + "numberOfChildren: number of children" + ControlChars.Lf + "salary: annual salary in F"
' Create a tax object
Dim taxObj As Tax = Nothing
Try
taxObj = New tax
Catch ex As Exception
Console.Error.WriteLine(("The following error occurred: " + ex.Message))
Environment.Exit(2)
End Try
' infinite loop
If Error Then As Boolean
While True
' initially no error
error = False
' Request the parameters for the tax calculation
Console.Out.Write("Tax calculation parameters in the format: married, number of children, salary, or 'nothing' to exit:")
Dim parameters As String = Console.In.ReadLine().Trim()
' anything to do?
If parameters Is Nothing Or parameters = "" Then
Exit While
End If
' Check the number of arguments in the entered line
Dim args As String() = parameters.Split(Nothing)
Dim numParameters As Integer = args.Length
If nbParameters <> 3 Then
Console.Error.WriteLine(syntax2)
error = True
End If
If Not error Then
' Checking the validity of the parameters
' groom
Dim married As String = args(0).ToLower()
If married <> "o" And married <> "n" Then
Console.Error.WriteLine((syntax2 + ControlChars.Lf + "Invalid 'married' argument: enter 'o' or 'n'")
error = True
End If
' nbEnfants
Dim nbEnfants As Integer = 0
Try
nbChildren = Integer.Parse(args(1))
If nbChildren < 0 Then
Throw New Exception
End If
Catch
Console.Error.WriteLine((syntax2 + ControlChars.Lf + "Invalid number of children argument: enter a positive integer or zero"))
error = True
End Try
' salary
Dim salary As Integer = 0
Try
salary = Integer.Parse(args(2))
If salary < 0 Then
Throw New Exception
End If
Catch
Console.Error.WriteLine((syntax2 + ControlChars.Lf + "Invalid salary argument: enter a positive integer or zero"))
error = True
End Try
' if the parameters are correct - calculate the tax
If Not error Then Console.Out.WriteLine(("tax=" + objTax.calculate(married = "o", numChildren, salary).ToString + " F"))
End If
End While
End Sub
End Module
We have the impots.dll proxy and the testimpots source code in the same folder.
dos>dir
03/09/2004 11:28 <REP> bin
03/09/2004 11:09 5,120 impots.dll
03/09/2004 11:34 3,396 testimpots.vb
03/09/2004 10:19 431 web.config
We compile the testimpots.vb source file:
dos>vbc /r:impots.dll /r:microsoft.visualbasic.dll /r:system.web.services.dll /r:system.dll testimpots.vb
dos>dir
03/09/2004 11:28 <REP> bin
03/09/2004 11:09 5,120 impots.dll
03/09/2004 11:05 3,364 impots.vb
03/09/2004 11:35 5,632 testimpots.exe
03/09/2004 11:34 3,396 testimpots.vb
03/09/2004 10:19 431 web.config
then run it:
dos>testimpots
Tax calculation parameters in married format: number of children, salary, or nothing to stop :o 2 200000
tax=22,504 F
We get the expected result.





