In this post we will prepare two tools that will help us trace WCF’s transaction support in our demo. Both tools can be found in the Common class library in the source code.
The other posts in this series are:
WCF Transactions – Barebones Demo – Overview (Tools)
WCF Transactions – Barebones Demo – Part 2 (Service Code)
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 first tool is an implementation IEnlistmentNotification - our own Resource Manager. As mentioned in the overview this class is far from complete. For a start, it doesnt support isolation between transactions. But in this demo we will assume that there is at most only one transaction trying to access the resource.
public class VolatileResourceManager<T> : IEnlistmentNotification
{
#region Private
private T oldValue = default(T);
private T newValue = default(T);
private T currValue = default(T);
#endregion
#region Singleton
private static VolatileResourceManager<T> instance =
new VolatileResourceManager<T>();
private VolatileResourceManager() {}
public static VolatileResourceManager<T> Instance
{
get { return instance; }
}
#endregion
#region Public
public T Get()
{
return this.currValue;
}
public void Set(T value)
{
Transaction transaction = Transaction.Current;
if (transaction == null)
{
Console.Write("Set without transaction");
this.currValue = value;
}
else
{
transaction.Log();
Console.Write("Set with Transaction with EnlistVolatile");
transaction.EnlistVolatile(this, EnlistmentOptions.None);
oldValue = this.currValue;
newValue = value;
}
}
#endregion
#region IEnlistmentNotification Members
public void Commit(Enlistment enlistment)
{
Console.Write("Commit");
oldValue = default(T);
}
public void InDoubt(Enlistment enlistment)
{
Console.Write("InDoubt");
}
public void Prepare(PreparingEnlistment preparingEnlistment)
{
Console.Write("Prepare");
this.currValue = newValue;
preparingEnlistment.Prepared();
// or
//preparingEnlistment.ForceRollback();
}
public void Rollback(Enlistment enlistment)
{
Console.Write("Rollback");
this.currValue = oldValue;
oldValue = default (T);
}
#endregion
}
The implementation of IEnlistmentNotification is fairly simple, using the two private variables oldValue and newValue to manage state until either Rollback or Commit determine the final value for the transaction.
The Set method is the interesting one.
Here, we check whether there is an ambient transaction in progress. If not, we just set the currValue and return. If there is, we update newValue and remember oldValue in preparation for the two phase commit protocol.
The call to transaction.Log() will print on the console whether the transaction is a local or distributed transaction and also print its identifier. This will be useful when we want to verify that a transaction initiated by a client has been propagated over a service boundary.
The Log() method is the second tool in the Common class library. It is an extension method for the Transaction class defined as follows:
public static class TransactionExtensions
{
public static void Log(this Transaction transaction)
{
TransactionInformation info = transaction.TransactionInformation;
if (info.DistributedIdentifier == Guid.Empty)
{
Console.WriteLine ("{0} :", Process.GetCurrentProcess().ProcessName);
Console.WriteLine(" Local Transaction " + info.LocalIdentifier);
}
else
{
Console.WriteLine("Distributed Transaction " + info.DistributedIdentifier);
}
}
}
You may have noticed that I defined the VolatileResourceManager as a singleton. The reason for that is related to the way WCF handles service instances that take part in transactions.
More on that in Part 2.