Using Transactions in the ShoppingCartService Service


The ShoppingCartService service currently enables users to add items to their shopping cart, but it does not perform many of the consistency checks that a production application should include. For example, the service always assumes that goods are in stock when adding them to the user’s shopping cart and makes no attempt to update stock levels. You will rectify these shortcomings in the exercises in this section.

Implementing OLE Transactions

You will start by examining how to configure a WCF service to use transactions with a TCP endpoint. Endpoints established by using the TCP transport can incorporate OLE transactions.

Enable transactions in the ShoppingCartService service

  1. Using Visual Studio 2005, open the solution file image from book ShoppingCartService.sln located in the Microsoft Press\WCF Step By Step\Chapter 8\ShoppingCartService folder under your \My Documents folder.

    This solution contains a modified copy of the ShoppingCartService, and ShoppingCartServiceHost and ShoppingCartClient projects from Chapter 7, “Maintaining State and Sequencing Operations.” The ShoppingCartHost project exposes a TCP endpoint rather than the HTTP endpoint that you configured in Chapter 7, and the ShoppingCartClient application has been simplified and modified to communicate using this TCP endpoint.

  2. Add a reference to the System.Transactions assembly to the ShoppingCartService project. This assembly contains some of the classes and attributes required to manage transactions. Some other types and attributes you will use are in the System.ServiceModel assembly, which is already referenced by the ShoppingCartService project.

  3. Edit the image from book ShoppingCartService.cs file in the ShoppingCartService project.

  4. Locate the ServiceBehavior attribute preceding the ShoppingCartServiceImpl class. Add the TransactionIsolationLevel property, shown in bold below, to this attribute:

     [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession, TransactionIsolationLevel=System.Transactions.IsolationLevel.RepeatableRead)] public class ShoppingCartServiceImpl : IShoppingCartService {      }

    The TransactionIsolationLevel property determines how the database management system (SQL Server in the exercises in this book) lets concurrent transactions overlap. In a typical system, you need to allow multiple concurrent users to access the database at the same time. However, this can lead to problems if two users try to modify the same data at the same time or one user tries to query data that another user is modifying. You must ensure that concurrent users cannot interfere adversely with each other–they must be isolated. Typically, whenever a user modifies, inserts, or deletes data during a transaction, the database management system locks the affected data until the transaction completes. If the transaction commits, the database management system makes the changes permanent. If an error occurs and the transaction rolls back, the database management system undoes the changes. The TransactionIsolationLevel property specifies how the locks taken out during a transaction when it modifies data affect other transactions attempting to access the same data. It can take one of several values. The most common ones are:

    • IsolationLevel.ReadUncommitted. This isolation level enables the transaction to read data that another transaction has modified and locked but not yet committed. This isolation level provides the most concurrency, at the risk of the user being presented with “dirty” data that might change unexpectedly if the modifying transaction rolls back the changes rather than committing them.

    • IsolationLevel.ReadCommitted. This isolation level prevents the transaction from reading data that another transaction has modified, but not yet committed. The reading transaction will be forced to wait until the modified data is unlocked. Although this isolation level prevents read access to dirty data, it does not guarantee consistency; if the transaction reads the same data twice, there is the possibility that another transaction might have modified the data in between reads, so the reading transaction would be presented with two different versions of the data.

    • IsolationLevel.RepeatableRead. This isolation level is similar to the ReadCommitted isolation level but causes the transaction reading the data to lock it until the reading transaction finishes (the ReadCommitted isolation level does not cause a transaction to lock data that it reads). The transaction can then safely read the same data as many times as it wants and it cannot be changed by another transaction until this transaction has completed. This isolation level therefore provides more consistency, at the cost of reduced concurrency.

    • IsolationLevel.Serializable. This isolation level takes the RepeatableRead isolation level one stage further. When using the RepeatableRead isolation level, data read by a transaction cannot change. However, it is possible for a transaction to execute the same query twice and obtain different results if another transaction inserts data that matches the query criteria: new rows suddenly appear. The Serializable isolation level prevents this inconsistency from occurring by restricting the rows that other concurrent transactions can add to the database. This isolation level provides the greatest degree of consistency, but the degree of concurrency can be significantly reduced.

    Unless you have good reason to choose otherwise, use the IsolationLevel.RepeatableRead isolation level.

  5. In the ShoppingCartServiceImpl class, add the following OperationBehavior attribute to the AddItemToCart method:

     [OperationBehavior(TransactionScopeRequired=true,                    TransactionAutoComplete=false)] public bool AddItemToCart(string productNumber) {      }

    You are going to modify the AddItemToCart method to check the level of stock for the selected product and modify the stock level if the product is available. The client application should only invoke this operation in the context of a transaction, to ensure that the changes can be undone if some sort of failure occurs. Setting the TransactionScopeRequired property of the OperationBehavior attribute to true forces the operation to execute as part of a transaction: either the client application must initiate the transaction (you will see how to do this shortly) or the WCF runtime will automatically create a new transaction when this operation runs.

    The TransactionAutoComplete property specifies what happens to the transaction when the operation finishes. If you set this property to true, the transaction automatically commits and makes all its changes permanent. Setting this property to false keeps the transaction active; the changes are not committed yet. The default value for this property is true. In the case of the AddItemToCart method, you don’t want to commit changes and finish the transaction until the user has checked out, so the code sets this property to false.

  6. Add the TransactionFlow attribute shown below to definition of the AddItemToCart method in the IShoppingCartService interface, after the OperationContract attribute. Add the same attribute to the remaining methods in this interface, as shown in bold below:

     public interface IShoppingCartInterface {     [OperationContract(Name = "AddItemToCart", IsInitiating = true)]     [TransactionFlow(TransactionFlowOption.Mandatory)]     public bool AddItemToCart(string productNumber)     [OperationContract(Name = "RemoveItemFromCart", IsInitiating = false)]     [TransactionFlow(TransactionFlowOption.Mandatory)]     bool RemoveItemFromCart(string productNumber);     [OperationContract(Name = "GetShoppingCart", IsInitiating = false)]     [TransactionFlow(TransactionFlowOption.Mandatory)]     string GetShoppingCart();     [OperationContract(Name = "Checkout", IsInitiating = false,                        IsTerminating = true)]     [TransactionFlow(TransactionFlowOption.Mandatory)]     bool Checkout(); }

    The description of the TransactionScopeRequired property in the previous step mentioned that the WCF runtime automatically creates a new transaction when invoking an operation if necessary. In the shopping cart scenario, you want the client application to be responsible for creating its own transactions. You can enforce this rule by applying the TransactionFlow attribute to the operation contract. Specifying a parameter of TransactionFlowOption.Mandatory indicates that the client application must create a transaction before calling this operation and send the details of this transaction as part of the SOAP message header when invoking the operation. The other values you can specify are TransactionFlowOption.Allowed, which will use a transaction created by the client if one exists but the WCF runtime will create a new transaction if not, and TransactionFlowOption.NotAllowed, which will always cause the WCF runtime to disregard any client transaction and always create a new one.

    The default value is TransactionFlowOption.NotAllowed.

  7. You can now amend the code in the ShoppingCartServiceImpl class to check stock levels and update them in the database, safe in the knowledge that this functionality is protected by transactions–if anything should go wrong, the changes will be rolled back automatically.

    Add the decrementStock method, shown below, to the ShoppingCartServiceImpl class:

     public bool decrementStock(string productNumber) {     // Update the first row for this product in the     // ProductInventory table that has a quantity value     // of greater than zero     try     {         Database dbAdventureWorks =             DatabaseFactory.CreateDatabase("AdventureWorksConnection");         string inventoryUpdate =                 @"UPDATE Production.ProductInventory                 SET Quantity = Quantity - 1                 WHERE rowguid IN                  (SELECT TOP(1) rowguid                   FROM Production.ProductInventory                   WHERE Quantity > 0                   AND ProductID IN                       (SELECT ProductID                        FROM Production.Product                        WHERE ProductNumber = '" + productNumber + "'))";         // Execute the update statement and verify that it updated one row         // If it did, then return true.         // If it did not, then either the product does not exist,         // or there are none in stock, so return false         int numRowsChanged = (int)dbAdventureWorks.ExecuteNonQuery(             CommandType.Text, inventoryUpdate);         return (numRowsChanged == 1);     }     // On an exception, indicate failure     catch (Exception e)     {         return false;     } }

    Note 

    The code for this method is available in the file DecrementStock.txt located in the Chapter 8 folder. As with previous examples using SQL statements, this method is greatly simplified for clarity (and because this is a book about WCF rather than how to write database access code) and does no checking for a SQL Injection attack or any other potential hazards.

    The purpose of this method is to verify that the specified product is available and then update the stock level for this product. If you recall from Chapter 1, “Introducing Windows Communication Foundation,” a product can be stored in more than one location and so have more than one row in the ProductInventory table. The rather complicated-looking SQL UPDATE statement in this method updates the first row for the product that it finds in the ProductInventory table and that has a quantity of greater than zero. If the update fails to modify a row, this method returns false to indicate either insufficient stock (all rows have a zero for the quantity) or that no such product exists (there are no rows). If the update changes exactly one row, then this method returns true to indicate success.

    Note 

    Strictly speaking, the service should save the value in the rowguid column of the row it updates in the ProductInventory table, so that the corresponding row can be incremented again if the user decides to remove the item from the shopping cart later. This functionality is left as an exercise for you to perform in your own time.

    It is also possible for this method to cause a database deadlock if multiple service instances execute it simultaneously. In this situation, SQL Server picks one of the transactions (referred to rather prosaically as the “victim” by SQL Server) and aborts it, releasing any locks held and hopefully enabling other concurrent transactions to complete. This will cause the UPDATE operation to fail and the ExecuteNonQuery command to throw an exception. If this happens, the method returns false. The important point to learn from this is that using transactions ensures that the database will remain consistent, even in the face of unforeseen eventualities.

  8. In the AddItemToCart method, change the code that increments the volume of an item in the shopping cart also to update the stock level in the database, as shown in bold below:

     … // If so, increment the volume if (item != null) {     if (decrementStock(productNumber))     {         item.Volume++;         return true;     }     else     {         return false;     } }  

  9. Modify the else statement to check that sufficient stock is available in the database before retrieving the details of the product from the database, as shown in bold below:

     … // Otherwise retrieve the details of the product from the database else if (decrementStock(productNumber)) {      // Connect to the AdventureWorks database       } else {     return false; } catch (Exception e) {       }

    Leave the block of code that connects to the database and retrieves the product details untouched. Be sure to add the additional else statement to the end of the block, immediately before the catch block, as shown above.

  10. Add an OperationBehavior attribute to the RemoveItemFromCart method, setting the TransactionScopeRequired property to true and the TransactionAutoComplete property to false. The method should look like this:

     [OperationBehavior(TransactionScopeRequired=true,                    TransactionAutoComplete=false)] public bool RemoveItemFromCart(string productNumber) {      }

    Note 

    If you have the time, you might care to add the appropriate code to this method to increment the stock level in the database after removing the item from the shopping cart.

  11. Add another OperationBehavior attribute to the GetShoppingCart method, setting the TransactionScopeRequired property to true and the TransactionAutoComplete property to false:

     [OperationBehavior(TransactionScopeRequired=true,                    TransactionAutoComplete=false)] public bool GetShoppingCart() {      }

    The GetShoppingCart method does not actually query or modify the database but could be (and probably would be) called by the client application during a transaction. It is important that this method does not commit the transaction, hence the need to set the TransactionAutoComplete property to false. You cannot set the TransactionAutoComplete property to false without setting the TransactionScopeRequired property to true.

  12. Add a final OperationBehavior attribute to the Checkout method, setting the TransactionScopeRequired property to true and the TransactionAutoComplete property to false:

     [OperationBehavior(TransactionScopeRequired=true,                    TransactionAutoComplete=false)] public bool Checkout() {      }

Having modified the code in the service, you must also change the configuration of the service endpoint to enable the WCF runtime to “flow” transactions from the client application into the service. Information about transactions is included in the headers of the SOAP messages sent by client applications invoking the operations.

Configure the ShoppingCartService service to flow transactions from client applications

  1. Edit the image from book App.config file for the ShoppingCartHost project by using the WCF Service Configuration Editor.

  2. In the WCF Service Configuration Editor, in the left pane, click the Bindings folder. In the right pane, click the New Binding Configuration link.

  3. In the Create a New Binding dialog box, select the netTcpBinding binding type, and then click OK.

  4. In the right pane, change the Name property of the binding to ShoppingCartServiceNetTcpBindingConfig. In the General section of the pane, set the TransactionFlow property to True. Verify that the TransactionProtocol property is set to OleTransactions.

    The TransactionFlow property indicates that the service should expect to receive information about transactions in the SOAP messages it receives.

    The TransactionProtocol property specifies the transaction protocol the service should use. By default, endpoints based on the TCP transport use the internal DTC protocol when performing distributed transactions. However, you can configure them to use transactions that follow the WS-AtomicTransaction protocol by changing this property to WSAtomicTransactionOctober2004 (you can probably guess the date of the WS-AtomicTransaction specification to which WCF conforms).

  5. In the left pane, in the Services folder expand the ShoppingCartService.ShoppingCartServiceImpl node, expand the Endpoints folder, and then click the ShoppingCartServiceNetTcpEndpoint node. In the right pane, set the BindingConfguration property of the endpoint to ShoppingCartServiceNetTcpBindingConfig.

  6. Save the configuration file, and then exit the WCF Service Configuration Editor.

  7. In Visual Studio 2005, double-click the image from book App.config file in the ShoppingCartHost project to display it in the code view window. Locate the connection string that the service uses for connecting to the AdventureWorks database. Modify this connection string to include support for multiple active result sets:

     <connectionStrings>   <add name="AdventureWorksConnection" connectionString=     "Database=AdventureWorks;Server=(local)\SQLEXPRESS;Integrated Security=SSPI; MultipleActiveResultSets=True;"     providerName="System.Data.SqlClient" /> </connectionStrings>

    SQL Server 2005 requires you to enable multiple active result sets, also known as MARS, to operate with DTC in this environment.

You have configured the ShoppingCartService service to expect the client application to invoke operations within the scope of a transaction. You now need to modify the client application to actually create this transaction.

Create a transaction in the client application

  1. In Visual Studio 2005, add a reference to the System.Transactions assembly to the ShoppingCartClient project.

  2. Edit the image from book Program.cs file in the ShoppingCartClient project, and add the following using statement to the list at the top of the file:

     using System.Transactions;

  3. In the Main method, surround the statements that invoke the operations in the ShoppingCartService service with the using block shown in bold below:

     TransactionOptions tOpts = new TransactionOptions(); tOpts.IsolationLevel = IsolationLevel.RepeatableRead; tOpts.Timeout = new TimeSpan(0, 1, 0); using (TransactionScope tx =     new TransactionScope(TransactionScopeOption.RequiresNew, tOpts)) {     // Add two water bottles to the shopping cart     proxy.AddItemToCart("WB-H098");     proxy.AddItemToCart("WB-H098");     // Add a mountain seat assembly to the shopping cart     proxy.AddItemToCart("SA-M198");     // Query the shopping cart and display the result     string cartContents = proxy.GetShoppingCart();     Console.WriteLine(cartContents);     // Buy the goods in the shopping cart     proxy.Checkout();     Console.WriteLine("Goods purchased"); }     // Disconnect from the ShoppingCartService service proxy.Close();

    You can create a new transaction in several ways: a service can initiate a new transaction automatically by setting the TransactionScopeRequired attribute of the OperationBehavior property to true as described earlier, an operation can explicitly start a new transaction by creating a CommittableTransaction object, or the client application can implicitly create a new transaction. In a WCF client application, the recommended approach is to use a TransactionScope object.

    When you create a new TransactionScope object, any transactional operations that follow are automatically enlisted into a transaction. If the WCF runtime detects that there is no active transaction when you create a new TransactionScope object, it can initiate a new transaction and performs the operations in the context of this transaction. In this case, the transaction remains active until the TransactionScope object is destroyed. For this reason, it is common practice to use a using block to explicitly delimit the scope of a transaction.

    The TransactionScopeOption parameter to the TransactionScope constructor determines how the WCF runtime uses an existing transaction. If this parameter is set to TransactionScopeOption.RequiresNew, the WCF runtime will always create a new transaction. The other values you can specify are TransactionScopeOption.Required, which will only create a new transaction if there is not already another transaction in scope (referred to as the “ambient transaction”), and TransactionScopeOption.Suppress, which causes all operations in the context of the TransactionScope object to be performed without using a transaction (operations will not participate in the ambient transaction, if there is one).

    The transaction isolation level of any new transactions should match the requirements of the service. You can specify the isolation level by creating a TransactionOptions object and referencing it in the TransactionScope constructor, as shown in the code. You can also specify a timeout value for transactions. This can improve the responsiveness of an application, as transactions will not wait for an indeterminate period for resources locked by other transactions to become available–the WCF runtime throws an exception that the client application should be prepared to handle.

  4. Add the if block and statement shown below around the code that invokes the Checkout operation:

     // Buy the goods in the shopping cart if (proxy.Checkout()) {     tx.Complete();     Console.WriteLine("Goods purchased"); } 

    By default, when the flow of control leaves the using block (either by the natural flow of the code or because of an exception), the transaction will be aborted and the work it performed undone. This is probably not what you want! Calling the Complete method on the TransactionScope object before destroying it indicates that work has been completed successfully and that the transaction should be committed. In the ShoppingCartService service, the Checkout method returns true if the checkout operation is successful, false otherwise. If the Checkout method fails and returns false, the Complete method will not be called and any changes made to the database by the transaction will be rolled back.

    Note 

    Calling the Complete method does not actually guarantee that your work will be committed. It indicates only that the work performed inside the transaction scope was successful and can be committed in the absence of any other problems. You can nest transaction scopes; you can create a new TransactionScope object inside the using statement of another TransactionScope object. If the nested TransactionScope object creates a new transaction (called a nested transaction), calling the Complete method on the nested TransactionScope object commits the nested transaction with respect to the transaction (called the parent transaction) used by the outer TransactionScope object. If the parent transaction aborts, then the nested transaction will also be aborted.

  5. In an earlier exercise, you modified the contract for the ShoppingCartService by adding the TransactionFlow attribute to each operation. You must therefore update the proxy that the client application uses to ensure that the proxy sends the details of transactions to the service. You can either perform this task by regenerating the code for the proxy class by using the svcutil utility (Chapter 7 contains the steps for doing this) or you can modify the code manually. In this example it is instructive to perform this task by hand and edit the code yourself, as follows:

    • Open the image from book ShoppingCartServiceProxy.cs file in the ShoppingCartClient project.

    • Add the following using statement to the top of the file:

       using System.ServiceModel;

    • Locate the ShoppingCartService interface. This is the first interface in the ShoppingCartCient.ShoppingCartService interface.

    • Add the TransactionFlow attribute to each method in this interface, as shown in bold below. Do not change any other code or attributes in this interface (the properties of the OperationContractAttribute for each method have been omitted, for clarity–leave these intact in your code):

       public interface ShoppingCartService {     [System.ServiceModel.OperationContractAttribute(…)]     [TransactionFlow(TransactionFlowOption.Mandatory)]     bool AddItemToCart(string productNumber);     [System.ServiceModel.OperationContractAttribute()]     [TransactionFlow(TransactionFlowOption.Mandatory)]     bool RemoveItemFromCart(string productNumber);     [System.ServiceModel.OperationContractAttribute()]     [TransactionFlow(TransactionFlowOption.Mandatory)]     string GetShoppingCart();     [System.ServiceModel.OperationContractAttribute()]     [TransactionFlow(TransactionFlowOption.Mandatory)]     bool Checkout(); }

The final step is to configure the endpoint for the client application to send information about its transactions across the network to the service.

Configure the client application to flow transactions to the ShoppingCartService service

  1. Edit the image from book App.config file for the ShoppingCartClient project by using the WCF Service Configuration Editor.

  2. In the WCF Service Configuration Editor, in the left pane, click the Bindings folder. In the right pane, click the New Binding Configuration link.

  3. In the Create a New Binding dialog box, select the netTcpBinding binding type and then click OK.

  4. In the right pane, change the Name property of the binding to ShoppingCartClientNetTcpBindingConfig. In the General section of the pane, set the TransactionFlow property to True and verify that the TransactionProtocol property is set to OleTransactions.

  5. In the left pane, select the NetTcpBinding_ShoppingCartService node in the Endpoints folder under the Client folder. In the right pane, set the BindingConfguration property of the endpoint to ShoppingCartClientNetTcpBindingConfig.

  6. Save the configuration file and then exit the WCF Service Configuration Editor.

You can now test the transactional version of the ShoppingCartService service and the client application.

Test the transactional implementation of the ShoppingCartService service

  1. On the Windows Start menu, open the Control Panel, click Performance and Maintenance, click Administrative Tools, and then double-click Component Services.

    The Component Services console appears. You can use this console to monitor the transactions being processed by DTC.

    Note 

    If you are using Windows Vista, open Windows Explorer, move to the C:\Windows\System32 folder, and then double-click the file comexp.msc to start the Component Services console.

  2. In the Component Services console, expand the Component Services node, expand the Computers folder, right-click the My Computer node and then click Stop MS DTC. Right-click the My Computer node and then click Start MS DTC.

    Stopping and restarting DTC clears its statistics, so you can more easily monitor the progress of your transactions.

    Note 

    The Component Services console under Windows Vista does not provide the facility for stopping and restarting MS DTC, so you will have to work without clearing the statistics.

  3. Under the My Computer node, expand the Distributed Transaction Coordinator folder and then click Transaction Statistics.

    Note 

    Under Windows Vista, expand the Local DTC node in the Distributed Transaction Coordinator folder, and then click Transaction Statistics.

    The right pane displays the statistics, which should all currently be set to zero (unless you are using Windows Vista).

  4. Return to Visual Studio 2005, and start the solution without debugging.

    Note 

    If a Windows Security Alert appears, click Unblock to allow the service to use TCP port 9080.

    In the ShoppingCartClient console window displaying the message “Press ENTER when the service has started,” press Enter.

    The client application displays the shopping cart containing two water bottles and a mountain seat assembly, followed by the “Goods purchased” method. However, there also appears to be a problem as the application throws an exception reporting, “The transaction has aborted”:

    image from book

    Press Enter to close the client application console window. In the host application console window, press Enter to stop the service.

  5. Switch to the Component Services console. It should confirm that the one transaction you have performed since you restarted DTC has aborted:

    image from book

    The problem is actually quite subtle. Remember that the Complete method of a TransactionScope object indicates only that the transaction can be committed. However, before committing a transaction, the transaction must have actually done some work and completed this work successfully. Although the AddItemToCart operation invoked in the ShoppingCartService service clearly updates the database, it never actually indicates that the work was successfully completed. The same is true of the other operations. Consequently, when the runtime examines the state of the transaction created for the TransactionScope object, in the absence of any information indicating success, it decides to abort the transaction and rollback the changes.

    You need to make some modifications to the ShoppingCartService service to indicate when a transaction has completed successfully. Bear in mind that you can complete a transaction only once, so in the shopping cart scenario, the best place to do this is in the Checkout method.

  6. In Visual Studio 2005, edit the image from book ShoppingCartService.cs file in the ShoppingCartService project and find the Checkout method towards the end of the file. The OperationBehavior attribute for this method currently sets the TransactionAutoComplete property to false. You could set this property to true, and this would cause the transaction to complete successfully at the end of the method, as long as it did not throw an unhandled exception (if the method throws an exception that you handle in the same method, the transaction will not abort). But in the real world, you would probably want to be a bit more selective than this; for example, the transaction should only commit if this method ascertains that the user has a valid account with Adventure-Works, for billing purposes. However, for this exercise you will simply add a statement that indicates that the transaction can be committed.

    Modify the code in the Checkout method as shown in bold below:

     [OperationBehavior(TransactionScopeRequired = true,                    TransactionAutoComplete = false)] public bool Checkout() {     // Not currently implemented     // - just indicate that the transaction completed successfully     // and return true     OperationContext.Current.SetTransactionComplete();     return true; }

    The OperationContext object provides access to the execution context of the operation. The SetTransactionComplete method of the Current property indicates that the current transaction has completed successfully and can be committed when the client application calls the Complete method of the TransactionScope object containing this transaction. If you need to abort the transaction, just exit the method without calling the SetTransactionComplete method, as you did before.

    Note 

    Calling the SetTransactionComplete method indicates that you have finished all the transactional work. If a transaction spans multiple operations, you cannot invoke any further operations that have the TransactionScopeRequired property of the OperationBehavior attribute set to true and that execute in the same transaction scope. Additionally, you can call the SetTransactionComplete method only once in a transaction. A subsequent call to this method inside the scope of the same transaction will raise an exception. Finally, if you call the SetTransactionComplete method, but later fail to call the Complete method of the TransactionScope object, the transaction will be silently rolled back.

  7. Start the solution without debugging. In the ShoppingCartClient console window, press Enter.

    This time, the client application executes without reporting the message, “The transaction has aborted.”

  8. Press Enter to close the client application console window. In the host application console window, press Enter to stop the service.

  9. Return to the Component Services console. This time, you can see that the transaction committed:

    image from book

  10. To verify that the database is being updated, open a Visual Studio 2005 command prompt window, and move to the Microsoft Press\WCF Step By Step\Chapter 8 folder under your \My Documents folder. Type the following command:

     StockLevels

    This command executes a script that queries the AdventureWorks database, displaying the current stock level of water bottles and mountain seat assemblies:

    image from book

    Make a note of these stock levels.

    Note 

    Your stock levels might be different from those shown in this image.

  11. Leave the command prompt window open and return to Visual Studio 2005. Start the solution again without debugging. In the ShoppingCartClient console window, press Enter. When the client application has finished, press Enter to close the client application console window. In the host application console window, press Enter to stop the service.

  12. Return to the command prompt window and execute the StockLevels command again. Verify that the stock level for water bottles has decreased by two, and the stock level for mountain seat assemblies has decreased by one.

  13. Examine the transaction statistics in the Component Services console. You should see that the number of committed transactions is now 2.

  14. Close the Component Services console.

Implementing WS-AtomicTransaction Transactions

The NetTcpBinding binding uses OLE transactions and Microsoft’s own protocol for communicating through DTC to other Microsoft-specific services, such as SQL Server. In a heterogeneous environment, you cannot use OLE transactions. Instead, you should use a more standardized mechanism such as the WS-AtomicTransaction protocol. When using the NetTcpBinding or NetNamedPipeBinding bindings, you can explicitly specify which transaction protocol to use by setting the TransactionProtocol property that these bindings provide. With the HTTP family of bindings, the WCF runtime itself selects the transaction protocol based on the Windows configuration, the transport you are using, and the format of the SOAP header used to flow the transaction from the client application to the service. For example, a WCF client application connecting to a WCF service through an endpoint based on the “http” scheme will use OLE transactions. If the computers hosting the WCF client application and WCF service are configured to support the WS-AtomicTransaction protocol over a specific port, and the client application connects to the service through an endpoint based on the “https” scheme that uses this port, then transactions will follow the WS-AtomicTransaction protocol.

The choice of transaction protocol should be transparent to your services and client applications. The code that you write to initiate, control, and support transactions based on the WS-AtomicTransaction protocol is the same as that for manipulating OLE transactions; the same service can execute using OLE transactions or WS-AtomicTransaction transactions, depending on how you configure the service.

Important 

The BasicHttpBinding binding does not support transactions (OLE or WS-AtomicTransaction).

If you wish to use the implementation of the WS-AtomicTransaction protocol provided by the .NET Framework 3.0 with the HTTP bindings, you must configure support for the WS-AtomicTransaction protocol in DTC.

Note 

If you are using Windows XP, Service Pack 2, you must install the hotfix for adding WS-AtomicTransaction support to DTC. You can download this hotfix from the Microsoft Web site at http://www.microsoft.com/downloads/details.aspx?familyid=&displaylang=en.

The .NET Framework 3.0 contains a command line tool called wsatConfig.exe that you can use to configure WS-AtomicTransaction protocol support. The Microsoft Windows SDK provides a graphical user interface component that performs the same tasks, and that plugs into the Component Services console, as shown in Figure 8-1. You can access this interface by opening the Properties dialog box for My Computer and clicking the WS-AT tab.

image from book
Figure 8-1: The WS-AtomicTransaction configuration tab in the Component Services console.

The implementation of the WS-AtomicTransaction protocol over HTTP requires mutual authentication, integrity, and confidentiality for all messages. This means that you must use the HTTPS transport. If the WCF service listens on a port other than 443 (the default HTTPS port), you should specify the port in the WS-AT tab. You must also provide a certificate that the service can use to encrypt messages. Additionally, the WS-AT tab lets you specify which users are authorized to access your service, identifying these accounts by their Windows credentials or certificates.

Note 

You must register the assembly that implements the user interface before you can use it in the Component Services console. From a command prompt window, move to the \Program Files\Microsoft SDKs\Windows\V6.0\Bin folder and type the following command:

 regasm /codebase WsatUI.dll

More Info 

See Chapter 5, “Protecting a WCF Service over the Internet,” for more details about configuring SSL and using certificates to identify users.




Microsoft Windows Communication Foundation Step by Step
Microsoft Windows Communication Foundation Step by Step (Step By Step Developer Series)
ISBN: 0735623368
EAN: 2147483647
Year: 2007
Pages: 105
Authors: John Sharp

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net