Foundations

WCF Transaction Propagation

Juval Lowy

Code download available at:  Transactions 2007_05.exe(211 KB)

Contents

Transaction Flow Configuration
Transaction Flow and Operation Contract
The Ambient Transaction
Transactional Service Programming
Transaction Propagation Modes
Client and Service Transactions
Voting and Completion
Conclusion

Transactions are the key to building robust, high-quality service-oriented applications. Windows® Communication Foundation provides simple, declarative transaction support for service developers, enabling you to configure parameters such as transaction flow and voting, all outside the scope of your service. In addition, Windows Communication Foundation allows client applications to create transactions and to propagate them across service boundaries. In this column, I take a closer look at configuring transaction propagation and voting in Windows Communication Foundation and the resulting programming models.

Transaction Flow Configuration

Windows Communication Foundation can propagate transactions across service boundaries. This enables a service to participate in the client’s transaction and for the client to include operation calls on multiple services in the same transaction. The client itself may or may not be a Windows Communication Foundation service. Both the binding and the operation contract configuration control the decision of whether the client transaction is propagated to the service.

I call any binding that is capable of propagating the client’s transaction to the service (if configured to do so) a transaction-aware binding. Specifically, only NetTcpBinding, NetNamedPipeBinding, WSHttpBinding, WSDualHttpBinding, and WSFederationHttpBinding are transaction-aware. By default, transaction-aware bindings do not propagate transactions. The reason is that, like most everything else in Windows Communication Foundation, it is an opt-in setting.

The service host or administrator has to explicitly consent to accepting incoming transactions, potentially from across an organization or across business boundaries. To propagate a transaction, you must explicitly enable it at the binding on both the service host and the client sides. All transaction-aware bindings offer the Boolean property TransactionFlow, which defaults to false, as shown here:

public class NetTcpBinding : Binding,...
{
   public bool TransactionFlow {get;set;}
   //More members 
}

To enable propagation, simply set this property to true, either programmatically or in the host config file. For example, when using a config file this code snippet will set the property:

<bindings>
   <netTcpBinding>
      <binding name = “TransactionalTCP” transactionFlow = “true” />
   </netTcpBinding>
</bindings>

Note that the value of the TransactionFlow property is not published in the service metadata. If you use Visual Studio® 2005 to generate the client config file, you will still need to enable transaction flow manually.

Transaction Flow and Operation Contract

Using a transaction-aware binding and even enabling transaction flow does not mean that the service wants to use the client’s transaction in every operation or that the client has a transaction to propagate in the first place. Such decisions should be part of the contractual agreement between the client and the service. To that end, Windows Communication Foundation provides the TransactionFlowAttribute method attribute that controls if and when the client’s transaction flows into service:

public enum TransactionFlowOption
{
   Allowed,
   NotAllowed,
   Mandatory
}

[AttributeUsage(AttributeTargets.Method)]
public sealed class TransactionFlowAttribute : 
   Attribute,IOperationBehavior
{
   public TransactionFlowAttribute(TransactionFlowOption flowOption);
}

Note that the TransactionFlow attribute is a method-level attribute because Windows Communication Foundation insists that the decision on transaction flow be made on a per-operation level:

[ServiceContract]
interface IMyContract 
{
   [OperationContract]
   [TransactionFlow(TransactionFlowOption.Allowed)]      
   void MyMethod(...);
}

This is deliberate to enable the granularity of having some methods that use the client’s transaction and some that do not.

The value of the TransactionFlow attribute is included in the published metadata of the service. When you import a contract definition, the imported definition will contain the configured value. These attributes are TransactionFlowOption.NotAllowed, TransactionFlowOption.Allowed, and TransactionFlowOp­tion.Mandatory.

When the operation is configured to disallow transaction flow via the attribute TransactionFlowOption.NotAllowed, the client cannot propagate its transaction to the service. Even if transaction flow is enabled at the binding and the client has a transaction, it will be silently ignored and not propagate to the service. As a result, the service will never use the client’s transaction, and the service and the client can select any binding with any configuration. TransactionFlowOption.NotAllowed is the default TransactionFlowOption value of the TransactionFlow attribute.

When the operation is configured to allow transaction flow by using TransactionFlowOption.Allowed, if the client has a transaction, then the service will allow the client’s transaction to flow across the service boundary. However, the service may or may not use the client’s transaction even though it was propagated. When choosing TransactionFlowOption.Allowed, the service can be configured to use any binding, transaction-aware or not, but the client and the service must be compatible in their binding configuration. When the service operation allows transaction flow, but the binding disallows it, the client should also disallow it in the binding on its side. Trying to flow the client transaction will cause an error because the transaction information in the message will not be understood by the service. However, when the service-side binding configuration is set to allow transaction flow, the client may or may not want to enable propagation on its side, and so may elect to set TransactionFlow to false in the binding even if the service has it set to true.

When the operation is configured for TransactionFlowOp­tion.Mandatory, the service and client must use a transaction-aware binding with transaction flow enabled. Windows Communication Foundation verifies this requirement at the service load time and throws an InvalidOperationException if the service has at least one incompatible endpoint. TransactionFlowOption.Mandatory means the client must have a transaction to propagate to the service. Trying to call a service without a transaction throws an exception on the client. With mandatory flow, the client’s transaction always propagates to the service. Once again, the service may or may not use the client’s transaction.

Propagating the client transaction to the service requires, by its very nature, allowing the service to abort the client transaction. This implies that you cannot flow the client transaction to a service over a one-way operation because that call does not have a reply message. Windows Communication Foundation validates this at service load time and throws an exception when a one-way operation is configured for anything but TransactionFlowOp­tion.NotAllowed:

//Invalid definition:
[ServiceContract]
interface IMyContract 
{
   [OperationContract(IsOneWay = true)]
   [TransactionFlow(TransactionFlowOption.Allowed)]      
   void MyMethod(...);
}

The Ambient Transaction

Introduced in the Microsoft® .NET Framework 2.0, the Transaction class from the System.Transactions namespace is used to represent a Windows Communication Foundation transaction:

[Serializable]
public class Transaction : IDisposable,ISerializable
{
   public static Transaction Current {get;set;}
   public TransactionInformation TransactionInformation {get;}
   public void Dispose(); 

   //More members 
}

Developers rarely need to interact with the Transaction class directly. Instead, the .NET Framework 2.0 defines a concept called an ambient transaction, which is the transaction that is used for code currently executing. To obtain a reference to the ambient transaction, call the static Current property of Transaction:

Transaction ambientTransaction = Transaction.Current;

If there is no ambient transaction, Current returns null. Every piece of code, be it client or service, can always reach out for its ambient transaction. The ambient transaction object is stored in thread-local storage. As a result, when the thread winds its way across multiple objects and methods on the same call chain, all objects and methods can access their ambient transaction.

In the context of Windows Communication Foundation, the ambient transaction is paramount. When present, any Windows Communication Foundation resource manager (such as a SQL Server™ or a volatile resource manager) will automatically enlist in the ambient transaction. When a client calls a Windows Communication Foundation service, if the client has an ambient transaction and the binding and the contract are configured to allow transaction flow, the ambient transaction will propagate to the service.

The Transaction class is used both for local and distributed trans­actions. If no distributed transaction support is required, Windows Communication Foundation will use a local transaction manager. If the client tries to flow its transaction to a service, or if multiple durable resources are involved, the transaction will be promoted to a distributed transaction and managed by the Distributed Transaction Control (DTC). Each transaction has two identifiers used to identify the local and the distributed transaction. You obtain the transaction identifiers by accessing the TransactionInformation property of the Transaction class. TransactionInformation is of the type TransactionInformation defined as follows:

public class TransactionInformation
{
   public Guid DistributedIdentifier {get;}
   public string LocalIdentifier {get;}
   //More members
} 

LocalIdentifier is never null; it represents the local transaction. DistributedIdentifier may be Guid.Empty if the transaction has not been promoted yet to a distributed transaction. The main use of these identifiers is for logging, tracing, and analysis. In this column, I use them as a convenient way in code to demonstrate transaction flow as a result of configuration.

Transactional Service Programming

For services, Windows Communication Foundation offers a simple and elegant declarative programming model. By default, the service class and its operations have no ambient transaction. This is the case even when the client transaction is propagated to the service.

Consider the service shown in Figure 1. The ambient transaction of the service will be null, even though the mandatory transaction flow guarantees the client’s transaction propagation. In order to have an ambient transaction, for each contract method the service must indicate it wants Windows Communication Foundation to scope the body of the method with a transaction using the TransactionScopeRequired property of OperationBehavior attribute, as shown in the following code:

Figure 1 Simple Service Contract

[ServiceContract]
interface IMyContract 
{
   [OperationContract]
   [TransactionFlow(TransactionFlowOption.Mandatory)]      
   void MyMethod(...);
}

class MyService : IMyContract 
{
   public void MyMethod(...)
   {
      Transaction transaction = Transaction.Current;
      Debug.Assert(transaction == null); 
   } 
}

[AttributeUsage(AttributeTargets.Method)]
public sealed class OperationBehaviorAttribute : Attribute,...
{
   public bool TransactionScopeRequired {get;set;}
   //More members
}

The default value of TransactionScopeRequired is false, which is why by default the service has no ambient transaction. Setting TransactionScopeRequired to true provides the operation with an ambient transaction:

class MyService : IMyContract 
{
   [OperationBehavior(TransactionScopeRequired = true)]   
   public void MyMethod()
   {
      Transaction transaction = Transaction.Current;
      Debug.Assert(transaction != null); 
   } 
}

If the client transaction is propagated to the service, Windows Communication Foundation will set the client transaction as the operation’s ambient transaction. Otherwise, Windows Communication Foundation will create a new transaction for that operation and set the new transaction as the ambient transaction.

The diagram in Figure 2 demonstrates which transaction a Windows Communication Foundation service uses as a product of the binding configuration, the contract operation and the local operation behavior attribute.

In Figure 2, a non-transactional client calls Service 1. The operation contract is configured with TransactionFlowOption.Allowed. Even though transaction flow is enabled in the binding, since the client has no transaction, no transaction is propagated. The operation behavior on Service 1 is configured to require a transaction scope. As a result, Windows Communication Foundation creates a new transaction for Service 1, Transaction A. Service 1 then calls three other services, each configured differently.

Figure 2 Transactions Propagation

Figure 2** Transactions Propagation **(Click the image for a larger view)

The binding used for Service 2 has transaction flow enabled, and the operation contract mandates the flow of the client transaction. Since the operation behavior is configured to require transaction scope, Windows Communication Foundation sets Transaction A as the ambient transaction for Service 2. The call to Service 3 has the binding and the operation contract disallow transaction flow. However, since Service 3 has its operation behavior require a transaction scope, Windows Communication Foundation creates a new transaction for Service 3 (Transaction B) and sets it as the ambient transaction for Service 3. Similar to Service 3, the call to Service 4 has the binding and the operation contract disallow transaction flow. Since Service 4 does not require a transaction scope, it has no ambient transaction.

Transaction Propagation Modes

Which transaction the service uses is a product of the flow property of the binding (two values), the flow option in the operation contract (three values), and the value of the transaction scope property in the operation behavior (two values). There are therefore 12 possible configuration settings. Out of these 12, 4 are inconsistent and are precluded by Windows Communication Foundation (such as flow disabled in the binding yet mandatory flow in the operation contract) or are just plain impractical. Figure 3 lists the remaining 8 permutations.

Figure 3 Transaction Propagation Modes

Binding Transaction Flow Transaction- FlowOption Transaction- ScopeRequired Transaction Mode
False Allowed False None
False Allowed True Service
False NotAllowed False None
False NotAllowed True Service
True Allowed False None
True Allowed True Client/Service
True Mandatory False None
True Mandatory True Client

Those 8 permutations actually result in only four transaction propagation modes. I call these four modes Client/Service, Client, Service, and None. Figure 3 also shows in green font the recommended way to configure each mode. Each of these modes has its place in designing your application. Understanding how to select the correct mode greatly simplifies thinking about and configuring transaction support.

The Client/Service mode, as its name implies, ensures the service uses the client transaction if possible, or a service-side transaction when the client does not have a transaction. The Client/Service mode is the most decoupled configuration, because the service minimizes its assumptions about what the client is doing. The service will join the client transaction if the client has a transaction to flow. Joining the client transaction is always good for overall system consistency. Imagine the service has a transaction separate from that of the client: that opens the way for one of these two transactions to commit while the other one aborts and leave the system in an inconsistent state.

When the service joins the client transaction, all the work done by the client and the service (and potentially other services the client calls) will be committed or aborted as one atomic operation. If the client does not have a transaction, the service still requires the protection of the transaction, and so this mode provides a contingent transaction to the service, by making it the root of a new transaction. Being the root makes the service control the lifespan of the transaction. Figure 4 shows a service configured for Client/Service transaction. Note that the service asserts it always has a transaction. The service cannot assume or assert whether it is the client’s transaction or a locally created one.

Figure 4  Configuring Client/Service Mode

[ServiceContract]
interface IMyContract 
{
   [OperationContract]
   [TransactionFlow(TransactionFlowOption.Allowed)]      
   void MyMethod(...);
}

class MyService : IMyContract 
{
   [OperationBehavior(TransactionScopeRequired = true)]
   public void MyMethod(...)
   {
      Transaction transaction = Transaction.Current;
      Debug.Assert(transaction != null);
   } 
}

The Client/Service mode is applicable when the service can be used standalone or it can be used as part of a bigger transaction. When selecting this mode you should be mindful of potential deadlocks. Specifically, if the resulting transaction is a service-side transaction, it may deadlock with other transactions trying to access the same resources because the resources would isolate access per transaction, and the service-side transaction will be a new transaction. When using the Client/Service mode, the service may or may not be the root of the transaction, and the service must not behave differently when it is the root or when it is joining the client’s transaction.

Client and Service Transactions

The Client mode ensures the service only uses the client’s transaction. You select the Client transaction mode when, by design, the service must use its client’s transactions and can never be used standalone. The main motivation for this is to avoid deadlocks and maximize overall system consistency. By having the service share the client’s transaction you reduce the potential for a deadlock, because all resources accessed will enlist in the same transaction so there will not be another transaction that competes for access to the same resources and underlying locks. By having a single transaction you maximize consistency because that transaction will commit or abort as one atomic operation. Figure 5 shows a service configured for the Client transaction mode.

Figure 5 Configuring Client Transaction Mode

[ServiceContract]
interface IMyContract 
{
   [OperationContract]
   [TransactionFlow(TransactionFlowOption.Mandatory)]      
   void MyMethod(...);
}

class MyService : IMyContract 
{
   [OperationBehavior(TransactionScopeRequired = true)]   
   public void MyMethod(...)
   {
      Transaction transaction = Transaction.Current;
      Debug.Assert(transaction.TransactionInformation.
                   DistributedIdentifier != Guid.Empty);
   } 
}

Note that the method asserts the fact that the ambient transaction is a distributed one, meaning the transaction originated with the client. The Service mode ensures the service always has a transaction, separate from any transaction its clients may or may not have. The service will always be the root of a new transaction.

You select the Service transaction mode when the service needs to perform transactional work outside the scope of the client’s transaction. For example, when you want to perform some logging or audit operations, or when you want to publish events to subscribers regardless of whether your client transaction commits or aborts. Consider a logbook service that performs error logging into a database. When an error occurs on the client side, the client would use the logbook service to log the error or some other entries. In case of an error, after logging, the error on the client side aborts the client’s transaction. If the service were to use the client transaction, once the client transaction aborts, the logged error would be discarded from the database, and you would have no trace of it, thus defeating the purpose of the logging in the first place.

By configuring the service to have its own transaction, logging the error would occur even when the client transaction aborts. The downside, of course, is the potential for jeopardizing the system consistency, because the service transaction could abort while the client’s commits. The heuristic made when selecting this mode is that the service transaction is much more likely to succeed and commit than the client’s transaction. In the example of the logging service, this is often the case, because once deterministic logging is in place it will usually work, as opposed to business transactions that may fail due to a variety of reasons. In general, you should be extremely careful when using the Service transaction mode, and verify that the two transactions (the client’s transaction and the service’s transaction) do not jeopardize consistency if one aborts and the other commits. Logging and auditing services are the classic candidates for this mode.

The code in Figure 6 shows a service configured for Service transaction mode. You should note that the service asserts that it actually has a local transaction.

Figure 6 Configuring Service Transaction Mode

[ServiceContract]
interface IMyContract 
{
   [OperationContract]
   void MyMethod(...);
}

class MyService : IMyContract 
{
   [OperationBehavior(TransactionScopeRequired = true)]
   public void MyMethod(...)
   {
      Transaction transaction = Transaction.Current;
      Debug.Assert(transaction.TransactionInformation.
                   DistributedIdentifier == Guid.Empty);
   } 
}

The None transaction mode means the service never has a transaction. This mode is useful when the operations performed by the service are nice to have, though not essential, and should not abort the client’s transaction if the operations fail. For example, a service that prints a receipt for a money transfer should not be able to abort the client transaction if the printer is out of paper. Another reason for employing the None mode is when you want to provide some custom behavior, and you need to perform your own programmatic transaction support or manually enlist resources such as calling legacy transactional code.

Obviously, there is danger in using the None mode because it can jeopardize the system consistency. For instance, if the calling client has a transaction and it calls a service configured as None, and then the client aborted its transaction, the result is that changes made to the system state by the service will not rollback. Another pitfall of this mode is when a service configured for None calls another service configured for a Client transaction. Such a call will fail because the calling service has no transaction to propagate.

Figure 7 shows a service configured for the None Transaction mode. Note that the service asserts it has no ambient transaction.

Figure 7 Configuring the None Transaction Mode

[ServiceContract]
interface IMyContract 
{
   [OperationContract]
   void MyMethod();
}

class MyService : IMyContract 
{
   public void MyMethod()
   {
      Transaction transaction = Transaction.Current;
      Debug.Assert(transaction == null);
   } 
}

The None mode allows you to have a non-transactional service called by a transactional client. As stated previously, configuring for the None mode is mostly for nice-to-have operations. The problem with that is that any exception thrown by the None service will abort the calling client’s transaction, something that should be avoided with a nice-to-have operations. The solution is having the client catch all relevant exceptions from the None service to avoid contaminating the client’s transaction.

Out of the four modes, the Service and the None modes are somewhat esoteric. They are useful in the context of the particular scenarios mentioned, but remember that they harbor the danger of jeopardizing the system consistency. You should use the Client/Service or the Client transaction modes, and choose the mode based on the ability of the service to be used standalone as a function of potential deadlocks and consistency.

Voting and Completion

While Windows Communication Foundation is responsible for every aspect of the transaction propagation and overall management of committing or aborting the transaction across resources, it does not know whether the transaction should commit or abort. Windows Communication Foundation simply has no way of knowing if the changes made to the system state are consistent—that is, if they make sense. Every participating service must vote on whether the transaction should commit or abort. In addition, Windows Communication Foundation does not know when the transaction ends and when all the services complete their work. That is something the root service needs to indicate.

Windows Communication Foundation can automatically vote on behalf of the service to commit or abort the transaction. Automatic voting is controlled via the Boolean TransactionAutoComplete property of the OperationBehavior attribute:

[AttributeUsage(AttributeTargets.Method)]
public sealed class OperationBehaviorAttribute : Attribute,...
{
   public bool TransactionAutoComplete {get;set;}
   //More members
}

The TransactionAutoComplete property defaults to true. When set to true, if there is no unhandled exception in the operation, Windows Communication Foundation will automatically vote to commit the transaction. If there is an unhandled exception, Windows Communication Foundation will vote to abort the transaction. Only a service with a session can set TransactionAutoComplete to false, and doing do has drastic implications on the programming model which are beyond the scope of this column.

When the transaction ends is a product of who starts it. Consider a client that either does not have a transaction or just does not propagate its transaction to the service, and that client calls a service operation configured with TransactionScopeRequired set to true. That service operation becomes the root of the transaction. The root service can call other services and propagate the transaction to them. The transaction will end once the root operation completes the transaction. This is partly why TransactionAutoComplete is named that way. It does more than mere voting; it completes and terminates the transaction for a root service. Be aware that any of the down-stream services called by the root operation can only vote on the transaction and not complete it. Only the root both votes and completes the transaction. Using a transaction scope is practically the only way a non-service client can group multiple service calls into single transaction, as shown in Figure 8.

Figure 8 Using Transaction Scope

Figure 8** Using Transaction Scope **(Click the image for a larger view)

When a non-service client starts a transaction, the transaction ends when the client disposes of the transaction object it uses, typically when disposing of a TransactionScope object. Having the option to create a root transaction scope enables the client to flow its transaction to services and to manage and commit the transaction based on the aggregated result of the services:

using(MyContractClient proxy1 = new MyContractClient())
using(MyOtherContractClient proxy2 = new MyOtherContractClient())
using(TransactionScope scope = new TransactionScope())
{
   proxy1.MyMethod(...); 
   proxy2.MyOtherMethod(...); 
   scope.Complete();
}

Conclusion

Transactions are instrumental in many applications, ensuring the proper recovery of the system after an error. Being able to group multiple services from multiple vendors across technology and service boundaries into a single atomic transaction is a key feature of Windows Communication Foundation and the standards it supports. Yet all developers have to do is chose the appropriate logical transaction propagation mode by setting a few attributes and enabling the flow in the binding. This is a testimony to the simple and capable programming model of Windows Communication Foundation. There is much more to transactions support in Windows Communication Foundation, especially when it comes to synchronization and instance management, and I will address these aspects in a future column.

To learn more about transactions and Windows Communication Foundation, see "Introducing System.Transactions in the .NET Framework 2.0" at and "Volatile Resource Managers in .NET Bring Transactions to the Common Type" in the December 2005 issue of MSDN Magazine.

Send your questions and comments to  mmnet30@microsoft.com.

Juval Lowy is a software architect with IDesign providing WCF training and WCF architecture consulting. His recent book is Programming WCF Services (O’Reilly, 2007). He is also the Microsoft Regional Director for the Silicon Valley. Contact Juval at www.idesign.net.