Chapter 4. Sessions, Reliable Sessions, Queues, and Transactions

<feature><title>In This Chapter</title> </feature>

Introduction

This chapter covers an assortment of facilities that the Windows Communication Foundation provides for making systems more robust. Those features are Reliable Sessions, session management, queued delivery, and transaction flow. Besides introducing each feature, this chapter shows how to use them together to properly implement distributed, transactional systems.

Reliable Sessions

The Windows Communication Foundation’s Reliable Sessions facility is for providing assurances at the binding level that messages will be delivered exactly once, and in order. When communicating with the Transmission Control Protocol (TCP), a measure of reliability is ensured by the protocol itself. However, those assurances are strictly at the packet level and between just two points. The Windows Communication Foundation’s Reliable Sessions feature provides assurances against the possibility of messages being lost in transmission, duplicated, or received out of order, and it provides those assurances at the message level, across any number of intermediate nodes, and independent of the transport protocol. Furthermore, with Reliable Sessions enabled, the Windows Communication Foundation will attempt to re-establish dropped connections, and free the resources associated with a session if attempts at reconnection fail. It will also attempt to compensate for network congestion by adjusting the rate at which messages are sent.

To use the Windows Communication Foundation’s Reliable Sessions facility, one must select a binding that supports it. The predefined bindings that support the feature are the WSHttpBinding, the WSDualHttpBinding, the WSFederationBinding, the NetTcpBinding, and the NetNamedPipesBinding. In the case of the WSHttpBinding, the WSDualHttpBinding, and the WSFederationBinding, the option of using Reliable Sessions is turned off by default, whereas it is turned on by default on the other bindings. Toggling reliable sessions on or off is done by customizing the binding in the usual way:

<system.serviceModel>
        <services>
          <service
                <endpoint
                        binding="wsHttpBinding"
                        bindingConfiguration="MyReliableConfiguration"
                         [...]
                        />
          </service>
        </services>
        <bindings>
          <wsHttpBinding>
                <binding name="MyReliableConfiguration">
                  <reliableSession
                        enabled="true"
                        ordered="true" />
                </binding>
          </wsHttpBinding>
        </bindings>
</system.serviceModel>

The Reliable Sessions facility can also be added to custom bindings by including the binding element, System.ServiceModel.Channels.ReliableSessionBindingElement:

<system.serviceModel>
        <services>
                <service name="[...]">
                        <endpoint
                              [...]
                             binding="customBinding"
                             bindingConfiguration="MyReliableCustomBinding">
                        </endpoint>
                </service>
        </services>
        <bindings>
                <customBinding>
                        <binding name="MyReliableCustomBinding">
                                <reliableSession ordered="false" />
                                <httpTransport />
                        </binding>
                </customBinding>
        </bindings>
</system.serviceModel>

Windows Communication Foundation developers can indicate that their code relies on some assurances about the delivery of messages. In particular, they can specify that they are assuming that the messages will be delivered in the order in which they were sent:

[ServiceContract(SessionMode = SessionMode.Required)]
[DeliveryRequirements(RequireOrderedDelivery = true)]
public interface IMyServiceContract

Adding this specification to a service contract will cause the Windows Communication Foundation to confirm that, for any endpoint that includes the service contract, a binding that supports Reliable Sessions has been selected and appropriately configured to ensure ordered delivery.

Reliable Sessions in Action

To see the effect of using the Windows Communication Foundation’s Reliable Sessions facility, follow these steps:

  1. Copy the code associated with this chapter downloaded from http://www.cryptmaker.com/WindowsCommunicationFoundationUnleashed to the folder C:WCFHandsOn. The code is all in a folder called ReliableSessionsQueuesAndTransactions, which has two subfolders, one of which is called ReliableSessions.

  2. Open the solution C:WCFHandsOn ReliableSessionsQueuesAndTransactionsReliableSessionsReliableSessions.sln. The solution, which was the brainchild of Windows Communication Foundation program manager Shy Cohen, consists of four projects. The Sender project is for building a Windows application that sends a picture to the Windows application that is built by the Receiver project. In so doing, it breaks the picture being transmitted down into 100 parts and sends each part to the receiver as a separate message. The RouterController project is for controlling the likelihood of messages getting lost en route between the Sender and the Receiver.

  3. Choose Debug, Start Debugging from the Visual Studio 2005 menus. The Sender, Receiver, and Router Controller applications should all start.

  4. Click the Sender application’s Open button, select the C:WCFHandsOnReliableSessionsQueuesAndTransactionsReliableSessionsSeattle.jpg file in the Open dialog that appears, and click the Open button on that dialog. A picture of the Seattle skyline should be displayed in the Sender application’s window.

  5. Click the Sender application’s Send button. The picture displayed in the Sender application’s window will evidently be transferred via 100 separate messages to the Receiver application.

  6. Click the Clear button on the Receiver application.

  7. Click the Clear button on the Router Controller application.

  8. Move the Network Message Loss gauge on the Router Controller application all the way over to the right, so that it indicates that about 10% of the messages transferred are to be lost in transmission.

  9. Click the Sender application’s Send button. This time, only some of the picture displayed in the Sender application’s window will be reproduced in the Receiver application’s window.

  10. Choose Debug, Stop Debugging from the Visual Studio 2005 menus.

  11. Open the App.config file in the Receiver project and uncomment the line

    <!--<reliableSession ordered="false" />-->
    

    so that the custom binding configured in that file now includes the System.ServiceModel.Channels.ReliableSessionBindingElement that provides the Windows Communication Foundation’s Reliable Session facility:

    <bindings>
            <customBinding>
                    <binding name="ServiceBinding">
                            <reliableSession ordered="false" />
                            <tcpTransport />
                    </binding>
            </customBinding>
    </bindings>
    
  12. Open the App.config file of the Sender project and uncomment the line

    <!--<reliableSession ordered="false" />-->
    

    so that the custom binding element configured in that file now also includes System.ServiceModel.Channels.ReliableSessionBindingElement:

    <customBinding>
            <binding name="ClientBinding">
                    <reliableSession ordered="false" />
                    <MyCustomBindingElement/>
                    <tcpTransport/>
            </binding>
    </customBinding>
    
  13. Choose Debug, Start Debugging from the Visual Studio 2005 menus.

  14. Click the Sender application’s Open button, select the C:WCFHandsOnReliableSessionQueuesAndTransactionsReliableSessionsSeattle.jpg file in the Open dialog that appears, and click the Open button on that dialog. A picture of the Seattle skyline should be displayed in the Sender application’s window.

  15. Move the Network Message Loss gauge on the Router Controller application so that it indicates that about 4% of the messages transferred are to be lost in transmission.

  16. Click the Sender application’s Send button, and watch the Receiver application’s window carefully. It will be apparent that messages are being lost en route. Now, however, with Reliable Sessions enabled, the lost messages are being detected and resent so that all the parts of the picture sent by the Sender application are received at their destination.

  17. Choose Debug, Stop Debugging from the Visual Studio 2005 menus.

Session Management

The session management capabilities of the Windows Communication Foundation are different from the Reliable Sessions feature. Reliable Sessions are for providing assurances of messages being delivered. Session management allows a Windows Communication Foundation application to treat a message that it receives as part of a session—as part of a unified sequence of messages exchanged with another application.

Therefore, developers of Windows Communication Foundation applications can write the code for processing one message in such a way that it depends on information from an earlier message. If they find themselves having to do that, they can indicate that they are doing so by assigning the value System.ServiceModel.SessionMode.Required to the SessionMode parameter of the System.ServiceModel.ServiceContract attribute:

[ServiceContract(SessionMode=SessionMode.Required)]
public interface IMyServiceContract

Doing so will cause the Windows Communication Foundation to verify that the binding chosen for any endpoint in which that contract is included can support a session and can support incorporating information into the messages to identify the session. The predefined bindings that do so are WSHttpBinding, WSDualHttpBinding, WSFederationBinding, NetTcpBinding, NetNamedPipesBinding, and NetMsmqBinding.

Developers can store and retrieve data pertaining to a session in instance context sessions:

public class MyExtension: IExtension<InstanceContext>
{
        public string sessionIdentifier = null;
        public MyDataType MyData = null;
}
public void FirstRequest(MyDataType myData)
{
        MyExtension extension = new MyExtension();
        extension.sessionIdentifier = OperationContext.SessionId;
        extension.MyDataType = myData;
        OperationContext.InstanceContext.Extensions.Add(myData);
}

public MyDataType SubsequentRequest()
{
        Collection<MyExtension> extensions =
          OperationContext.InstanceContext.Extensions.FindAll<MyExtension>();
        foreach(MyExtension extension in extensions)
        {
                if(string.Compare(
             extension.sessionIdentifier,OperationContext.SessionId,true)==0)
                        return extension.MyData;
        }
        return null;
}

To better manage the resources associated with a session, developers can stipulate which operation may be invoked to initiate a session and which operations signal the end of a session:

[ServiceContract(SessionMode=SessionMode.Required)]
public interface IMyServiceContract
{
        [OperationContract(IsInitiating=true)]
        void StartSession();
        [OperationContract(IsTerminating=true)]
        void StopSession();
}

Queued Delivery

The assurances provided by Reliable Sessions extend only to the lifetime of the host application domain. If a message is lost en route to its destination but the application domain from which the message was sent terminates before the loss of the message is detected, when the application domain is restored, it will be unaware of the message having been lost. In fact, it will have lost the context of the session in which the message went missing.

The Windows Communication Foundation’s capability of sending messages via Microsoft Message Queuing (MSMQ) queues provides message delivery guarantees that are independent of the lifetime of the sending and receiving application domains. The queues store messages from a sending application on behalf of a receiving application. At some point after the message has been placed on the queue, the receiving application gets the message.

Using queues to convey messages yields a considerable number of benefits. First, if the receiving application is unavailable, perhaps because of an issue with that application or possibly due to a breakdown in connectivity, the sending application can still transmit messages and continue its work. The messages will be held on the queue until the receiving application is ready to accept them. Second, the speed and capacity of the network between the sending and the receiving application is mostly of no consequence, the sole limitation being that MSMQ can only accommodate messages up to 4MB in size. Third, the receiving application cannot be overwhelmed by unanticipated spikes in the frequency and volume of requests. It can consume them from the inbound queue at its own rate. Finally, MSMQ is a familiar technology for most administrators, and a Microsoft Management Console Snap-In is provided with Windows operating systems for them to use in managing MSMQ.

Queues can be used for communication between two Windows Communication Foundation applications, as well as between a Windows Communication Foundation application and a non–Windows Communication Foundation application that uses MSMQ. This chapter focuses on the case in which queues are used for exchanging messages between two Windows Communication Foundation applications. Exchanging messages between Windows Communication Foundation applications and other MSMQ applications is covered in Chapter 11, “Legacy Integration.”

To have a message go from one Windows Communication Foundation application to another via an MSMQ queue, select the predefined NetMsmqBinding:

<services>
        <service name="Fabrikam.TradeRecorder">
                <host>
                        <baseAddresses>
                          <add baseAddress="net.msmq://localhost/private/"/>
                        </baseAddresses>
                </host>
                <endpoint
                        address="EndpointAddress"
                        binding="netMsmqBinding"
                        [...]/>
      </service>
</services>

The absolute address of the endpoint in this example is net.msmq://localhost/private/EndpointAddress. For an endpoint that uses the predefined NetMsmqBinding, the net.msmq scheme is mandatory. The next segment of the address, which in this case is localhost, identifies the host on which the queue resides. The segment private is mandatory if the queue is a private one. The last segment, which is the address of the endpoint itself, must be the name of a transactional MSMQ queue on the specified host.

A service that is configured to receive messages via an MSMQ queue must be deployed on the same machine as the queue itself. That restriction is due to MSMQ allowing remote reads only from nontransactional queues, and the Windows Communication Foundation allowing services to receive messages only via the NetMsmqBinding from transactional queues.

Multiple instances of a service can be configured to receive messages from the same queue. In that case, the most available application will receive the next available message.

When an endpoint is configured with a predefined binding by which messages are received from a queue, the Windows Communication Foundation will confirm that all the operations of the endpoint’s contract are explicitly one-way:

public interface IMyServiceContract
{
    [OperationContract(IsOneWay=true)]
    void FirstOperation(string input);
    [OperationContract(IsOneWay = true)]
    void SecondOperation(string input);
}

The developers of a Windows Communication Foundation application can add an attribute to a service contract to indicate that the developer expects the application to receive messages via a queue:

[ServiceContract(SessionMode=SessionMode.Required)]
[DeliveryRequirements(QueuedDeliveryRequirements
    = QueuedDeliveryRequirementsMode.Required)]
public interface IMyServiceContract

If the developer does so, the Windows Communication Foundation will confirm that the binding of any endpoints that include those service contracts is a binding by which messages are delivered to the service via a queue.

Enhancements in Windows Vista

There are some enhancements to MSMQ in Windows Vista and later operating systems from which Windows Communication Foundation applications that communicate via MSMQ can benefit. Those enhancements concern dead-letter and poison queues.

Dead-Letter Queues

MSMQ messages have a configurable time to reach their destination queues and a configurable time to be received from the destination queues. When either of those times expires, the message is placed in a system queue called the dead-letter queue. In addition, if a message was sent to a transactional queue and the queue manager on the sending host does not receive positive confirmation that the message was read from the destination queue, the message will be moved to a transactional dead-letter queue.

On Windows Vista and later operating systems, it is possible to create a queue and designate it as the dead-letter queue for another specified queue. The Windows Communication Foundation provides support for that enhancement by allowing one to specify, for an application that is to send or receive messages from a queue, another queue that is to serve as the dead-letter queue:

<client>
       <endpoint
                 address="net.msmq://localhost/private$/EndpointAddress"
                 binding="netMsmqBinding"
                 bindingConfiguration="QueuedBinding"
                 [...]/>
</client>
<bindings>
  <netMsmqBinding>
    <binding
     name="QueuedBinding"
     deadLetterQueue="Custom"
     customDeadLetterQueue="net.msmq://localhost/private$/myDeadLetterQueue">
    </binding>
  </netMsmqBinding>
</bindings>

Poison Queues

A poison message is a message on a transactional queue that cannot be processed by the receiving application. When the message is read from the queue and the receiving application fails to process it, the application rolls back the transaction by which the message was read, and the message is thereby restored to the queue. The application will then proceed to read the message again, and the cycle of reading and rolling back the poison message could continue indefinitely.

Prior to Windows Vista, MSMQ left one to one’s own devices in detecting and coping with poison messages. The Windows Communication Foundation renders assistance by allowing one to specify values for the ReceiveRetryCount and ReceiveErrorHandling properties.

<services>
        <service name="Fabrikam.TradeRecorder">
                <host>
                        <baseAddresses>
                         <add baseAddress="net.msmq://localhost/private/"/>
                         </baseAddresses>
                </host>
                <endpoint
                     address="EndpointAddress"
                     binding="netMsmqBinding"
                     bindingConfiguration="QueuedBinding"
                     [...]/>
      </service>
</services>
<bindings>
  <netMsmqBinding>
    <binding
          name="QueuedBinding"
          receiveRetryCount="0"
      receiveErrorHandling="Fault">
    </binding>
  </netMsmqBinding>
</bindings>

The value of the ReceiveRetryCount property serves to define what constitutes a poison message—it is one that is rolled back onto the queue a number of times exceeding the value of ReceiveRetryCount property. The value of the ReceiveErrorHandling property is used to signify what is to be done with the poison message. The options are to move the receiving service into a faulted state so that it cannot receive any further messages or to ignore the poison message.

On Windows Vista and later operating systems, there is a richer set of options. Poison messages can be sent back to their source by assigning the value Move to the ReceiveErrorHandling property. In that case, they will end up on the dead-letter queue there. Otherwise, by assigning the value Reject to the ReceiveErrorHandling property, they can be moved to a poison subqueue of the receiving queue. For a queue named net.msmq://localhost/private/EndpointAddress, the address of the poison subqueue is net.msmq://localhost/private/EndpointAddress;Poison.

Being able to designate a custom dead-letter queue and being able to dispatch poison messages to a poison subqueue raises interesting design possibilities. In particular, one could have Windows Communication Foundation services configured to read messages from the dead-letter queue and the poison subqueue and programmed to take actions to compensate for the problem messages.

Transactions

The Windows Communication Foundation implements both the standard WS-AtomicTransaction (WS-AT) protocol and Microsoft’s proprietary OleTx protocol in certain predefined bindings. Those protocols are for conveying information about the state of transactions in messages. Windows Communication Foundation developers can indicate that the code for a given operation is written so as to execute within the scope of a transaction:

[ServiceContract]
public interface IMyServiceContract
{
    [OperationContract(IsOneWay = false)]
    [TransactionFlow(TransactionFlowOption.Required)]
    void MyMethod();
}

public class MyServiceType: IMyServiceConract
{
    [OperationBehavior(TransactionScopeRequired=true)]
    void MyMethod()
    {
      [...]
    }
}

Any operation that the developer indicates must execute within the scope of a transaction cannot also be marked as a one-way method because information about the state of the transaction at the end of the operation must be transmitted back to the caller.

The developer can also indicate that the Windows Communication Foundation should automatically vote on behalf of the operation to commit the transaction if no exception occurs:

public class MyServiceType
{
    [OperationBehavior(TransactionScopeRequired=true,TransactionAutoComplete=true)]
    void MyMethod()
    {
      [...]
    }
}

If the developer would rather vote to commit the transaction deliberately, it can be done via the Windows Communication Foundation’s static System.ServiceModel.OperationContext object:

public class MyServiceType
{
    [OperationBehavior(
      TransactionScopeRequired=true,
      TransactionAutoComplete=false)]
    void MyMethod()
    {
     //Work gets done here
     OperationContext.Current.SetTransactionComplete();
    }
}

If a developer specifies that the operations of a service must execute within the context of a transaction, the Windows Communication Foundation will confirm that the service has been configured with a binding that supports sending information about the state of transactions within messages. The predefined bindings that provide that support are WSHttpBinding, the WSDualHttpBinding, WSFederationBinding, NetTcpBinding, and NetNamedPipesBinding. The last two allow one to choose between using the WS-AT protocol and the OleTx protocol, whereas the others use the standard WS-AT protocol only.

<bindings>
  <netTcpBinding>
    <binding
                name="..."
                transactionFlow="true"
                transactionProtocol="OleTransactions"/>
  </netTcpBinding>
  <wsHttpBinding>
    <binding
                name="..."
                transactionFlow="true"/>
  </wsHttpBinding>
</bindings>

Developers of Windows Communication Foundation clients can use the syntax provided by the System.Transactions namespace to include service operations within the scope of a transaction.

ServiceClient client = new ServiceClient("MyServiceEndpointConfiguration");
using(TransactionScope scope =
        new TransactionScope(TransactionScopeOption.RequiresNew))
{
        client.DoSomething([...]);

        scope.Complete();
}
client.Close();

If those service operations support transactions, and the binding is configured to convey information about the state of the transaction, the client’s transaction will not commit unless the service operations vote to commit. Conversely, any actions performed by the service on transactional resources within the scope of the client’s transaction will be rolled back if the client does not vote to commit.

Designing a system in this way is generally unwise, though. The operations of the service and its resources will be tied up while a remote client decides what action to take. Even if the client can be trusted to decide expeditiously, the latency entailed by the very fact that the components are distributed will cause some delay. As a general principle, one should avoid extending transactions across boundaries of trust.

A smarter way of implementing distributed transactions with the Windows Communication Foundation is depicted in Figure 4.1. This approach combines the support for session management, queued delivery, and transactional messaging.

Distributed transactions.

Figure 4.1. Distributed transactions.

The service shown in the diagram is configured to receive messages from clients via a queue. A client initiates a transaction and then a session, and sends a series of messages to the service. The client completes the session, and if and only if the client commits its transaction will the messages that it sent go onto the queue. The separate messages that the client sent will appear on the queue as a single item. On the service’s side, the Windows Communication Foundation detects the item on the queue and initiates a transaction and then a session. It decomposes the single item on the queue into the separate messages that were sent by the client, and delivers each of those messages to the service. If all goes well, the service closes the session and commits the transaction. If something goes awry in processing any of the messages, the transaction can be aborted, in which case anything done in the processing of any of the messages on a transactional resource will be rolled back. The batch of messages is transferred to the poison message queue, where a service charged with compensating for the failure picks up the messages. That service could alert an administrator, or even notify the client application, that processing failed and that action to compensate should be taken.

By virtue of this design, operations on the client and the server are not held up by one another or by the latency of the connection between them. The client’s transaction and the server’s transaction commit independently. The client’s transaction ensures that the client is always in a consistent state, and the server’s transaction ensures that the server is always in a consistent state. If the client fails in the scope of its transaction, the server is unaffected because messages sent by the client as part of that transaction are never delivered to the server’s queue. If the server fails to process messages sent by the client, although the client will be in a consistent state and the server will be in a consistent state, the client’s state and the server’s state will not be mutually consistent. In that case, some actions to compensate for the inconsistency will have to be taken. However, one might be able to confirm that such cases occur only infrequently.

To see an implementation of this design and to witness the Windows Communication Foundation’s support for session management, queued delivery, and transacted messaging in action, follow these steps:

  1. Assuming that the code associated with this chapter has been downloaded and copied to the folder C:WCFHandsOn, as instructed previously in this chapter, proceed to open the solution, ComposingSessionsQueuesAndTransactions.sln in the ComposingSessionsQueuesAndTransactions subdirectory of the ReliableSessionQueuesAndTransactions folder. There are four console application projects in the solution. The Sender project is for building the client application, and the Receiver project is for building the Windows Communication Foundation service to which the client application sends messages via a queue. The Target project is for building another Windows Communication Foundation service that also receives messages via a queue. That application represents the transacted resource that the Receiver service performs operations on in response to the client’s messages. Imagine, then, that the Target project represents a database, although, unlike a database, it is easier for readers to install and the effects of the operations performed on it will be more obvious. The fourth project in the solution, the Compensator project, is for building the service that initiates compensatory action when the server fails in processing messages received from the client.

  2. Look at the service contract in the IMyServiceContract.cs file of the Receiver project. It defines a session consisting of a sequence of two operations.

    [ServiceContract(SessionMode=SessionMode.Required)]
    [DeliveryRequirements(QueuedDeliveryRequirements
        = QueuedDeliveryRequirementsMode.Required)]
    public interface IMyServiceContract
    {
        [OperationContract(IsOneWay=true,IsInitiating=true)]
        void FirstOperation(string input);
        [OperationContract(IsOneWay = true,IsTerminating=true)]
        void SecondOperation(string input);
    }
    
  3. Study the code of the Sender application in the Program.cs file of the Sender project. The code starts a transaction and, by virtue of how the contract is defined, implicitly initiates a session. The client invokes the two operations provided by the service in the proper sequence, and then, if no errors have occurred, commits the transaction.

    using (TransactionScope transaction =
            new TransactionScope(TransactionScopeOption.Required))
    {
        MyClient client = new MyClient("MyService");
    
        client.Open();
    
        client.FirstOperation("First set of data");
    
        client.SecondOperation("Second set of data");
    
        if (fail)
        {
            throw new Exception("Something bad.");
        }
    
        client.Close();
    
        transaction.Complete();
    }
    
  4. See how the service is configured to receive messages from the client by studying the configuration in the App.config file of the Receiver project, shown in Listing 4.1. The service is to receive messages via the predefined NetMsmqBinding. By virtue of the values assigned to the ReceiveRetryCount and ReceiveErrorHandling properties, messages that cannot be processed will be immediately removed from the queue.

    Example 4.1. Service Configuration

    <system.serviceModel>
           [...]
             <services>
             <service
                    name="Compensation.MyService">
                    <host>
                          <baseAddresses>
                            <add baseAddress="net.msmq://localhost/private/"/>
                          </baseAddresses>
                   </host>
               <endpoint
                   address="MyService"
                   binding="netMsmqBinding"
                   bindingConfiguration="MyQueuedBinding"
                   contract="Compensation.IMyServiceContract" />
           </service>
        </services>
        <bindings>
            <netMsmqBinding>
                <binding
                    name="MyQueuedBinding"
                    receiveRetryCount="0"
                    receiveErrorHandling="Drop">
                    <security mode="None" />
                </binding>
            </netMsmqBinding>
        </bindings>
    </system.serviceModel>
    
  5. Examine how the service is programmed to process messages received from the client. The relevant code is in the MyService.cs file of the Receiver project and is reproduced in Listing 4.2. In processing either of the two messages that may be received from the client, the service sends a message to the Target service. If an error occurs, the service sends the messages that it has received to the compensation service. The processing of messages from the client takes place in the context of a transaction.

    Example 4.2. Service Processing

    [OperationBehavior(
            TransactionScopeRequired = true,
            TransactionAutoComplete = false)]
    public void FirstOperation(string input)
    {
        try
        {
            MyClient targetClient = new MyClient("TargetService");
            targetClient.Open();
            targetClient.FirstOperation(input);
            targetClient.Close();
    
        }
        catch (Exception exception)
        {
            this.Compensate(extension);
    
            throw exception;
        }
    
    
    }
    
    [OperationBehavior(
            TransactionScopeRequired = true,
            TransactionAutoComplete = true)]
    public void SecondOperation(string input)
    {
        try
        {
             MyClient targetClient = new MyClient("TargetService");
             targetClient.Open();
             targetClient.SecondOperation(input);
             targetClient.Close();
    
    
        }
        catch (Exception exception)
        {
            this.Compensate(extension);
    
            throw exception;
        }
    }
    
    private void Compensate(MyExtension extension)
    {
        using (TransactionScope transaction =
                    new TransactionScope(TransactionScopeOption.RequiresNew))
        {
             [...]
             CompensationClient compensationClient =
                             new CompensationClient("CompensationService");
             compensationClient.Open();
    
             compensationClient.Compensate(Message.CreateMessage(
                             MessageVersion.Soap12WSAddressing10,
                             "*",
                             buffer == null ? string.Empty : buffer.ToString()));
             compensationClient.Close();
             transaction.Complete();
        }
    }
    
  6. Look again at the configuration of the service in the App.config file of the Receiver project to see how the service is configured to send messages to the target service. Those messages are to be sent using the predefined NetMsmqBinding. And because that binding has messages delivered via a transacted queue, in sending messages to the Target service, the Receiver service is performing operations on a transacted resource.

    <system.serviceModel>
            <client>
                    <endpoint
                             name="TargetService"
                             address="net.msmq://localhost/private/MyTarget"
                             binding="netMsmqBinding"
                             bindingConfiguration="MyQueuedBinding"
                             contract="Compensation.IMyServiceContract"/>
                     [...]
            </client>
            [...]
    </system.serviceModel>
    

To see the solution in action, do the following:

  1. Right-click on the Sender project, and choose Debug, Start New Instance from the context menu. A console for the Sender application should appear.

  2. Enter a keystroke into the Sender application’s console. After a few moments, the output in the console window should confirm that the application has transmitted messages to the service.

  3. Choose Debug, Stop Debugging from the Visual Studio 2005 menus.

  4. Open Administrative Tools from the Windows Control Panel, and choose Computer Management.

  5. Expand the Services and Applications node in the left pane.

  6. Expand the Message Queuing subnode.

  7. Select the Private Queues subnode.

  8. Look at the number of messages shown to be on the myservice queue through which the Sender application communicates with the Receiver service. There should be just one MSMQ message containing both of the Windows Communication Foundation messages sent by the Sender application as part of one session.

  9. Choose Debug, Start Debugging from the Visual Studio 2005 menus to start the Receiver, the Target, and the Compensator applications, as well as the Sender. In spite of not having been running when the Sender dispatched messages to the Receiver, the Receiver receives the Sender’s messages, which were held on the queue, and the output in the Target application console should indicate that the Receiver passed the messages from the Sender along.

  10. Choose Debug, Stop Debugging from the Visual Studio 2005 menus.

  11. Open the App.config file of the Sender application and modify the entry

    <add key="Succeed" value="true"/>
    

    to instead read

    <add key="Succeed" value="false"/>
    

    That change will cause the Sender application to fail in its processing.

  12. Choose Debug, Start Without Debugging from the Visual Studio 2005 menus.

  13. Enter a keystroke into the console of the Sender application. It should show that messages were sent to the Receiver application, but that an error occurred in the Sender application. Because the transaction in which the Sender dispatched the messages aborted, the Receiver application is unaffected, and the Sender and the Receiver are both in a consistent state and consistent with one another.

  14. Close the consoles of the four applications.

  15. Reverse the change made to the App.config file of the Sender application so that the entry that now reads

    <add key="Succeed" value="false"/>
    

    once again reads,

    <add key="Succeed" value="true"/>
    
  16. Now cause the Receiver application to fail by modifying the App.config file in the Receiver project so that the entry

    <add key="Succeed" value="true"/>
    

    instead reads

    <add key="Succeed" value="false"/>
    
  17. Choose Debug, Start Without Debugging from the Visual Studio 2005 menus.

  18. Enter a keystroke into the console of the Sender application. It should show that messages were sent to the Receiver application. The output in the console of the Receiver application should show that it received both messages but that an error occurred in processing the second. There should be no output in the console of the Target application, which is very important. That shows that although the Receiver application will have passed the first message from the Sender along to the target, it did so in the context of a transaction that was rolled back due to the failure in processing the second message. Therefore, the Receiver application and its resources are left in a consistent state. The output in the console of the compensator application should indicate that it has been notified of the problem messages, in which case it could initiate efforts to restore the Sender application to a state that is consistent with the Receiver’s state.

Summary

This chapter covered the Windows Communication Foundation’s Reliable Sessions feature, which provides assurances that messages will be delivered in order and exactly once. The chapter also dealt with the Windows Communication Foundation’s session management, queued delivery, and transacted messaging facilities. It showed how those features can be used together to yield efficient distributed transaction processing.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset