In this post we will develop a WCF service that is capable of participating in a client transaction.
The other posts in this series are:
WCF Transactions – Barebones Demo – Overview
WCF Transactions – Barebones Demo – Part 1 (Tools)
WCF Transactions – Barebones Demo – Part 3 (Client Code)
WCF Transactions – Barebones Demo – Part 4 (Analysis)
You can download the source code for the series from here.
The Service Contract
The contract, implementation and hosting code for the service are defined in the Server project in the source code. This is the contract:
namespace TransactionalService
{
[ServiceContract(SessionMode=SessionMode.NotAllowed)]
public interface ITextContainer
{
[OperationContract]
[TransactionFlow(TransactionFlowOption.Allowed)]
void Set(string text);
[OperationContract]
string Get();
}
}
The interface itself is self-explanatory, but the WCF attributes deserve some attention.
First, note that the ServiceContract attribute explicitly forbids sessions. Truth be told, this isnt absolutely necessary, because by default the service wont exhibit sessionful behavior when propagating transactions to WCF service methods. In fact, the reason I explicitly disallowed sessions in this contract is to remind us that sessions are not the recommended practice for transactional WCF services.
The reason for this is as follows. A service method that votes to complete a transaction is expected to leave the service instance in a consistent state and to support a rollback to the previous consistent state if the transaction aborts. For this to happen, any changes made in instance state during the call must be stored in a resource manager that enlists to the current transaction. You are not supposed to have session state stored in the service instance, because all such state should be stored in a resource manager and retrieved whenever needed.
TransactionAutoComplete = true is the default for the OperationalBehavior attribute, so unless an unhandled exception is thrown from a service method, a WCF method that joins a transaction votes to complete it when the method returns.
So, by default, WCF will release the service instance upon such a method. This will happen even if you explicitly demand session mode by setting SessionMode = SessionMode.Required on the ServiceContract attribute on the contract and InstanceContextMode to PerSession in the ServiceBehavior attribute on the service implementation. In fact, even if you set InstanceContextMode to Single in the ServiceBehavior attribute for singleton behavior, the single instance will also be released at the end of an auto completing transactional method of the Service. In such case the only difference between the single and per call instance mode is that in single mode there can only be one instance of the service object at any time.
You can change this behavior by setting the ReleaseServiceInstanceOnTransactionComplete property to true on the ServiceBehavior attribute. You might want to do this, if, apart from the transactional resources that your service accesses you are also doing non-transactional work (for instance logging) that you do not want to roll back even if a transaction aborts.
The second attribute to note is TransactionFlowAttribute on the method Set(). The setting TransactionFlow.Allowed indicates that if the client is calling this method within a transaction, that transaction should be propagated to the Service (but if not, the method may be called without a client transaction).
The Service Implementation
This is the implementation of the service:
namespace TransactionalService
{
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public class TextContainer : ITextContainer
{
VolatileResourceManager<string> rm;
public TextContainer()
{
rm = VolatileResourceManager<string>.Instance;
}
#region ITextContainer Members
[OperationBehavior(TransactionScopeRequired = true)]
public void Set(string text)
{
rm.Set(text);
}
public string Get()
{
return rm.Get() as string;
}
#endregion
}
}
Equipped with the VolatileResourceManager class, the implementation is again self-explanatory. Here also, attention is drawn to the WCF attributes.
First, the InstanceContextMode is set to PerCall, surrendering to the WCF philosophy described above that transaction methods should not be sessionful. For this reason, I implemented VolatileResourceManager as a singleton, so that the Set and Get methods would still access the same resource.
If you do need sessionful behavior such that each client holds its own transactional text entry, you have two options. One is to add a session id to the contract and have the client store the id and pass it to every method on the service. The resource manager might then provide a dictionary-like interface allowing access to a particular transactional object stored for that session id.
Another option is to store the id in the service instance and set ReleaseServiceInstanceOnTransactionComplete to true as described above. This prevents the instance from being released after a call to Set and the id will be persisted in the service instance for the next time on of its methods are called.
The second attribute is OperationalBehavior - with the TransactionScopeRequired property set to true.
This setting simply means that the Set method must execute in the scope of a transaction. If transaction propagation is enabled and a client is calling the service in the context of a client-side transaction, the method will join the client transaction (promoting the transaction from local to distributed if it was a local transaction to begin with). If there is no transaction to join, the service will create its own local transaction to run in, and this will terminate no later than when the method completes.
The Service Configuration File
The only points to note in the configuration file are that:
- In order to propagate transactions from the client to the service you need to choose a binding that supports transaction flow (only TCP, IPC and WS bindings are so “transaction aware”).
- You need to set the transactionFlow property of the binding to true.
Here is the complete configuration file:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<services>
<service behaviorConfiguration="TransactionalServiceBehavior"
name="TransactionalService.TextContainer">
<host>
<baseAddresses>
<add baseAddress="http://localhost/Services" />
</baseAddresses>
</host>
<endpoint address="TextContainer"
binding="wsHttpBinding"
bindingConfiguration="TransactionalBindingConfiguration"
contract="TransactionalService.ITextContainer" />
<endpoint address="mex"
binding="mexHttpBinding"
contract="IMetadataExchange" />
</service>
</services>
<bindings>
<wsHttpBinding>
<binding name="TransactionalBindingConfiguration"
transactionFlow="true">
</binding>
</wsHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior name="TransactionalServiceBehavior">
<serviceMetadata/>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
In the next post we will take a look at the client code.