Introduction
Microsoft has released its first WinFx CTP with Indigo, which includes the much anticipated support for Distributed Transaction (WS-Atomic Transaction). In this article, I will write some very simple C# code to illustrate the concept of Distributed Transaction in Indigo. The only requirements for running the sample application is to download and install the WinFx CTP onto Windows XP with SP2, no need for Visual Studio. NET 2005 Beta. I hope this will help you to understand the semantics of transaction programming and how you can leverage Transaction Model in dealing with common business problems.
Transaction Code
Most developers are familiar with database transaction code as illustrated in the following code segment:
void DoLocalDBTransaction(int OrderID)
{
System.Configuration.AppSettingsReader reader =
new System.Configuration.AppSettingsReader();
string cnstr =
reader.GetValue("ConnectionString",typeof(string)).ToString();
SqlConnection cn = new SqlConnection(cnstr);
cn.Open((new SqlCommand("update orders set orderDate='" +
DateTime.Now.ToString() + "' where OrderID=" + OrderID.ToString());
cn.ExecuteNonQuery();
cn.Close();
}
This type of transaction code can be found in local executables as well as in a WebMethod of a remote WebService. Interesting scenarios arise when multiple calls to this function are made: what if the first call succeeds and last call to the function fails, resulting in partial update of the database? The standard solution in the .NET 1.1 Framework is to use COM+ or ADO.NET transaction support to rollback. But if one of the transactions is executed through a call to a remote Web Service, then the rollback will not happen since COM+ and ADO.NET cannot run on SOAP/HTTP. This is where the .NET framework 2.0 System.Transaction
and Indigo System.ServiceModel
Transaction support come into play.
Basic Model of Distributed Transaction
WS-Atomic Transaction is a very simple but elegant idea as depicted in the following diagram:
This diagram illustrates several key elements of WS-Atomic Transaction (also known as WS-AT):
- Each WS-AT has one and only one Transaction Coordinator. Its sole purpose is to complete or rollback transactions as a whole.
- Every transaction participant has to register with the Transaction Coordinator (MSDTC), including Local Database Transaction and Web Service Transaction Context (DB or non-DB). Also Indigo Messaging bus will contact the coordinator when the Coordinator Context flows through boundaries.
This is just a high level simplified model of WS-AT transaction specification. Let us now take a look at some code.
WS-AT Coordinator
Coordinator can be represented by System.Transaction.TransactionScope
:
using (TransactionScope scope = new TransactionScope())
{
DoLocalDBTransaction(10248);
DoWSDBTransaction(10249);
DoWSNonDBTransaction(CustomerID);
scope.Complete();
}
We already saw code for local database transaction in the Introduction. And here are the codes for the other two functions:
void DoWSDBTransaction(int OrderID)
{
EndpointAddress address =
new EndpointAddress("http://localhost/TransactionalWS/Service.svc");
WSProfileBinding binding = new WSProfileBinding();
binding.FlowTransactions = ContextFlowOption.Required;
TransactionalWebServiceProxy proxy =
new TransactionalWebServiceProxy(address, binding);
proxy.NewInnerProxy.DoDBTransaction(OrderID);
}
void DoWSNonDBTransaction(int CustomerID)
{
EndpointAddress address =
new EndpointAddress("http://localhost/TransactionalWS/Service.svc");
WSProfileBinding binding = new WSProfileBinding();
binding.FlowTransactions = ContextFlowOption.Required;
TransactionalWebServiceProxy proxy =
new TransactionalWebServiceProxy(address, binding);
proxy.NewInnerProxy.DoNonDBTransaction(CustomerID);
}
Just as ASMX web services model, Indigo still uses Proxy/Stub Network communication architecture and here is the Indigo Proxy code generated by the svcutil.exe tool first and then manually changed:
class TransactionalWebServiceProxy :
ProxyBase<ITransactionalWebService><ITRANSACTIONALWEBSERVICE>
{
public TransactionalWebServiceProxy(
EndpointAddress address, WSProfileBinding binding)
: base(address, binding)
{
}
public ITransactionalWebService NewInnerProxy
{
get { return InnerProxy; }
}
}
where ITransactionalWebService
is the Indigo Web Service Contract Interface:
public interface ITransactionalWebService
{
[OperationContract()]
void DoDBTransaction( int OrderID);
[OperationContract()]
void DoNonDBTransaction(int CustomerID);
}
These are boiler-template code to �get on Indigo Messaging Bus�, much like adding Web Reference in VS.NET 2003. In essence, we are building an Indigo Communication by establishing a proxy and flowing Context through it. Still there are some interesting details about Indigo Messaging bus for network communication and we will now take a look at it.
Indigo ABC
A stands for Address, B stands for Binding and C is standard for Contract. To establish an Indigo communication, we must take care of ABC on both ends of the communication.
For IndigoABC on the physical machine 1 where the Transaction Coordinator resides, we have seen the following code above:
EndpointAddress address =
new EndpointAddress("http://localhost/TransactionalWS/Service.svc");
WSProfileBinding binding = new WSProfileBinding();
binding.FlowTransactions = ContextFlowOption.Required;
TransactionalWebServiceProxy proxy =
new TransactionalWebServiceProxy(address, binding);
proxy.NewInnerProxy.DoNonDBTransaction(CustomerID);
Here A is http://localhost/TransactionalWS/Service.svc; B is wsProfileBinding
, a standard binding built into Indigo Runtime; C is contractType="ITransactionalWebService"
. Notice that we modified standard wsProfileBinding
to set flowTransactions=ContextFlowOption.Required
, since WS-AT needs to flow transaction context to the other physical machine through Indigo.
To take care of IndigoABC on physical machine 2, I used VS.NET 2005 Feb CTP Indigo Service Project template to generate a special Web virtual directory �TransactionalWS� with one sub directory �App_Code� and three files:
\TransactionalWS
\App_Code
Service.cs
Service.svc
Web.Config
The content of Service.svc is similar to that of Service.asmx:
<%@ Service Language="C#"
CodeBehind="~/App_Code/Service.cs" Class="MyService" %>
Service.cs is the code-behind file:
[ServiceContract()]
public interface ITransactionalWebService
{
[OperationContract]
[OperationBehavior(AutoCompleteTransaction =true,AutoEnlistTransaction =true)]
void DoDBTransaction(int OrderID);
[OperationContract]
[OperationBehavior(AutoCompleteTransaction = true, AutoEnlistTransaction = true)]
void DoNonDBTransaction(int CustomerID);
}
public class MyService : ITransactionalWebService
{
public void DoDBTransaction(int OrderID) {...}
public void DoNonDBTransaction(int CustomerID) { �.}
}
Web.Config has the following content:
<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
<system.serviceModel>
<services>
<service serviceType="MyService">
<endpoint contractType="ITransactionalWebService"
bindingSectionName="wsProfileBinding"
bindingConfiguration="wsProfileConfig"/>
</service>
</services>
<behaviors>
<behavior configurationName="" returnUnknownExceptionsAsFaults="true" />
</behaviors>
<bindings>
<wsProfileBinding>
<binding flowTransactions="Required" configurationName="wsProfileConfig"/>
</wsProfileBinding>
</bindings>
</system.serviceModel>
</configuration>
Here A is the virtual directory address http://localhost/TransactionWS/Service.svc; B is wsProfileBinding
with flowTransactions="Required"
modification; C is ITransactionalWebService
.
Ideally, I would like to also use code rather than configuration on the Indigo Transaction Web Services end of the sample application. But I could not get it to work stably using the other host (such as console, Windows services). So I decided to stay with hosting the Indigo Service inside such a Web Directory structure. Future releases of Indigo will un-doubtfully allow WS-AT in any other type of hosts.
You may have noticed two modifications of �behaviors� for Indigo Web Service.
[OperationBehavior(AutoCompleteTransaction = true, AutoEnlistTransaction = true)]
<behavior configurationName="" returnUnknownExceptionsAsFaults="true" />
And let us discuss these behavior changes briefly.
AutoCompleteTransaction= true or false
A participating transactional WebService can impact the Coordinator in one of the four ways:
- it can explicitly vote to complete;
- it can explicitly vote to rollback;
- it can throw an exception;
- it can decide not to vote at all (refrain from voting, abstain).
Here is the C# code representing the above four scenarios:
public void DoNonDBTransaction(int CustomerID)
{
switch (CustomerID)
{
case 1:
OperationContext.Current.SetTransactionComplete();
break;
case 2:
System.Transactions.Transaction.Current.Rollback(new
Exception("Customer has bad credit"));
break;
case 3:
throw new Exception("Unknown exception" +
" for CustomerID=3 and converted into fault");
break;
default:
break;
}
Obviously, the Coordinator has to know if the participant voted complete or rolled back before it made the final decision. And this is where AutoCompleteTransaction
comes into play:
- If
AutoCompleteTransaction =true
, then abstain by a participant implies it is voting complete.
- If
AutoCompleteTransaction =false
, then abstain by a participant implies it is voting rollback.
For example, if we are calling a credit report web service inside a loan transaction context, a customer with no credit (because the credit report Web Service cannot decide), can be either a good customer getting loan or a bad customer not getting loan, depending on our judgment of risk versus revenue. Bottom line, setting AutoCompleteTransaction= true
or false
both make sense in real world.
It is interesting to know that Rollback actually throws a known exception and Indigo actually allows scope.Complete()
to execute in the Coordinator before throwing an exception. For unknown exceptions, Coordinator code will freeze unless we set returnUnknownExceptionAsFaulst="true"
. It is not clear why this WinFx CTP would default this attribute to "false
" instead of true
, since letting the Coordinator know about all exceptions even not related to the transaction seems more appropriate.
How to set up and run the sample code
- Install Windows XP SP2 onto a clean physical PC or virtual PC. Since we are dealing with pre-beta software, mixing up with other application and data is not a good idea.
- Install SQL Server 2000 with Northwind database. Note that I have used sa password=xxxxxx in the sample and you may need to adjust it by changing the connection string in all three configuration files (App.Config, Web.Config and TransactionOriginator.exe.config).
- Install WinFx March 2005 CTP. Please follow the installation instructions for the CTP. Note that WinFx CTP is free to download here while Visual Studio .NET 2005 Beta is for MSDN subscribers only. But to run the sample, you do not need VS.NET 2005. But if you do want to use VS.NET, the sample will work only with VC.NET 2005 Feb 2005 CTP.
- Unzip the sample code file into c:\TransactionalWebService directory and allow Web Sharing this directory as TransactionalWS.
- Use IIS MMC to configure the TransactionWS virtual directory as a Web Application and set security to allow Anonymous and Windows Integrated Authentication:
- Use the IIS MMC to edit the .svc application extension to allow all verbs: (I did this after the sample code refused to run for errors like �bad request�, �Invalid Content type�. So this is just my tweak for pre-beta software and I do not fully understand why.)
- Restart IIS and start MSDTC service (e.g.: type �iisreset�, �net start msdtc� in a command prompt).
- Open a DOS prompt and change to C:\TransactionalWebService\ TransactionalWebService\TransactionOriginator\bin\Debug.
And run TransactionOriginator.exe and you should see results like the following:
- You may use the SQL Query Analyzer to execute �
Select * from Orders
� against the Northwind database and you should see the first two OrderDate
get updated:
- For customer ID =1, two dates get updated, meaning complete.
- For customer ID =2, none get updated, due to explicit Rollback in Web Service.
- For customer ID =3, none get updated, due to unknown exception.
- For customer ID =4, two get updated, meaning complete (since we set
AutoCompleteTransaction =true
).
Warning: Since this is pre-beta software, there could be abnormal behavior for sample code. For example, I have experienced sudden break-down of the sample and had to re-build the Web Directory, re-start IIS/DTC etc. Also, future releases of CTP or Beta may break the code.
Conclusion
Indigo transaction programming is relative simple and straightforward, if we spend enough time to understand the basics of WS-AT model. Hope this article and included sample code proved just that.