.netCoders Contact Us
Search:

XML Web Services

Underneath all the hype, Web Services are about exposing functionality using standard Internet protocols such as HTTP and XML technologies. Unlike previous binary communication formats (eg. DCOM), this framework of standard technologies opens the door for new levels of interoperability between disparate systems.

The .NET Framework leverages ASP.NET to provide a simple mechanism for developing web services. To justify this claim, take a look at the following code sample.

<%@ WebService language="C#" class="Calc1" %>
using System;
using System.Web.Services;

public class Calc1 : System.Web.Services.WebService
{
    [WebMethod]
    public int Add(int a, int b)
    {
        return a+b;
    }
}
Saving this code in a file with an ASMX extension, and placing it on the web server is all that is needed to expose your first web service. If you load the page in a browser, the ASP.NET framework will generate some help information about your service, and will even generate a form for testing your service via the HTTP-GET protocol.

Using Attributes

There are several attributes you can use to decorate web service methods. The one that you will almost always find is the [WebMethod] attribute, which in an indicates that a class method is an XML Web Service method. The following table outlines some of the more common attributes you can use:

WebMethod Indicates a method is an XML Web service method.
WebService Applied at the class level to provide more information about the Web Service, such as a description and default namespace.
SoapDocumentMethod Indicates that the XML Web Service method expects Document-based SOAP messages.
SoapRpcMethod Indicates that the XML Web Service method expects RPC-based SOAP messages.
SoapHeader Indicates that the XML Web Service method can process a specific SOAP Header.
SoapExtension Indicates a SOAP extension should execute when the XML Web Service method is called (Note: This attribute is abstract. You have to create a custom attribute deriving from this class for your SOAP extension).
For example, the following code snippet from a web service shows a Web Service method that accepts a SOAP Header, and expects SOAP messages in Document format.

[WebMethod, SoapHeader("aSoapHeader"), SoapDocumentMethod]
public string GetProductName()

SOAP Extensions

SOAP Extensions are used to plug into the message framework involved in SOAP Requests and Responses, and run your custom code to view and/or alter the SOAP messages. Uses for SOAP Extensions include encrypting/decrypting communications, or logging messages sent to and from a service.

The following diagram, taken from the .NET Framework SDK Documentation, outlines the steps involved in a Web Service Request/Response communication. It's important to understand the order of the phases when working with SOAP Extensions.

SOAP Extensions can be create to inspect and modify the SOAP message before and after the Serialize and Deserialize stages. These opportunities are defined in the SoapMessageStage enumeration. Possible values for this enumeration are:

Value Description
AfterDeserialize The stage just after a SoapMessage is deserialized from a SOAP message into an object.
AfterSerialize The stage just after a SoapMessage is serialized, but before the SOAP message is sent over the wire.
BeforeDeserialize The stage just before a SoapMessage is deserialized from the SOAP message sent across the network into an object.
BeforeSerialize The stage just prior to a SoapMessage being serialized.

Now that you understand what a SOAP Extension is used for, and the Request/Response process, you can write a SOAP Extension. There is no special Visual Studio.NET project for creating a SOAP Extension, so you have to create the code manually. The steps you need to follow are:

1. Derive a class from SoapExtension
The SoapExtension class is the base class for all SoapExtensions. You will need to inherit your SOAP Extension from this class.

2. Override the ChainStream method
Prior to any SoapMessageStage, the ChainStream method of the SOAP Extension is called and a Stream reference is passed in. This Stream is used to access the contents of the SOAP Message, which may have been modified by lower priority SOAP Extensions. Your SOAP Extension should save a reference to this object. This method also returns a Stream, although you should not use the Stream passed in as a parameter. Rather, create a new Stream, also saving that as a member variable, and return that. This way, you can both access the original stream and write modifications to the new stream. Here is an implementation in C# of an overridden ChainStream method:

public override Stream ChainStream(Stream stream)
{
    //Save the Old Stream Locally
    m_OldStream = stream;
    
    //Create a New Stream, and Save it Locally
    m_NewStream = new MemoryStream();
    
    //Return the New Stream
    return m_NewStream;
}

3. Initialize the SOAP Extension
There are two methods for initializing a SOAP Extension. The first is GetInitializer, and it is called in instances where the SOAP Extension is configured using an attribute, then the method is called once when XML Web Services are called on a per method basis. If you have 3 methods, the GetInitializer would be called 3 times, once when each of the web service methods was invoked. If however, the SOAP Extension is configured in a configuration file, then the GetInitializer method is called by the .NET Infrastructure only when the XML Web Service is first accessed, and not on a per method basis. The data returned from the GetInitializer method is cached, and passed to the Initialize method each time a web service method is invoked. This two-method process forces caching of data to eliminate the costly initialization time that services can have.

4. Process the SOAP Messages
Processing the SOAP Messages is what a SOAP Extension is all about, right? The key method then is the ProcessMessage method. This method has a single parameter: a SoapMessage object. It's important to note too that this method will be called for each of the stages defined in the SoapMessageStage enumeration: BeforeSerialize, AfterSerialze, BeforeDeserialize, and AfterDeserialize. You can determine the current stage by checking the SoapMessage parameter's Stage property.

//Process the Message
public override void ProcessMessage(SoapMessage message)
{
    //Check the Stage and Do Some Work
    switch (message.Stage)
    {
        case SoapMessageStage.BeforeSerialize:
            break;
        case SoapMessageStage.AfterSerialize:
            break;
        case SoapMessageStage.BeforeDeserialize:
            break;
        case SoapMessageStage.AfterDeserialize:
            break;
    }
}    

Now that you have a SOAP Extension, you need to configure it to run with an web service method. There are two ways to configure SOAP Extensions: with attributes on the WebMethod, or via a configuration file. To configure an extension using attributes, you need to derive a class from SoapExtensionAttribute. This class has two properties, ExtensionType and Priority. ExtensionType should be set to the Type of your Soap Extension. You can these use this custom attribute on xml web methods to attach your extension.

The second method of indicating SOAP Extensions is via a configuration file, such as the web.config file. Using a configuration file will affect all web service methods in the scope of the configuration file. Under the <system.web> tag, add a <webServices> tag, and under that add a <soapExtensionTypes> tag. Within the <soapExtensionTypes> tag, you can insert one or more <add> tags to indicate SOAP Extensions. The syntax for this tag is shown in the example below:

<configuration>
    <system.web>
        <webServices>
            <soapExtensionTypes>    
                <add type="DotnetCoders.LogSoapExtension, extensions" Priority="1" Group="0" />
            <soapExtensionTypes>
        <webServices>
    <system.web>
<configuration>

Asynchronous Web Methods

An asynchronous call to a web method requires no special asynchronous support by the Web Service code. Rather, the framework necessary is found within the Proxy object created by the Web Services Description Language (WSDL) utility. The proxy object created by this utility implements 3 methods for every 1 method in the web service: a synchronous method with the same name, a Begin[MethodName] method for initiating an asynchronous call, and an End[MethodName] method for getting the result from an asynchronous call.

For example, the following code snippet comes from the proxy object generated for the Amazon.com Web Service. Notice that for the KeywordSearchRequest web service method, there are actually 3 methods generated in the proxy class.

//Synchronous Method
public ProductInfo KeywordSearchRequest([System.Xml.Serialization.SoapElementAttribute("KeywordSearchRequest")] KeywordRequest KeywordSearchRequest1)
{
    object[] results = this.Invoke("KeywordSearchRequest", new object[] {KeywordSearchRequest1});
    return ((ProductInfo)(results[0]));
}

//Asynchronous Begin Method
public System.IAsyncResult BeginKeywordSearchRequest(KeywordRequest KeywordSearchRequest1, System.AsyncCallback callback, object asyncState)
{
    return this.BeginInvoke("KeywordSearchRequest", new object[] {KeywordSearchRequest1}, callback, asyncState);
}

//Asynchronous End Method
public ProductInfo EndKeywordSearchRequest(System.IAsyncResult asyncResult)
{
    object[] results = this.EndInvoke(asyncResult);
    return ((ProductInfo)(results[0]));
}

There are two ways for implementing client calls to asynchronous methods. The first involves using the IAsyncResult return value from the Begin method to periodically poll the status of the request using the AsyncWaitHandle property (type WaitHandle). The WaitHandle class has methods WaitOne, WaitAny, and WaitAll for blocking the client until the web method call is complete. The IAsyncResult object also has a property, IsCompleted, that returns true when the asynchronous call has been completed, and the results are available. To get the results of the asynchronous call, the client needs to call the End method. The second way of implementing the call is by using a callback delegate. By passing an AsyncCallback object to the Begin method, the callback method will be called when the asynchronous method is completed. In the callback function, you can get the results by calling the proxy object's End method.

The following example shows the use of a Callback function to handle the completion of the asynchronous call.

using System;
using System.Runtime.Remoting.Messaging;
using com.dotnetcoders.www;

class TestCallback
{        
    public static void Main()
    {
        //Create AsyncCallback delegate for the BeginAdd method.
        AsyncCallback objCallBack = new AsyncCallback(TestCallback.FactorizeCallback1);

        // Begin the Asynchronous call to the Web Service
        Calc1 objCalcService = new Calc1();
        IAsyncResult objAsyncResult = objCalcService.BeginAdd(5, 5, objCallBack, objCalcService);
            
        // Keep track of the time it takes to complete the async call
        int start = DateTime.Now.Second;
        int currentSecond = start;
        while (objAsyncResult.IsCompleted == false)
        {
            if (currentSecond < DateTime.Now.Second)
            {
                currentSecond = DateTime.Now.Second;
                Console.WriteLine("Seconds Elapsed..." + (currentSecond - start).ToString() );
            }
        }

        // Pause this thread and wait for Callback to finish
        Console.Write("Press any key to quit...");
        Console.Read();
    }

    // Callback function invoked by the proxy class when the asynchronous operation completes
    public static void FactorizeCallback1(IAsyncResult objAsyncResult)
    {
        // Use the AsyncState property to get the Web Service proxy object
        Calc1 objCalcService = (Calc1) objAsyncResult.AsyncState;

        // Get the completed results.    
        int results = objCalcService.EndAdd(objAsyncResult);
        
        //Output the results.
        Console.Write("5 + 5 = " + results);
    }
}

XML Wire Format

There are three common protocols used when working with XML Web Services: HTTP-GET, HTTP-POST, and SOAP.

HTTP-GET Parameters are passed as name/value pairs in the URL, appended as a querystring.
HTTP-POST Parameters are passed as name/value pairs in the HTTP header of the request.
SOAP Call is made using the lighweight, xml-based SOAP protocol.

When using a proxy object generated by wsdl.exe, SOAP is the protocol. Because SOAP is based on XML technologies, data types for variables can be defined using XSD, whereas with HTTP, all variables are unvalidated strings.

Instantiating and Invoking

For the exam, you should be familiar with invoking Web Services as well as creating them.
To instantiate and invoke a web service, follow these steps:
  1. Add a Web Reference - Select Project->Add Reference, and enter in the path to the WSDL for the web service
  2. Instantiate the .NET proxy object
  3. Call methods on the proxy object

When you add a Web Reference, Visual Studio will use the Web Services Description Language Utility (wsdl.exe) utility to create a proxy object for the web service.

You can see this reference in Solution Explorer under the Web References tab. Then, you create an instance of the proxy object like any other .NET object. For example, after adding the Web Reference, the following code calls the Add method on the Calculator service.

com.dotnetcoders.www.Calc1 objCalc = new com.dotnetcoders.www.Calc1();
MessageBox.Show("3 + 5 = " + objCalc.Add(3,5));
You can also call the Web Services Description Language (wsdl.exe) utility from the command line, passing it the location of the WSDL, and the utility will generate a proxy object for you.

When calling Web Services, you also have the option of calling web methods asynchronously. The methods that make this possible are generated automatically by the wsdl.exe utility in the proxy object. The naming convention for asynchronous web methods is Begin[MethodName] and End[MethodName]. For example, the following screenshot shows the methods of the Amazon.com Web Service. Notice that there is an AuthorRequest method for synchronous calls, and a BeginAuthorRequest method for asynchronous calls.

In order to call the web method asynchronously, supply a callback delegate to the Begin[MethodName] method. When the web service returns it's response, a thread will invoke your callback function. See the section above, Asynchronous Web Methods, for more information on calling web service methods asynchronously.

In the event that the author of the web service changes their service after you have already added a reference to your project, you can just right-click the service in the Solution Explorer, and select Update Reference. This will re-generate the proxy object with the updated methods.

Additional Resources