
he previous two articles in this series provided an
overview of the Windows Communication Foundation (WCF, formerly code-named Indigo) programming model, service addressing, binding and contracts, and a discussion of how to
secure Windows Communication Foundation services using the built-in attribution method.
If you haven't already done so, it would be a good idea to go through those articles to get up and running quickly with WCF. In particular, if you are having any trouble getting a development environment installed and working, you'll find that those articles explain the process.
The pillars of WCF are intended to help you build secure, reliable, and transactable services for .NET. In this article, you will build on the code from the previous two articles to create your first
transactable service.
Transactions in WCF
Transactions ensure that a group of related operations occur as a single atomic unit. In other words, every operation in the unit must either all succeed or all fail. WCF provides a centralized transaction system that you can use to handle transaction operations for you. In the past, transaction logic was commonly available as a standard way to handle database transactions (remember
BeginTrans and
CommitTrans in VB?), but there was no standard way to perform non-database transactions. WCF aims to solve this with a single unified transaction system that you can use for database, communications, or other transactable actions.
The WCF programming model makes transactions very easy to use. You group operations into a transaction
scope. This scope defines the atom of a transaction. The following pseudo code demonstrates this:
Using(TransactionScope theScope = new
TransactionScope())
{
Service1.submitRequest(myRequest);
Service2.submitRequest(myOtherRequest);
Service3.submitRequest(myFinalRequest);
theScope.Complete();
}
Building Your First Transactable Service
To get started, you first need a service that is transactable. This means that you are willing to let your service participate in a client-initiated transaction on your contracted operations. You will specify (using attributes and configuration files) how this transaction will behave. After building the service, you'll step through the process of building a client that uses a transaction scope when calling the service. If you have set up Visual Studio.NET as outlined in the
WCF security article, you should be able to get up and running very quickly.
To get started, create a new Web Site in Visual Studio.NET. Select the IndigoService project type, and call it
TService. Your screen should look like
Figure 1.
 | |
| Figure 1. Creating a New Indigo Service: After selecting the Indigo Service project type and giving it the name TService, your screen will look like this. |
Visual Studio will create a default service for you with an interface called IMyService, and a service class called MyService. You'll find the service class code in the
Service.cs file in the
App_Code subfolder. Replace the code in this file with the code shown in
Listing 1. You'll also need to add a reference to the System.Transactions namespace for the code to work correctly when you compile it.
Note that the code for the transactable Web service is identical to the original "temperatures" Web service from the
primer tutorial except for the new attributes that describe it as being transactable.
At the interface level, when the methods are defined, the OperationContract is attributed with a
[TransactionFlow] attribute. This notifies the runtime of how to respond in a transaction situation when operating on the OperationContract in question. The valid values are
Allowed whereby the operation may or may not be used in a transaction;
NotAllowed where it is never to be used in a transaction, and
Required where it must only be used within a transaction scope.
At the service level, the
[ServiceBehavior] attribute specifies the transaction properties, using the
TransactionIsolationLevel property. Ideally transactions should exhibit the four key properties of being
atomic,
consistent,
isolated and
durable, described by the acronym ACID. However, keeping them fully isolated from one another can result in resource locks being held longer than necessary, which can cause lock contention, degrading performance, or even deadlock situations, where different transactions each need locks held by the other transaction to complete. Setting the isolation level lets you choose the degree of isolation most compatible with other applications. You set the isolation level by setting the
IsolationLevel property value to the ServiceBehavior's
TransactionIsolationLevel property value. In
Listing 1, the
IsolationLevel is set to
ReadCommited, meaning that volatile data cannot be read in the transaction, but can be modified. For a full exploration of the different isolation level property values you can use, refer to the documentation for the System..Transactions.IsolationLevel enumeration. Note that there's also a System.Data.IsolationLevel enumeration, so if you refer to the System.Data namespace in your code then you should fully qualify the IsolationLevel to avoid confusion.
Finally,
Listing 1 defines the transaction behavior for the Web method within the service using the
[OperationBehavior] attribute, which has its
TransactionScope property set to
true. This
TransactionScope setting indicates that the operation must be called within a scope (as will be demonstrated within the client). The
TransactionAutoComplete property is also set to
true to indicate that the transaction will be flagged as complete when the method finishes executing. If the
TransactionAutoComplete property were set to false, then you would have to call the
OperationContext.Current.SetTransactionComplete() method to set the correct completion of the transaction manually; otherwise the transaction would flag it as failed.