Reliable XML Web Services

 

Eric Schmidt
Microsoft Corporation

December 11, 2001

Download ericrp.exe.

Note The following are required to run the download associated with this article:

At PDC, I delivered a session on the topic of reliable XML Web services (Web services). This talk spawned from numerous conversations that I have had over the past year. Among the various FAQs about building XML Web services, reliability falls into the top five issues facing developers implementing decentralized Web services. The problem space, when broken down into small pieces, is not that difficult. So, this month I decided to jump off into the extreme area of building reliable XML Web services.

Overview

One of the most exciting aspects of the Global XML Web Services Architecture (GXA) is the ability to extend the architecture with composable processing protocols. These protocols, predominantly implemented through SOAP headers, can provide a wide spectrum of services including security, encryption, routing, and reliability. As you start building GXA-based applications, you will discover that GXA is a messaging architecture at the core. This messaging architecture provides interoperability between systems and services through a standards based encoding technology—SOAP. The majority of implementation work to date has been focused on SOAP 1.1 and WSDL compliant services so that Web service implementations could interoperate across various languages and operating systems.

This is an elegant concept. Any system can talk to any other system as long as they can parse XML and understand the rules of the SOAP specification. However, simple message exchange is not sufficient for sophisticated business applications. Real world applications, regardless of their internal domain architecture, need standardized services like security, licensing, and reliability exposed at the Web services messaging layer. There is tremendous momentum behind the creation and implementation of the Global XML Web Service Architecture, specifically SOAP, SOAP modules, and infrastructure protocols. With the introduction of four new specifications this past October (WS-Routing, WS-Referral, WS-Licensing, and WS-Security), we are at the forefront of the next generation of XML Web service implementations. Even with this flood of new specifications, there are two areas that do not yet have public specifications—transactions and reliable messaging. This is mainly because these infrastructure protocols are reliant upon lower level SOAP modules.

For this column, I'm writing about what reliability and reliable messaging means in terms of a GXA environment. Specifically, I want to spend some time drilling down on what it takes to develop a reliability protocol by extending the existing Web service classes in the .NET Framework. I have two main goals with this column:

  1. I want to get you thinking about reliability concepts so that you are prepared for future specifications. Keep in mind this is not a specification, it's merely a document to get you thinking about the issue in question.
  2. I want to demonstrate the awesome, standards-based power of the Web service and SOAP classes in the .NET framework.

Reliability in XML Web Services

Let's break down the problem. As previously stated, GXA service implementations are messaging services. They need to send and receive standards-based encoded messages in a decentralized environment. The predominant transport protocol for sending SOAP messages in a Web services implementation is HTTP. HTTP is easy to implement and manage, but it's inherently unreliable. Without getting into the details of why HTTP is unreliable, suffice it to say that HTTP has no standards-based service for guaranteeing that a request was received by an end point or server. Granted, there are built-in network layer facilities for raising errors in case of common catastrophic failures like resource not found, but there are no mechanisms to ensure that a request was received or that a response was received by the client in a reliable manner.

Traditionally, HTTP failures are handled with simple resend actions, but this is neither efficient nor effective in business processing environments. This creates unwanted traffic and a heightened risk for duplication of transactions.

There are numerous messaging technologies available on the market today that address the problem in a more robust manner—ranging from transport protocols like HTTPR, to enterprise infrastructure like MSMQ and MQ Series, to business process protocols like ebXML. Each of these technologies has benefits for specific implementations, but none of them addresses reliability in an extensible manner that can be applied across domains on any transport protocol. They also have different features levels on how messages are exchanged and processed.

With all of these issues stacking up, I decided to distill down a list of requirements to see what it would take to implement a reliability prototype for a Web services environment.

Let's look at a concentrated list of requirements for my homegrown reliability layer:

  1. Standards-based and applied at the message protocol layer
  2. Acknowledged delivery
  3. Delivery is ordered
  4. Symmetric conversations
  5. Promotes asynchronous processing

Standards Based

The protocol must be composed of existing standards-based technologies. Specifically, the protocol should extend the SOAP 1.1 specification and, therefore, be applied at the message-encoding layer and not at the transport layer. This would enable the message to be transported on any available mechanism, ranging from HTTP to some proprietary socket implementation.

Acknowledged Delivery

In order to have an axiom to build from, the protocol must implement some type of acknowledged delivery mechanism. Meaning that a message sent using the protocol should receive an acknowledgement back from the processor about the status of the message once and only once.

Ordered Delivery

Ordered delivery introduces the concept of conversations—meaning that a client and server can exchange messages and acknowledgments that are part of a uniquely identifiable conversation. Messages are checked for ordering when they are received to ensure that the processor has received a serial set of messages.

Symmetric Conversations

Building on the conversation mechanism, it is also prudent to ensure that messages and acknowledgements are symmetric. Each message should be guaranteed to processed only once and only generate one acknowledgement.

Promotes Asynchronous Processing

This is the biggest point in my requirements list, so I saved it for last. HTTP is based on a synchronous request response model. This is nice for simple applications that don't have to do heavy or long-running processing. One of the dirty little secrets of a Web services implementation is that you don't necessarily know how a service is implemented on the back end from a processing perspective—meaning that it may take three seconds to three hours to process your request. This leads to an inefficient messaging architecture that is not scalable. What's needed is a processing model that promotes an asynchronous messaging architecture. Be forewarned that an asynchronous model is a more complicated implementation than tightly coupled request response implementations because it demands additional infrastructure.

Let me explain. In a synchronous messaging model using standard HTTP, the client sends a request to the server and then blocks until a response is received from the server. During this time, numerous mishaps can occur:

  • The connection may be broken by an external source, dropping either the request or response.
  • The server may timeout because it is offline or overloaded.
  • The server process may be dependant upon down stream services that have uncontrollable response times.

Synchronous Model

Figure 1. Synchronous model

Regardless of whether the problem is network or application related, some type of additional protocol driven infrastructure needs to be implemented to ensure reliability. For this discussion, I am going to focus on how the message is being processed at the transport layer. This is an application neutral critical section that can be exploited to implement a layer of reliability and decouple the ultimate processing of the message. More specifically, every message, no matter what application, needs to be read from the network layer and dispatched to the appropriate application resource. It is here where we can add an additional protocol for sending reliability acknowledgments and performing durable storage so that application resources can choose when and how to process the messages. In addition, this new protocol can help decouple or promote an asynchronous processing model. Let's explore how this is accomplished.

In the asynchronous model below, a request is sent to a SOAP server. The server reads the message stream from the networking layer and immediately returns an HTTP 202 response back to the client. This process is synchronous only for the time that it takes to remote the message to the server, thus minimizing connectivity issues. Once at the server, the message is passed through a reliability layer where the message is checked for expiration, duplication, and ordering. Next, the message is stored in a durable store (relational database) and an acknowledgement is sent to the client regarding its status. Finally, the message is dispatched to the intended application function.

Asynchronous Model

Figure 2. Asynchronous model

In an HTTP environment, you have the ability to control when a response is sent back to the client. By controlling when a response is sent to the client, you can minimize your risk that down-stream processing will not jeopardize the reliability of communication. This is accomplished in the SOAP world through one-way messages. This instructs the underlying SOAP processor to immediately send an HTTP 202 response to the client, informing them that the message was received and that it was successfully dispatched to the correct resource for processing. Later, the processor can send a response to the client about the status of the message. I'll discuss the benefits of this type of model in more detail later in the article.

Building a Reliability Layer

With the requirements above in mind, I'm going to show you how to use the .NET Framework to build a reliability protocol for a Web services implementation. I have modeled a small API around the requirements in order to provide a usable implementation.

Protocol: ericRP

The first problem I attacked was defining how I wanted to factor my reliability protocol (ericRP). Here are the highlights of the protocol:

  • The protocol is a prototype for educational purposes.

  • The protocol is implemented primarily at the SOAP processing layer by extending the messaging layer (SOAP).

    • SOAP headers were used to encode information required by the processing layer.
  • The protocol requires that implementations have some means of durable storage for message logging. For this implementation, Microsoft SQL Server 2000 was used.

    Note Regardless of how you implement a reliability layer for a SOAP environment, it will require additional infrastructure beyond a SOAP parser.

  • The protocol supports the concept of conversations, meaning that multiple messages can be serialized so that order of delivery can be guaranteed.

  • All implementation of the protocol is qualified by the ericRP namespace.

  • ericRP is based on a two party conversation scenario. Specifically, a service will converse with another service through XML Web service architecture (HTTP, SOAP, WSDL).

  • The client is responsible for all remediation of messages. (More about this later)

  • The server is solely responsible for sending acknowledgements based on certain criteria.

  • Expired messages received by the server are not logged.

  • Unordered messages received by the server are not logged.

  • Duplicate messages received by the server are not logged.

Processing API

For this prototype, I built six main classes and a small database. I will refer to the classes as the processing API. The Web service client and server will use these classes to monitor and remediate messages that are using the ericRP reliability protocol. All of the classes belong to the ericRP namespace:

  • Client.ConversationManager—is used by the client to create a conversation context for Web service message association and message monitoring.
  • Client.RPClientTrace—is used by Web service clients whose methods implement the ericRP reliability protocol for outbound messages.
  • Server.ConversationManager—is used by the Web service server to log and process inbound messages.
  • Server.RPServerTrace—is used by Web service servers whose methods implement the ericRP reliability protocol for inbound messages.
  • ReliabilityInfo—serves a dual purpose. It is used by the Client.ConversationManager to provide reliability info for logging. It is also used by the Web service client proxy to create the necessary SOAP header information for outbound messages.
  • Acknowledgment—is used by the Server.ConversationManager to send acknowledgements back to the client.

How does ericRP Work?

Before reviewing the code, I'm going to show how the protocol works from a consumer's perspective. In my example, I have a simple Web service proxy class that is used to send purchase order messages to a Web service. The client who is going to use the API would perform the following below.

The first step is creating an instance of the Client.ConversatioManager class and starting a new conversation. For example:

private void begin()
{
rpClient = new ericRP.Client.ConversationManager();

rpClient.MessageSent += new _
   ericRP.Client.ConversationManager.MessageSentEventHandler(process);

rpClient.ConversationStarted += new _
   ericRP.Client.ConversationManager.ConversationStartedHandler(constarted);
   
rpClient.BeginConversation();
}

The rpClient variable has class level scope and is used later. In addition, I setup some event handlers.

Next, we're going to use the purchase order proxy, coupled with the ReliabilityInfo class to send a reliable message. First, create an instance of the PurchaseOrderProxy as you normally would for a Web service client. Next, create an instance of the ReliabiltiyInfo class passing the ConversationManager into the constructor, then set your reliability properties. The main properties to focus on are MaxRetry, ExpireDate, and AckURL. MaxRetry and ExpireDate are used dampen the activity of the message so it doesn't infinitely attempt to deliver itself. AckURL is used by the Web service when sending receipt acknowledgements back to the client. Once you have set the properties, you set the ReliableHeader property of the proxy and call you desired method.

private void sendMessage()
{
ClientProxies.PurchaseOrderProxy po = new ClientProxies.PurchaseOrderProxy();
         
   ericRP.ReliabilityInfo rInfo = new ericRP.ReliabilityInfo(rpClient);
   rInfo.Status = ReliabilityInfo.MessageStatus.New;
   rInfo.SendDate = System.DateTime.Now;
   rInfo.ExpireDate = System.DateTime.Now.AddHours(4);
   rInfo.MaxRetry = 5;
   rInfo.AckURL = "https://localhost:8082/ericRPAck/POAck.asmx";
         
   po.ReliableHeader = rInfo;
   po.SubmitMessage("sure hope they get this purchase order!");
}

Here is a screen shot of a client test harness that I wrote to demonstrate the functionality. Notice that five messages have been sent. The third message expired before reaching the destination. According to the ericRP protocol, this message is thrown out and not processed by the server. The fourth message is deemed to be unordered because the server never received a valid message three. Any subsequent messages will be unordered until message three is resent. If you were to re-query the Client.ConversationManager, message five will also be unordered.

Figure 3. Client test harness

Headers Are Your Friend

Before we jump into the code we need to talk about a SOAP topic called headers. One of the least talked about areas of the SOAP 1.1 specification are SOAP headers. Headers provide a simple way to extend your messaging architecture. The SOAP 1.1 specification notes that headers are helpful in implementing processing rules that do not specifically relate to the message body, such as authentication and transaction management. SOAP headers are a perfect solution for encoding reliability information in a neutral manner for any type of message. The specification also outlines what standards or rules should be used to enforce and process these headers.

Let's take a look at how to implement a SOAP header that contains reliability information. The first step is to decide on the schema for the header. This is important because it's what the end consumer will see in your WSDL file for the Web service that supports the header. For this implementation, I efficiently mapped my processing API directly to the SOAP header.

Let me explain. In my processing API, I have a class called ReliabilityInfo. This class, when instantiated, becomes a dynamic object at runtime, meaning that you can set properties that will determine how an outbound message will be treated from a reliability perspective. This object can also be serialized into a SOAP header at run time. Below is a snapshot of the class and its members. I have removed the implementation and private members for clarity.

[XmlRootAttribute(ElementName="ReliableHeader", _
   Namespace="http://ericRP/ReliableHeader/2001/", IsNullable=false)]
   public class ReliabilityInfo : SoapHeader
   {      
      public string Destination{}
      public string ConversationId{}
      public int MessageId{}
      public MessageStatus Status{}
      public DateTime SendDate{}
      public DateTime ExpireDate{}
      public string AckURL{}
public enum MessageStatus{}

[XmlIgnore]
      public int MaxRetry{}
      [XmlIgnore]
      public string Text{}
   }

When serialized into an outbound SOAP message, the header looks like this:

<soap:Header>
<ReliableHeader xmlns="http://ericRP/ReliableHeader/2001/">
<ConversationId>b9e029e1-af0f-42cb-83b0-7888f9e3ffc4</ConversationId>
<MessageId>1</MessageId>
<Status>New</Status>
<SendDate>2001-11-06T14:59:02.1021226-08:00</SendDate>
<ExpireDate>2001-11-06T18:59:02.1021226-08:00</ExpireDate>
<AckURL>https://localhost:8082/ericRPAck/POAck.asmx</AckURL>
</ReliableHeader>
</soap:Header>

This is accomplished through two very important classes in the .NET Framework. The base XML serialization piece is handled using the System.Xml.Serialization namespace classes. I used the XmlRootAttribute attribute class to tell the serializer that I want the root of the document fragment to be called ReliableHeader, and associated it with a namespace. From here, any public member will also be serialized unless you the tell the serializer to ignore the member. The other important namespace that I used was System.Web.Services.Protocols. Specifically, I used the SoapHeader class which, when inherited from, will automatically inject the serialized XML into a SOAP message. I'll show you how to hook the header up to the message later in the article.

This is extremely powerful because not only do I get a strongly typed and compiled object to work with as the basis of my headers, but these classes can also have very specific implementations inside of their members.

Putting It All Together

Ok, so you've listened to me randomly pontificate about reliability and I reviewed my homegrown specification. Now we're going to review the code that makes it work.

Extending a Web Service Client Proxy

The first step is to re-tool an existing .NET Web service client proxy. Keep in mind that this protocol could be implemented inside of any SOAP processing engine. I chose the .NET framework because it's easy, standards based. and extensible.

Take an existing web service client proxy, for example PurchaseOrderProxy, and add the following code:

  1. The class must have a public member called ReliableHeader which is of type ericRP.ReliabilityInfo. This member will be set at runtime by the calling client. This member is later used to provide header information for outbound messages and provides state information about messages that the headers are applied to during the tracing process. For example:

    public class PurchaseOrderProxy : 
          System.Web.Services.Protocols.SoapHttpClientProtocol
       {
          public ReliabilityInfo ReliableHeader;
    
          //Additional code omitted for clarity
       }
    
  2. The method that is being invoked in the Web service client proxy must be annotated with the following attribute:

    [SoapHeader("ReliableHeader", Required=true)]
    

    This attribute will inject the appropriate header values at run time in the outbound SOAP message during the serialization process. Here, the method SubmitMessage is tagged with the SoapHeader attribute. Notice that ReliableHeader is the member implemented in step 1. Also, notice that this is required, meaning that if this is not set at runtime, an exception will be thrown. For example:

    [SoapHeader("ReliableHeader", Required=true)]
    public void SubmitMessage(object message) 
    {
    this.Invoke("SubmitMessage", new object[] {message});      
    }
    
    
  3. The method that is being invoked in the Web service client proxy must include the following attribute:

    [ericRP.Client.RPClientTrace.TraceExtension()]
    

    This attribute denotes that the method supports a custom SOAP extension. This SOAP extension will be invoked at run time before and after the message is serialized, but before it is sent onto the underlying transport mechanism. I'm basically doing some simple tracing and logging of the message before it leaves the client machine. I will review the implementation of this extension later. For example:

    [ericRP.Client.RPClientTrace.TraceExtension()]
    [SoapHeaderAttribute("ReliableHeader", Required=true)]
    public void SubmitMessage(object message) 
    {
    this.Invoke("SubmitMessage", new object[] {message});      
    }
    
  4. The class must implement from ericRP.Client.RPClientTrace.IClientTrace. This interface implementation provides the underlying tracing functionality to inspect to see if the caller supports the specific tracing protocol. You will see this code later in the tracing functionality. For example:

    public class PurchaseOrderProxy : 
       System.Web.Services.Protocols.SoapHttpClientProtocol, 
       ericRP.Client.RPClientTrace.IClientTrace
    {
    public ReliabilityInfo ReliableHeader;
       }
    
  5. Finally, the class must implement the IClientTrace.GetReliabilityInfo function, which is the only required function on the IClientTrace interface. This is a simple mechanism for the client to pass message state information to the tracing service at run time. For example:

    public class PurchaseOrderProxy : 
       System.Web.Services.Protocols.SoapHttpClientProtocol, 
       ericRP.Client.RPClientTrace.IClientTrace
    {
    public ReliabilityInfo ReliableHeader;
    
    ericRP.ReliabilityInfo _
       ericRP.Client.RPClientTrace.IClientTrace.GetReliabilityInfo()
    {      
    return ReliableHeader;
    }
       }
    

It may look like a lot of code, but it's quite simple. In fact, if I spent some more time I could have created a new class inheriting from SoapHttpClientProtocol and explicitly implemented this for you, but this is more fun on to the server side.

Extending a Web Service Server Stub

Next, we are going to extend an existing Web service server stub. Take an existing .NET web service class (ProcessPurchaseOrder) and add the following code:

  1. The class must have a public member called ReliableHeader, which is of type ericRP.ReliabilityInfo. This member is later used to provide deserialized header information for inbound messages and provides state information about messages that the headers are applied to during the tracing process. For example:

    public class ProcessPurchaseOrder : 
          System.Web.Services.WebService
       {
          public ReliabilityInfo ReliableHeader;
       }
    
  2. The method that is being invoked in the Web service stub must be annotated with the following attribute:

    [SoapHeader("ReliableHeader", Required=true)]
    

    This attribute will deserialize the appropriate header values at run time from the inbound SOAP message during the deserialization process. Here, the method SubmitMessage is tagged with the SoapHeader attribute. For example:

    [WebMethod]
    [SoapHeader("ReliableHeader", Required=true)]
    public void SubmitMessage(object message) 
    {
    //Code omitted for clarity   
    }
    
  3. The method that is being invoked in the Web service stub must be annotated with the following attribute:

    [ericRP.Server.RPServerTrace.TraceExtension()]
    

    This attribute denotes that the method supports a custom SOAP extension. I will review the implementation of this extension later.

    Note This extension is different than the client extension.

    For example:

    [SoapHeader("ReliableHeader", Required=true)]
    [ericRP.Server.RPServerTrace.TraceExtension()]
    public void SubmitMessage(object message) 
    {
    //Code omitted for clarity
    }
    
  4. The method that is being invoked in the Web service stub must be annotated with the following attribute:

    [SoapDocumentMethod(OneWay=true)]
    

    This attribute denotes that the function being invoked does not return a value. More specifically, this attribute forces the Web service to return an HTTP 202 response to the client once the message has been deserialized. This is an effective mechanism for decoupling the ultimate processing of the message. Otherwise, the client would have to wait synchronously for a response from the service. For example:

    [WebMethod]
    [SoapDocumentMethod(OneWay=true)]
    [SoapHeader("ReliableHeader", Required=true)]
    [ericRP.Server.RPServerTrace.TraceExtension()]
    public void SubmitMessage(object message) 
    {
    //Code omitted for clarity   
    }
    
  5. The class must implement from ericRP.Server.RPServerTrace.IServerTrace. This interface implementation provides the underlying tracing functionality the ability to inspect to see if the caller supports the specific tracing protocol. You will see this code later in the tracing functionality. For example:

    public class ProcessPurchaseOrder : 
    System.Web.Services.WebService, ericRP.Server.RPServerTrace.IServerTrace
       {
          public ReliabilityInfo ReliableHeader;
       }
    
  6. Finally, the class must implement the IServerTrace.GetReliabilityInfo function. This is the only required function on the IServerTrace interface. For example:

    public class ProcessPurchaseOrder : 
    System.Web.Services.WebService, ericRP.Server.RPServerTrace.IServerTrace
       {
          public ReliabilityInfo ReliableHeader;
    
    ericRP.ReliabilityInfo _
       ericRP.Server.RPServerTrace.IServerTrace.GetReliabilityInfo()
          {   
             return ReliableHeader;
          }
       }
    

Ok, so we have finished retrofitting our existing Web service client and server. Next, I'm going to review how the SoapExtension works.

Reviewing RPClientTrance and RPServerTrace

In order to implement my reliability processing, I decided to use a SoapExtension. A SoapExtension is an inheritable-base class that enables you to trace outbound serialization and inbound deserialization of SOAP messages. It is during this tracing process that I do my logging and message state checking. Remember from the steps above that my Web service client proxy method implements [ericRP.Client.RPClientTrace.TraceExtension()]. When the method is called, this attribute invokes the code below during the outbound serialization of the SOAP message.

The main function to point out is ProcessMessage. If you are sending an outbound message, ProcessMessage provides you with full state information about the message before and after serialization. At this point, I do some inspection of who is calling me and I set class level member to the Client property off the current message. Next, I check to see if I am in the AfterSerialize state. Once I have serialized, I can do my logging before the message is sent to the server. Using a custom function called ProcessOutgoingMessageText, I first do some stream swapping so I don't taint the underlying message stream. Next, I check to see if my client supports the IClientTrace interface. If they do, then I know that they support my reliability protocol. Through the power of interface inspection, I call GetReliabilityInfo in order to get the current back to the ConversationManager applicable to the message, then I set some properties and call LogMessage. LogMessage writes the current message information to a local database and throws an event notifying the client that the message has been logged.

public class RPClientTrace : SoapExtension 
{      
 public override void ProcessMessage(SoapMessage message) 
 {
  SoapClientMessage tmpMsg = (SoapClientMessage)message;
  _client = tmpMsg.Client;

  if (message.Stage == SoapMessageStage.AfterSerialize) 
  {
    ProcessOutgoingMessageText(message);
  }
 }

  public void ProcessOutgoingMessageText(SoapMessage message)
  {
   newStream.Position = 0;
   TextReader reader = new StreamReader(newStream);
   StringBuilder strMessage = new StringBuilder();
   strMessage.Append(reader.ReadToEnd());
   newStream.Position = 0;
   Copy(newStream, oldStream);

   if(_client is Client.RPClientTrace.IClientTrace)
   {
    try
    { 
Client.RPClientTrace.IClientTrace _ptrClient = _
   (Client.RPClientTrace.IClientTrace)_client;
   ReliabilityInfo rInfo = _ptrClient.GetReliabilityInfo();
      rInfo.Text = strMessage.ToString();
      rInfo.Destination = message.Url;
   rInfo.Manager.LogMessage(rInfo);
     }
     catch(Exception e)
     {
      throw e;
     }
    }
   }
}

The server side is a bit more involved because the server needs to do some additional work for inbound messages. The Web method that is being called implements [ericRP.Server.RPServerTrace.TraceExtension()]. The code below will be invoked before and after the deserialization of the inbound message:

public class RPServerTrace : SoapExtension 
{
public override void ProcessMessage(SoapMessage message) 
{
 try
 {
  switch (message.Stage) 
  {
   case SoapMessageStage.BeforeDeserialize:
        ReadIncomingMessageText(message);
    break;
   case SoapMessageStage.AfterDeserialize:
     SoapServerMessage tmpMsg = (SoapServerMessage)message;
     _server = tmpMsg.Server;
     if(_server is Server.RPServerTrace.IServerTrace)
     {
          Server.RPServerTrace.IServerTrace _ptrServer = _
   (Server.RPServerTrace.IServerTrace)_server;
     ReliabilityInfo rInfo = _ptrServer.GetReliabilityInfo();
   ericRP.ReliabilityInfo tempInfo = (ericRP.ReliabilityInfo)message.Headers[0];
   tempInfo.Text = _tempMessage;
   Server.ConversationManager manager = new Server.ConversationManager();
   manager.ProcessMessage(tempInfo);

     }
   break;
   }
}

After doing some stream swapping and interface inspection, I call ProcessInboundMessage on the server conversation manager. ProcessInboundMessage does the core message status checking. This is where the message dampeners are applied. First, I check to see if the message is expired, then if it is a duplicate, then if it is ordered. If it passes these criteria, the message is logged and an acknowledgement is sent back to the client. If it fails any of these checks, the message status is changed and an acknowledgement is sent back to the client.

public void ProcessInboundMessage(ericRP.ReliabilityInfo rInfo)
{         
    try
    {
   if(IsExpired(rInfo))
   {
   rInfo.Status = ericRP.ReliabilityInfo.MessageStatus.Expired;
   SendAck(rInfo);
   }
   else if(IsDulplicate(rInfo))
   {
   rInfo.Status = ericRP.ReliabilityInfo.MessageStatus.Duplicate;
   SendAck(rInfo);
   }
   else if(IsNotOrdered(rInfo))
   {
   rInfo.Status = ericRP.ReliabilityInfo.MessageStatus.NotOrdered;
   SendAck(rInfo);
   }
   else
{
   rInfo.Status = ericRP.ReliabilityInfo.MessageStatus.Success;
   LogMessage(rInfo);
   SendAck(rInfo);
   }
    }
    catch(Exception e)
    {
   throw e;
    }
  }
}

There is a great deal of implementation work left to make this example viable in a production environment. More importantly, the API should be based on a public standard, which will be available in the future. The main goal was to get us thinking about the problem space and to get our feet wet with the core Web service classes in the .NET Framework, and I think we've accomplished. If you are looking for a mature off-the-shelf implementation of reliable asynchronous message processing, I would suggest looking into Microsoft BizTalk™ Server 2000.

Finally, you could use similar tracing functionality to perform all types of operations like encryption, custom dispatching, and notifications. Keep in mind that this added functionality increases that amount of required processing infrastructure for your Web services.

Futures

The future is bright for XML Web services, both at the specification and implementation level. Microsoft is committed to working in a collaborative, standards-driven manner to ensure that XML Web services is the best architecture to write loosely-coupled decentralized applications. Reliable messaging and transaction specifications are the next to be released. Although there is still a great deal of implementation work to be done, you can start building your frameworks today using SOAP and WSDL. The closer you align yourself with the public specification process, the easier your migration will be in the future. The SOAP specification is a great example of this concept. Developers around the world were able to track the iterations of the SOAP spec and adjust their implementations accordingly with minimal difficulty.

I wish I had a magic ball to tell you exactly what the future looks like for XML Web service infrastructure. My hope is that all of the common services that we rely on today (security, transactions, storage, queuing, remoting, process orchestration, and so on) will map directly to the Web services architecture. In addition, implementations of GXA will permeate into the core development languages, application frameworks, business process frameworks, and enterprise infrastructure. We should truly be able to couple any service with any other service in a seamless manner. Hopefully, ericRP won't be the reliability layer behind the service <grin>.