Export (0) Print
Expand All

Web Service Messaging with Web Services Enhancements 2.0

 

Simon Horrell
DevelopMentor

July 2004

Applies to:
   Web services specifications
   Web Services Enhancements 2.0 for Microsoft .NET

Summary: With the XML messaging API included in the Web Services Enhancements 2.0 toolkit, you can send and receive messages asychronously over a variety of communications protocols. This enables you to build XML messaging applications with much more complex message exchange patterns than previously available. (34 printed pages)

Download the associated msdn-wse-messaging-Code.exe sample code.


Contents

Introduction
XML Messaging over Different Communication Protocols
The State of XML Messaging Today
WSE 2.0 Support for XML Messaging
An Example of Using XML Messaging: Publish and Subscribe
WS-Eventing
Security Considerations
Conclusion
Related Books

Introduction

This article explores the benefits of transport-neutral XML messaging, and introduces the Web Services Enhancements (WSE) 2.0 messaging application programming interface (API). It will also explain how the WS-Addressing specification allows messages to be addressed in a transport-independent way, and shows how it is implemented in WSE 2.0.

XML Messaging over Different Communication Protocols

In the early days of integrating applications together across the Internet, it was all about trying to blend together the most ubiquitous communication protocol, HTTP, with the most popular data format, XML. After all, every platform had an HTTP stack and an XML parser.

HTTP is a protocol that was designed to do a particular job, however, and has a very precise mode of operation—the client establishes a connection, and then initiates the conversation by sending a request to the server and synchronously getting back a response, with either party (or any HTTP intermediary between them) able to close the connection. Such characteristics make HTTP inappropriate for certain message exchange scenarios, such as peer-to-peer communication, where either party may establish the connection or initiate the conversation, or perhaps a single request/multiple response communication. It is lucky, then, that there are other successful Internet-friendly protocols, such as TCP, FTP, and SMTP, which have slightly different characteristics and that lend themselves to other types of application not well catered for by HTTP.

To carry XML messages unchanged over different communication protocols, those XML messages need to be self-contained and isolated from the underlying transport. This means that messages must hold all the information required to get the message where it is going and to process it. For instance, holding addressing information outside the XML message in the communication protocol layer alone makes it more difficult to use different transports to send the message. If addressing information is held inside the XML message, then it can be mapped to the needs of any transport used to send it. Similarly, imagine you wanted to exchange a series of XML messages that built up some conversational state. Holding the session identification information outside the XML messages in the communication protocol layer means it is difficult to change the transport used to send the messages. If such information is held inside the XML messages, it can be carried over any transport.

One of the fundamental tenets of SOAP is transport-neutral messaging. The SOAP message format, shown below, allows the protocol information needed to build higher-level message exchanges to be carried in the XML message, and independently of the underlying communication protocol.

<soap:Envelope 
  xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Header>
    <!-- Information needed by communication protocol or needed 
         to implement higher-level application protocols goes 
         here -->
  </soap:Header>
  <soap:Body>
    <!-- Data goes here -->
  </soap:Body>
</soap:Envelope> 

The Body element has an open content model and is used to hold arbitrary data. The Header element also has an open content model, and is used to carry the information that is needed by whatever communication protocol is used to carry the message. The Header element is also used to carry the information that is needed to build higher-level application protocols, such as a secure conversation protocol. Obviously the sender and receiver of the message must agree on the syntax and semantics of these headers. In fact, over time, many headers have been standardized by the community to promote interoperability. This can be seen with specifications such as WS-Addressing (a transport-neutral way to address Web services and messages) and WS-Security (a transport-neutral way to provide message integrity, message confidentiality, and single message authentication).

Carrying XML messages over different communications protocols enables the many different message exchange patterns (MEPs) made possible by those protocols—for instance, the ability to send one request and get nothing back. For this reason, SOAP does not mandate any particular message exchange patterns, preferring to talk instead about transmitting messages from a SOAP sender to a SOAP receiver (via zero or more SOAP intermediaries), so that a variety of different message exchange patterns can be built.

The State of XML Messaging Today

Today one might expect to see many Web services, built over a variety of communication protocols and employing many different message exchange patterns. In reality, however, the vast majority of Web services are implemented as a collection of Remote Procedure Calls (RPC) over HTTP. This is primarily because that is the model promoted by most of today's Web service frameworks and toolkits. Such frameworks map the synchronous exchange of SOAP messages, carried in HTTP request and response messages onto the remote invocation of a method, on a class. HTTP was chosen because both client-side and server-side HTTP plumbing are available on every platform. The RPC model was adopted because of its familiarity and relative ease of use—it doesn't scare off developers. The RPC model is limiting, though. There are many other message-exchange possibilities if you are not tied to the predominantly synchronous, request-response model of RPC over HTTP. Also, the RPC approach eschews an XML-oriented view of data in favor of an object-oriented view, and this tends to lead to very tightly-coupled systems. There are real opportunities for building loosely-coupled systems that can evolve over time, if you are prepared to discard RPC and adopt a more XML-oriented view of the world.

It is generally possible to build a Web service that deviates from what the Web service runtime and tools want you to do. Even though the Microsoft ASP.NET .ASMX framework is geared towards implementing RPC-oriented Web services, it would be possible to write a more XML-oriented service by having the WebMethods accept an XmlNode as a parameter. HTTP cannot easily be replaced as the underlying protocol, however, as ASP.NET is centred on HTTP.

WSE 2.0 Support for XML Messaging

What is needed is an API that provides the ability to send and receive XML messages asynchronously over a variety of different communication protocols. Enter the Microsoft Web Services Enhancements (WSE) 2.0. It is intended to offer support to early adopters of the new wave of advanced Web service protocols and, in turn, provide feedback to Microsoft about how developers want to build the next generation of Web services. In addition to implementing some of the WSA specifications, it also has messaging support. Specifically, it provides a new messaging API that offers a more flexible model than the .ASMX framework. It is message—and not necessarily method—based and is transport-neutral, supporting in-process, TCP and HTTP communication out of the box, with an architecture that allows custom transports to be plugged in. The API enables asynchronous messaging, which makes it possible to implement custom message exchange patterns. WSE 2.0 also has support for the WS-Addressing specification, which defines SOAP headers to represent message addressing information such as for where the message is bound, from where it is has come, and where replies, if there are any, must be sent. By pulling that information out of the communication protocol layer and into the SOAP message, it provides for much more flexible addressing scenarios over a variety of communication protocols. This article targets the WSE 2.0 release version.

An Example of Using XML Messaging: Publish and Subscribe

At this point I am going to develop an example well-suited to messaging—a "publish and subscribe" system. In such a system, one Web service application (the "subscriber") registers an interest (the "subscription") in receiving notification messages about particular events with another Web service application (the "publisher"). So, for instance, a stock application may publish the fact that it is prepared to accept subscriptions regarding certain stocks, and will generate notification messages to any interested subscribers in the event those stocks change prices. Any stock-tracking application that subscribes to the stock service will be informed about the change in price of the stocks in question. Sometimes the publisher is referred to as an "event source," and the subscriber is referred to as an "event sink."

While you could imagine the subscription being carried out synchronously over HTTP, it is not likely that the events would be dispatched via HTTP. This would mean the subscriber would be able to advertise an HTTP endpoint to receive them, and most subscribers (clients) won't necessarily have any HTTP server-side plumbing. It is easier to imagine a subscriber being able to advertise a TCP endpoint, though (security issues notwithstanding).

While the .ASMX framework, with or without WSE, is client-server oriented, the WSE 2.0 messaging API adds support for peer-to-peer communication. It stays true to the original intentions of SOAP, because it is defined in terms of senders and receivers; that is, there are SOAP nodes that are designed to construct and send messages, and those that are designed to receive and process messages. The beauty of the API, though, is that an application is not tied to any one role and can, and often will, play both roles at once.

Sending Messages

A SOAP message is represented by the SoapEnvelope class from the Microsoft.Web.Services2 namespace. It is a specialization of the familiar XmlDocument class, and has additional properties and methods geared to the fact that the document object model (DOM) represents a SOAP message. Let us consider the SOAP message below, which represents a subscription.

<soap:Envelope 
  xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <subscribe xmlns="urn:stockservice">
      <stock>Acme</stock>
    </subscribe>
  </soap:Body>
</soap:Envelope> 

This could be constructed, by using the WSE 2.0 messaging API, to implement the code below.

private static SoapEnvelope CreateSubscriptionMessage() {
  SoapEnvelope env = new SoapEnvelope();
  XmlElement body = env.CreateBody();
  body.AppendChild(
    env.CreateElement("subscribe", Literals.StockServiceNs));
  body.FirstChild.AppendChild(
    env.CreateElement("stock", Literals.StockServiceNs));
  body.FirstChild.FirstChild.AppendChild(env.CreateTextNode("Acme"));
  return env;
}

Notice how SoapEnvelope has methods to handle the specifics of a SOAP message, such as CreateBody(), but also has access to the XmlDocument base class methods, such as AppendChild().

To send a message, you use the SoapSender class from the Microsoft.Web.Services2.Messaging namespace. The SoapSender must be associated with a URI that specifies the destination address of any messages sent. The URI format is as follows.

protocol scheme://host:port/path

Protocol scheme determines which communication protocol to use, host:port determines to which host and port number to send the messages, and path determines at which exact resource on the host the messages are targeted. The easiest way to associate the destination URI with the SoapSender is to pass it as a construction parameter. As mentioned before, WSE 2.0 supports three protocols out of the box—HTTP, TCP, and in-proc. An example of a URI that is used to specify a TCP endpoint is shown below.

class Literals {
  internal static readonly Uri SendUri = 
    new Uri("soap.tcp://localhost:8080/stockservice");
  internal const string StockServiceNs = "urn:stockservice";
  internal const string SubscribeAction = 
    "urn:stockservice:subscribe";
}

The soap.tcp protocol is not a standardized protocol scheme; rather, it is there so developers can experiment with sending SOAP messages over TCP.

Before sending a message, its Action SOAP header must be set. This is a unique URI that specifies the semantics implied by the message, and is often used by the receiver to figure out what is the intent of the message and where to dispatch it for processing. The Action SOAP header is defined in the WS-Addressing specification, which will be covered in more detail later.

WSE uses a pipeline of input and output filters to map protocol header information, such as the WS-Addressing Action SOAP header, to and from SOAP messages. The protocol header information for a SOAP message is held in a SoapContext class that is available from the SoapEnvelope Context property—output filters use the SoapContext to update an outbound message with its SOAP headers, and input filters use the inbound message SOAP headers to update the SoapContext. So in the code below, the SoapEnvelope Context.Addressing.Action property is set so it can be applied as the value of the WS-Addressing Action SOAP header in the message to be sent.

Finally, to send the message the SoapSender Send() method is used, specifying the SoapEnvelope to send. The code below shows how the subscriber console application sends a message.

class SubscriberApp {
  static void Main(string[] args) {

    SoapEnvelope env = CreateSubscriptionMessage();
    env.Context.Addressing.Action = Literals.SubscribeAction;
    SoapSender sender = new SoapSender(Literals.SendUri);
    Console.Write("Sending message...");
    sender.Send(env);
    Console.WriteLine("done");
    
    // Keep application alive
    Console.WriteLine("Press enter to end subscriber");
    Console.ReadLine();
  }
  private static SoapEnvelope CreateSubscriptionMessage() {
    // Code shown previously ...
  }
}

class Literals {
  // Class definition shown previously ...
}

Note, in this case, that the Send() method performs an asynchronous message send and will return immediately after delivering the message to the receiver, but without waiting for the receiver to process the message. That is why the Console.ReadLine() has been added: to keep the subscriber application alive.

For those who prefer to deal with .NET types rather than XML, it is possible to do so. The SoapEnvelope class has a SetBodyObject() method that will take a .NET object and set it as the contents of the SOAP message Body element. The code below illustrates this method.

class SubscriberApp {

  // Rest of class definition elided for clarity

  private static SoapEnvelope CreateSubscriptionMessage() {
    SoapEnvelope env = new SoapEnvelope();
    Subscribe s = new Subscribe();
    s.stock = "Acme";
    env.SetBodyObject(s);
    return env;
  }
}

[XmlRoot(ElementName="subscribe", Namespace=Literals.StockServiceNs)]
public class Subscribe {
  public string stock;
}

// class Literals elided for clarity ...

Under the covers, SetBodyObject() is using the XmlSerializer class to do the work.

Receiving Messages

To receive a message, you derive from the SoapReceiver class from the Microsoft.Web.Services.Messaging2 namespace and override its Receive() method. This method gets handed a SoapEnvelope, and your code must dig into it to process the message. The code below shows how to do this.

class PublisherReceiver : SoapReceiver {
  protected override void Receive(SoapEnvelope env) {
    switch(env.Context.Addressing.Action.Value) {
      case Literals.SubscribeAction:
        AcceptSubscription(env);
        break;
      default:
        Console.WriteLine(
          "Unknown message, action is {0}, body is {1}",
          env.Context.Addressing.Action.Value, env.Body.InnerXml);
     break;
    }
  }
  private void AcceptSubscription(SoapEnvelope env) {
    XmlNamespaceManager nsm = new XmlNamespaceManager(env.NameTable);
    nsm.AddNamespace("x", Literals.StockServiceNs); 
    XmlNode n = env.Body.SelectSingleNode(
      "x:subscribe/x:stock/text()", nsm);
    if (n!=null)
      Console.WriteLine("Got subscription for stock {0}", n.Value);
  }
}

Notice how the WS-Addressing Action SOAP header value is available via the received message's Context.Addressing.Action property, and how it is used to distinguish the message's intent and to dispatch the message to the correct code for processing. Of course, the element name of the first child of the message's SOAP Body could have similarly been used to identify the message's intent, assuming it was unique.

The code above is not enough, though. The receiving code needs to be told on which URI it is listening for messages. The code below in the publisher console application—which is our message-oriented Web service—uses the SoapReceivers class to register an instance of PublisherReceiver with a URI.

class PublisherApp {
  static void Main(string[] args) {

    PublisherReceiver receiver = new PublisherReceiver();
    SoapReceivers.Add(Literals.ReceiveUri, receiver);

    // Keep application alive
    Console.WriteLine("Press enter to end publisher");
    Console.ReadLine();
  }
}

class Literals {
  internal static readonly Uri ReceiveUri = 
    new Uri("soap.tcp://localhost:8080/stockservice");
  internal const string StockServiceNs = "urn:stockservice";
  internal const string SubscribeAction = 
    "urn:stockservice:subscribe";
}

class PublisherReceiver : SoapReceiver {
  // Class definition shown previously ...
}

Notice that this code uses a singleton pattern—it creates one instance of PublisherReceiver to handle all messages on that URI. I could have used another version of the overloaded SoapReceivers.Add() method that allowed me to specify the type of the receiver, and the WSE framework would have created instances as required.

Again, for those who prefer dealing in .NET types, SoapEnvelope has a GetBodyObject() method that will return the contents of the SOAP Body element as an instance of the specified type, as shown below.

// class PublisherApp and class Literals elided for clarity...

class PublisherReceiver : SoapReceiver {

  // Rest of class definition elided for clarity ...

  private void AcceptSubscription(SoapEnvelope env) {
    Subscribe s = (Subscribe)env.GetBodyObject(typeof(Subscribe));
    Console.WriteLine("Got subscription for stock {0}", s.stock);
  }
}

[XmlRoot(ElementName="subscribe", Namespace=Literals.StockServiceNs)]
public class Subscribe {
  public string stock;
}

It would be interesting to experiment a little at this point. What would be the effect on the sender if, in the first line of the AcceptSubscription() method shown above, I added a call to System.Threading.Sleep() and slept for 20 seconds to simulate the received subscription message taking a long time to process? Of course, there would be no noticeable effect on the message sender, as the sending of the message and the processing of the message are not synchronized with each other.

Addressing Messages

I talked earlier about how SOAP headers are used to store protocol information in an XML message, thus isolating it from the underlying transport. One example I gave concerned storing transport-neutral addressing information in SOAP headers, which map to the specific needs of whatever communication protocol is used to carry the message. This is the remit of the WS-Addressing specification; it defines addressing information that is "typically provided by transport protocols and messaging systems," and is "[normalized] into a uniform format that can be processed independently of transport or application." What the specification is saying is that WS-Addressing defines a standardized transport-neutral way of specifying addresses, so that they can be mapped to the needs of any underlying communication protocol.

Tracing the subscription message sent to the publisher—by turning on WSE diagnostics—reveals how the subscription message looks on the wire. It shows three of WS-Addressing's SOAP headers from the http://schemas.xmlsoap.org/ws/2004/03/addressing namespace—Action, MessageID, and To.

<soap:Envelope 
  xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
  xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/03/addressing" ...>

  <soap:Header>
    <wsa:Action>urn:stockservice:subscribe</wsa:Action>
    <wsa:MessageID>
    uuid:6ddc2552-82a1-4c97-8d99-ef38fb196113
    </wsa:MessageID>
    <wsa:To>soap.tcp://localhost:8080/stockservice</wsa:To>
    <!-- Rest of headers elided for clarity ... -->
  </soap:Header>
  <soap:Body>
    <subscribe xmlns="urn:stockservice">
      <stock>Acme</stock>
    </subscribe>
  </soap:Body>
</soap:Envelope>

The Action header—introduced earlier—is a URI that is used by the message receiver to figure out what is the intent of the message, and where to dispatch it for processing. It is similar to the SOAPAction HTTP header defined by the SOAP specification in its binding for HTTP. To enable the intent to be conveyed when the message is transmitted over communication protocols other than HTTP, however, and to allow it to be conveyed in all messages, not just request messages, it is now sent as a SOAP header instead of as an HTTP-specific header. The To header is a URI that indicates the destination of the message. This may be a physical network address or a logical address, allowing for more dynamic addressing scenarios. For instance, a SOAP processor could translate a logical address to a physical address using some configuration information, such as a routing table. Both of the Action and To headers are required in a message. The MessageID header is a URI that uniquely identifies this message. It is useful to have messages uniquely identified, as in some cases one message needs to refer to another.

At the moment, the sending of the subscription message is "fire and forget" as far as the sender is concerned—the subscriber doesn't get to find out about the success or failure of its subscription. For this reason, there are some other WS-Addressing headers, related to message replies, that don't appear in the traced subscription message: ReplyTo, FaultTo, RelatesTo, and From. The ReplyTo header describes an endpoint where any replies related to a message are to be sent. The FaultTo header describes an endpoint where any faults related to a message are to be sent. The RelatesTo header is used to correlate messages, and must be present in a reply message. Its value is a URI that corresponds to the related message's MessageID header, and its RelationshipType attribute indicates the type of the relationship. Currently, wsa:Reply is the only defined relationship type indicating that one message is a reply to another. If either of the ReplyTo or FaultTo headers is present, then the MessageID header must also be present. The From header describes the endpoint from where the message originated.

If the ReplyTo header is missing and a reply needs to be sent, the From header can be used instead. If the FaultTo header is missing and a fault needs to be sent, the ReplyTo header can be used instead, if it exists; if not, the From header can be used instead.

Rather than just using a URI to indicate an endpoint where a messages receiver should send associated messages, WS-Addressing defines the EndpointReferenceType, which allows for a much fuller description of the target endpoint. The ReplyTo, FaultTo, and From headers are all of type EndpointReferenceType. An instance of the EndpointReferenceType contains additional information, such as service definition information and/or policy information, and its structure is shown below.

<wsa:EndpointReference>
    <wsa:Address>xs:anyURI</wsa:Address>
    <wsa:ReferenceProperties> ... </wsa:ReferenceProperties>
    <wsa:PortType>xs:QName</wsa:PortType>
    <wsa:ServiceName PortName="xs:NCName">xs:QName</wsa:ServiceName>
    <wsp:Policy> ... </wsp:Policy>
</wsa:EndpointReference>

The Address URI identifies the endpoint and can, as with the value of the To header seen earlier, be a physical or logical address allowing addressing flexibility. Each reference property inside ReferenceProperties contains further identification information, which could be interpreted by the communication protocol carrying the message to the endpoint, the framework dispatching the message at the endpoint, the application processing the message at the endpoint, or all three. When a SOAP message needs to be addressed to an endpoint based on the contents of an EndpointReference, the Address field becomes the WS-Addressing To SOAP header and every item in the ReferenceProperties field becomes a header in the SOAP Header element. Consider the EndpointReference below.

<SomeEndPoint xmlns:wsa='http://schemas.xmlsoap.org/ws/2004/03/addressing'>
  <wsa:Address>http://www.develop.com/SomeAddress</wsa:Address>
  <wsa:ReferenceProperties>
    <x:MyIdentifier xmlns:x="urn:mystuff">2597</x:MyIdentifier>
  </wsa:ReferenceProperties>
</SomeEndPoint>

Any SOAP message sent to the corresponding endpoint would look like this.

<soap:Envelope 
  xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
  xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/03/addressing" ...>

  <soap:Header>
    <wsa:To>http://www.develop.com/SomeAddress</wsa:To>
    <x:MyIdentifier xmlns:x="urn:mystuff">2597</x:MyIdentifier>
     <!-- Rest of headers elided for clarity ... -->
  </soap:Header>
  <soap:Body>
     <!-- Body elided for clarity ... -->
  </soap:Body>
</soap:Envelope>

The PortType and ServiceName elements refer to a web services description language (WSDL) description of the referenced endpoint. A recipient of an EndpointReference can use these to correctly format messages to send to the endpoint. Each Policy element describes a policy in effect for the referenced endpoint. These allow a recipient of an EndpointReference to know about the requirements, capabilities, and preferences of the endpoint.

In the WSE API, WS-Addressing's EndpointReference is represented as the EndpointReference class from the Microsoft.Web.Services2.Addressing namespace. For instance, an alternative way of addressing a message to be sent is shown below.

SoapSender sender = new SoapSender();
sender.Destination = new EndpointReference(Literals.SendUri);

Note that in the face of networking technologies such as NAT, DHCP, and firewalls, it is common that a meaningful global URI cannot be assigned to an endpoint. For this reason, WS-Addressing defines a well-known URI, http://schemas.xmlsoap.org/ws/2004/03/addressing/role/anonymous, to indicate an anonymous endpoint that does not have a stable, resolvable URI. Bear in mind that any of the ReplyTo, FaultTo, or From headers that are needed to send a reply message may be missing from a message, or may have their Address element set to WS-Addressing's anonymous URI. In this case, there has to be some out-of-band mechanism for delivering replies or faults. For example, WSE attempts to return the reply/fault on the same transport connection used to receive the message, sometimes referred to as the "back channel."

Responding to Messages and Correlation Between Messages

To enable the subscriber to find out about the success, or otherwise, of its subscription, the two applications will need to reverse roles. That is, the publisher will need to send a subscription response message back for the subscriber to receive. The destination of the subscription response message will be set by the publisher, from the value of the ReplyTo SOAP header sent with the subscription message. So the subscriber now uses SoapReceivers.Add() to register a receiver to listen for the subscription response message. The URI that is used to listen is the same one that is specified in the SOAP ReplyTo header of the subscription message. The code below shows the changes to the subscriber.

class SubscriberApp {
  static void Main(string[] args) {

    SubscriberReceiver receiver = new SubscriberReceiver();
    SoapReceivers.Add(Literals.ReceiveUri, receiver);

         
    SoapEnvelope env = CreateSubscriptionMessage();
    env.Context.Addressing.Action = Literals.SubscribeAction;
    env.Context.Addressing.ReplyTo = 
      new ReplyTo(Literals.ReceiveUri);
    SoapSender sender = new SoapSender(Literals.SendUri);
    sender.Send(env);

    Console.WriteLine("Press enter to end subscriber");
    Console.ReadLine();
  }
  // Rest of class definition elided for clarity ...
}

class Literals {
  internal static readonly Uri SendUri = 
    new Uri("soap.tcp://localhost:8080/stockservice");
  internal const string StockServiceNs = "urn:stockservice";
  internal const string SubscribeAction = 
    "urn:stockservice:subscribe";
  internal const string SubscribeResponseAction = 
    "urn:stockservice:subscribeResponse";
  internal static readonly Uri ReceiveUri = 
    new Uri("soap.tcp://localhost:8081/stocksubscriber");
}

class SubscriberReceiver : SoapReceiver {
  protected override void Receive(SoapEnvelope env) {
    switch (env.Context.Addressing.Action.Value)  {
      case Literals.SubscribeResponseAction:
        SubscribeResponse(env);
        break;
      default:
        Console.WriteLine(
          "Unknown message, action is {0}, body is {1}",
          env.Context.Addressing.Action.Value, env.Body.InnerXml);
        break;
    }
  }
  private void SubscribeResponse(SoapEnvelope env) {
    XmlNamespaceManager nsm = new XmlNamespaceManager(env.NameTable);
    nsm.AddNamespace("x", Literals.StockServiceNs); 
    XmlNode n = env.Body.SelectSingleNode(
      "x:subscriberesponse/x:stock/text()", nsm);
    if (n!=null)
      Console.WriteLine(
        "Succesfully subscribed to stock {0}", n.Value);
  }
}

The code below shows the changes to the publisher.

// PublisherApp class elided for clarity ...

class Literals {
  internal static readonly Uri ReceiveUri = 
    new Uri("soap.tcp://localhost:8080/stockservice");
  internal const string StockServiceNs = "urn:stockservice";
  internal const string SubscribeAction = 
    "urn:stockservice:subscribe";
  internal const string SubscribeResponseAction = 
    "urn:stockservice:subscribeResponse";
}

class PublisherReceiver : SoapReceiver {

  // Rest of class definition elided for clarity ...

  private void AcceptSubscription(SoapEnvelope env) {
    SoapEnvelope responseEnv = null;
    XmlNamespaceManager nsm = new XmlNamespaceManager(env.NameTable);
    nsm.AddNamespace("x", Literals.StockServiceNs); 
    XmlNode n = env.Body.SelectSingleNode(
      "x:subscribe/x:stock/text()", nsm);
    if (n!=null) {
      Console.WriteLine("Got subscription for stock {0}", n.Value);
      if (env.Context.Addressing.ReplyTo!=null) {
        responseEnv = CreateSubscriptionResponseMessage();
        responseEnv.Context.Addressing.Action = 
          Literals.SubscribeResponseAction;
        responseEnv.Context.Addressing.RelatesTo = 
          new RelatesTo(env.Context.Addressing.MessageID.Value);
        SoapSender sender = 
          new SoapSender(env.Context.Addressing.ReplyTo);
        sender.Send(responseEnv);
      }
    }
  }
  private SoapEnvelope CreateSubscriptionResponseMessage() {
    SoapEnvelope env = new SoapEnvelope();
    XmlElement body = env.CreateBody();
    body.AppendChild(env.CreateElement(
      "subscriberesponse", Literals.StockServiceNs));
    body.FirstChild.AppendChild(env.CreateElement("stock", Literals.StockServiceNs));
    body.FirstChild.FirstChild.AppendChild(
      env.CreateTextNode("Acme"));
    return env;
  }
}

There are three things to notice here: First, the RelatesTo SOAP header of the subscription response message is set to correspond to the MessageID SOAP header of the subscription message, so that the subscriber can correlate the two messages as a request/response pair if desired; second, the URI that is used to send the subscription response message is set to the value of the ReplyTo SOAP header of the subscription message; and third, there is no exception handling in place in this example to deal with any error conditions. The SoapReceiver class will catch any exception and send it back as a generic SOAP fault if it can, using either the FaultTo, ReplyTo, or From header of the incoming message, in that order. If the deduced address is the WS-Addressing anonymous URI (or if all three headers are missing) WSE uses the "back-channel" to send the fault. The code that accompanies this article contains more stringent error handling for your perusal.

Here is what the subscription message and the matching subscription response message look like on the wire.

<soap:Envelope 
  xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
  xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/03/addressing"  ...>

  <soap:Header>
    <wsa:Action>urn:stockservice:subscribe</wsa:Action>
    <wsa:MessageID>
    uuid:8cf92bc5-d139-4a8b-aece-c4dad6d195d2
    </wsa:MessageID>
    <wsa:ReplyTo>
      <wsa:Address>soap.tcp://localhost:8081/stocksubscriber</wsa:Address>
    </wsa:ReplyTo>
    <wsa:To>soap.tcp://localhost:8080/stockservice</wsa:To>
    <!-- Rest of headers elided for clarity ... -->
  </soap:Header>
  <soap:Body>
    <subscribe xmlns="urn:stockservice">
      <stock>Acme</stock>
    </subscribe>
  </soap:Body>
</soap:Envelope>

<soap:Envelope 
  xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
  xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/03/addressing"  ...>

  <soap:Header>
    <wsa:Action>urn:stockservice:subscribeResponse</wsa:Action>
    <wsa:MessageID>uuid:47b386f5-610b-44e4-be5e-f2657437742d</wsa:MessageID>
    <wsa:RelatesTo RelationshipType="wsa:Response">
    uuid:8cf92bc5-d139-4a8b-aece-c4dad6d195d2
    </wsa:RelatesTo>
    <wsa:To>soap.tcp://localhost:8081/stocksubscriber</wsa:To>
    <!-- Rest of headers elided for clarity ... -->
  </soap:Header>
  <soap:Body>
    <subscriberesponse xmlns="urn:stockservice">
      <stock>Acme</stock>
    </subscribe>
  </soap:Body>
</soap:Envelope>

Due to security concerns, covered in a later section, the publisher must instruct WSE to allow it to send messages back to the addresses contained in the ReplyTo and FaultTo SOAP headers. To do this, the allowRedirectedResponses element must be added to the publisher's configuration file under the messaging element. Note that the value of the type attribute has been formatted over several lines for ease of reading. In the actual configuration file this would have to be on one line.

<configuration>
  <configSections>
    <section name="microsoft.web.services2" 
      type="Microsoft.Web.Services2.Configuration.
            WebServicesConfiguration, 
            Microsoft.Web.Services2, 
            Version=2.0.0.0, 
            Culture=neutral, 
            PublicKeyToken=31bf3856ad364e35" />
  </configSections>
  <microsoft.web.services2>
    <messaging>
      <allowRedirectedResponses enabled="true" />
    </messaging>
  </microsoft.web.services2>
</configuration>

Higher-Level Messaging

It turns out that there is a slightly higher-level API that could have been used to send and receive messages. The SoapClient and SoapService classes from the Microsoft.Web.Services2.Messaging namespace derive from SoapSender and SoapReceiver, respectively, and make life just that little bit easier.

Remember that when using SoapSender, the Action SOAP header value of the messages to send has to be set manually. Alternatively, by deriving a class from SoapClient, it is possible to define methods on the class and annotate them with the SoapMethod attribute, which associates the method with an Action SOAP header value. When the method is called, the message gets sent with the associated Action SOAP header value. The code below illustrates how to derive a class from SoapClient that contains a method to send a subscription message to the publisher.

class PublisherClient : SoapClient {
  public PublisherClient(Uri uri) : base(uri) {}
  [SoapMethod(Literals.SubscribeAction)]
  public void Subscribe(SoapEnvelope env) {
    return base.SendOneWay("Subscribe", env);
  }
}

The SoapClient class needs to know the destination URI to send messages to, so this is provided via a constructor. The Subscribe() method accepts the message to send as a parameter. SoapClient's SendOneWay() method uses the method name ("Subscribe") to figure out the action associated with the method's SoapMethod attribute, and then sets this as the Action SOAP header value on the message before using the Send() method of its base class to send it.

The following code shows how to call the method and send the subscription message.

class SubscriberApp {
  static void Main(string[] args) {

    SubscriberReceiver receiver = new SubscriberReceiver();
    SoapReceivers.Add(Literals.ReceiveUri, receiver);
         
    SoapEnvelope env = CreateSubscriptionMessage();
    env.Context.Addressing.ReplyTo = 
      new ReplyTo(Literals.ReceiveUri);
    // Notice no Action needs to be set
    PublisherClient pclient = new PublisherClient(
      Literals.SendUri);
    pclient.Subscribe(env);

    Console.WriteLine("Press enter to end subscriber");
    Console.ReadLine();
  }
  // Rest of class definition elided for clarity ...
}

class PublisherClient : SoapClient {
  // Class definition shown previously ...
}

// SubscriberReceiver class and Literals class elided for clarity ...

At the moment, the publisher sends back a subscription response message that is received asynchronously by the subscriber in the SubscriberReceiver class derived from SoapReceiver. It might be easier for the subscriber to handle the subscription response message synchronously. This would be possible if the PublisherClient were to change slightly, as shown below.

class PublisherClient : SoapClient {
  public PublisherClient(Uri uri) : base(uri) {}
  [SoapMethod(Literals.SubscribeAction)]
  public SoapEnvelope Subscribe(SoapEnvelope env) {
    return base.SendRequestResponse("Subscribe", env);
  }
}

SoapClient's SendRequestResponse() method sends a request message and returns the response message synchronously. Under the covers, SendRequestResponse() sets up an anonymous endpoint to listen for the correlated response message before it sends the request message. It then sends the request message, and waits for the response message to come back on the same channel—it sets the ReplyTo header to WS-Addressing anonymous URI. In this case, the Subscribe() method signature has changed and returns the response message as its return value.

Now the subscriber doesn't need a receiver to handle the subscription response message at all, and the subscriber code to send the subscription message and process the subscription response message looks like this.

class SubscriberApp {
  static void Main(string[] args) {

    // Notice no receiver needs to be registered

    SoapEnvelope env = CreateSubscriptionMessage();
    // Notice no Action needs to be set
    // Notice no ReplyTo needs to be set
    PublisherClient sender = new PublisherClient(
      Literals.SendUri);
    env = sender.Subscribe(env);
    if (env!=null) {
      XmlNamespaceManager nsm = new XmlNamespaceManager(
        env.NameTable);
      nsm.AddNamespace("x", Literals.StockServiceNs); 
      XmlNode n = env.Body.SelectSingleNode(
        "x:subscriberesponse/x:stock/text()", nsm);
      if (n!=null)
        Console.WriteLine(
          "Succesfully subscribed to stock {0}", n.Value);
    }

    Console.WriteLine("Press enter to end subscriber");
    Console.ReadLine();

  }
  // Rest of class definition elided for clarity ...
}

class Literals {
  internal static readonly Uri SendUri = 
    "soap.tcp://localhost:8080/stockservice";
  internal const string StockServiceNs = "urn:stockservice";
  internal const string SubscribeAction = 
    "urn:stockservice:subscribe";
  // Notice no receive URI necessary
}

class PublisherClient : SoapClient {
  // Class definition shown previously ...
}

// SubscriberReceiver class no longer needed

Just to reiterate, it is entirely up to the subscriber whether it processes the subscription response message synchronously or asynchronously. It has no bearing on the implementation of the publisher, which is able to send the subscription response message asynchronously in either case. To bear out that fact, if I now try and run this new "high-level" synchronous subscriber against the old "low-level" asynchronous publisher, it works as expected. The reason for this is, when the SoapClient SendRequestResponse() method is used to send a message and wait for the reply, it sets the ReplyTo SOAP header of the subscription message to WS-Addressing's anonymous URI on behalf of the subscriber. The publisher deduces the destination of the subscription response message from the ReplyTo SOAP header of the incoming subscription message, and this instructs WSE to use the "back-channel" to send the subscription response message, if possible.

Currently, the PublisherReceiver class is derived from SoapReceiver, and it overrides SoapReceiver's Receive() method and performs two tasks. First, it checks the Action SOAP header value of the incoming message, and uses it to figure out how to dispatch the message for processing. Second, it sends a correlated response message back to the right place. Alternatively, by deriving the PublisherReceiver class from SoapService, I can get these two tasks implemented for free. All I need to do is define a method on the PublisherReceiver class, and annotate it with the SoapMethod attribute that associates the method with an Action SOAP header value. There is no Receive() method to implement, because SoapService derives from SoapReceiver. Its Receive() method will check the Action SOAP header value of the incoming message, and use reflection to call a method with a matching action as specified by its SoapMethod attribute. As in the case of SoapClient seen earlier, the name of the method is irrelevant. The signature of the method is important, however. If the method returns void, there is assumed to be no response message required. If the method returns SoapEnvelope, it is assumed to be a response message that is to be sent back. WSE decides the destination of the response message based on the WS-Addressing headers of the incoming message—the address in the ReplyTo header is used if it is present and, if not, the address in the From header is used. If the deduced address is the WS-Addressing anonymous URI (or if both headers are missing), WSE uses the "back-channel" to send the response message, if possible.

In other words, much of the hard work is done for me, and the publisher code looks much simpler. The code below shows the changes to the publisher.

// PublisherApp class and Literals class elided for clarity ...

class PublisherReceiver : SoapService {
  [SoapMethod(Literals.SubscribeAction)]
  public SoapEnvelope AcceptSubscription(SoapEnvelope env) {
    SoapEnvelope responseEnv=null;
    XmlNamespaceManager nsm = new XmlNamespaceManager(env.NameTable);
    nsm.AddNamespace("x", Literals.StockServiceNs); 
    XmlNode n = env.Body.SelectSingleNode(
      "x:subscribe/x:stock/text()", nsm);
    if (n!=null) {
      Console.WriteLine("Got subscription for stock {0}", n.Value);
      responseEnv = CreateSubscriptionResponseMessage();
    }
    return responseEnv;
  }
  // Rest of class definition elided for clarity ...
  }
}

Notice that the PublisherReceiver still has to be registered, to listen for subscription messages using SoapReceivers.Add(). Any exception that might be generated by the AcceptSubscription() method is translated and sent back as a generic SOAP fault if it can be, using either the FaultTo, ReplyTo, or From header of the subscription message, in that order. Again, if the deduced address is the WS-Addressing anonymous URI (or if all three headers are missing), WSE uses the "back-channel" to send the fault, if possible.

The subscriber code and publisher code have no need to access the SoapEnvelope now, as no headers are being explicitly set or retrieved—that is done by the SoapClient and SoapService classes. In this case there is an even easier way to write the code. A class derived from either SoapClient or SoapService can also define its sending methods and receiving methods in terms of any XmlSerializer-compliant types instead of SoapEnvelope. Instances of these types will be automatically serialize into and deserialize from the Body of the SOAP message. The following code shows the simplified subscriber.

class SubscriberApp {
  static void Main(string[] args) {
    Subscribe s = new Subscribe();
    s.stock="Acme";
    PublisherClient sender = new PublisherClient(
      Literals.senduri);
    SubscribeResponse sr = sender.Subscribe(s);
    if (sr!=null) {
      Console.WriteLine("Succesfully subscribed to stock {0}", 
        sr.stock);
      Console.WriteLine("Press enter to end subscriber");
      Console.ReadLine();
    }
  }
}

class PublisherClient : SoapClient {
  public PublisherClient(Uri uri) : base(uri) {}
  [SoapMethod(Literals.SubscribeAction)]
  public SubscribeResponse Subscribe(Subscribe s) {
    return (SubscribeResponse)base.SendRequestResponse(
      "Subscribe", s).GetBodyObject( typeof( SubscribeResponse) );
  }
}

[XmlRoot(ElementName="subscribe", 
  Namespace=Literals.StockServiceNs)]
public class Subscribe {
  public string stock;
}

[XmlRoot(ElementName="subscriberesponse", 
  Namespace=Literals.StockServiceNs)]
public class SubscribeResponse {
  public string stock;
}

// class Literals elided for clarity...

Likewise, the publisher is also much simpler.

// class PublisherApp and class Literals elided for clarity...

class PublisherReceiver : SoapService {
  [SoapMethod(Literals.SubscribeAction)]
  public SubscribeResponse AcceptSubscription(Subscribe s) {
    SubscribeResponse sr=new SubscribeResponse();
    Console.WriteLine("Got subscription for stock {0}", s.stock);
    sr.stock = s.stock;
    return sr;
  }
}
[XmlRoot(ElementName="subscribe", 
  Namespace=Literals.StockServiceNs)]
public class Subscribe {
  public string stock;
}

[XmlRoot(ElementName="subscriberesponse", 
  Namespace=Literals.StockServiceNs)]
public class SubscribeResponse {
  public string stock;
}

HTTP Messaging

So far, I have concentrated solely on TCP as a transport. As mentioned before, however, WSE has an architecture in which it selects the transport to use based on the protocol scheme of the URI that is used to send or receive messages. As long as WSE has a transport that matches the protocol scheme, it will work. As well as using the built-in transports (HTTP, TCP, and in-proc) it is possible to plug in custom transports.

Now I am going to change the publisher so it listens for requests on an HTTP endpoint. It is fairly straightforward to do this, because SoapReceiver implements System.Web.IhttpHandler, and so it (or anything that derives from it) can be deployed as an ASP.NET handler. First, I created a virtual directory called messaging-stage4, based on the directory containing the publisher source files. Then I authored a web.config that sits in the virtual directory. It configures the handler, and looks like this.

<configuration>
   <system.web>
        <httpHandlers>
            <add verb="*" path="stockservice.ashx" 
                 type="messaging.PublisherReceiver, publisher"/>
        </httpHandlers>
    </system.web>
</configuration>

Next I got rid of the code that registered the listening endpoint, as this is no longer necessary—ASP.NET already knows how to listen on an .ashx endpoint. Lastly, I changed the publisher from a console application to a class library, and ensured that the resulting assembly was built into the bin subdirectory of the virtual directory, as required by ASP.NET.

To change the subscriber, I just changed the URI it uses to send the subscription message to point at the HTTP endpoint I just set up.

class Literals {
  internal static readonly Uri senduri = 
    new Uri("http://localhost/messaging-stage4/stockservice.ashx");
   ...
}

It has been shown that the SoapSender Send() method sends a message asynchronously, without waiting for the message processing to occur at the receiver. Even if the sender doesn't yet know that the message has been processed by the receiver, however, they need to know whether the message was successfully delivered or not. With TCP, the sending of the message and the processing of the message are inherently decoupled, but because HTTP is a higher-level request-response protocol, the sender typically relies on a response from the receiver to know that the message was both delivered and processed. To decouple the delivery of a message from the processing of a message, HTTP defines a status code, 202 Accepted, which means that the request has been accepted for processing but the processing has not been completed. Its purpose is to allow a server to accept a request for some processing, without requiring that the user agent's connection to the server persist until the process is completed. The SoapReceiver class, when configured to listen on an HTTP URI, will return a 202 Accepted status code in situations where no response message is to be sent back, or the response is not to be returned on the "back-channel."

One advantage to the publisher having a listener that derives from SoapService that is hooked up to an HTTP endpoint is that it now responds to HTTP GET requests by returning the WSDL description of the endpoint. This is useful, as it makes it possible for developers to build a regular Web service proxy against a messaging endpoint using the techniques they are already used to. For instance, the .NET wsdl.exe tool or "Add Web Reference" in Microsoft Visual Studio could be used to do this. This is exactly what I did, and the resulting code looked like this.

class SubscriberApp {
  static void Main(string[] args) {
    XmlElement subscription = CreateSubscriptionMessage();

    PublisherReceiverWse s = 
      new PublisherReceiverWse();
    XmlElement[] parameters = {subscription};
    s.AcceptSubscription(ref parameters);
    XmlElement subscriptionResponse = parameters[0];
    if (subscriptionResponse!=null) {
      XmlNamespaceManager nsm = new XmlNamespaceManager(
        subscriptionResponse.OwnerDocument.NameTable);
      nsm.AddNamespace("x", Literals.StockServiceNs); 
      XmlNode n = subscriptionResponse.SelectSingleNode(
        "self::x:subscriberesponse/x:stock/text()", nsm);
      if (n!=null)
        Console.WriteLine(
          "Succesfully subscribed to stock {0}", n.Value);
    }
    
    Console.WriteLine("Press enter to end subscriber");
    Console.ReadLine();

  }
  // Rest of class definition elided for clarity ...
}

// Literals class elided for clarity ...

Conversely, WSE has its own tool called wsewsdl.exe, which is used for generating code from WSDL. This allows a "messaging" proxy to be generated that can communicate with any Web service endpoint that is described by WSDL.

Eventing

Having looked at the different ways that messages can be sent and received over different protocols, I am now ready to add support to the publisher for generating notification events. The real question is, though, to where should these events be sent? My initial thought was to send them to the response URI for the subscription message. If you remember, this was deduced from looking at the ReplyTo SOAP header of the subscription message or, if that was not present, the From header. On thinking about it further, though, there seemed to be two good reasons not to do this.

First, if neither the ReplyTo nor From SOAP headers were present in the message, or if either were set to WS-Addressing's anonymous URI, then it means, in effect, that the transport channel that was used to receive the subscription message is the same one that should be used to send back the subscription response message. There really is no long-lived, referenceable URI that can be used to send the notification event messages at some later date.

Second, even if I didn't have to worry about the subscription operation's message exchange being initiated from an anonymous endpoint, there are two distinct operations here—the subscription operation and the notification operation. Sure, they are related at some level—the notification event messages cannot be sent unless the subscription message has been successfully received—but they are nonetheless different operations. For instance, if you consider the WSDL portType definition for the publisher service, it might contain two operation definitions, as shown below.

<portType name="PublisherReceiverPortType">
  <operation name="AcceptSubscription">
    <input message="s0:AcceptSubscriptionMessageIn" /> 
    <output message="s0:AcceptSubscriptionMessageOut" /> 
  </operation>
  <operation name="Notification">
    <output message="s0:NotificationMessageOut" /> 
  </operation>
</portType>

So, the destination URI used for the subscription response message is part of the subscription operation, and should not be used as the destination URI for the event notification message that is part of the notification operation. Where, then, should the notification URI be specified? One way would be to make the notification URI part of the body of the subscription message, as shown below.

<soap:Envelope 
  xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <subscribe xmlns="urn:stockservice">
      <stock>Acme</stock>
      <notifyto>
        soap.tcp://localhost:8081/stocksubscriber
      </notifyto>
    </subscribe>
  </soap:Body>
</soap:Envelope> 

This changes the code in the subscriber, to generate the subscription message to look like this.

class SubscriberApp {
  // Rest of class definition elided for clarity ...
  private static SoapEnvelope CreateSubscriptionMessage() {
    SoapEnvelope env = new SoapEnvelope();
    XmlElement body = env.CreateBody();
    body.AppendChild(env.CreateElement(
      "subscribe", Literals.StockServiceNs));
    body.FirstChild.AppendChild(env.CreateElement("stock", Literals.StockServiceNs));
    body.FirstChild.FirstChild.AppendChild(
      env.CreateTextNode("Acme"));
    body.FirstChild.AppendChild(env.CreateElement("notifyto", Literals.StockServiceNs));
    body.FirstChild.ChildNodes[1].AppendChild(
      env.CreateTextNode(Literals.ReceiveUri.ToString()));
    return env;
  }
}
class Literals {
  internal static readonly Uri SendUri = 
    new Uri("soap.tcp://localhost:8080/stockservice");
  internal const string StockServiceNs = "urn:stockservice";
  internal const string SubscribeAction =
    "urn:stockservice:subscribe";
  internal static readonly Uri ReceiveUri = 
    new Uri("soap.tcp://localhost:8080/stockservice");
  internal const string NotifyAction = "urn:stockservice:notify";
}

The publisher can then scrape the notification URI out of the subscription message, and use it as the destination for event notification messages whenever the stock changes value. The changes to the publisher code are shown below. It uses a timer to generate the random stock updates.

// PublisherApp class elided for clarity ...

class Literals {
  internal static readonly Uri ReceiveUri =
    new Uri("soap.tcp://localhost:8080/stockservice");
  internal const string StockServiceNs = "urn:stockservice";
  internal const string SubscribeAction = 
    "urn:stockservice:subscribe";
  internal const string NotifyAction = "urn:stockservice:notify";
}
class PublisherReceiver : SoapService {
  Timer _t;
  ArrayList _subscriptions = new ArrayList();
  public PublisherReceiver() {
    _t = new Timer(new TimerCallback(OnTimer),null,0,5000);
  }
  [SoapMethod(Literals.SubscribeAction)]
  public SoapEnvelope AcceptSubscription(SoapEnvelope env) {
    SoapEnvelope responseenv = null;
    XmlNamespaceManager nsm = new XmlNamespaceManager(env.NameTable);
    nsm.AddNamespace("x", Literals.StockServiceNs); 
    XmlNode n = env.Body.SelectSingleNode(
      "x:subscribe/x:stock/text()", nsm);
    if (n!=null) {
      XmlNode n1 = env.Body.SelectSingleNode(
        "x:subscribe/x:notifyto/text()", nsm);
      if (n1!=null) {
        Console.WriteLine(
          "Got subscription for stock {0} - notifications to {1}", 
          n.Value, n1.Value);
        responseenv = CreateSubscriptionResponseMessage();
        StockSubscription s = new StockSubscription();
        s.stock = n.Value;
        s.senduri = new Uri(n1.Value);
        _subscriptions.Add(s);
      }
    }
    return responseenv;
  }
  private SoapEnvelope CreateNotificationMessage(
    string stock, string val) {
    SoapEnvelope env = new SoapEnvelope();
    XmlElement body = env.CreateBody();
    body.AppendChild(env.CreateElement(
      "notify", Literals.StockServiceNs));
    body.FirstChild.AppendChild(env.CreateElement("stock", Literals.StockServiceNs));
    body.FirstChild.FirstChild.AppendChild(env.CreateElement(stock, Literals.StockServiceNs));
    body.FirstChild.FirstChild.FirstChild.AppendChild(
      env.CreateTextNode(val));
    return env;
  } 
  private void OnTimer(object o) {
    string stock, val;
    GetStockUpdate( out stock, out val);
    SoapEnvelope env = CreateNotificationMessage(stock, val);
    foreach (StockSubscription s in _subscriptions) {
      if (s.stock==stock) {
        SubscriberClient sender = new SubscriberClient(s.senduri);
        sender.Notify(env);
      }
    }
  }
  private void GetStockUpdate(out string stock, out string val) {
    string[] stocks={"Acme", "MegaCorp"};
    Random r = new Random();
    int i = r.Next(stocks.Length);
    stock = stocks[i];
    val = r.Next(100).ToString();
  }
  // Rest of class definition elided for clarity ...
}
class SubscriberClient : SoapClient {
  public SubscriberClient(Uri uri) : base(uri) {}
  [SoapMethod(Literals.NotifyAction)]
  public void Notify(SoapEnvelope e) {
    SendOneWay("Notify", e);
  }
}
class StockSubscription {
  internal string stock;
  internal Uri senduri;
}

Finally, the subscriber needs to process the event notification messages, as shown here.

class SubscriberApp {
  static void Main(string[] args) {
    SoapEnvelope env = CreateSubscriptionMessage();
    
    SubscriberReceiver receiver = new SubscriberReceiver();
    SoapReceivers.Add(Literals.ReceiveUri, receiver);
         
    // Code to show message send elided for clarity...
  }
  // Rest of class definition elided for clarity...
}

// Literals class and PublisherClient class elided for clarity...

class SubscriberReceiver : SoapService {
  [SoapMethod(Literals.NotifyAction)]
  public void Notification(SoapEnvelope env) {
    XmlNamespaceManager nsm = new XmlNamespaceManager(env.NameTable);
    nsm.AddNamespace("x", Literals.StockServiceNs); 
    XmlNode n = env.Body.SelectSingleNode("x:notify/x:stock", nsm);
    if (n!=null)
      Console.WriteLine(
        "Got notification for stock {0} which has value {1}", 
        n.FirstChild.LocalName, n.FirstChild.FirstChild.Value);
  }
}

WS-Eventing

The publish-and-subscribe example just presented is quite simple, and is missing a few features. For example, it would have been more scalable if the subscriptions had been "leased" to the event sink by the event source for a specified period of time. At subscription time, the event sink could suggest the lease time for the subscription, and specify where it wants a message sent to inform it of the lease expiry. The event source ultimately gets to decide on the lease expiry time, and will send a message to the event sink when the lease expires. If the event sink is interested in receiving notifications after this time, it can renew the subscription for a further period. Also, neither the event sink nor the event source had the chance to cancel the subscription. Additionally, perhaps it would have been useful for the subscriber to have specified some kind of a filter condition; for example "only send me a notification if the stock rises above 53." That way, the event source could decide exactly which notifications needed to be sent and which didn't.

Microsoft, BEA and TIBCO have identified the need to build interoperable publish-and-subscribe systems. Consequently, they have recently released a specification called WS-Eventing, to standardize a protocol that allows Web service applications to participate in event-based systems with some level of control over the aspects mentioned above, and more. WSE does not currently have a WS-Eventing implementation, but the techniques shown in this article could be used to build WS-Eventing compliant applications.

Security Considerations

To guard against a variety of security attacks, the WS-Addressing SOAP headers within a message should be adequately secured using the mechanisms provided by WS-Security.

Certainly it is advisable for the sender of a message to sign the SOAP body of the message, along with any relevant WS-Addressing SOAP headers. In this case, a verifiable signature tells the receiver that the body of the message and the addressing headers are "bound" to each other as a unit, that the origin of the sender of this unit can be ascertained, and that no part of the unit has been tampered with since it was sent. In particular, if the WS-Addressing ReplyTo, FaultTo, and From SOAP headers are describing endpoint addresses that are not anonymous, then those addresses could be anywhere. As the receiver could use these endpoint addresses to send further messages, they had better have a high level of trust in the signing party—specifically in their ability to speak on behalf of those endpoint addresses. Moreover, because of addressing restrictions caused by firewalls and the like, it is highly likely that the receiver will restrict the set of URLs to which the sender can ask them to reply,fault or notify.

With these types of security concerns in mind, and to mitigate these and other types of denial of service attack, WSE provides some security-related configuration elements that aim to reduce the application's vunerability. The sample configuration file below provides a summary of these configuration elements.

<configuration>

  <microsoft.web.services2>

    <messaging>

      <!-- Specifies whether a SOAP message can be sent to the values 
           specified in the incoming message's FaultTo or ReplyTo 
           headers. The default value is false. -->
      <allowRedirectedResponses enabled="true"/>

      <!-- Maximum time in seconds to process a message before 
           processing thread gets shut down. A value of -1 means 
           there is no limit. The default value is 90. -->
      <executionTimeout value="20" />

      <!-- Maximum message size accepted in Kb. A value of -1 means 
           there is no limit. The default is 4 Mb. -->
      <maxRequestLength value="8192" />

      <transports>

        <!-- Settings for a particular communication protocol -->
        <add scheme="soap.tcp">

          <!-- Maximum inbound/outbound TCP connections to an 
               application. A value of -1 means there is no limit.
               The default value is 16. Doesn't affect connections 
               from/to same machine. -->
          <connectionLimit Inbound="4" Outbound="4"/>

          <!-- Specifies whether clients are by default allowed or 
               denied access. The default value is allow.
          <hosts default="allow">

            <!-- A list of host names, IP addresses, and/or IP 
                 address masks that are explicitly allowed/denied 
                 access. The keyword all specifies that all clients 
                 are allowed/denied access. -->
            <allow>192.168.1.1</allow>
            <deny>192.168.1.0/24 10.10.100.0/24</deny>

          </hosts>

          <!-- Time in ms before an inactive connection is closed.
               A value of -1 means there is no limit. The default is 
               2 minutes. -->
          <idleTimeout ="60000"/>

          <!-- Maximum time in ms to send/receive a SOAP message. 
               A value of -1 means no limit. The default is 90 
               seconds.
          <sendTimeout value="30000"/>
          <receiveTimeout value="30000"/>

        </add>

      </transports>

    </messaging>

  </microsoft.web.services2>

</configuration>

It may also be necessary for the sender of a message to encrypt certain WS-Addressing SOAP headers to keep them private, always bearing in mind that certain WS-Addressing SOAP headers, such as To, need to be visible to SOAP intermediaries.

Conclusion

The majority of Web services today are implemented as a collection of Remote Procedure Calls over HTTP. There is a large class of Web services, however, that would benefit from adopting a more XML-oriented view, and would profit from the characteristics offered by alternate communications protocols. To this end, Microsoft has provided an XML messaging API as part of its Web Services Enhancements toolkit. This API allows messages to be sent and received asynchronously over a variety of communications protocols, thus enabling XML messaging applications to be built that employ much more complex message exchange patterns than those that were previously allowed.

Related Books

.NET Web Services: Architecture and Implementation

Microsoft .Net and J2EE Interoperability Toolkit

Real World XML Web Services: For VB and VB .Net Developers

Show:
© 2015 Microsoft