“It is new fancy rathert than taste which produces so many new fashions” --Voltaire
Chapter 1 showed you how easy it is to create and invoke a simple Web service using .NET. This was a good start and was intended to get you hooked on creating Web services. Practically however, the Web services you create will need to do much more than just add two numbers. This chapter will build on what you’ve learned in Chapters 1 through 4 and dive deeper into the details of creating and invoking Web services using .NET. You will learn how to build .NET Web services, customize their WSDL, and invoke them using .NET clients. Even if you have not read chapters 2 – 4, you can still learn a lot from this chapter. If that’s the case, you might want to go back and read chapters 2 – 4 after you’ve read this chapter and worked through the code.
Web services are part of the ASP.NET framework which means you do not need a special IDE to write a .NET Web service, you can even build one with just notepad. A .NET Web service consists of a file with the .asmx extension and an optional assembly (for example .dll or .exe). This .asmx file is considered the Web service’s end point and therefore must be placed in a Web accessible folder such as a Web application’s vroot. The .asmx file contains a WebService processing directive such as:
<%@ WebService Language="vb" Class="MathService" %>
The Language attribute specifies the language that the Web service code is written in. The Class attribute specifies the name of the class that represents the Web service. This includes the class’s namespace if any. The rest of the .asmx file can contain the Web service implementation as well as any supporting code such as private classes etc. For example, a Web service that exposes one method called Add could be implemented in an .asmx file like this:
<%@ WebService Language="vb" Class="MathService" %>
Imports System.Web.Services
Public Class MathService
<WebMethod()> _
Public Function Add(ByVal a As Double, _
ByVal b As Double) As Double
return a + b
End Function
End Class
Putting all implementation code in a single file is usually a bad idea because it is so easy to accidentally change the code on a production server. It is also easier to maintain code that is logically divided into different files. To do this, you should compile the Web service implementation in an assembly, e.g. a .dll and place this assembly in the application’s bin folder. For example, if your Web server has an application vroot called /services, you would place the .asmx files directly in /services and place the Web service implementation assemblies in /services/bin. The resulting .asmx file would contain only the WebService directive:
<%@ WebService Language="vb" Class="MathService" %>
The actual Web service implementation would be in an assembly inside the application’s bin folder. If you use Visual Studio .NET to create a Web service it will automatically create the servicename.asmx file and another file called servicename.asmx.vb (or .cs if you are using C#). For example, listing 6-1 shows the equivalent math service built with Visual Studio .NET.
Listing 6‑1 Contents of the .asmx and .asmx.vb files that VS .NET automatically generates. Note that the code in this listing is from two different files.
'Contents of MathService.asmx
<%@ WebService Language="vb"
Codebehind="MathService.asmx.vb"
Class="ClassWS.MathService" %>
'Contents of MathService.asmx.vb
Imports System.Web.Services
Public Class MathService
Inherits System.Web.Services.WebService
<WebMethod()> _
Public Function Add(ByVal a As Double, _
ByVal b As Double) As Double
Return a + b
End Function
End Class
Figure 6-1 shows how the .asmx and .asmx.vb file relate to one another. The .asmx file contains a WebService directive which has an additional attribute called Codebehind which names the file that contains the Web service implementation. This attribute is not needed by the ASP.NET framework, but VS .NET needs it to correlate the .asmx file with the corresponding .asmx.vb file.
![]() |
Figure 6‑1
Relation between .asmx and .asmx.vb files
Although you can create a Web service by creating an .asmx file with any text editor, using Visual Studio .NET makes it easy to code and debug Web services. Simply start Visual Studio .NET and choose File, New, Project then choose the ASP.NET Web Service project template. In the location field, enter the URL where you want the new project to be located. The project name is automatically derived from the last segment of this URL (the word after the last forward slash). The project name is displayed for you in the Name field but you cannot edit it there. Figure 6-2 shows a project being created. This new project will be called Chapter6 and project files will be located at the URL http://VBWSServer/VBWSBook/Chapter6/. Visual Studio will create a new IIS virtual directory called VBWSBook/Chapter6 (if it doesn’t exist) and configure it to allow scripting and program execution.

Figure 6‑2
Creating a new ASP.NET Web Service project called Chapter6
If you already have an existing IIS application and you want to place a new project in it, you can select the project template called “New Project In Existing Folder” and enter the new project name. On the next screen you’ll be asked to enter a folder name, instead enter the URL where you want the new project to be located, e.g. http://VBWSServer/VBWSBook/chapter6 as in figure 6-3. When you click OK, Visual Studio will put the new project in this virtual directory instead of creating a new virtual directory for the project.

Figure 6‑3
Creating a new project in an existing folder. When asked to enter a folder location, enter the URL where you want the new project to be created
When you create an ASP.NET Web Service project, you can then add to it multiple Web services. You can also add new Web services to other project types such as ASP.NET Web Applications. (I’ve added Web services to Windows Applications, not that you’d want to do that!). Don’t assume that you need to create a new project for every new Web service that you want to add.
If you’ve created a new ASP.NET Web Service project, you should see several files in Solution Explorer. One of these files, service1.asmx is a Web service. There are actually two files, service1.asmx and service1.asmx.vb, but VS .NET by default shows you only the .asmx file.
To see the file service1.asmx.vb, go to the Project menu and select Show All Files. Now you should see a + next to service1.asmx. Clicking on this expands the tree and you see service1.asmx.vb.
The file name, service1, appears in five different places: First you have the two file names service1.asmx and service1.asmx.vb. Then, if you open the code view for service1.asmx (by right clicking on service1.asmx and choosing code view or selecting it and pressing F7) it opens service1.asmx.vb and you see the class that implements the Web service:
Public Class Service1
Finally, if you right click on service1.asmx and choose Open With then choose Source Code (Text) Editor you get the real .asmx file which contains the WebService directive as explained above:
<%@ WebService
Language="vb" Codebehind="Service1.asmx.vb"
Class="Chapter6.Service1" %>
This directive contains the name Service1 twice: In the codebehind file name and in the class name. Renaming a Web service requires changing all five occurrences of Service1. Unfortunately when you rename an .asmx file in VS .NET, it will not rename the Web service class nor will it update the class name in the WebService directive. Therefore the easiest thing to do when you create a new Web service project is to delete Service1.asmx then add a new Web service from the Project menu and specify the service name, e.g. DataService. Then open the code view for DataService.asmx and you should see a class called DataService and an example HelloWorld method that’s commented out.
To add methods to your Web service, simply add a Function or Sub, like you normally would, making sure it is Public and prefixing it with the WebMethod attribute. The WebMethod attribute is what makes your methods accessible as operations on the Web service. Listing 6-2 shows an example method that returns the server time.
Listing 6‑2 Adding a WebMethod to your Web service (VBWSBook\Chapter6\DataService.asmx.vb)
<WebMethod()> _
Public Function GetServerTime() As DateTime
Return System.DateTime.Now()
End Function
Note that you can put the WebMethod attribute on the same line as the method definition. For example this would work just as well:
<WebMethod()> Public Function GetServerTime() As DateTime
To test your project, first right click on DataService.asmx and choose Set As Start Page then choose Start from the Debug menu or press F5 to build and run the project. This will launch Internet Explorer with the service documentation page which should look like the one in figure 6-4.
Figure 6‑4
The default documentation page which is automatically generated when you navigate to an .asmx file with a browser.
If you then click on the GetServerTime link this will take you to a test page shown in figure 6-5, where you can click on Invoke to test this operation. You should get a new Internet Explorer window with a <dateTime> element that contains the current date and time on the machine where the service is running. If you close Internet Explorer, debugging will automatically stop then you can go back and edit the code and rebuild and so on.

Figure 6‑5
You can test your Web service methods from this HTML page by clicking Invoke.
The next logical step after creating and testing a Web service is to create a client for that service. You can call a Web service from practically any kind of application. The rule is: if you can send an HTTP POST request to the Web server, you can invoke the Web service. To create a Windows Forms client, start Visual Studio .NET and create a new project using the Windows Application template. This will create the project file for you and add a form called Form1. As you learned in Chapter 1 you can use wsdl.exe to create a proxy class that makes it easy to invoke the Web service. You can also have VS .NET generate this proxy class for you by choosing Add Web Reference from the project menu, which will bring up the dialog shown in figure 6-6. Here you type the Web service URL (e.g. http://VBWSServer/vbwsbook/Chapter6/DataService.asmx) and hit enter which will bring up the service documentation page and will enable the Add Reference button on the bottom right. Clicking on Add Reference will generate the proxy class and add it to your project. By default, this new class belongs to a .NET namespace that’s taken from the host name where the Web service is located. For example, if the Web service is on the VBWSServer, the namespace is simply VBWSServer. Similarly if the Web service is at www.LearnXmlws.com, the namespace is com.learnxmlws.www.
Examining the project files in Solution Explorer reveals a new folder called Web References that contains within it another folder called VBWSServer as shown on the left in figure 6-7. Switching to Class View shown on the right in figure 6-7, you’ll see a namespace called VBWSServer that contains the generated proxy class called DataService (same name as the service).

Figure 6‑6
Using the Add Web Reference dialog


Figure 6‑7
Solution Explorer (top) and Class View (bottom) showing the files and classes created when you add a Web reference. This view has “show all files” turned on.
You might want to change the VBWSServer namespace to something more meaningful such as LocalServices. You can do this by selecting the VBWSServer folder in Solution Explorer and renaming it to LocalServices. You can then instantiate an object form the proxy class and call its GetServerTime method which will turn around and invoke the Web service and return the server time. Listing 6-3 shows a button click event handler that invokes the Web service and displays the returned server time in a message box.
Listing 6‑3 Invoking a Web service in .NET (VBWSClientCode\Chapter6\WSClient\Form1.vb)
Private Sub Button1_Click( _
ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Button1.Click
Dim ws As New LocalServices.DataService()
MessageBox.Show(ws.GetServerTime().ToString())
End Sub
Some local area networks have HTTP proxies installed to help secure machines on the LAN. An HTTP proxy works by receiving requests from clients and forwarding those requests to their ultimate destination. The proxy then receives the response from the Web server and forwards it back to the client.
Note that the term proxy is overloaded: An HTTP proxy or proxy server is a network device that receives and forwards HTTP requests. A Web service proxy is a class that acts as a wrapper to the Web service calls like the one VS .NET generates for you when you add a Web reference. Throughout this discussion, I’ll use the terms proxy server and Web service proxy to avoid confusion.
If you have a proxy server on your network, you might already have Internet Explorer configured to use that server. If that’s the case, .NET will use the same proxy server settings by default. You can also programmatically specify a proxy server when invoking a Web service. To do this you instantiate a System.Net.WebProxy object and use it to set the Web service’s Proxy property, for example:
Dim ws As New LocalServices.DataService()
ws.Proxy = New System.Net.WebProxy("http://proxyserveraddress")
Using an HTTP proxy can also be extremely useful for debugging. There’s an excellent utility called ProxyTrace which you can download from http://www.pocketsoap.com/tcptrace/pt.asp (for your convenience, ProxyTrace is also on the book’s CD)[1]. This tool acts as a proxy server receiving and forwarding requests while showing you the content of those requests. When you run ProxyTrace.exe you get a dialog where you can configure the port on which it will listen as shown in figure 6-8. This can be any available port on your machine, for example 8080.

Figure 6‑8
Configuring ProxyTrace to use port 8080.
You can then update the client code to make it go through this proxy server. Assuming the client is on the same machine that’s running ProxyTrace, you can use http://localhost:8080 as the proxy server’s address:
Dim ws As New LocalServices.DataService()
ws.Proxy = New System.Net.WebProxy("http://localhost:8080")
When you run the client and invoke the Web service, you’ll see the request and response messages in ProxyTrace as in figure 6-9. This is an extremely valuable tool when troubleshooting Web service problems especially those that have to do with interoperability.
![]() |
Figure 6‑9
ProxyTrace captures and displays HTTP requests and responses
Note: If the client is using a localhost address to access the Web service, .NET will not use an HTTP proxy even if you tell it to. Therefore, you should use the machine name instead of localhost to invoke Web service if you want to capture request/response messages using ProxyTrace.
.NET Web services execute within the ASP.NET runtime and can leverage many of ASP.NET’s useful features. This section explains how you can take advantage of features such as ASP.NET sessions, output caching, and distributed transactions (a feature of .NET Enterprise services). There are many other features of ASP.NET that don’t make much sense in the context of Web services. For example, cookie-less sessions are very cool and useful for Web applications but they rely on URL munging which doesn’t work for Web services.
When a client invokes a Web method on your service, a new instance of your Web service class is created to serve that request. Therefore, a Web service class is by definition stateless because each request is served by a new instance of the Web service class. If you want to retain some state information on the server between Web service calls, you can use the ASP.NET session object[2]. Sessions are disabled by default, to enable session state, you must set the WebMethod attribute’s EnableSession property to True. Listing 6-3 shows an example method that retrieves and returns a value called TheData out of the session then replaces it with the input parameter.
Listing 6‑4 Enabling session state on a Web method (VBWSBook\Chapter6\DataService.asmx.vb)
<WebMethod(EnableSession:=True)> _
Public Function SessionData(ByVal newVal As String) As String
Dim oldVal As String
If Session("TheData") Is Nothing Then
Session("TheData") = newVal
oldVal = "Session was empty"
Else
oldVal = Session("TheData")
Session("TheData") = newVal
End If
Return oldVal
End Function
To invoke the Web service and retain session state, the client must receive the session cookie then send it back with each subsequent request. The Web service proxy doesn’t support cookies by default (and that’s a good thing too), you have to set its CookieContainer property to a new instance of System.Net.CookieContainer to enable cookie support. Listing 6-5 shows how a client can invoke SessionData while maintaining session state.
When you change a Web service, e.g. by adding a new Web method like the DataService example above, clients must also regenerate the Web service proxy. First compile the Web service itself then open the client project, right click on the Web service proxy (e.g. LocalService folder) in Solution Explorer and choose Update Web Reference. Visual Studio will read the new WSDL document and regenerate the proxy class with the new SessionData method.
Listing 6‑5 Invoking a Web service that uses sessions. Note that the client must keep the Web service proxy object alive between requests. (VBWSClientCode\Chapter6\WSClient\Form1.vb)
Dim m_ws As LocalServices.DataService
Private Sub Form1_Load( _
ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MyBase.Load
m_ws = New LocalServices.DataService()
'use ProxyTrace to show request/response messages
m_ws.Proxy = New System.Net.WebProxy("http://localhost:8080")
m_ws.CookieContainer = New System.Net.CookieContainer()
End Sub
Private Sub btnSession_Click( _
ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnSession.Click
MessageBox.Show(m_ws.SessionData(txtSessionVal.Text))
End Sub
Note that the Web service proxy is declared as a form member variable and instantiated in the form load event. Also in the form load event, the Web service proxy is configured to use a proxy server (to capture the request/response messages) and its CookieContainer property is set to a new System.Net.CookieContainer object. Then in the bthSession click event, the SessionData method is called passing it the contents of the text box called txtSessionVal. Figure 6-10 shows an example request/response message pair. Note the Set-Cookie: ASP.NET_SessionId in the HTTP header of the response message. This is the value of the session cookie being sent to the client. The client then stores this cookie in the cookie container and resends it back to the server with each subsequent request.

Figure 6‑10
An example request/response to a service using sessions. Notice the Set-Cookie header in the HTTP response. This cookie is then sent back with each subsequent request.
ASP.NET output caching lets you retain in memory the output from a Web method for a specified period of time. During this time, requests to the same method with the same parameter values will get the output directly from cache without really invoking the Web method. This works well for Web methods designed to return data without having any side effects on the server. For example, a Web method that returns the current temperature for a given zip code would benefit from output caching. To enable output caching you set the WebMethod’s CacheDuration property to the number of seconds that the output should be retained in memory:
<WebMethod(CacheDuration:=10)> _
Public Function CachedGetServerTime() As DateTime
Output caching would not be an option if this Web method also inserts a record into a database to keep a log of all requests. That’s because the Web method would not be called for some requests and therefore the database log would not reflect the actual number of requests. To fully leverage output caching, you need to keep it in mind while designing your Web service. Whenever possible, methods that you expect will be highly accessed should be designed to take advantage of output caching by not having any side effects such as writing to the event log or a database.
If however your Web service design requires such side effects (for example, you need to log every single request because you will be billing your customers at the end of the month), you can still benefit from another type of caching called data or programmatic caching.
Instead of caching the entire response, your Web method uses the Cache object to cache specific pieces of data in the first request. With subsequent requests, your Web method retrieves the data from the cache and returns it. Listing 6-6 shows two examples of using data caching in a Web service.
Listing 6‑6 Using data caching with a file dependency and with a timeout value. (VBWSBook\Chapter6\DataService.asmx.vb)
<WebMethod()> _
Public Function DataCachingFileDependency() As String
Dim data As String
data = Context.Cache.Get("MyDataDep")
If data Is Nothing Then
Dim cd As New System.Web.Caching.CacheDependency( _
Context.Server.MapPath("DataFile.txt"))
Context.Cache.Insert("MyDataDep", "cached at " + _
System.DateTime.Now.ToString(), cd)
data = "Was not yet cached"
End If
Return data
End Function
<WebMethod()> _
Public Function DataCachingTimeOut() As String
Dim data As String
data = Context.Cache.Get("MyDataTimeOut")
If data Is Nothing Then
Context.Cache.Insert("MyDataTimeOut", "cached at " + _
System.DateTime.Now.ToString(), Nothing, _
System.DateTime.Now.AddMinutes(20), TimeSpan.Zero)
data = "Was not yet cached"
End If
Return data
End Function
Data caching is exposed via the Context.Cache property which returns an object of type System.Web.Caching.Cache. This object lets you add and remove items to and from the cache using simple methods. The caching model is quite flexible in letting you specify the criteria for when cached items should expire. For example, the DataCachingFileDependency method in listing 6-6 first gets an item from the cache using Context.Cache.Get and passing it the item’s key which is MyDataDep in this example. If the item is not in the cache (the Get method returned Nothing), it then creates a CacheDependency object passing it the path to a file named DataFile.txt. Next it calls Context.Cache.Insert to insert a new item called MyDataDep. The actual item’s data is the string “cached at “ followed by the current data and time. The last parameter to the Insert method is the CacheDependency object. This means any changes to DataFile.txt will automatically invalidate the item in the cache (remove it from the cache). This is an example of caching data with the expiration criteria depending on changes to a file. When you create the CacheDependency object you can also specify an array of files so that changes to any of them would invalidate cached data.
The second method, DataCachingTimeout, shows an example of specifying a time when cached data should be invalidated. Instead of passing a CacheDependency object to the Insert method, it passes a specific time, namely the current time plus 20 minutes, meaning the data will be invalid in 20 minutes from now.
Notice that the last parameter is TimeSpan.Zero. You can use this last parameter to specify a duration of inactivitiy after which the cache becomes invalid. For example, if you wanted the cache to become invalid if it had not been used in the last 60 minutes, you specify this parameter as TimeSpan.FromMinutes(60). This gives the cached item a sliding scale of 60 minutes from the last-used time. You can’t combine absolute timeout with sliding scale timeout. If you do specify an absolute timeout, you must specify TimeSpan.Zero (as in listing 6-5). If you specify a sliding scale TimeSpan, you must specify System.DateTime.MaxValue for the absolute timeout.
One last note, output and data caching obviously use memory. Therefore, the caching mechanisms are implemented so that data may be removed from the cache if the system is low on resources. Which data gets removed from the cache is based on heuristics that take into account cached data usage and cache misses. What this means is that even though you specified that data should be cached, it may be removed from the cache prematurely due to low available memory.
In certain scenarios, you might want a Web method to act as the root of a distributed transaction so that all transactional components that are invoked from that method participate in one transaction. There are two scenarios when distributed transactions make sense: If the Web method needs to do work against two or more transactional resources for example updating two relational databases or inserting into a database and writing to a transactional message queue. The second scenario is when the Web method utilizes several transactional components that all need to participate in the same transaction. Practically, you shouldn’t run into this second scenario unless you are building a sophisticated system where transactional components can be used together in different ways and can participate in different types of business transactions.
If you decide you need distributed transactions, you can set the WebMethod’s TransactionOption property to TransactionOption.Required or TransactionOption.RequiresNew. The TransactionOption enumeration is defined in System.EnterpriseServices and has five possible values: Disabled, NotSupported, Supported, Required, RequiresNew. A Web method cannot participate in an existing distributed transaction, therefore both Required and RequiresNew mean that the Web method will always start a new distributed transaction. The other three values Disabled, NotSupported, and Supported mean that the Web method will not run in a transaction.
When a transactional Web method completes successfully, the transaction is automatically committed to release resources such as database locks. To abort a transaction, the Web method must either throw an exception or call System.EnterpriseServices.ContextUtil.SetAbort.
At the risk of being repetitious: Before using distributed transactions consider first using your database’s built-in transactions (e.g. T-SQL transactions) or ADO.NET transactions. If neither of these satisfies your application’s requirements, then you can easily use distributed transactions by setting the TransactionOption property.
MSDN has an article titled “Performance Comparison: Transaction Control” that compares the performance of TSQL, ADO.NET, and COM+ (Enterprise Services) transactions. At the time of this writing the article is at this URL:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnbda/html/bdadotnetarch13.asp
If the article has been moved by the time you read this, try to search for it by title.
In Chapters 3 and 4 you learned about SOAP messages and WSDL documents. You saw examples of WSDL and learned how WSDL is used to specify a Web service’s interface. This section explains how you can control the auto-generated WSDL document and the service’s SOAP messages to better fit your requirements.
By default, .NET lets you invoke Web services using HTTP GET, HTTP POST, and SOAP over HTTP. In fact, the auto-generated test page relies on HTTP GET to test the Web service. While supporting various protocols is a good thing for testing purposes, you might want to remove some of these protocols before you go into production. To remove HTTP GET and POST support from your Web service, you need to edit the Web application’s web.config file. You need to add a <webServices> section to this config file. Within this new section add a <protocols> element and add a <remove> element for each protocol that you want to remove. For example, this web.config removes HTTP GET and HTTP POST:
<configuration>
<system.web>
<webServices>
<protocols>
<remove name="HttpGet"/>
<remove name="HttpPost"/>
</protocols>
</webServices>
</system.web>
</configuration>
Configuring your Web service like this has three effects: First, the removed protocols are excluded from the service’s WSDL document. Second, clients receive an error if they attempt to use these protocols to invoke the web service. Finally, disabling HTTP GET will disable the auto-generated test page, which is a bit of an inconvenience especially if you rely on that page for quick testing.[3]
You can also apply these settings to all Web services running on a given machine by editing the machine.config file which contains a <webServices> section that, by default, enables all three protocols. You can remove from that section the protocols you want to disable which will affect all Web services running on that machine.
A Web service’s auto-generated WSDL document contains many names and namespaces that are based on various identifiers you use in your code. For example, the service name is, by default, the same as the name of the class that implements this service. You might want to use characters in the service name which are legal for XML element names but illegal for VB class names, e.g. a period. To change the default service name and namespace you apply the WebService attribute to the class that implements the Web service. Listing 6-7 shows an example of a class called CustomWS which exposes a Web service called MyWeb.Service. In this example, the Namespace property is set to urn:my-unique-namespace (instead of the default http://tempuri.org) and the name property is set to MyWeb.Service. I also set the Description property to display some useful documentation. Note that you can include HTML tags in your Description and they will be rendered as HTML on the default documentation page.
Listing 6‑7 Setting the Web service’s name and namespace using WebService(). Also setting a method’s message name using WebMethod(). (VBWSBook\Chapter6\CustomWS.asmx.vb).
'Using the Namespace property to control the service namespace
'Using the Name property to set the web service name
'with a . which is illegal in a VB class name
'And the Description property to document the service
'with html tags in the documentation
<WebService([Namespace]:="urn:my-unique-namespace", _
Name:="MyWeb.Service", _
Description:="Example of customizing Web services. " + _
"Written by <a href='mailto:shohoudy@devxpert.com'>Yasser Shohoud</a>")> _
Public Class CustomWS
Inherits System.Web.Services.WebService
'using Description to display a useful
'method description
'Also MessageName to control the name of
'the SOAP message
<WebMethod( _
Description:="This method has a custom operation name", _
MessageName:="My.Message")> _
Public Function CustomMsgName() As Integer
Return 0
End Function
End Class
You can also add a description to each method by setting the WebMethod’s Description property. The WebMethod attribute in listing 6-7 has its Description and MessageName properties both set. The MessageName property of WebMethod lets you control the name of the SOAP message as defined in the WSDL binding. In this example, I set the MessageName to My.Message which shows up as the input and output message names in the WSDL as shown in figure 6-11. You can also use MessageName with overloaded Web methods to give them different message names in the generated WSDL (because a Web service cannot have two messages with the same name).

Figure 6‑11
WSDL for the CustomWS Web service. Notice the service name is MyWeb.Service and the input and output message names are My.Message.
Pulling up this service’s documentation page, you’ll see the name MyWeb.Service and the custom description at the top as shown in figure 6-12. You’ll also note the absence of the long recommendation about changing the Web service’s namespace to something other than http://tempuri.org because we’ve already done that.
Figure 6‑12
![]() |
The documentation page for CustomWS
.NET Web services use document/literal SOAP messages by default. This is generally acceptable as messaging Web services are centered on XML documents and their schemas. In some cases however, you’ll need to use RPC/encoded SOAP messages. For example, if you want to expose your Web service to clients that understand only RPC/encoded SOAP.
At the Web service level, you control the style of SOAP message by applying SoapRpcService or SoapDocumentService (the default) attributes to the class that implements the Web service. For finer-grained control at the method level, you apply SoapRpcMethod or SoapDocumentMethod attributes on each individual method.
You can apply SoapRpcMethod and SoapDocumentMethod to different methods in the same Web service. But before you mix RPC/encoded and document/literal SOAP messages in the same Web service make sure your target clients’ tools support such combinations. .NET clients have no problem with this but other clients might.
Both SoapRpcMethod and SoapDocumentMethod expose four properties that let you specify the request and response element names and namespaces. The property names are RequestElementName, RequestElementNamespace, ResponseElementName, and ResponseElementNamespace,
Listing 6-8 shows an example of applying SoapDocumentMethod and setting the RequestNamespace property to http://xmlws.com/messages and listing 6-9 shows the resulting SOAP request message with the custom namespace.
Listing 6‑8 Setting the request namespace for a specific method.
'set the namespace to use
'for the body payload in the
'SOAP request message
<WebMethod(), _
SoapDocumentMethod(RequestNamespace:="http://xmlws.com/messages")> _
Public Function CustomNS() As Integer
...
End Function
Listing 6‑9 The resulting SOAP request message corresponding to CustomNS method
<soap:Envelope ...>
<soap:Body>
<CustomNS xmlns="http://xmlws.com/messages" />
</soap:Body>
</soap:Envelope>
When using document-style SOAP messages, .NET lets you specify whether you want to use encoded or literal parameters. The default is literal, which is by far the most common parameter format for document-style messages. However, if for some reason you need to use document/encoded messages, you can easily do this at the service or method levels by setting the Use property of SoapDocumentService or SoapDocumentMethod respectively. The three possible values are Literal, Encoded and Default. If you use Default with SoapDocumentMethod, it’ll default to whatever the containing service is using. Using Default with SoapDocumentService means you are accepting the default setting which is literal. Listing 6-10 shows an example method that uses document/encoded messages and listing 6-11 shows the corresponding operation binding definition. Note that .NET supports three of the four message styles: Document/literal, document/encoded, and RPC/encoded. RPC/literal is not supported but that’s OK because, as mentioned in Chapters 3 and 4, practically only two message styles are used: Document/literal and RPC/encoded and both are supported by .NET.
Listing 6‑10 Using Document/encoded SOAP by setting the Use property of SoapDocumentMethod
<WebMethod(), _
SoapDocumentMethod(Use:=SoapBindingUse.Encoded, _
RequestNamespace:="http://xmlws.com/messages")> _
Public Function CustomNS() As Integer
...
End Function
Listing 6‑11 The resulting WSDL binding information
<operation name="CustomNS">
<soap:operation
soapAction="urn:my-unique-namespace/CustomNS"
style="document" />
<input>
<soap:body use="encoded"
namespace="http://xmlws.com/messages"
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" />
</input>
<output>
<soap:body use="encoded"
namespace="urn:my-unique-namespace/encodedTypes"
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" />
</output>
</operation>
SoapDocumentMethod and SoapDocumentService expose a property called ParameterStyle which you can use to control whether method parameters and return value appear directly inside the <soap:Body> element (ParameterStyle := SoapParameterStyle.Bare) or are wrapped within another element (ParameterStyle := SoapParameterStyle.Wrapped). Listing 6-12 shows the effect of setting this property on request SOAP messages for an operation that takes two parameters like this:
Public Function ParamStyleTest( _
ByVal a As Integer, _
ByVal b As String) As String
Listing 6‑12 Two SOAP messages showing wrapped and bare parameter styles respectively
<!-- this message corresponds to ParameterStyle := Wrapped -->
<soap:Envelope ...>
<soap:Body>
<ParamStyleTest xmlns="urn:my-unique-namespace">
<a>int</a>
<b>string</b>
</ParamStyleTest>
</soap:Body>
</soap:Envelope>
<!-- this message corresponds to ParameterStyle := Bare -->
<soap:Envelope ...>
<soap:Body>
<a xmlns="urn:my-unique-namespace">int</a>
<b xmlns="urn:my-unique-namespace">string</b>
</soap:Body>
</soap:Envelope>
SoapParameterStyle.Bare is especially useful when you want to take full control over what goes inside the <Body> element and you don’t want .NET to add wrapper elements around your data. You’ll see an example Web service implemented with SoapParameterStyle.Bare in Chapter 13.
WSDL defines four types of operations as explained in Chapter 4. One of those operations is the request-only operation where a client sends a request message and receives nothing back, not even error information. If you have such a method, you can apply a SoapDocumentMethod or a SoapRpcMethod attribute and set its OneWay property to True. This tells .NET that your method will not return an output message which means incoming requests will return as soon as the method begins execution. For example, the method definition in listing 6-13 results in the WSDL operation in listing 6-14. Note that the WSDL operation has no output message.
Listing 6‑13 A one way Web method
<WebMethod(), SoapDocumentMethod(OneWay:=True)> _
Public Sub OneWayMethod(ByVal theData As String)
...
End Sub
Listing 6‑14 The WSDL binding information for a one way method
<operation name="OneWayMethod">
<input message="s0:OneWayMethodSoapIn" />
</operation>
By default, the SOAP Action HTTP header value is generated by combining the Web service namespace with the operation (method) name. Also by default, .NET reads the SOAP Action value for each Web service request and uses it to determine which operation is to be invoked. Although this will work for most cases, you’re better off not relying on SOAP Action at all because it is a highly controversial topic in the SOAP world today.
At the time of this writing, the SOAP 1.2 W3C Working Draft states that SOAPAction is an optional feature and should not be required by a Web service unless there’s a particular purpose for doing so.
Listing 6-15 shows an example of setting the SoapDocumentServiceAttribute.RoutingStyle property to not rely on SOAPAction.
Listing 6‑15 Specifying that requests are to be routed based on element name not SOAP Action
<SoapDocumentService( _
RoutingStyle:=SoapServiceRoutingStyle.RequestElement)> _
Public Class CustomWS
...
End Class
SoapDocumentService and SoapRpcService both have a RoutingStyle property which you can set to SoapServiceRoutingStyle.RequestElement to specify that the SOAP Action value should be ignored. Note that this property can only be set at the service level and not the method level.
If you use SoapServiceRoutingStyle.RequestElement with SoapParameterStyle.Bare parameter style, you must be careful about the number of parameters (must be exactly one) and parameter names for each Web method. That’s because you are telling .NET to route incoming messages to the corresponding methods based on the name of the child of the <soap:Body> element. At the same time you’re also telling it to include method parameters as direct children of <soap:Body> with no wrapper element. Although this can be tricky, .NET lets you get away with this combination provided that the method has exactly one parameter and that each method parameter is serialized to a different element name. .NET needs this restriction so it can tell which Web method to invoke based on the incoming element name. You’ll see an example of this combination in Chapter 13.
It’s also possible to set the required SOAP Action value for each method individually using the Action property of SoapRpcMethod or SoapDocumentMethod. Whatever string you specify as this property value becomes the soapAction attribute value in the WSDL SOAP binding section.
Listing 6-16 shows the completed CustomWS Web service with the Web methods and attributes discussed in this section.
Listing 6‑16 The complete CustomWS Web service. (VBWSBook\Chapter6\CustomWS.asmx.vb)
<SoapDocumentService( _
RoutingStyle:=SoapServiceRoutingStyle.SoapAction), _
WebService([Namespace]:="urn:my-unique-namespace", _
Name:="MyWeb.Service", _
Description:="Example of customizing Web services. " + _
"Written by <a href='mailto:shohoudy@devxpert.com'>Yasser Shohoud</a>")> _
Public Class CustomWS
Inherits System.Web.Services.WebService
<WebMethod( _
Description:="This method has a custom operation name", _
MessageName:="My.Message")> _
Public Function CustomMsgName() As Integer
End Function
<WebMethod(), _
SoapDocumentMethod(RequestNamespace:="http://xmlws.com/messages")> _
Public Function CustomNS() As Integer
End Function
<WebMethod(), _
SoapDocumentMethod(ParameterStyle:=SoapParameterStyle.Bare)> _
Public Function ParamStyleTest( _
ByVal a As Integer, ByVal b As String)_
As String
End Function
<WebMethod(), SoapDocumentMethod(OneWay:=True)> _
Public Sub OneWayMethod(ByVal theData As String)
End Sub
End Class
Now that you know how to create and customize Web services, the next section explains how .NET Web service clients work and how you can customize them to meet your needs.
A Web service proxy class inherits from either HttpGetClientProtocol, HttpPostClientProtocol, or SoapHttpClientProtocol depending on whether it uses HTTP GET, HTTP POST or SOAP to invoke the Web service. As I mentioned earlier, HTTP GET and POST are somewhat interesting for quickly testing the service, but any real-world Web service will likely use SOAP which means the Web service proxy will inherit from SoapHttpClientProtocol. Figure 6-13 shows the class hierarchy for a Web service proxy, note that SoapHttpClientProtocol inherits from HttpWebClientProtocol which in turn inherits from WebClientProtocol.

Figure 6‑13
Inheritance hierarchy for a Web service proxy class. The classes at the bottom of the hierarchy are Web service proxies
A Web service proxy class that uses SOAP inherits from SoapHttpClientProxy. Similarly, if the proxy uses HTTP GET or POST, it inherits from HttpGetClientProtocol or HttpPostClientProtocol respectively. Each class in this inheritance hierarchy is responsible for a different portion of the task of sending a Web service request and receiving the response. For example, HttpWebClientProtocol implements functionality that is specific to HTTP such as the Proxy property explained above. On the other hand, SoapHttpClientProtocol exposes an interesting method called Invoke which is responsible for invoking the Web service. This method does all the heavy lifting including serializing input parameters, sending the SOAP request, parsing the response and deserializing all output parameters and return value. Invoke takes in the name of the method you want to invoke and an array of objects that contains the input parameters (i.e. the parts of the input message) and returns an array of objects that contains the output parameters and return value (the parts of the output message):
Protected Function Invoke( _
ByVal methodName As String, _
ByVal parameters() As Object _