Deploying the Service
When all the necessary configuration steps are completed, you can add a reference to the new queued service from a client project. You then can deploy the service as follows:
using (TransactionScope ts = new TransactionScope())
WcfMsmqService.SendMessageClient client = new WcfMsmqService.SendMessageClient();
WcfMsmqService.ApplicationMessage message = new WcfMsmqService.ApplicationMessage();
message.Message = "Hello from a WCF client !";
message.ValidUntil = DateTime.Now.AddDays(7);
The calling code uses a transaction that is flowed to the service, but the service implementation does not actually participate in that transaction. From the client’s perspective, the transaction is completed once the message is successfully transmitted to the remote queue. MSMQ then starts another transaction when the message is delivered from the queue to the service. The service participates in this second transaction. Of course, this assumes that a transactional queue is being used.
The interaction between MSMQ and the target service, as it relates to transactions, essentially guarantees that no message will ever be lost.
MSMQ Failure Scenarios
You must be prepared to deal with two important failure scenarios in regards to MSMQ:
- The inability to deliver a message from MSMQ to the target WCF service
- The inability of the WCF service to process the message
In the first scenario, the message cannot be transferred from the queue to the service. In the second, the WCF service accepts the message but is unable to process it (i.e., it throws an exception). This is an important distinction because the first scenario describes a non-dispatchable or "dead" message (one that could not be delivered) and the second describes a "poison" message (one that could not be processed by the target service). You can handle dead and poison messages differently.
WCF offers automatic retry logic for non-dispatchable or poison messages. This retry logic can be configured within the binding as follows:
<binding name="netMsmq" maxRetryCycles="1" retryCycleDelay="00:00:10" receiveRetryCount="2">
<transport msmqAuthenticationMode="None" msmqProtectionLevel="None"/>
<message clientCredentialType="None" />
Notice the mexRetryCycles, retryCycleDelay, and receiveRetryCount attributes on the binding element. Upon the first message failure, MSMQ retires the delivery until the receiveRetryCount is surpassed, and then the message is moved into a retry sub queue. The system then waits at least the amount of time specified in the retryCycleDelay and attempts to deliver the message again, up to the number of times indicated in the receiveRetryCount. This entire pattern will repeat itself up to the number of times indicated in maxRetryCycles. If the maxRetryCycles is surpassed or the message is older than the value indicated in the timeToLive setting, the message is moved into the dead letter queue. By default, the system dead letter queue is used but you can also configure a custom dead letter queue as follows:
<binding name="netMsmq" deadLetterQueue="Custom" customDeadLetterQueue="net.msmq://localhost/private/sampledeadletter">
While non-dispatchable messages are moved into the dead letter queue, poison messages are handled a little differently. After the retry logic has been exhausted, the poison message is routed according to the setting indicated in the receiveErrorHandling attribute:
<binding name="netMsmq" receiveErrorHandling="Fault">
Table 1 outlines the possible values for the receiveErrorHandling attribute.
|Table 1. Values for the receiveErrorHandling Attribute|
||The message will simply be removed and ignored. The message is lost.
||A fault is sent to the queue manager. No further messages will be processed until the faulted message is removed from the queue.
||The message will be moved into a sub-queue named "poison." (Available in MSMQ 4 only)
||A rejection is sent to the queue manager and the message is placed into the dead letter queue. (Available in MSMQ 4 only)
Notice that "Move" and "Reject" are available only in MSQM 4, which ships with Windows Vista and Windows Server 2008. If receiveErrorHandling is set to "Move," WCF will automatically create a poison sub-queue and move the message into it.
Table 2 provides a quick overview of the attributes you can use to configure NetMsmqBinding. It is by no means a complete list; it merely reviews the key points covered so far.
|Table 2. Overview of Settings for NetMsmqBinding|
||Tells WCF/MSMQ how to deal with poison messages. The default value is "Fault."
||Indicates how many times a poison or non-dispatchable message should be retired after an initial failure. The default value is 5.
||Default is 2.
||Indicates the maximum amount of time a message can sit in a queue waiting to be delivered to a target service. This value supersedes all retry logic. The default value is 1 day.
||Informs WCF/MSMQ how dead messages should be handled. The default value is "System."
||Targets an application-specific queue to hold dead messages. The default is "null."
||Indicates that each message should be processed only one time. The default value is "true."
The Benefits of WCF and MSMQ Integration
Adding MSMQ via the NetMsmqBinding
to your WCF-based service does add complexity, but any challenges from that complexity are greatly outweighed by the benefits:
- Increased robustness
- Decreased coupling
- Message durability
- Greater scalability
All these can be had with little or no additional code. This is another example of how WCF makes leveraging the features of the platform simple and frees the developer to concentrate on the business.