Transactional Enhancements Samples

This topic contains the complete source code for sample applications that demonstrate transactional enhancements that increase application reliability on Windows ServerĀ® 2008.

Developing with Transactional Enhancements

The sample applications in this article demonstrate the concepts and programming techniques presented in the companion topics Understanding Transactional Enhancements and Developing with Transactional Enhancements.

Sample: Developing with KTM and TxF

Cc303707.collapse_all(en-us,MSDN.10).gifDescription

This section contains the complete source code for a managed console application that demonstrates using Kernel Transaction Manager (KTM) and Transactional NTFS (TxF) to create files as part of a transaction or outside of a transaction. A detailed discussion of the core components of this sample application can be found in Developing with Transactional Enhancements. The application consists of three classes. The first section of code contains the source code for the NativeMethods and TransactedFile classes. It also contains the definition of the COM interface IKernelTransaction. The third class is KTMSample. This class implements the user interface and application logic. KTMSample contains the application's entry point (main).

The application supports the following main-menu options:

  1. Begin a new transaction.

  2. Write a normal (non-transacted) file.

  3. Write a transacted file.

  4. Commit.

  5. Rollback.

  6. Display the directory where files are being written.

  7. Empty the directory where files are being written.

  8. Exit.

To see that the transacted file does not exist until the application user commits the transaction, compile and run the application. Perform the following actions, in order, where the numbers in parentheses refer to the main-menu options:

  1. Begin a new transaction (1).

  2. Write the transacted file (3).

  3. Display the directory contents (6).

  4. Commit the transaction (4).

  5. Display the directory contents (6) again.

The following classes handle communication with KTM and TxF.

namespace Microsoft.KtmIntegration
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Runtime.InteropServices;
    using System.Transactions;
    using Microsoft.Win32.SafeHandles;

    public static class NativeMethods
    {
        // Enumerations that capture Win32 values.
        [Flags]
        public enum FileShare
        {
            FILE_SHARE_NONE = 0x00,
            FILE_SHARE_READ = 0x01,
            FILE_SHARE_WRITE = 0x02,
            FILE_SHARE_DELETE = 0x04
        }

        public enum FileMode
        {
            CREATE_NEW = 1,
            CREATE_ALWAYS = 2,
            OPEN_EXISTING = 3,
            OPEN_ALWAYS = 4,
            TRUNCATE_EXISTING = 5
        }

        public enum FileAccess
        {
            GENERIC_READ = unchecked((int)0x80000000),
            GENERIC_WRITE = 0x40000000
        }

        // Win32 Error codes.
        internal const int ERROR_SUCCESS = 0;
        internal const int ERROR_FILE_NOT_FOUND = 2;
        internal const int ERROR_NO_MORE_FILES = 18;
        internal const int ERROR_RECOVERY_NOT_NEEDED = 6821;

        // Win32 APIs.
        [DllImport("KERNEL32.dll", EntryPoint = "CreateFileTransacted", 
            CharSet = CharSet.Unicode, SetLastError = true)]
        internal static extern SafeFileHandle CreateFileTransacted(
            [In] string lpFileName,
            [In] NativeMethods.FileAccess dwDesiredAccess,
            [In] NativeMethods.FileShare dwShareMode,
            [In] IntPtr lpSecurityAttributes,
            [In] NativeMethods.FileMode dwCreationDisposition,
            [In] int dwFlagsAndAttributes,
            [In] IntPtr hTemplateFile,
            [In] KtmTransactionHandle hTransaction,
            [In] IntPtr pusMiniVersion,
            [In] IntPtr pExtendedParameter);

        [DllImport("KERNEL32.dll", 
         CharSet = CharSet.Unicode, SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        internal static extern bool CloseHandle(
            [In] IntPtr handle);
     
        internal static void HandleCOMError(int error)
        {
            throw new System.ComponentModel.Win32Exception(error);
        }
    }

    [Guid("79427A2B-F895-40e0-BE79-B57DC82ED231"), 
        InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    public interface IKernelTransaction
    {
        int GetHandle(out IntPtr pHandle);
    }

    public class KtmTransactionHandle : 
              SafeHandleZeroOrMinusOneIsInvalid
    {
        internal KtmTransactionHandle(IntPtr handle)
            : base(true)
        {
            this.handle = handle;
        }

        public static KtmTransactionHandle CreateKtmTransactionHandle()
        {
            if (Transaction.Current == null)
            {
                throw new InvalidOperationException(
                    "Cannot create a KTM handle without Transaction.Current");
            }

            return KtmTransactionHandle.CreateKtmTransactionHandle(
                Transaction.Current);
        }

        public static KtmTransactionHandle 
            CreateKtmTransactionHandle(
                Transaction managedTransaction)
        {
            IDtcTransaction dtcTransaction = 
                TransactionInterop.GetDtcTransaction(managedTransaction);
            IKernelTransaction ktmInterface = (IKernelTransaction)dtcTransaction;

            IntPtr ktmTxHandle;
            int hr = ktmInterface.GetHandle(out ktmTxHandle);
            HandleError(hr);
            return new KtmTransactionHandle(ktmTxHandle);
        }

        protected override bool ReleaseHandle()
        {
            return NativeMethods.CloseHandle(this.handle);
        }

        private static void HandleError(int error)
        {
            if (error != NativeMethods.ERROR_SUCCESS)
            {
                throw new System.ComponentModel.Win32Exception(error);
            }
        }
    }

    public static class TransactedFile
    {
        public static FileStream Open(
            string path, 
            FileMode mode, 
            FileAccess access, 
            FileShare share)
        {
            // Get the KTM transaction.
            using (TransactionScope scope =
                new TransactionScope())
            using (KtmTransactionHandle ktmTx =
                KtmTransactionHandle.CreateKtmTransactionHandle())
            {
                // Translate the managed flags to unmanaged flags.
                NativeMethods.FileMode internalMode = 
                    TranslateFileMode(mode);
                NativeMethods.FileShare internalShare = 
                    TranslateFileShare(share);
                NativeMethods.FileAccess internalAccess = 
                    TranslateFileAccess(access);

                // Create the transacted file using P/Invoke.
                SafeFileHandle hFile = NativeMethods.CreateFileTransacted(
                    path,
                    internalAccess,
                    internalShare,
                    IntPtr.Zero,
                    internalMode,
                    0,
                    IntPtr.Zero,
                    ktmTx,
                    IntPtr.Zero,
                    IntPtr.Zero);

                // Throw an exception if an error occured.
                if (hFile.IsInvalid)
                {
                    NativeMethods.HandleCOMError(Marshal.GetLastWin32Error());
                }

                // Return a FileStream created using the 
                // transacted file's handle.
                FileStream stream = new FileStream(hFile, access);
                scope.Complete();

                return stream;
            }
        }

        // Translate managed FileMode member to unmanaged file mode flag.
        private static NativeMethods.FileMode 
            TranslateFileMode(FileMode mode)
        {
            if (mode != FileMode.Append)
            {
                return (NativeMethods.FileMode)(int)mode;
            }
            else
            {
                return (NativeMethods.FileMode)(int)FileMode.OpenOrCreate;
            }
        }

        // Translate managed FileAcess member to unmanaged file access flag.
        private static NativeMethods.FileAccess 
            TranslateFileAccess(FileAccess access)
        {
            return access == FileAccess.Read ? 
                NativeMethods.FileAccess.GENERIC_READ : 
                NativeMethods.FileAccess.GENERIC_WRITE;
        }

        // FileShare members map directly to their unmanaged counterparts.
        private static NativeMethods.FileShare 
            TranslateFileShare(FileShare share)
        {
            return (NativeMethods.FileShare)(int)share;
        }
    }
}

The following class implements the user interface for the application and executes the actions selected by the user.

using System;
using System.Transactions;
using System.IO;

namespace Microsoft.KtmIntegration
{
    class KTMSample
    {
        private static string status;
        private static TransactionScope myTransactionScope;

        //Store the location of the files and the work directory.
        private static FileInfo myTransactedFile = new FileInfo(
            Path.Combine(Directory.GetCurrentDirectory(),
            @"FileStore\testTransactedFile.txt"));
        private static FileInfo myNonTransactedFile = new FileInfo(
            Path.Combine(Directory.GetCurrentDirectory(), 
            @"FileStore\testNonTransactedFile.txt"));
        private static DirectoryInfo workspace = new DirectoryInfo(
            Path.Combine(Directory.GetCurrentDirectory(), @"FileStore"));

        // Loop executing user's menu selection until exit.
        static void Main(string[] args)
        {
            int choice;

            // Create a directory for the files.
            PrepareWorkspace();
            do
            {
                choice = DisplayMenu();
                switch (choice)
                {
                    case 1: BeginTransaction();
                        break;
                    case 2: WriteNonTransactedFile();
                        break;
                    case 3: WriteTransactedFile();
                        break;
                    case 4: CommitTransaction();
                        break;
                    case 5: RollbackTransaction();
                        break;
                    case 6: DisplayDirectory();
                        break;
                    case 7: EmptyDirectory();
                        break;
                    case 8: break;
                }
            }
            while (choice != 8);

            // Remove any files and the work directory.
            CleanupWorkspace();
        }

        // Set the application back to its initial state.
        private static void EmptyDirectory()
        {
            if (myTransactionScope != null)
            {
                RollbackTransaction();
            }
            CleanupWorkspace();
            PrepareWorkspace();
            status = "Cleanup complete.";
        }

        private static void CleanupWorkspace()
        {
            workspace.Delete(true);
        }

        private static void PrepareWorkspace()
        {
            workspace.Create();
        }

        private static void DisplayDirectory()
        {
            FileInfo[] f = workspace.GetFiles();
            Console.WriteLine();
            if (f.Length == 0)
            {
                Console.WriteLine("No files found.");
                return;
            }
            Console.WriteLine("The workspace contains the following files:");
            foreach (FileInfo fi in f)
            {
                Console.WriteLine(fi.Name);
            }
            Console.WriteLine();
            status = String.Empty;
        }

        private static void RollbackTransaction()
        {
            // If we are in a transaction, 
            // just call Dispose on the scope to cause a rollback.
            if (Transaction.Current != null)
            {
                myTransactionScope.Dispose();
                myTransactionScope = null;
                status = "Rolled back transaction.";
            }
            else
            {
                status = "There is no transaction to rollback!";
            }
        }

        private static void CommitTransaction()
        {
            if (myTransactionScope != null)
            {
                myTransactionScope.Complete();
                myTransactionScope.Dispose();
                myTransactionScope = null;
                status = "Committed transaction.";
            }
            else
            {
                status = "There is no pending transaction to commit!";
            }
        }

        private static void WriteTransactedFile()
        {
            if (Transaction.Current == null)
            {
                status = "Write failed - There is no transaction scope!";
                return;
            }

            using (StreamWriter myFile =
                new StreamWriter(TransactedFile.Open(
                    myTransactedFile.FullName,
                    FileMode.Create,
                    FileAccess.Write,
                    FileShare.None)))
            {

                myFile.Write("I am a transacted file");
            }
            status = "Created transacted file.";
        }

        private static void WriteNonTransactedFile()
        {
            using (StreamWriter myFile = 
                new StreamWriter(File.Open(myNonTransactedFile.FullName, 
                    FileMode.Create)))
            {
                myFile.Write("I am NOT a transacted file");
            }

            status = "Created non-transacted file.";
        }

        private static void BeginTransaction()
        {
            if (myTransactionScope == null && Transaction.Current == null)
            {
                // For demo purposes only! Do not allow transactions to 
                // abort due to timeout issue.
                myTransactionScope = new 
                    TransactionScope(TransactionScopeOption.Required, 
                    TimeSpan.Zero);
            }
            status = String.Empty;
        }

        private static int DisplayMenu()
        {
            string separator = "__________________________";
            string answer;
            int choice = 0;
            bool valid = false;
            Console.WriteLine(separator);
            Console.WriteLine("TxF Demo - Main Menu");
            Console.WriteLine(separator);
            Console.WriteLine("1 - Begin transaction");
            Console.WriteLine("2 - Write non-transacted file");
            Console.WriteLine("3 - Write transacted file");
            Console.WriteLine("4 - Commit");
            Console.WriteLine("5 - Rollback");
            Console.WriteLine("6 - Display Directory");
            Console.WriteLine("7 - EmptyDirectory (cancels pending transaction!)");
            Console.WriteLine("8 - Exit");
            Console.WriteLine("Status: {0}", status);
            Console.WriteLine("{0}Transaction in progress...",
                Transaction.Current == null? "No ": "");
            Console.WriteLine();
            Console.Write("Select an option: ");
            do
            {
                // Get the selected choice and convert to an integer.
                answer = Console.ReadLine();
                valid = Int32.TryParse(answer,out choice);
                // Make sure the end-user chose a valid option.
                if (valid && choice < 1 || choice > 8)
                {
                    valid = false;
                }
            } while (!valid);
            return choice;
        }
    }
}

Sample: Developing with TxF and WCF

Cc303707.collapse_all(en-us,MSDN.10).gifDescription

In this sample application, a client communicates with a Windows Communication Foundation (WCF) service to create reports that reside on the server's file system. The client and server share a transaction. Because the client can choose to commit or rollback the transaction, the client controls the file operations that take place in the server's file system. A detailed walkthrough for this application can be found in Developing with Transactional Enhancements.

The client application supports the following main-menu options:

  1. Begin a new transaction.

  2. Create a report on the server.

  3. Commit the transaction.

  4. Rollback the transaction.

  5. Exit.

To see that the transacted file containing the report does not exist until the client commits the transaction, compile and run the application. Locate the directory where the server's executable file is located. The reports will be written to the Reports subdirectory if the transaction is committed. Perform the following actions, in order, where the numbers in parentheses refer to the main-menu options:

  1. Client: begin a new transaction (1).

  2. Client: create the report (2).

  3. Server: view the Reports subdirectory on the server.

  4. Client: commit the transaction (3).

  5. Server: view the Reports subdirectory on the server again.

Cc303707.collapse_all(en-us,MSDN.10).gifServer

The following code contains the helper functions used by the server. This code should be compiled into a library (DLL) that is referenced by the server.

namespace Microsoft.KtmIntegration
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Runtime.InteropServices;
    using System.Transactions;
    using Microsoft.Win32.SafeHandles;

    public static class NativeMethods
    {
        // Enumerations that capture Win32 values.
        [Flags]
        public enum FileShare
        {
            FILE_SHARE_NONE = 0x00,
            FILE_SHARE_READ = 0x01,
            FILE_SHARE_WRITE = 0x02,
            FILE_SHARE_DELETE = 0x04
        }
        public enum FileMode
        {
            CREATE_NEW = 1,
            CREATE_ALWAYS = 2,
            OPEN_EXISTING = 3,
            OPEN_ALWAYS = 4,
            TRUNCATE_EXISTING = 5
        }

        public enum FileAccess
        {
            GENERIC_READ = unchecked((int)0x80000000),
            GENERIC_WRITE = 0x40000000
        }

        // Win32 Error codes.
        internal const int ERROR_SUCCESS = 0;
        internal const int ERROR_FILE_NOT_FOUND = 2;
        internal const int ERROR_NO_MORE_FILES = 18;
        internal const int ERROR_RECOVERY_NOT_NEEDED = 6821;

        // Win32 APIs.
        [DllImport("KERNEL32.dll", EntryPoint = "CreateFileTransacted", 
            CharSet = CharSet.Unicode, SetLastError = true)]
        internal static extern SafeFileHandle CreateFileTransacted(
            [In] string lpFileName,
            [In] NativeMethods.FileAccess dwDesiredAccess,
            [In] NativeMethods.FileShare dwShareMode,
            [In] IntPtr lpSecurityAttributes,
            [In] NativeMethods.FileMode dwCreationDisposition,
            [In] int dwFlagsAndAttributes,
            [In] IntPtr hTemplateFile,
            [In] KtmTransactionHandle hTransaction,
            [In] IntPtr pusMiniVersion,
            [In] IntPtr pExtendedParameter);

        [DllImport("KERNEL32.dll", 
               CharSet = CharSet.Unicode, SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        internal static extern bool CloseHandle(
            [In] IntPtr handle);
     
        internal static void HandleCOMError(int error)
        {
            throw new System.ComponentModel.Win32Exception(error);
        }
    }

    [Guid("79427A2B-F895-40e0-BE79-B57DC82ED231"), 
        InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    public interface IKernelTransaction
    {
        int GetHandle(out IntPtr pHandle);
    }

    public class KtmTransactionHandle : SafeHandleZeroOrMinusOneIsInvalid
    {
        internal KtmTransactionHandle(IntPtr handle)
            : base(true)
        {
            this.handle = handle;
        }

        public static KtmTransactionHandle CreateKtmTransactionHandle()
        {
            if (Transaction.Current == null)
            {
                throw new InvalidOperationException(
                    "Cannot create a KTM handle without Transaction.Current");
            }

            return KtmTransactionHandle.CreateKtmTransactionHandle(
                Transaction.Current);
        }

        public static KtmTransactionHandle 
            CreateKtmTransactionHandle(
                Transaction managedTransaction)
        {
            IDtcTransaction dtcTransaction = 
             TransactionInterop.GetDtcTransaction(managedTransaction);
            IKernelTransaction ktmInterface = 
             (IKernelTransaction)dtcTransaction;

            IntPtr ktmTxHandle;
            int hr = ktmInterface.GetHandle(out ktmTxHandle);
            HandleError(hr);
            return new KtmTransactionHandle(ktmTxHandle);
        }

        protected override bool ReleaseHandle()
        {
            return NativeMethods.CloseHandle(this.handle);
        }

        private static void HandleError(int error)
        {
            if (error != NativeMethods.ERROR_SUCCESS)
            {
                throw new System.ComponentModel.Win32Exception(error);
            }
        }
    }

    public static class TransactedFile
    {
        public static FileStream Open(
            string path, 
            FileMode mode, 
            FileAccess access, 
            FileShare share)
        {
            // Get the KTM transaction.
            using (TransactionScope scope =
                new TransactionScope())
            using (KtmTransactionHandle ktmTx =
                KtmTransactionHandle.CreateKtmTransactionHandle())
            {
                // Translate the managed flags to unmanaged flags.
                NativeMethods.FileMode internalMode = 
                    TranslateFileMode(mode);
                NativeMethods.FileShare internalShare = 
                    TranslateFileShare(share);
                NativeMethods.FileAccess internalAccess = 
                    TranslateFileAccess(access);

                // Create the transacted file using P/Invoke.
                SafeFileHandle hFile = 
                  NativeMethods.CreateFileTransacted(
                    path,
                    internalAccess,
                    internalShare,
                    IntPtr.Zero,
                    internalMode,
                    0,
                    IntPtr.Zero,
                    ktmTx,
                    IntPtr.Zero,
                    IntPtr.Zero);

                // Throw an exception if an error occured.
                if (hFile.IsInvalid)
                {
                   NativeMethods.HandleCOMError(
                     Marshal.GetLastWin32Error());
                }

                // Return a FileStream created using the 
                // transacted file's handle.
                FileStream stream = new FileStream(hFile, access);
                scope.Complete();
                return stream;
            }
        }

        // Translate managed FileMode member to 
        // unmanaged file mode flag.
        private static NativeMethods.FileMode 
            TranslateFileMode(FileMode mode)
        {
            if (mode != FileMode.Append)
            {
                return (NativeMethods.FileMode)(int)mode;
            }
            else
            {
                return (NativeMethods.FileMode)
                     (int)FileMode.OpenOrCreate;
            }
        }

        // Translate managed FileAcess member to 
        // unmanaged file access flag.
        private static NativeMethods.FileAccess 
            TranslateFileAccess(FileAccess access)
        {
            return access == FileAccess.Read ? 
                NativeMethods.FileAccess.GENERIC_READ : 
                NativeMethods.FileAccess.GENERIC_WRITE;
        }

        // FileShare members map directly to 
        // their unmanaged counterparts.
        private static NativeMethods.FileShare 
            TranslateFileShare(FileShare share)
        {
            return (NativeMethods.FileShare)(int)share;
        }
    }
}

The following code contains the service contract for the client and server.

using System;
using System.Collections.Generic;
using System.ServiceModel;
using System.Text;

namespace TxFAndWcf.Server
{
    [ServiceContract(
        Namespace = "http://Microsoft.TransactionalNtfs.Samples",
        SessionMode = SessionMode.Required)]
    interface IReportCreator
    {
        [OperationContract]
        [TransactionFlow(TransactionFlowOption.Mandatory)]
        void CreateReport(string reportName);
    }
}

The following code implements the interface for the server side of the service contract. This class writes the transacted file on the server.

using System;
using System.Collections.Generic;
using System.IO;
using System.ServiceModel;
using System.Text;
using System.Transactions;
using Microsoft.KtmIntegration;

namespace TxFAndWcf.Server
{
    class ReportCreatorService : IReportCreator
    {
        // Store the location where reports get written.
        private DirectoryInfo myFileStore = 
            new DirectoryInfo(
                Path.Combine(
                Directory.GetCurrentDirectory(), "Reports"));
        
        // Implement the operation contract.
        [OperationBehavior(TransactionScopeRequired = true, 
            TransactionAutoComplete = true)]
        public void CreateReport(string reportName)
        {
            Console.WriteLine("Writing Report");

            // Create the report directory if necessary.
            if (!myFileStore.Exists)
                myFileStore.Create();

            // Get the file information for the report.
            FileInfo report = new FileInfo(
                Path.Combine(myFileStore.FullName, reportName));

            // Write the report as a transacted file.
            // The report won't be stored on disk until the current
            // transaction is committed.
            using (StreamWriter writer = new 
              StreamWriter(TransactedFile.Open(
                report.FullName,
                FileMode.Create,
                FileAccess.Write,
                FileShare.Write)))
            {
                writer.WriteLine("This is my report");
            }

            Console.WriteLine("Report written for client.");
        }
    }
}

The following code contains the entry point for the server console application. This code starts the service and waits for clients.

using System;
using System.Collections.Generic;
using System.ServiceModel;
using System.Text;

namespace TxFAndWcf.Server
{
    class Program
    {
        static void Main(string[] args)
        {
            // Create the service and start listening for clients.
            using (ServiceHost host = new 
               ServiceHost(typeof(ReportCreatorService)))
            {
                host.Open();
                Console.WriteLine("Service Running");
                Console.WriteLine();
                Console.WriteLine("Press any key to exit ...");
                Console.ReadLine();
            }
        }
    }
}

The configuration file for the server is shown here.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>

  <system.serviceModel>

    <services>
      <service name="TxFAndWcf.Server.ReportCreatorService">

        <endpoint address="net.tcp://localhost:8000/TxFSamples/ReportCreator"
                  binding="netTcpBinding"
                  bindingConfiguration="ReportCreatorConfiguration"
                  contract="TxFAndWcf.Server.IReportCreator" />
      </service>

    </services>

    <bindings>
      <netTcpBinding>
        <binding name="ReportCreatorConfiguration"
                transactionFlow="true"
                transactionProtocol="OleTransactions" />
      </netTcpBinding>
    </bindings>
  </system.serviceModel>

</configuration>

Cc303707.collapse_all(en-us,MSDN.10).gifClient

The following code was generated by Svcutil.exe for the client-side implementation of the service contract.

//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by a tool.
//     Runtime Version:2.0.50727.312
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

namespace TxFAndWcf.Client
{
    [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]
    [System.ServiceModel.ServiceContractAttribute(
        Namespace = "http://Microsoft.TransactionalNtfs.Samples", 
        ConfigurationName = "TxFAndWcf.Client.IReportCreator", 
        SessionMode = System.ServiceModel.SessionMode.Required)]
    public interface IReportCreator
    {

        [System.ServiceModel.OperationContractAttribute(
            Action = "http://Microsoft.TransactionalNtfs.Samples/IReportCreator/CreateReport", 
            ReplyAction = "http://Microsoft.TransactionalNtfs.Samples/IReportCreator/CreateReportResponse")]
        [System.ServiceModel.TransactionFlowAttribute(
            System.ServiceModel.TransactionFlowOption.Mandatory)]
        void CreateReport(string reportName);
    }

    [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]
    public interface IReportCreatorChannel : 
        IReportCreator, System.ServiceModel.IClientChannel
    {
    }

    [System.Diagnostics.DebuggerStepThroughAttribute()]
    [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]
    public partial class ReportCreatorClient : 
        System.ServiceModel.ClientBase<IReportCreator>, IReportCreator
    {

        public ReportCreatorClient()
        {
        }

        public ReportCreatorClient(string endpointConfigurationName)
            :
                base(endpointConfigurationName)
        {
        }

        public ReportCreatorClient(string endpointConfigurationName, string remoteAddress)
            :
                base(endpointConfigurationName, remoteAddress)
        {
        }

        public ReportCreatorClient(string endpointConfigurationName, System.ServiceModel.EndpointAddress remoteAddress)
            :
                base(endpointConfigurationName, remoteAddress)
        {
        }

        public ReportCreatorClient(
            System.ServiceModel.Channels.Binding binding, 
            System.ServiceModel.EndpointAddress remoteAddress)
            :
                base(binding, remoteAddress)
        {
        }

        public void CreateReport(string reportName)
        {
            base.Channel.CreateReport(reportName);
        }
    }
}

The following code implements the client console application.

using System;
using System.Transactions;

namespace TxFAndWcf.Client
{
    // Client that creates reports on the server.

    static class Program
    {
        private static TransactionScope myTransactionScope;
        private static string status;

        // Loop performing the end-user's menu choices until exit.
        static void Main()
        {
            int choice;
            do
            {
                choice = GetUserChoice();
                switch (choice)
                {
                    case 1: BeginTransaction();
                        break;
                    case 2: CreateReport();
                        break;
                    case 3: CommitTransaction();
                        break;
                    case 4: RollbackTransaction();
                        break;
                    case 5:
                        break;
                }
            }
            while (choice != 5);
        }

        // Display menu and get end-user's menu choice.
        private static int GetUserChoice()
        {
            string separator = "---------------------------------------";

            Console.WriteLine(separator);
            Console.WriteLine("TxF WCF Client- Main Menu");
            Console.WriteLine(separator);
            Console.WriteLine();
            Console.WriteLine("1 - Begin Transaction");
            Console.WriteLine("2 - Create Report");
            Console.WriteLine("3 - Commit Transaction");
            Console.WriteLine("4 - Rollback Transaction");
            Console.WriteLine("5 - Exit");
            Console.WriteLine();
            Console.WriteLine(status);
            Console.WriteLine(separator);
            Console.Write("Select an option: ");

            bool valid = false;
            int choice;
            string answer;

            do
            {
                answer = Console.ReadLine();
                valid = Int32.TryParse(answer, out choice);

                // Check for a valid option.
                if (valid && choice < 0 || choice > 5)
                    valid = false;

            }
            while (valid == false);
            return choice;
        }
        
        private static void BeginTransaction()
        {
            if (Transaction.Current == null)
            {
                // Disable transaction timeouts 
                // for demo purposes only!
                myTransactionScope = new TransactionScope(
                    TransactionScopeOption.Required,
                    TimeSpan.Zero);

                status = "Transaction started.";
            }
            else
            {
                status = "Transaction already started.";
            }
        }

        private static void CommitTransaction()
        {
            if (myTransactionScope != null)
            {
                myTransactionScope.Complete();
                myTransactionScope.Dispose();
                myTransactionScope = null;
                status = "Committed transaction.";
            }
            else
                status = "No transaction to commit!";
        }

        private static void RollbackTransaction()
        {
            // If we are in a transaction, 
            // just dispose it to rollback.
            if (Transaction.Current != null)
            {
                myTransactionScope.Dispose();
                myTransactionScope = null;
                status = "Rolled back transaction.";
            }
            else
                status = "No transaction to roll back!";
        }

        // Only allow a report to be created when client has
        // started a transaction.
        private static void CreateReport()
        {
            if (myTransactionScope != null)
            {
                ReportCreatorClient reportCreator =
                    new ReportCreatorClient();
                reportCreator.CreateReport("myReport.txt");
                status = "Report created.";
            }
            else
                status = "There is no transaction to work with!";
        }

    }
}

The configuration file for the client is shown here.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>

  <system.serviceModel>
    <client>
      <endpoint address="net.tcp://localhost:8000/TxFSamples/ReportCreator"
                binding="netTcpBinding"
                bindingConfiguration="ReportCreatorConfiguration" 
                contract="TxFAndWcf.Client.IReportCreator" />
    </client>

    <bindings>
      <netTcpBinding>
        <binding name="ReportCreatorConfiguration"
                transactionFlow="true"
                transactionProtocol="OleTransactions" />
      </netTcpBinding>
    </bindings>
  </system.serviceModel>
  
</configuration>

See Also

Concepts

Developing with Transactional Enhancements

Understanding Transactional Enhancements

Transactional Enhancements