Developing with Transactional Enhancements

Windows Server 2003

This article demonstrates creating managed applications that use transactional enhancements to Windows Server® 2008. The companion topic, Understanding Transactional Enhancements provides a conceptual overview of these technologies. The Transactional Enhancements Samples topic contains sample applications that demonstrate using the new transactional features.

For managed applications, transactions are supported by the objects in the System.Transactions namespace. This namespace provides a programming model for explicit transactions using the Transaction class as well as a programming model for implicit transactions using the TransactionScope class. When an application uses implicit transactions, the transactions are automatically managed by the infrastructure. The implicit transaction model is the recommended programming model for most applications.

A managed application can use the TransactionScope class to make a block of code transactional. In the following code example, if a transaction already exists, it is used; otherwise, a new transaction is created.

using (TransactionScope tx = new TransactionScope())
{
    // Operations performed as part of the transaction.
    tx.Complete();
} // End of transacted operations.

If the transaction has not been committed by the end of the code block, the transaction will be rolled back.

To determine whether a transaction already exists, check the Current property on the Transaction class. If Current is not null, there is a transaction. It is recommended that applications work with the current transaction using the TransactionScope class as shown above.

The Kernel Transaction Manager (KTM) provides Win32 APIs to allow developers to directly access transactions. However, the preferred mechanism for working with transactions is using the Microsoft Distributed Transaction Coordinator (DTC). System.Transactions uses DTC or its in-process lightweight counterpart.

The following code example shows the definition of the COM interface used to convert a DTC transaction handle to a KTM transaction handle.

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

In the following code example, the CreateKtmTransactionHandle method demonstrates getting a KTM transaction handle using the DTC transaction exposed by the System.Transactions.TransactionInterop class.

public class KtmTransactionHandle : SafeHandleZeroOrMinusOneIsInvalid
{
     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);
     }
}

Transactional NTFS (TxF) provides a mechanism for handling file operations on an NTFS volume using transaction semantics. Whether a file operation is transactional or not is determined by the file handle involved in the operation. For file operation APIs that take a file handle parameter, developers call the same API in transactional and non-transactional scenarios; the file handle determines the API behavior. For file APIs that take a filename parameter, there are separate APIs for transacted operations. For example, to create a file as a transacted operation, call CreateFileTransacted. This function creates a transacted file handle, which can then be used for all file operations requiring a handle. All operations using this handle are transacted operations.

The code examples in this section are part of a managed console application sample that demonstrates using KTM and TxF to create files as part of a transaction or outside of a transaction. A detailed description and the complete source code for the sample can be found in the Transactional Enhancements Samples topic.

The examples that follow show the programming elements used to P/Invoke CreateFileTransacted to create a file as part of a transaction. The elements are:

  1. Enumerations that capture the Win32 flags that specify file share, file mode, and file access.

  2. The P/Invoke declaration for CreateFileTransacted.

  3. Helper functions to translate between managed and unmanaged values for file share, file mode and file access.

  4. The managed method that wraps the P/Invoke call to CreateFileTransacted.

Cc303700.collapse_all(en-us,MSDN.10).gifEnumerations for Win32 Flag Values

The following example shows the enumerations referenced by the P/Invoke declaration for the CreateFileTransacted function, which is shown later in this section. These enumerations are declared in a class named NativeMethods to distinguish them from the System.IO enumerations used for managed file operations.

[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
}

Cc303700.collapse_all(en-us,MSDN.10).gifP/Invoke Declaration for CreateFileTransacted

The following example shows the P/Invoke declaration for the CreateFileTransacted function.

[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);

The definition for the KTMTransactionHandle data type is shown is the third example in the Kernel Transaction Manager section.

Cc303700.collapse_all(en-us,MSDN.10).gifMapping Between Managed and Unmanaged Flags

The members of managed enumerations such as FileAccess, FileShare and FileMode do not necessarily have a one-to-one mapping with their unmanaged counterparts. The following code example demonstrates how helper methods can be used to map managed values to their corresponding unmanaged values. This functionality is useful when a managed library will wrap a P/Invoke call and expose it using a managed method. Parameters for the managed wrapper should use the managed enumerations to be consistent with managed APIs that perform file operations. Internally the managed values must be converted to the unmanaged values.

private static NativeMethods.FileMode 
    TranslateFileMode(FileMode mode)
{
    if (mode != FileMode.Append)
    {
        return (NativeMethods.FileMode)(int)mode;
    }
    else
    {
        return (NativeMethods.FileMode)
           (int)FileMode.OpenOrCreate;
    }
}
private static NativeMethods.FileAccess
    TranslateFileAccess(FileAccess access)
{
    return access == FileAccess.Read ? 
        NativeMethods.FileAccess.GENERIC_READ : 
        NativeMethods.FileAccess.GENERIC_WRITE;
}
private static NativeMethods.FileShare 
                TranslateFileShare(FileShare share)
{
    return (NativeMethods.FileShare)(int)share;
}

Cc303700.collapse_all(en-us,MSDN.10).gifCalling CreateFileTransacted from Managed Code

The following code example demonstrates using KTM and TxF to open a file as part of a transaction. This managed method wraps the P/Invoke call to CreateFileTransacted. The KtmTransactionHandle.CreateKtmTransactionHandle method is defined in the Kernel Transaction Manager section of this article.

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;
    }
}

The following code example demonstrates calling the Open method defined above.

private static void WriteTransactedFile()
{
    using (StreamWriter myFile =
        new StreamWriter(TransactedFile.Open(
            myTransactedFile.FullName,
            FileMode.Create,
            FileAccess.Write,
            FileShare.None)))
    {
        myFile.Write("I am a transacted file");
    }
}

TxF can be combined with other technologies to enable a wide variety of scenarios that involve remote file systems. In this section, TxF is paired with Windows Communication Foundation (WCF). In the following code examples, a client communicates with a 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. The following sections highlight the steps required to implement the full code sample. The complete source code for the sample can be found in Transactional Enhancements Samples.

Cc303700.collapse_all(en-us,MSDN.10).gifCreating the Server

Creating the TxF-aware WCF server requires the following steps:

  1. Define the service contract.

  2. Implement the service contract.

  3. Specify configuration for the server and service.

  4. Create and run an instance of the server.

Cc303700.collapse_all(en-us,MSDN.10).gifDefine the Service Contract

The following code example shows the service contract for the server. The server implements a single operation: CreateReport. Note that by using the TransactionFlowAttribute attribute and specifying TransactionFlowOption.Mandatory, the service will not perform the operation unless there is a transaction propagated from the client to the service.

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

Cc303700.collapse_all(en-us,MSDN.10).gifImplement the Service Contract

The next section of code shows the class that implements the service contract. Note that the OperationBehaviorAttribute on the CreateReport method indicates that a transaction is mandatory and that the transaction will be completed automatically if the method executes with no unhandled exceptions. Completion does not ensure that the transaction will be committed. The client controls whether the transaction is committed or rolled back. The CreateReport method uses the TransactedFile.Open method to write the report within the scope of a transaction. This method is defined in Calling CreateFileTransacted from Managed Code.

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.");
        }
    }
}

Cc303700.collapse_all(en-us,MSDN.10).gifSpecify Configuration for the Server and Service

The configuration information provides the name of the class that implements the service, the endpoint and address used by the service and the binding to use. Note that the binding <netTcpBinding> element specifies that transactions propagate (flow) from the client to the service using the OleTransactions (OleTx) protocol.

<?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>

Cc303700.collapse_all(en-us,MSDN.10).gifCreate and Run an Instance of the Server

The following code example implements a console application that starts the service and handles incoming client requests.

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();
            }
        }
    }
}

Cc303700.collapse_all(en-us,MSDN.10).gifCreating the Client

Creating the client requires the following steps:

  1. Generate the client implementation of the service contract.

  2. Implement the client application.

Cc303700.collapse_all(en-us,MSDN.10).gifGenerate the Client Implementation of the Service Contract

The client can be thought of as having two parts. The first part, described here, is responsible for implementing the service contract and communicating with the server. The second part, described in the Implement the Client Application section, is the application logic that uses the service.

Once the service has been built into an executable file, the client implementation of the service contract can be generated using the ServiceModel Metadata Utility Tool (Svcutil.exe). This tool can generate the client side of the service contract and the configuration file required to connect to the server. In this code example, the generated client must implement the IReportCreator interface. The generated code is a class named ReportCreatorClient that has constructors and an implementation of the CreateReportmethod as required by the service contract for IReportCreator defined in Creating the Server.

Cc303700.collapse_all(en-us,MSDN.10).gifImplement the Client Application

The ReportCreatorClient class is responsible for connecting to and communicating with the service. The client application uses this class as a proxy for server-side operations. The client application must ensure that there is a transaction available to propagate to the server when requesting services via the proxy. If there is no transaction, the server will throw an exception. The following code example from the client application demonstrates requesting a report be written on the server if and only if there is a transaction to flow with the request. The variable named myTransactionScope holds a reference to the current thread's ambient transaction, if one exists.

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 complete source code for the client and server can be found in the Transactional Enhancements Samples topic.

The Transactional Registry (TxR) allows applications to perform registry operations in a transacted manner. Windows Server 2008 adds new APIs to handle registry operations in transactions:

Each of the APIs takes a transaction handle to associate registry operation with a transaction. The transactional nature of a registry key is not inherited by subkeys. Use the transacted APIs on the subkeys to enlist them in the transaction.

The transacted registry feature is not exposed by the .NET Framework. Managed applications can access this feature by P/Invoking the Win32 APIs as demonstrated in the code examples in this section. The following code examples demonstrate the steps required to create a registry key as part of a transaction.

To create a transacted registry key requires the following:

  • Get the KTM handle for the current transaction.

  • Create the P/Invoke declaration for the transacted registry operation.

  • P/Invoke the transacted registry API within the scope of a transaction.

A code example illustrating how to get the KTM handle for the current transaction is shown is the third example in the Kernel Transaction Manager section of this article. The implementation of the KtmHandle class appears in that example.

The APIs for creating and opening keys take a parameter that specifies the access rights for the key. The following enumeration contains a subset of the values that can be specified for the samDesired parameter. For the complete list, see Registry Key Security and Access Rights.

internal enum RegSam : uint
{
    KEY_WOW64_32KEY = 0x0200,
    KEY_WOW64_64KEY = 0x0100,
    KEY_READ = 0x20019
}

The RegCreateKeyTransacted API returns a disposition value indicating whether the key was created or opened. The following enumeration captures this information.

internal enum RegDisposition
{
    CreatedNewKey = 1,
    OpenedExsiting = 2
}

The RegCreateKeyTransacted and RegOpenKeyTranacted APIs take an options parameter that indicates whether the key is persisted to disk when the registry hive is unloaded. The following constants capture the valid values for this parameter.

internal const int REG_OPTION_VOLATILE = 0x01;
internal const int REG_OPTION_NONVOLATILE = 0x00;
internal const int REG_OPTION_BACKUP_RESTORE = 0x04;

The following code example shows the P/Invoke declaration for RegCreateKeyTransacted.

[DllImport("advapi32.dll", 
    EntryPoint="RegCreateKeyTransactedW",
    CharSet = CharSet.Unicode,
    SetLastError = true)]
internal static extern long RegCreateKeyTransactedW(
    uint hkey, 
    [MarshalAs(UnmanagedType.LPWStr)]string subkey, 
    uint reserved,
    UIntPtr objectClass, 
    uint options, 
    RegSam samDesired, 
    UIntPtr securityAttributes, 
    out uint outKey, 
    out RegDisposition disposition,
    KtmTransactionHandle transaction, 
    UIntPtr moreReserved);

Note that the import DLLImportAttribute specifies that SetLastError will be called after the function executes. This functionality ensures that an application can access the error code generated by the API. Because the function is being P/Invoked, the return value of the function cannot be used to get the Win32 error code. The correct process for getting the error code is demonstrated in the next section. The constant used to determine a successful call is:

internal const int ERROR_SUCCESS = 0; 

The following code example demonstrates creating a transacted registry key in the current user's registry hive, which is defined by the following constant value.

internal const uint HKEY_CURRENT_USER = 0x80000001;

The variable myTransactionScope holds a reference to the ambient transaction, if one exists.

private static string CreateTransactedKey()
{
    uint mySubKey = 0;
    NativeMethods.RegDisposition keyDisposition;
    long result;
    string status;

    // Don't allow end-user to create a transacted
    // key before they have begun a transaction.
    if (myTransactionScope == null)
    {
        status = "There is no transaction for this operation!";
        return status;
    }

    // Get the KTM transaction for the existing transaction.
    using (KtmTransactionHandle ktmTx =
        KtmTransactionHandle.CreateKtmTransactionHandle())
    {
         NativeMethods.RegCreateKeyTransactedW(
            NativeMethods.HKEY_CURRENT_USER,
            "txrDemo_transacted_key",
            0,
            UIntPtr.Zero,
            NativeMethods.REG_OPTION_VOLATILE,
            NativeMethods.RegSam.KEY_READ, 
            UIntPtr.Zero,
            out mySubKey,
            out keyDisposition,
            ktmTx, 
            UIntPtr.Zero);

        // Check for errors.
        result = Marshal.GetLastWin32Error();
        if (result != NativeMethods.ERROR_SUCCESS)
        {
            status = "Creating transacted key failed: ";
            status += new Win32Exception(Marshal.GetLastWin32Error()).Message;
            return status;
        }
        else
        {
            status = "Transacted key created.";

            // Close the handle to the key. 
            NativeMethods.RegCloseKey(mySubKey);
        }
    }
    return status;
}
Show: