Magazine > Issues > 2003 > September >  Introducing the Web Services Enhancements 2.0 M...
[ Editor's Update - 6/16/2004: This article was written about the Technical Preview release of WSE 2.0, and some of the code and concepts changed for the final release. For up-to-date information on WSE 2.0, see Web Services Enhancements.]
The XML Files
Introducing the Web Services Enhancements 2.0 Messaging API
Aaron Skonnard

Code download available at: XMLFiles0309.exe (263 KB)
Browse the Code Online
The ASP.NET WebMethod framework makes it possible to build XML-based Web Services that communicate over HTTP. It accomplishes this by mapping a pair of SOAP messages—one for the request and another for the response—to traditional remote procedure call (RPC) invocations. This is the only mechanism provided by the Microsoft® .NET Framework for building Web Services without resorting to writing most of the SOAP infrastructure yourself (which would require writing a custom HTTP handler, for instance).
The popularity of the WebMethod framework and others like it has nearly convinced mainstream developers that Web Services are only useful for RPC over HTTP. The irony is that some of the most interesting and powerful Web Services of the future probably won't use either. The Web Services concept is designed around XML messaging over arbitrary transports. XML messaging applications can benefit from a variety of different communications protocols as long as they aren't detrimental to the fundamental interoperability goals of the system.
For example, when performance and reliability are key goals, some Web Services might benefit from using TCP directly instead of a higher-level protocol like HTTP. Other Web Services might benefit from using asynchronous communication patterns such as those offered by TCP, SMTP, or Microsoft Message Queue (MSMQ). In-process integration between different components of the same system could also be beneficial to certain Web Services. There are a variety of messaging possibilities and the Web Services platform is designed to accommodate all of them.
The Web Services Enhancements (WSE) 2.0 toolkit is the first of its kind for the .NET Framework that lets you start experimenting with other XML messaging possibilities beyond HTTP. WSE 2.0 is an add-on to the .NET Framework that provides additional Web Services support, mostly for the various Web Services Architecture (WSA) specifications that are continuing to evolve (note that WSA was previously known as GXA). WSE 2.0 specifically provides support for WS-Security, WS-Trust, WS-SecureConversation, WS-Policy, WS-SecurityPolicy, WS-Addressing, and several other specifications that were introduced with version 1.0 including WS-Routing, WS-Referral, and Direct Internet Message Encapsulation (DIME). In addition to the new WSA functionality, WSE 2.0 also provides an improved messaging API that supports other transports and custom message exchange patterns (MEPs).
The WSE 2.0 messaging API really spurs the imagination. As you begin working with it, you'll realize the potential of the Web Services platform and the common misconceptions surrounding its relationship to RPC and HTTP. This column introduces the new messaging features provided by WSE 2.0 and shows you how to begin using them to build XML-based messaging systems that leverage asynchronous messaging patterns.
You can download WSE 2.0 from the MSDN® Web Services Developer Center. You'll need to install the WSE 2.0 bits before you begin experimenting with the code that is shown throughout this column.

Senders and Receivers
Once you move away from the traditional RPC model you have to stop thinking in terms of clients and servers. These terms often lead to confusion when you start using nontraditional MEPs. Instead, it's more appropriate to think of the different entities in your Web Services architecture as SOAP senders and receivers. Some nodes are designed to send SOAP messages while others are designed to receive and process them. And these roles are completely interchangeable, meaning that a given node could sometimes act as a sender and at other times as a receiver.
WSE 2.0 emphasizes this thinking by providing two fundamental classes for sending and receiving SOAP messages: SoapSender and SoapReceiver. As their names suggest, SoapSender encapsulates the process of sending a SOAP message to a URI while SoapReceiver encapsulates the process of receiving a SOAP message at a particular URI.
WSE 2.0 supports sending and receiving messages using three communications protocols: in-process, TCP, and HTTP. In the case of in-process, the SOAP messages are delivered directly to the receiver without hitting the network layer. You indicate which mechanism you want to use (from either a SoapSender or a SoapReceiver) through a URI, which must conform to the following syntax in WSE 2.0:
protocol_scheme://host[:port_number]/path_info/...
The protocol scheme indicates which protocol to use, while the host and port number identify where to send the messages. The path info identifies which resource to access on that particular host. Figure 1 shows the WSE 2.0 protocol schemes for each one, along with an example. The new protocol scheme is soap.tcp, for using TCP communications.

Protocol URI Scheme Example
TCP soap.tcp soap.tcp://mymachine:333/math
HTTP http http://mymachine/math/myservice.wse
The .NET Framework provides the System.Uri class for representing URIs in your code. You can instantiate Uri objects by supplying the appropriate URI string to the constructor, like this:
Uri myuri = new Uri("soap.tcp://mymachine/math");
SoapSender and SoapReceiver require you to supply Uri objects to indicate the protocol and endpoint details to be used.
In addition to the support for new transports, another big difference between the WSE 2.0 messaging API and the existing WebMethod framework is that SoapSender and SoapReceiver require the developer to work with SOAP messages directly. Although this raises the bar a bit on the level of XML and SOAP development experience required, it also increases flexibility and places the emphasis on XML messaging.
WSE 2.0 provides the Microsoft.Web.Services.SoapEnvelope class for building and processing SOAP messages in your code. The SoapEnvelope class derives from System.Xml.XmlDocument and provides additional members that only apply to SOAP messages like the Envelope, Header, and Body properties. Since the SoapEnvelope class is really just a special implementation of the Document Object Model (DOM), the standard DOM APIs are available along with the other DOM helpers like SelectNodes and SelectSingleNode that provide XPath support.
For example, consider the following SOAP message that represents the canonical example of adding two numbers:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
   <s:Body>
      <v:add xmlns:v='urn:math'>
         <x>33</x>
         <y>66</y>
      </v:add>
   </s:Body>
</s:Envelope>
You can build this SOAP message programmatically through the SoapEnvelope class, as shown here:
SoapEnvelope CreateAddMessage()
{
    SoapEnvelope env = new SoapEnvelope();
// must create body before setting it
    env.CreateBody(); 
    env.Body.InnerXml = "<v:add xmlns:v='urn:math'><x>33</x>
                        <y>66</y></v:add>";
    return env;
}
Once you've created the SoapEnvelope object you can send it to an endpoint using SoapSender (more on this shortly). If you're building a SoapReceiver, on the other hand, you'd write code to process the incoming SoapEnvelope object, as shown here:
double ProcessAddMessage(SoapEnvelope e)
{
    // extract x and y values from incoming SOAP message
    double x = XmlConvert.ToDouble(
         e.SelectSingleNode("//x").InnerText);
    double y = XmlConvert.ToDouble(
         e.SelectSingleNode("//y").InnerText);
    return x+y;
}
Then, if in the future you need to send a response back to the original sender, you'd create another SoapEnvelope with the response information. The way you send SOAP messages around ultimately defines your MEP as well as the types of SoapSender and SoapReceiver objects you'll need to write.

Receiving Messages
A SoapReceiver is implemented by deriving a new class from Microsoft.Web.Services.SoapReceiver and overriding its Receive method, which takes a SoapEnvelope argument:
public class MathReceiver : SoapReceiver
{
protected override void Receive(SoapEnvelope e)
    {
        // implement Receive here
     }
}
Within Receive, it's your job to process the incoming SOAP message and perform the requested operation. For example, in the case of the add message shown before this last example, you would simply extract the x and y values and calculate the sum:
public class MathReceiver : SoapReceiver
{
    protected override void Receive(SoapEnvelope e)
    {
   // extract x and y values from incoming SOAP 
   // message
   double x = XmlConvert.ToDouble(
       e.SelectSingleNode("//x").InnerText);
   double y = XmlConvert.ToDouble(
       e.SelectSingleNode("//y").InnerText);
   double result = x+y;

       // do something with result
     }
}
If you want your receiver to support multiple operations, you need to key off of either the message's action (available through SoapEnvelope.Context.Action) or the request element's name. For example, you could update the implementation of the Receive method to support several math operations (see Figure 2).
public class MathReceiver : SoapReceiver
{
    protected override void Receive(SoapEnvelope e)
    {
       // extract x and y values from incoming SOAP message
       double x = XmlConvert.ToDouble(
           e.SelectSingleNode("//x").InnerText);
       double y = XmlConvert.ToDouble(
           e.SelectSingleNode("//y").InnerText);
       double result;

       // determine which operation to perform from 'action'
       switch (e.Context.Action)
       {
        case "urn:math:add":
            result = x + y;
            break;
        case "urn:math:sub":
            result = x - y;
            break;
        case "urn:math:mul":
            result = x * y;
            break;
        case "urn:math:div":
            result = x / y;
            break;
        default:
            throw new Exception("unsupported operation");
       }

       // do something with result
    }
}
The Receive method is called whenever a message arrives for a particular URI. You use the Microsoft.Web.Services.SoapReceivers class to register a SoapReceiver object with a particular URI. SoapReceivers provides a static Add method that accepts a Uri object along with the receiver to call when messages arrive for that endpoint. The following example configures an instance of MathReceiver to listen on the soap.tcp://localhost/math URI:
Uri receiverUri = new Uri("soap.tcp://localhost/math");
MathReceiver math = new MathReceiver();
SoapReceivers.Add(receiverUri, math);
Since you should only register the receiver once, make sure to place this code in the application initialization—SoapReceivers throws an exception if you try to register two receivers for the same URI. Once you've configured the receiver, it will be called whenever future messages arrive at the specified endpoint. Now let's take a look at how to send messages to the receiver using SoapSender.

Sending Messages
To send a SOAP message using SoapSender, first you instantiate a SoapSender object and bind it to a destination URI. You should then specify the message's action through the SoapEnvelope.Context.Action property. Finally, you call the Send method, passing in the SoapEnvelope object you want to send:
SoapEnvelope env = CreateAddMessage();
env.Context.Action = "urn:math:add";
SoapSender ssender = 
   new SoapSender(new Uri("soap.tcp://localhost/math"));
ssender.Send(env);
You can change the destination URI at any point through SoapSender's Destination property:
ssender.Destination = 
   new Uri("soap.tcp://localhost/mathv2");
ssender.Send(env);
The Send method delivers a one-way message and therefore doesn't return anything to the caller. However, if the sender wants a response from the receiver, it would have to implement a SoapReceiver of its own and indicate where the original receiver should send the response messages.
Figure 3 MessageOneWay 
I've provided a sample Windows® Forms application called MessagingOneWay that you can download from the link at the top of this article. The sample illustrates the concepts I covered up to this point. I dropped the MathReceiver class into the project and configured it to listen on soap.tcp://localhost/math. When the user presses any of the buttons, a SoapEnvelope is created representing the appropriate operation and is sent to the same URI. Since the receiver is in the same process as the sender, it can get access to the textbox and update it with the result value from within the Receive method, as shown in Figure 3.

Correlating Messages
In the canonical math example, MathReceiver needs to send response messages back to the sender. This means the sender and receiver switch roles, but everything else works the same. The original sender must implement a SoapReceiver (to receive the response messages) and the original SoapReceiver must use SoapSender (to send the response messages). When the receiver processes the message and then needs to send a response, it constructs a SoapEnvelope and returns it to the original sender.
The math operations shown in the MessagingOneWay sample could generate a SOAP response message that looks like the following code snippet:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
   <s:Body>
      <v:addResponse xmlns:v='urn:math'>
         <result>99</result>
      </v:addResponse>
   </s:Body>
</s:Envelope>
The receiver can generate this SOAP message using SoapEnvelope just like the original sender did. The original sender also needs to implement a SoapReceiver class to process the response messages as they come in. Here's an example of one that writes the result element to the result textbox on the form:
public class MathResponseReceiver : SoapReceiver
{
    public Form1 SenderForm;

    protected override void Receive(SoapEnvelope e)
    {
       // write result to sender's form
       SenderForm.ResultBox.Text = 
         e.SelectSingleNode("//result").InnerText
    }
}
Then, you must register the response receiver using SoapReceivers.Add and specifying a unique URI (see Figure 4).
// target URIs
Uri receiverUri = new Uri("soap.tcp://localhost/receiver");
Uri responseUri = new Uri("soap.tcp://localhost/response");

// initialize and register SOAP receiver objects
MathReceiver math = new MathReceiver();
SoapReceivers.Add(receiverUri, math);
MathResponseReceiver mathResponse = new MathResponseReceiver();
mathResponse.SenderForm = this;
SoapReceivers.Add(responseUri, mathResponse);
So basically I've set up the reverse scenario for communicating in the opposite direction. The only thing left is to specify in the original message where the receiver should send the response messages. This can be accomplished using the ReplyTo property made available through WS-Addresssing:
private SoapEnvelope CreateMessage(string op)
{
    // create SOAP message
    SoapEnvelope env = new SoapEnvelope();
    env.Context.ReplyTo = new ReplyTo(responseUri);
 
   // continue creating envelope like before
}
Now when the receiver processes the message, it can use the ReplyTo address as the destination for response messages (see Figure 5).
public class MathReceiver : SoapReceiver
{
    protected override void Receive(SoapEnvelope e)
    {
        ... // process incoming envelope (like before)

        // send response message to original sender
        SoapEnvelope env = new SoapEnvelope();
        env.Context.Action = e.Context.Action;
        env.CreateBody(); 
        env.Body.InnerXml = String.Format("<v:{0}Response 
            xmlns:v='urn:math'><result>{1}</result>
            </v:{0}Response>", 
            e.Body.SelectSingleNode("*").LocalName, result);

        // create SoapSender for the reply address and send message
        SoapSender responseSender = new SoapSender(
               e.Context.ReplyTo);
        responseSender.Send(env);
}
With this code in place, I've extended the previous one-way example to support the request/response MEP. I've provided an updated sample called MessagingRequestResponse in the code download that illustrates how to evolve the one-way sample to support request/response messaging.

Higher-level Messaging
Up to this point, I've been manually performing two common but somewhat tedious tasks. The first task was to determine which operation should be processed based on the incoming SOAP message. I did this in the previous examples by inspecting the message's action within the Receive method. The second task was correlating response messages with request messages. I accomplished this by inspecting the ReplyTo value in the received message and sending another message back to the original sender. When using SoapSender and SoapReceiver directly, you're always required to handle these tasks manually.
Since both of these tasks are quite common in many messaging scenarios, WSE 2.0 offers a higher-level API that builds on the SoapSender and SoapReceiver classes. This API is provided through the SoapClient and SoapService classes, which automate the two tasks that I have just described.
SoapService derives from SoapReceiver and provides the basic functionality that most receivers need. When using SoapService, you no longer need to manually implement a SoapReceiver. To implement a SoapService, you simply derive a new class from SoapService and define the operations you want to support as methods on the new class, like so:
public class MathService : SoapService
{
... // define operations here
}
Each of the operations should be defined as a method taking a SoapEnvelope as input and returning a SoapEnvelope as output, unless it doesn't need to send response messages. In that case, the return type should be void. Then, you annotate each method with the Microsoft.Web.Services.SoapMethod attribute, specifying the action that should be associated with the given method, like this:
public class MathService : SoapService
{
    [SoapMethod("urn:math:add")]
    public SoapEnvelope Add(SoapEnvelope e)
    {
       ... // implement Add here
    }
    [SoapMethod("urn:math:sub")]
    public SoapEnvelope Sub(SoapEnvelope e)
    {
       ... // implement Sub here
    }
•••
}
The SoapService implementation of Receive determines which method to call based on the incoming message's action and some .NET reflection. This makes it possible for you to focus on implementing a single operation at a time while avoiding the long switch statement I had to use earlier.
The other benefit of this approach is that the operation can simply return a SoapEnvelope object containing the response message without having to instantiate a SoapSender object and harvesting the ReplyTo address. All of this work is handled by the SoapService base class. The example in Figure 6 illustrates how you could implement the math operations using this technique.
public class MathService : SoapService
{
    [SoapMethod("urn:math:add")]
    public SoapEnvelope Add(SoapEnvelope e)
    {
        double x = XmlConvert.ToDouble(
            e.SelectSingleNode("//x").InnerText);
        double y = XmlConvert.ToDouble(
            e.SelectSingleNode("//y").InnerText);
        return CreateResponseMessage(e, x+y);
    }
    [SoapMethod("urn:math:sub")]
    public SoapEnvelope Sub(SoapEnvelope e)
    {
        double x = XmlConvert.ToDouble(
            e.SelectSingleNode("//x").InnerText);
        double y = XmlConvert.ToDouble(
            e.SelectSingleNode("//y").InnerText);
        return CreateResponseMessage(e, x-y);
    }
•••
}
You still need to register your SoapService class using SoapReceivers.Add just like you would with any SoapReceiver:
MathService mathService = new MathService();
SoapReceivers.Add(
   new Uri("soap.tcp://localhost/soapservice", 
   mathService);
To take advantage of this same approach on the client, you'll need to implement another class that derives from SoapClient. This class will encapsulate the process of using SoapSender to send messages to a particular endpoint. Hence, using SoapClient, you no longer need to use SoapSender directly. You basically follow the same procedure as with SoapService; the only difference is how you implement the operations.
As with SoapService, you define methods for the operations you intend to invoke and then annotate them with SoapMethodAttribute, specifying the operation's action, as shown in Figure 7.
public class MathClient : SoapClient
{
    public MathClient() {}
    public MathClient(Uri uri) : base(uri) {}

    [SoapMethod("urn:math:add")]
    public SoapEnvelope Add(SoapEnvelope e)
    {
        return base.SendRequestResponse("Add", e);
    }
    [SoapMethod("urn:math:sub")]
    public SoapEnvelope Sub(SoapEnvelope e)
    {
        return base.SendRequestResponse("Sub", e);
    }
    •••
}
The SoapClient base class provides various methods for invoking the operation, including Send (inherited from SoapSender), SendOneWay, and SendRequestResponse. SendRequestResponse encapsulates the process of sending a message with SoapSender and setting up a receiver to handle the response message. When you call SendRequestResponse, you must specify the name of the method you want to invoke along with the SOAP envelope.
My custom MathClient class makes it much easier for clients to interact with MathService since they no longer have to worry about specifying the action or correlating request and response messages manually. The code in Figure 8 illustrates how to use MathClient to send a message and process the response.
private void CreateMessageAndSend(string op)
{
    // create & send message 
    SoapEnvelope reqEnv = CreateMessage(op);
    MathClient mathClient = new MathClient(receiverUri);
    SoapEnvelope respEnv; 
    switch(op)
    {
      case "add":
        respEnv = mathClient.Add(reqEnv);
        break;
      case "sub":
        respEnv = mathClient.Sub(reqEnv);
        break;
      case "mul":
        respEnv = mathClient.Mul(reqEnv);
        break;
      case "div":
        respEnv = mathClient.Div(reqEnv);
        break;
      default:
        throw new Exception("unsupported operation");
    }
    this.textBox3.Text = respEnv.SelectSingleNode("//result").InnerText;
}
These classes simplify the tasks that are involved in setting up different messaging scenarios and they have optimized for request/response messaging. I've provided another sample in the code download called MessagingHigherLevel that provides the same functionality as the previous sample, but implements it through SoapService and SoapClient classes.

Using TCP and HTTP
Up to this point, both the sender code and the receiver code have been running in the same process. As I mentioned, the WSE 2.0 messaging API also lets you use TCP or HTTP as the communications mechanism. Using one of these protocols makes it possible to isolate the sender code from the receiver code in separate processes or even separate machines. To illustrate this, I created a new project called MessagingTcpService and only included the MathService code shown earlier. This project contains a simple form displaying the URI it's listening on (see Figure 9).
Figure 9 MessagingTcpService 
I updated the initialization code to register a soap.tcp URI instead of soap.tcp, as illustrated here:
receiverUri = new Uri(String.Format(
        "soap.tcp://{0}:123/receiver",
        System.Net.Dns.GetHostName()));

// initialize and register SOAP receiver objects
  MathService mathService = new MathService();
  SoapReceivers.Add(receiverUri, mathService);
In addition, I updated the previous sample and called it MathTcpClient. I removed the MathService code, since that's now in the MathTcpService project, and updated the URI to be the same one that's used by the service. These are the only changes I had to make to enable SOAP over TCP. Now MathTcpClient can communicate with MathTcpService over TCP even if the processes are running across the Internet.
Figure 10 New Unformatted Trace 
It's an interesting exercise to trace the TCP messages between the two applications using the Trace Utility that ships with Microsoft SOAP Toolkit 3.0. To do this, you need to update your MathService code to listen on a different port (say 456). Then create a new unformatted trace and specify 123 as the port to listen on and 456 as the destination port, as shown in Figure 10. Now when you run the client program and invoke one of the operations, you should see the captured TCP messages containing the SOAP request and response messages (see Figure 11).
Figure 11 Tracing SOAP over TCP 
In addition to the in-process and TCP communications mechanisms, it's also possible to integrate SoapReceivers with HTTP pipeline in ASP.NET. If you look at the definition for SoapReceiver, you'll notice that it implements IHttpHandler:
public abstract class SoapReceiver : SoapPort, IHttpHandler
{
   •••
}
Thanks to this, any SoapReceiver or SendService class can now be configured in ASP.NET as an HTTP handler. You can configure HTTP handlers by adding a new mapping in the httpHandlers section of your web.config file. The web.config entry should map a verb/path combination to your SoapReceiver type:
<configuration>
    <system.web>
        <httpHandlers>
            <add verb="*" path="receiver.ashx" 
                  type="MathService, Messaging"/>
        </httpHandlers>
    </system.web>
</configuration>
With this in place, MathService will be called for every message that enters this virtual directory and targets receiver.ashx. Now you don't have to worry about configuring your SoapReceiver/SoapService by calling SoapReceivers.Add since ASP.NET is essentially doing that on your behalf.
If you update the client to send messages to the HTTP endpoint (http://localhost/messaginghttpservice/receiver.ashx), it will work just like it did when using TCP, only now it's communicating over HTTP. I've provided sample projects in the download that illustrate the TCP and HTTP examples described here.

WseChat
As you can see, the SoapSender and SoapReceiver classes in WSE 2.0 make it possible to build SOAP messaging applications that can run over different transport protocols. SoapClient and SoapService build on this foundation to simplify the common tasks. To illustrate the flexibility that comes from these core classes, I've provided a more interesting sample called WseChat.
Figure 12 Bob and Mary Chat 
WseChat is a SOAP-based chat program built completely in terms of SoapSender and SoapReceiver running over TCP. The chat program allows the user to specify a user name and port number. When the user clicks the Listen button, it constructs a URI based on those values and the program begins listening over TCP. The user can type in a target URI indicating where to send the chat messages. Then, when the user types a message and presses Send (at the bottom of the form), the application sends the message over TCP to the target URI. You can run the program on separate machines and chat between them. Figure 12 shows the program in action.

Conclusion
WSE 2.0 provides a flexible API for sending and receiving SOAP messages via TCP or HTTP. SoapSender and SoapReceiver provide the basic functionality for sending and receiving SOAP messages. WSE 2.0 also provides a higher-level API through the SoapClient and SoapService classes which help you deal with multiple operations at a given endpoint and correlate the request and response messages. Overall, the use of WSE 2.0 helps developers take a large step towards realizing the full potential of XML-based Web Services.

Send your questions and comments for Aaron to  xmlfiles@microsoft.com.


Aaron Skonnard is an instructor/researcher at DevelopMentor, where he develops the XML and Web Service-related curriculum. Aaron coauthored Essential XML Quick Reference (Addison-Wesley, 2001) and Essential XML (Addison-Wesley, 2000). Reach him at http://staff.develop.com/aarons.

Page view tracker