Configuration of Transactions for use in WCF
After starting to look into WCF and transactions, I feel it has been unclear how you can flow transactions from the client to the server to enlist in. Prompted by the umpteenth of such discussions, I decided to experiment my way of this. Along the lines of the ATM sample posted earlier, I wrote a small client and a small server. Contrary to last posted sample, this time I am creating everything from code, so no configuration files (I am actually slightly ambivalent to configuration files: For development and test purposes, I like them, but I don't like the idea of end users fiddling with them).
Enough talk. Let's look at the contract definition for the server:
[ServiceContract]
public interface ITransactionServer
{
[OperationContract]
[TransactionFlow(TransactionFlowOption.Mandatory)]
void MandatoryOperation(InvocationInfo invocationInfo);
[OperationContract]
[TransactionFlow(TransactionFlowOption.Allowed)]
void AllowedOperation(InvocationInfo invocationInfo);
}
The operations themselves actually do nothing more than log the existence of an ambient transaction and the nature of that ambient transaction to the console. Only interesting property about the actual implementation of the server methods is that they have the OperationBehavior TransactionScopeRequired property set to true. The service has an endpoint that exposes the functionality using a TCP binding that can flow transactions. Oh, another point: The InvocationInfo is just a simple wrapper around some configuration details that I use on the server to echo some meaningful information to the console.
The client, on the other hand, runs through multiple configurations to test how transactions are sent across the wire. The table below illustrates the configurations. All configurations are based on TCP transport, but depending on the configuration may or may not allow the flow of transactions from the client to the server.
The procedure on the client is as follows: For each of the configurations listed below, the client will first create a binding that differs only from run to run by the presence of a TransactionFlowBindingElement (the “permanent members” of the binding element collection are: ReliableSessionBindingElement, SecurityBindingElement, TextMessageEncodingBindingElement and finally TcpTransportBindingElement). Further, based on the configuration, the client will then create a channel to the service. This will happen within a transaction scope based on the value of the field “Client Supplied Tx” in the table below. Finally, the client will try to invoke both of the operations offered by the (somewhat rich, or…) service. The results can be seen below.
Operation Invoked |
Binding Allows Flow of Tx |
Client Supplied Tx |
Result Client Side |
Results Server Side |
MandatoryOperation |
True |
False |
ProtocolException |
N/A |
AllowedOperation |
True |
False |
OK |
Local Tx |
MandatoryOperation |
True |
True |
OK |
Distributed Tx |
AllowedOperation |
True |
True |
OK |
Distributed Tx |
MandatoryOperation |
False |
False |
InvalidOperationException |
N/A |
AllowedOperation |
False |
False |
InvalidOperationException |
N/A |
MandatoryOperation |
False |
True |
InvalidOperationException |
N/A |
AllowedOperation |
False |
True |
InvalidOperationException |
N/A |
The exceptions encountered in the various configurations above are listed below with type and exception message.
ProtocolException: The service operation requires a transaction to be flowed.
InvalidOperationException: At least one operation on the 'ITransactionServer' contract is configured with the TransactionFlowAttribute attribute set to Mandatory but the channel's binding 'CustomBinding' is not configured with a TransactionFlowBindingElement. The TransactionFlowAttribute attribute set to Mandatory cannot be used without a TransactionFlowBindingElement.
Interesting to note, as soon as I changed the declaration of MandatoryOperation from being Mandatory to Allowed, all operations succeeded and the transactions seen on the server side were as follows:
Binding Allows Flow of Tx |
Client Supplied Tx |
Results Server Side |
True |
False |
Local Tx |
True |
True |
Distributed Tx |
False |
False |
Local Tx |
False |
True |
Local Tx |
I have taken out the column that indicates the operation invoked on the server, as the two operations now are identical. Further, I also ripped out the result on the client side, as all operation went through without a hitch.
Well, I think enough has been said (at least by me). It is now time to enjoy the wonderful support we have been given.
Comments
Anonymous
July 02, 2008
Do you have the code available for your tests? Im struggling to get one of the scenarios to work. I wanted to mark all my operations with Flow.Optional and then have the client either bundle them into a transaction scope or not but i find that this isnt resulting in a rollback for some reason. Thanks PeterKneale@gmail.comAnonymous
July 04, 2008
The comment has been removed