Based on my previous post, I managed to get distributed transaction scenario working using WCF, MTOM and WS-AtomicTransactions.
This means that you have the option to transport arbitrary files, using transactional ACID semantics, from the client, over HTTP and MTOM.
The idea is to integrate a distributed transaction with TxF, or NTFS file system transaction. This only works on Windows Server 2008 (Longhorn Server) and Windows Vista.
Download: Sample code
If the client starts a transaction then all files within it should be stored on the server. If something fails or client does not commit, no harm is done. The beauty of this is that it's all seamlessly integrated into the current communication/OS stack.
This is shipping technology; you just have to dive a little deeper to use it.
Here's the scenario:
There are a couple of issues that need to be addressed before we move to the implementation:
- You should use the managed wrapper included here
There is support for TransactedFile and TransactedDirectory built it. Next version of VistaBridge samples will include an updated version of this wrapper.
- Limited distributed transactions support on a system drive
There is no way to get DTC a superior access coordinator role for TxF on the system drive (think c:\ system drive). This is a major downside in the current implementation of TxF, since I would prefer that system/boot files would be transaction-locked anyway. You have two options if you want to run the following sample:
- Define secondary resource manager for your directory
This allows system drive resource manager to still protect system files, but creates a secondary resource manager for the specified directory. Do this:
- fsutil resource create c:\txf
- fsutil resource start c:\txf
You can query your new secondary resource manager by fsutil resource info c:\txf.
- Use another partition
Any partition outside the system partition is ok. You cannot use network shares, but USB keys will work. Plug it in and change the paths as defined at the end of this post.
OK, here we go.
Here's the service contract:
[ServiceContract(SessionMode = SessionMode.Allowed)]
interface ITransportFiles
{
[OperationContract]
[TransactionFlow(TransactionFlowOption.Allowed)]
byte[] GetFile(string name);
[OperationContract]
[TransactionFlow(TransactionFlowOption.Allowed)]
void PutFile(byte[] data, string name);
}
We allow the sessionful binding (it's not required, though) and allow transactions to flow from the client side. Again, transactions are not mandatory, since client may opt-out of using them and just transport files without a transaction.
The provided transport mechanism uses MTOM, since the contract's parameter model is appropriate for it and because it's much more effective transferring binary data.
So here's the service config:
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="MTOMBinding"
transactionFlow="true"
messageEncoding="Mtom"
maxReceivedMessageSize="10485760">
<readerQuotas maxArrayLength="10485760"/>
</binding>
</wsHttpBinding>
</bindings>
<services>
<service name="WCFService.TransportService">
<host>
<baseAddresses>
<add baseAddress="http://localhost:555/transportservice">
</baseAddresses>
</host>
<endpoint address=""
binding="wsHttpBinding"
bindingConfiguration="MTOMBinding"
contract="WCFService.ITransportFiles"/>
</service>
</services>
</system.serviceModel>
Here, MTOMBinding is being used to specify MTOM wire encoding. Also, quotas and maxReceivedMessageSize attribute is being adjusted to 10 MB, since we are probably transferring larger binary files.
Service implementation is straight forward:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
class TransportService : ITransportFiles
{
[OperationBehavior(TransactionScopeRequired = true)]
public byte[] GetFile(string name)
{
Console.WriteLine("GetFile: {0}", name);
Console.WriteLine("Distributed Tx ID: {0}",
Transaction.Current.TransactionInformation.DistributedIdentifier);
return ReadFully(TransactedFile.Open(@"C:\TxF\Service\" + name,
FileMode.Open, FileAccess.Read, FileShare.Read), 0);
}
[OperationBehavior(TransactionScopeRequired = true)]
public void PutFile(byte[] data, string filename)
{
Console.WriteLine("PutFile: {0}", filename);
Console.WriteLine("Distributed Tx ID: {0}",
Transaction.Current.TransactionInformation.DistributedIdentifier);
using (BinaryWriter bw = new BinaryWriter(
TransactedFile.Open(@"C:\TxF\Service\" + filename,
FileMode.Create, FileAccess.Write, FileShare.Write)))
{
bw.Write(data, 0, data.Length);
// clean up
bw.Flush();
}
}
}
Client does four things:
- Sends three files (client - server) - no transaction
- Gets three files (server - client) - no transaction
- Sends three files (client - server) - distributed transaction, all or nothing
- Gets three files (server - client) - distributed transaction, all or nothing
Before you run:
- Decide on the secondary resource manager option (system drive, enable it using fsutil.exe) or use another partition (USB key)
- Change the paths to your scenario. The sample uses C:\TxF, C:\TxF\Service and C:\TxF\Client and a secondary resource manager. Create these directories before running the sample.
Download: Sample code
This sample is provided without any warranty. It's a sample, so don't use it in production environments.