Distributed transaction using WCF

0
111
12 min read

(Read more interesting articles on WCF 4.0 here.)

Creating the DistNorthwind solution

In this article, we will create a new solution based on the LINQNorthwind solution.We will copy all of the source code from the LINQNorthwind directory to a new directory and then customize it to suit our needs. The steps here are very similar to the steps in the previous chapter when we created the LINQNorthwind solution.Please refer to the previous chapter for diagrams.

Follow these steps to create the new solution:

  1. Create a new directory named DistNorthwind under the existing C:SOAwithWCFandLINQProjects directory.
  2. Copy all of the files under the C:SOAwithWCFandLINQProjectsLINQNorthwind directory to the C:SOAwithWCFandLINQProjectsDistNorthwind directory.
  3. Remove the folder, LINQNorthwindClient. We will create a new client for this solution.
  4. Change all the folder names under the new folder, DistNorthwind, from LINQNorthwindxxx to DistNorthwindxxx.
  5. Change the solution files’ names from
  6. LINQNorthwind.sln to DistNorthwind.sln, and also from LINQNorthwind.suo to DistNorthwind.suo.

Now we have the file structures ready for the new solution but all the file contents and the solution structure are still for the old solution. Next we need to change them to work for the new solution.

We will first change all the related WCF service files. Once we have the service up and running we will create a new client to test this new service.

  1. Start Visual Studio 2010 and open this solution: C:SOAWithWCFandLINQProjectsDistNorthwindDistNorthwind.sln.
  2. Click on the OK button to close the projects were not loaded correctly warning dialog.
  3. From Solution Explorer, remove all five projects (they should all be unavailable).
  4. Right-click on the solution item and select Add | Existing Projects… to add these four projects to the solution. Note that these are the projects under the DistNorthwind folder, not under the LINQNorthwind folder: LINQNorthwindEntities.csproj, LINQNorthwindDAL.csproj, LINQNorthwindLogic.csproj, and LINQNorthwindService.csproj.
  5. In Solution Explorer, change all four projects’ names from LINQNorthwindxxx to DistNorthwindxxx.
  6. In Solution Explorer, right-click on each project, select Properties (or select menu Project | DistNorthwindxxx Properties), then change the Assembly name from LINQNorthwindxxx to DistNorthwindxxx, and change the Default namespace from MyWCFServices.LINQNorthwindxxx to MyWCFServices.DistNorthwindxxx.
  7. Open the following files and change the word LINQNorthwind to DistNorthwind wherever it occurs: ProductEntity.cs, ProductDAO.cs, ProductLogic.cs, IProductService.cs, and ProductService.cs.
  8. Open the file, app.config, in the DistNorthwindService project and change the word LINQNorthwind to DistNorthwind in this file.

The screenshot below shows the final structure of the new solution, DistNorthwind:

Distributed transaction using WCF

Now we have finished modifying the service projects. If you build the solution now you should see no errors. You can set the service project as the startup project, run the program.

Hosting the WCF service in IIS

The WCF service is now hosted within WCF Service Host.We had to start the WCF Service Host before we ran our test client.Not only do you have to start the WCF Service Host, you also have to start the WCF Test client and leave it open. This is not that nice. In addition, we will add another service later in this articleto test distributed transaction support with two databases and it is not that easy to host two services with one WCF Service Host.So, in this article, we will first decouple our WCF service from Visual Studio to host it in IIS.

You can follow these steps to host this WCF service in IIS:

  1. In Windows Explorer, go to the directory C:SOAWithWCFandLINQProjectsDistNorthwindDistNorthwindService.
  2. Within this folder create a new text file, ProductService.svc, to contain the following one line of code:
  3. <%@ServiceHost Service="MyWCFServices.DistNorthwindService.
    ProductService"%>

  4. Again within this folder copy the file, App.config, to Web.config and remove the following lines from the new Web.config file:
  5. <host>
    <baseAddresses>
    <add baseAddress="http://localhost:8080/
    Design_Time_Addresses/MyWCFServices/
    DistNorthwindService/ProductService/" />
    </baseAddresses>
    </host>

  6. Now open IIS Manager, add a new application, DistNorthwindService, and set its physical path to C:SOAWithWCFandLINQProjectsDistNorthwindDistNorthwindService. If you choose to use the default application pool, DefaultAppPool, make sure it is a .NET 4.0 application pool.If you are using Windows XP you can create a new virtual directory, DistNorthwindService, set its local path to the above directory, and make sure its ASP.NET version is 4.0.
  7. From Visual Studio, in Solution Explorer, right-click on the project item,DistNorthwindService, select Properties, then click on the Build Events tab, and enter the following code to the Post-build event command line box: copy .*.* ..With this Post-build event command line, whenever DistNorthwindService is rebuilt the service binary files will be copied to the C:SOAWithWCFandLINQProjectsDistNorthwindDistNorthwindServicebin directory so that the service hosted in IIS will always be up-to-date.
  8. From Visual Studio, in Solution Explorer, right-click on the project item, DistNorthwindService, and select Rebuild.

Now you have finished setting up the service to be hosted in IIS. Open Internet Explorer, go to the following address, and you should see the ProductService description in the browser: http://localhost/DistNorthwindService/ProductService.svc

Testing the transaction behavior of the WCF service

Before explaining how to enhance this WCF service to support distributed transactions, we will first confirm that the existing WCF service doesn’t support distributed transactions. In this article, we will test the following scenarios:

  1. Create a WPF client to call the service twice in one method.
  2. The first service call should succeed and the second service call should fail.
  3. Verify that the update in the first service call has been committed to the database, which means that the WCF service does not support distributed transactions.
  4. Wrap the two service calls in one TransactionScope and redo the test.
  5. Verify that the update in the first service call has still been committed to the database which means the WCF service does not support distributed transactions even if both service calls are within one transaction scope.
  6. Add a second database support to the WCF service.
  7. Modify the client to update both databases in one method.
  8. The first update should succeed and the second update should fail.
  9. Verify that the first update has been committed to the database, which means the WCF service does not support distributed transactions with multiple databases.

Creating a client to call the WCF service sequentially

The first scenario to test is that within one method of the client application two service calls will be made and one of them will fail. We then verify whether the update in the successful service call has been committed to the database. If it has been, it will mean that the two service calls are not within a single atomic transaction and will indicate that the WCF service doesn’t support distributed transactions.

You can follow these steps to create a WPF client for this test case:

  1. In Solution Explorer, right-click on the solution item and select Add | New Project… from the context menu.
  2. Select Visual C# | WPF Application as the template.
  3. Enter DistributedWPF as the Name.
  4. Click on the OK button to create the new client project.

Now the new test client should have been created and added to the solution. Let’s follow these steps to customize this client so that we can call ProductService twice within one method and test the distributed transaction support of this WCF service:

  1. On the WPF MainWindow designer surface, add the following controls (you can double-click on the MainWindow.xaml item to open this window and make sure you are on the design mode, not the XAML mode):
    • A label with Content Product ID
    • Two textboxes named txtProductID1 and txtProductID2
    • A button named btnGetProduct with Content Get Product Details
    • A separator to separate above controls from below controls
    • Two labels with content Product1 Details and Product2 Details
    • Two textboxes named txtProduct1Details and txtProduct2Details, with the following properties:
      • AcceptsReturn: checked
      • Background: Beige
      • HorizontalScrollbarVisibility: Auto
      • VerticalScrollbarVisibility: Auto
      • IsReadOnly: checked
    • A separator to separate above controls from below controls
    • A label with content New Price
    • Two textboxes named txtNewPrice1 and txtNewPrice2
    • A button named btnUpdatePrice with Content Update Price
    • A separator to separate above controls from below controls
    • Two labels with content Update1 Results and Update2 Results
    • Two textboxes named txtUpdate1Results and txtUpdate2Results with the following properties:
      • AcceptsReturn: checked
      • Background: Beige
      • HorizontalScrollbarVisibility: Auto
      • VerticalScrollbarVisibility: Auto
      • IsReadOnly: checked
    • Your MainWindow design surface should look like the following screenshot:
  2. In Solution Explorer, right-click on the DistNorthwindWPF project item, select Add Service Reference… and add a service reference of the product service to the project. The namespace of this service reference should be ProductServiceProxy and the URL of the product service should be like this:http://localhost/DistNorthwindService/ProductService.svc
  3. On the MainWindow.xaml designer surface, double-click on the Get Product Details button to create an event handler for this button.
  4. In the MainWindow.xaml.cs file, add the following using statement: using DistNorthwindWPF.ProductServiceProxy;
  5. Again in the MainWindow.xaml.cs file, add the following two class members: Product product1, product2;
  6. Now add the following method to the MainWindow.xaml.cs file:
  7. private string GetProduct(TextBox txtProductID, ref Product
    product)
    {
    string result = "";
    try
    {
    int productID = Int32.Parse(txtProductID.Text.ToString());
    ProductServiceClient client = new ProductServiceClient();
    product = client.GetProduct(productID);
    StringBuilder sb = new StringBuilder();
    sb.Append("ProductID:" +
    product.ProductID.ToString() + "n");
    sb.Append("ProductName:" +
    product.ProductName + "n");
    sb.Append("UnitPrice:" +
    product.UnitPrice.ToString() + "n");
    sb.Append("RowVersion:");
    foreach (var x in product.RowVersion.AsEnumerable())
    {
    sb.Append(x.ToString());
    sb.Append(" ");
    }
    result = sb.ToString();
    }
    catch (Exception ex)
    {
    result = "Exception: " + ex.Message.ToString();
    }
    return result;
    }

    This method will call the product service to retrieve a product from the database, format the product details to a string, and return the string. This string will be displayed on the screen. The product object will also be returned so that later on we can reuse this object to update the price of the product.

  8. Inside the event handler of the Get Product Details button, add the following two lines of code to get and display the product details: txtProduct1Details.Text = GetProduct(txtProductID1, ref product1); txtProduct2Details.Text = GetProduct(txtProductID2, ref product2);

Now we have finished adding code to retrieve products from the database through the Product WCF service. Set DistNorthwindWPF as the startup project, press Ctrl + F5 to start the WPF test client, enter 30 and 31 as the product IDs, and then click on the Get Product Details button. You should get a window like this image:

To update the prices of these two products follow these steps to add the code to the project:

  1. On the MainWindow.xaml design surface and double-click on the Update Price button to add an event handler for this button.
  2. Add the following method to the MainWindow.xaml.cs file:
  3. private string UpdatePrice(
    TextBox txtNewPrice,
    ref Product product,
    ref bool updateResult)
    {
    string result = "";
    try
    {
    product.UnitPrice =
    Decimal.Parse(txtNewPrice.Text.ToString());
    ProductServiceClient client =
    new ProductServiceClient();
    updateResult =
    client.UpdateProduct(ref product);
    StringBuilder sb = new StringBuilder();
    if (updateResult == true)
    {
    sb.Append("Price updated to ");
    sb.Append(txtNewPrice.Text.ToString());
    sb.Append("n");
    sb.Append("Update result:");
    sb.Append(updateResult.ToString());
    sb.Append("n");
    sb.Append("New RowVersion:");
    }
    else
    {
    sb.Append("Price not updated to ");
    sb.Append(txtNewPrice.Text.ToString());
    sb.Append("n");
    sb.Append("Update result:");
    sb.Append(updateResult.ToString());
    sb.Append("n");
    sb.Append("Old RowVersion:");
    }
    foreach (var x in product.RowVersion.AsEnumerable())
    {
    sb.Append(x.ToString());
    sb.Append(" ");
    }
    result = sb.ToString();
    }
    catch (Exception ex)
    {
    result = "Exception: " + ex.Message;
    }
    return result;
    }

    This method will call the product service to update the price of a product in the database. The update result will be formatted and returned so that later on we can display it. The updated product object with the new RowVersion will also be returned so that later on we can update the price of the same product again and again.

  4. Inside the event handler of the Update Price button, add the following code to update the product prices:
  5. if (product1 == null)
    {
    txtUpdate1Results.Text = "Get product details first";
    }
    else if (product2 == null)
    {
    txtUpdate2Results.Text = "Get product details first";
    }
    else
    {
    bool update1Result = false, update2Result = false;
    txtUpdate1Results.Text = UpdatePrice(
    txtNewPrice1, ref product1, ref update1Result);
    txtUpdate2Results.Text = UpdatePrice(
    txtNewPrice2, ref product2, ref update2Result);
    }

Testing the sequential calls to the WCF service

Let’s run the program now to test the distributed transaction support of the WCF service. We will first update two products with two valid prices to make sure our code works with normal use cases. Then we will update one product with a valid price and another with an invalid price. We will verify that the update with the valid price has been committed to the database, regardless of the failure of the other update.

Let’s follow these steps for this test:

  1. Press Ctrl + F5 to start the program.
  2. Enter 30 and 31 as product IDs in the top two textboxes and click on the Get Product Details button to retrieve the two products. Note that the prices for these two products are 25.89 and 12.5 respectively.
  3. Enter 26.89 and 13.5 as new prices in the middle two textboxes and click on the Update Price button to update these two products. The update results are true for both updates, as shown in following screenshot:
  4. Now enter 27.89 and -14.5 as new prices in the middle two textboxes and click on the Update Price button to update these two products. This time the update result for product 30 is still True but for the second update the result is False. Click on the Get Product Details button again to refresh the product prices so that we can verify the update results.

We know that the second service call should fail so the second update should not be committed to the database. From the test result we know this is true (the second product price didn’t change). However from the test result we also know that the first update in the first service call has been committed to the database (the first product price has been changed). This means that the first call to the service is not rolled back even when a subsequent service call has failed. Therefore each service call is in a separate standalone transaction. In other words, the two sequential service calls are not within one distributed transaction.

LEAVE A REPLY

Please enter your comment!
Please enter your name here