Praveen Katiyar' Blog

Learning along the way . . . .

Understanding Transactions in WCF

Introduction

A Transaction is a set of complex operations, where failure of any single operation causes entire set to fail. A successful transaction, that transfers the system from one consistent state (A) to another consistent state (B), called a committed transaction.

 

TransactionBlock[5]

Transaction Properties

When you write a transactional service, one must abide by four core properties, known as ACID (atomic, consistent, isolated, and durable), and they are not optional.

  1. Atomic : To qualify as atomic, when a transaction completes. all individual operations must be made, as if they were one indivisible operation.
  2. Consistent:  any transaction must leave the system in consistent state (that makes sense), so transaction is required to transfer the system from one consistent state to another.
  3. Isolated : No other entity (transactional or not) is able to see the intermediate state of the resources, for obvious reasons (as they may be inconsistent).
  4. Durable : For a transaction to be durable, it must maintain its committed state, even if there is a failure (power outage, hardware failure).  The transaction must survive, regardless of the type of failure.

Transaction Propagation

In WCF, transaction can be propagated across service boundary. This enables service to participate in a client transaction and it also allows client, to include operations on multiple services in a sme transaction. By default, transaction aware bindings do not propagate transactions. You can specify whether or not client transaction is propagated to service by changing Binding and operational contract configuration. as shown below.

    <bindings > 
     <wsHttpBinding > 
        <binding name ="MandatoryTransBinding" transactionFlow ="true"> 
              <reliableSession enabled ="true"/> 
        </binding> 
     </wsHttpBinding> 
   </bindings>

Note:

by default transaction does not require reliable messaging, how ever enabling reliability will decrease the likelihood of aborted transactions, means that the transactions will be less likely to abort due to communication problems.

Just enabling transactionFlow, will not be sufficient to state, that a service wants to use client’s transaction in its each operation. You need to specify the “TransactionFlowAttribute" attribute in each operation contract, to enable transaction flow, as shown below.

  [OperationContract] 
  [TransactionFlow(TransactionFlowOption.Mandatory)] 
  [FaultContract(typeof(CuboidFaultException))] 
  void AddTM(int nID, double dblL, double dblW, double dblH);
or
  [OperationContract] 
  [TransactionFlow(TransactionFlowOption.Allowed)] 
  [FaultContract(typeof(CuboidFaultException))] 
  void AddTM(int nID, double dblL, double dblW, double dblH);

the values for the TransactionFlowOption property come from TransactionFlowOption enumeration as shown below.

  • Allowed : Transaction Flow may be flowed, (Client may or may not create a transaction, to be flowed for this operation.)
  • Mandatory : Transaction Flow must be flowed, (Client must create a transaction to be flowed, for this operation.)
  • NotAllowed : Transaction Flow Can not be flowed. , (Transaction can not be flowed, for this operation.)

Creating WCF Transaction

Once the interface has been defined with the proper TransactionFlow attribute, we need to create the service class which implements the service contract and set the operation behavior stating that operation needs a transaction, with TransactionScopeRequired = true.  This attribute enables the service transaction, if the client’s (propagated) transaction is not available.

    public class CuboidService:ICuboidService
    {
        [OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)] 
        public void AddTM(int nID, double dblL, double dblW, double dblH) 
        {
            . . . 
        }
    }

another important attribute TransactionAutoComplete, specifies whether to commit the current transaction automatically. If set true, and no unhandled exceptions are found, the current transaction is automatically committed. the default value for this property is true.

Transaction Propagation

In WCF, transaction can be propagated across service boundary. This enables service to participate in a client transaction and it allows client to include operations on multiple services in same transaction.  By default, transaction aware bindings do not propagate transactions. You can specify whether or not client transaction is propagated to service by changing Binding and operational contract configuration. as shown below.

<bindings >

      <wsHttpBinding >

        <binding name ="MandatoryTransBinding" transactionFlow ="true" >

          <reliableSession enabled ="true"/>

        </binding>

      </wsHttpBinding>

    </bindings>

Note:

by default transaction does not require reliable messaging, how ever enabling reliability will decrease the likelihood of aborted transactions, means that the transactions will be less likely to abort due to communication problems.

Background

In this article, i have taken a Cuboid as an example, Cuboid is a rectangular shaped cube, which has 3 dimensions (length, width and height).

for the database, there are two tables, one is called CuboidInfo, which simply contains the dimensions of the said cuboid, every cuboid is identified by its ID (primary key). The schema of CuboidInfo table is shown below.

cuboidInfoTable

the other table is called CuboidDetail, which contains the Volume and Surface Area of the said Cuboid (identified by its ID). The schema of CuboidDetail table is shown below.

cuboidDetailTable

So, our service demonstrates the transaction in following ways.

  • when you add a new cuboid to the database, its information is added to the CuboidInfo table, and its volume and surface area must be added to the CuboidDetail table.
  • when you update an existing cuboid in the database, its information is updated in the CuboidInfo table, and its volume and surface area must be updated, to the CuboidDetail table.
  • when you delete an existing cuboid from  the database, its information is deleted from CuboidInfo table, and its details must be deleted from the CuboidDetail table too.

Using the code

This article has been divided into 3 modules:

  • WCF Service Library (TransactionLib.dll): Service logic, which defines a Service Contract Interface, OperationContract, and implements them, and exposes few functions to the world to use them, and also implements transaction infrastructure.
  • Windows Form based Application to host the WCF Service Library TransactionLibHost.exe): Host the WCF library
  • Windows Form based Application (TransactionClient.exe): Client Application which will use this service

First Module: WCF Service Library (TransactionLib.dll)

To create this project, you can simply take a "Class Library" project, while choosing from project wizard option. let’s name it "TransactionLib", it is the actual service which implements the business logic. The project already contains a file Class1.cs, let us do some house keeping, before we write any code for the service library  

Little House Keeping

  • Delete the file Class1.cs from the project workspace.
  • Add a new Interface named ICuboidService to the project, a new file ICuboidService.cs will be added to the project.
  • Add a new Class named CuboidService, to the project. that will implement the ICuboidService interface, a new file CuboidService.cs will be added to the project.

Defining ICuboidService Interface (ICuboidService.cs).

ICuboidService interface has seven methods, out of which

  • two methods with mandatory transactions as shown below.

void AddTM(int nID, double dblL, double dblW, double dblH);

   void UpdateTM(int nID, double dblL, double dblW, double dblH);

void DeleteTM(int nID);

notice that, each method has been decorated with

[TransactionFlow(TransactionFlowOption.Mandatory)]

mandates the transaction flow, from the client. that says, that client must create a transaction and propagate its transaction to the service, before executing these methods. if these methods are called without transaction, an exception is generated.

[FaultContract(typeof(CuboidFaultException))]

implements a FaultContract, which raises CuboidFaultException, if there is something not the way, as it supposed to be. 

  • the next two methods with allowed transactions as shown below.

void AddTA(int nID, double dblL, double dblW, double dblH);

   void UpdateTA(int nID, double dblL, double dblW, double dblH);

void DeleteTA(int nID);

notice that, each method has been decorated with

[TransactionFlow(TransactionFlowOption.Allowed)]

that says, that client may create a transaction and propagate its transaction, to the service, before executing these methods. if client does not create a transaction,  and  method implementation is marked with,

[OperationBehavior(TransactionScopeRequired = true)]

then service itself will create a transaction scope, to execute its method.

[FaultContract(typeof(CuboidFaultException))]

implements a FaultContract, which raises CuboidFaultException, if there is something not the way, as it supposed to be. 

  • the last method simply implements a fault contract, and does not mention anything about transaction, that means this method can be executed without any transaction overhead.

[OperationContract]

[FaultContract(typeof(CuboidFaultException))]

Dictionary<int, CuboidData> GetList();

Implement ICuboidService Interface (CuboidService.cs).

CuboidService class implements the ICuboidService interface, as shown below. Service has been decorated with some attributes.

[ServiceBehavior(TransactionIsolationLevel = System.Transactions.IsolationLevel.Serializable)]

public class CuboidService : ICuboidService

{

The isolation level of a transaction determines what level of access other transactions have to volatile data before a transaction completes.  The data affected by a transaction is called volatile. When you create a transaction, you can specify the isolation level that applies to the transaction. The isolation level of a transaction determines what level of access other transactions have to volatile data before a transaction completes. The highest isolation level, Serializable, provides a high degree of protection against interruptive transactions, but requires that each transaction complete before any other transactions are allowed to operate on the data.

Method AddTM ( int nID, double dblL, double dblW, double dblH)

AddTM is used to Add data to the table, passed cuboid information its information is added to the CuboidInfo table, and its volume and surface area is calculated and added to the CuboidDetail table.

[OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)]

public void AddTM(int nID, double dblL, double dblW, double dblH)

{

Operations are decorated with

TransactionScopeRequired = true, that simply says that this operation requires a TransactionScope, if client does not propagate its transaction, transaction is created at the service level itself.

TransactionAutoComplete = true signifies, to complete the transaction scope automatically on successful execution of the operation;

if ((nID <= 0) || (dblL <= 0) || (dblW <= 0) || (dblH <= 0))

{

    CuboidFaultException faultEx = new CuboidFaultException();

    faultEx.Reason = "Any dimension of Cuboid (length/width/height) can not be zero or negative value";

    faultEx.Source = "AddUpdateTM";

    StringBuilder sbDetail = new StringBuilder("");

    if (nID <= 0) sbDetail.Append("[ID is <= 0] ");

    if (dblL <= 0) sbDetail.Append("[Length is <= 0] ");

    if (dblW <= 0) sbDetail.Append("[Width is <= 0] ");

    if (dblW <= 0) sbDetail.Append("[Height is <= 0]");

    faultEx.Detail = sbDetail.ToString();

    throw new FaultException<CuboidFaultException>(faultEx);

}

then method checks the passed parameters, in case they are zero or negative a FaultException is thrown. if the input parameters are permissible, then usual business logic proceeds, that includes.

  • calculating the values to be added/updated in CuboidDetail table.

double dblVolume = (dblL * dblW * dblH);

double dblSArea = 2 * ((dblL * dblW) + (dblW * dblH) + (dblL * dblH));
        

  • preparing appropriate query to be run on database, for both the tables, according to the method called (add/update).

    strQuery1 = "INSERT INTO CuboidInfo (ID, Length, Width, Height ) VALUES (" + nID.ToString() + ", " + dblL.ToString("F2") + ", " + dblW.ToString("F2") + ", " + dblH.ToString("F2") + " )";

    strQuery2 = "INSERT INTO CuboidDetail (ID, SurfaceArea, Volume) VALUES (" + nID.ToString() + ", " + dblSArea.ToString("F2") + ", " + dblVolume.ToString("F2") + " )";

  • Reading Connection string from the App.config file of the host application.

// Get the all keys from the appSettings section from the configuration file.

System.Configuration.Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);

AppSettingsSection appSettings = (AppSettingsSection)config.GetSection("appSettings");

string[] keys = appSettings.Settings.AllKeys;

// Get the connection string.

string strConnStr = appSettings.Settings["ConnectionString"].Value;

  • Now the business logic, open Connection, execute both the queries, if there is any error throw a fault exception encapsulating error information.

try

{

     //  Open Connection

     dbConn = new SqlConnection(strConnStr);

     dbConn.Open();

     //  Create Command for Query 1

     SqlCommand dbCmd1 = new SqlCommand(strQuery1, dbConn);

     nCnt1 = dbCmd1.ExecuteNonQuery();

     //  Create Command for Query 2

     SqlCommand dbCmd2 = new SqlCommand(strQuery2, dbConn);

     nCnt2 = dbCmd2.ExecuteNonQuery();

     //  If TransactionAutoComplete=false, you need the line below.

     //  OperationContext.Current.SetTransactionComplete();

     //  Close Connection.

     dbConn.Close();

     dbConn = null;

}

catch (Exception eX)

{

     //  Close Connection.

     if (dbConn != null)

     {

          if (dbConn.State == ConnectionState.Open) dbConn.Close();

          dbConn = null;

     }

     CuboidFaultException faultEx = new CuboidFaultException();

     faultEx.Reason = "Failed while performing Add " ; 
     faultEx.Source = "AddUpdateTM";

     faultEx.Detail = eX.Message;

     throw new FaultException<CuboidFaultException>(faultEx);

}

Method AddTA (int nID, double dblL, double dblW, double dblH)

business logic for method AddTA is same as for AddTM. 

Method UpdateTM (int nID, double dblL, double dblW, double dblH)

UpdateTM is used to Update data to the table, passed cuboid information is updated to the CuboidInfo table, and its volume and surface area is calculated and updated to the CuboidDetail table.

[OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)]

public void UpdateTM(int nID, double dblL, double dblW, double dblH)

{

Operations are decorated with

TransactionScopeRequired = true, that simply says that this operation requires a TransactionScope, if client does not propagate its transaction, transaction is created at the service level itself.

TransactionAutoComplete = true signifies, to complete the transaction scope automatically on successful execution of the operation;

if ((nID <= 0) || (dblL <= 0) || (dblW <= 0) || (dblH <= 0))

{

    CuboidFaultException faultEx = new CuboidFaultException();

    faultEx.Reason = "Any dimension of Cuboid (length/width/height) can not be zero or negative value";

    faultEx.Source = "AddUpdateTM";

    StringBuilder sbDetail = new StringBuilder("");

    if (nID <= 0) sbDetail.Append("[ID is <= 0] ");

    if (dblL <= 0) sbDetail.Append("[Length is <= 0] ");

    if (dblW <= 0) sbDetail.Append("[Width is <= 0] ");

    if (dblW <= 0) sbDetail.Append("[Height is <= 0]");

    faultEx.Detail = sbDetail.ToString();

    throw new FaultException<CuboidFaultException>(faultEx);

}

then method checks the passed parameters, in case they are zero or negative a FaultException is thrown. if the input parameters are permissible, then usual business logic proceeds, that includes.

  • calculating the values to be added/updated in CuboidDetail table.

double dblVolume = (dblL * dblW * dblH);

double dblSArea = 2 * ((dblL * dblW) + (dblW * dblH) + (dblL * dblH));
        

  • preparing appropriate query to be run on database, for both the tables, according to the method called (add/update).

 strQuery1 = "Update CuboidInfo SET Length=" + dblL.ToString("F2") + ", Width=" + dblW.ToString() + ", Height=" + dblH.ToString() + " where ID=" + nID.ToString();

    strQuery2 = "Update CuboidDetail SET SurfaceArea=" + dblSArea.ToString("F2") + ", Volume=" + dblVolume.ToString() + " where ID=" + nID.ToString();

  • Reading Connection string from the App.config file of the host application.

// Get the all keys from the appSettings section from the configuration file.

System.Configuration.Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);

AppSettingsSection appSettings = (AppSettingsSection)config.GetSection("appSettings");

string[] keys = appSettings.Settings.AllKeys;

// Get the connection string.

string strConnStr = appSettings.Settings["ConnectionString"].Value;

  • Now the business logic, open Connection, execute both the queries, if there is any error throw a fault exception encapsulating error information.

try

{

     //  Open Connection

     dbConn = new SqlConnection(strConnStr);

     dbConn.Open();

     //  Create Command for Query 1

     SqlCommand dbCmd1 = new SqlCommand(strQuery1, dbConn);

     nCnt1 = dbCmd1.ExecuteNonQuery();

     //  Create Command for Query 2

     SqlCommand dbCmd2 = new SqlCommand(strQuery2, dbConn);

     nCnt2 = dbCmd2.ExecuteNonQuery();

     //  If TransactionAutoComplete=false, you need the line below.

     //  OperationContext.Current.SetTransactionComplete();

     //  Close Connection.

     dbConn.Close();

     dbConn = null;

}

catch (Exception eX)

{

     //  Close Connection.

     if (dbConn != null)

     {

          if (dbConn.State == ConnectionState.Open) dbConn.Close();

          dbConn = null;

     }

     CuboidFaultException faultEx = new CuboidFaultException();

     faultEx.Reason =  "Failed while performing Update";

     faultEx.Source = "UpdateTM";

     faultEx.Detail = eX.Message;

     throw new FaultException<CuboidFaultException>(faultEx);

}

Method UpdateTA (int nID, double dblL, double dblW, double dblH)

business logic for method UpdateTA is same as for UpdateTM. 

Method void DeleteTM(int nID)

  [OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)]

public void DeleteTM(int nID )

{

Operations are decorated with

TransactionScopeRequired = true, that simply says that this operation requires a TransactionScope, if client does not propagate its transaction, transaction is created at the service level itself.

TransactionAutoComplete = true signifies, to complete the transaction scope automatically on successful execution of the operation;

preparing appropriate query to be run on database, for both the tables, as records from both the tables need to be deleted.StringBuilder sbErr = new StringBuilder();

string strQuery1 = "delete from CuboidInfo where ID=" + nID.ToString();

string strQuery2 = "delete from CuboidDetail where ID=" + nID.ToString();

Fetch connection string from the app.config file.

System.Configuration.Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);

AppSettingsSection appSettings = (AppSettingsSection)config.GetSection("appSettings");

string[] keys = appSettings.Settings.AllKeys;

// Get the connection string.

string strConnStr = appSettings.Settings["ConnectionString"].Value;

Do the business logic, which involves, opening connection, deleting the matching row from both the tables. execute both the queries inside a try block,  if there is any error throw a fault exception encapsulating error information.

try

{

        //  Open Connection

        dbConn = new SqlConnection(strConnStr);

        dbConn.Open();

        //  Create Command for Query 1

        SqlCommand dbCmd1 = new SqlCommand(strQuery1, dbConn);

        nCnt1 = dbCmd1.ExecuteNonQuery();

        //  Create Command for Query 2

        SqlCommand dbCmd2 = new SqlCommand(strQuery2, dbConn);

        nCnt2 = dbCmd2.ExecuteNonQuery();

        //  As TransactionAutoComplete = false, so you manually need to notify, that transaction is compplete.

        //  OperationContext.Current.SetTransactionComplete();

        //  Close Connection.

        dbConn.Close();

        dbConn = null;

    }

    catch (Exception eX)

    {

        //  Close Connection.

        if (dbConn != null)

        {

            if (dbConn.State == ConnectionState.Open) dbConn.Close();

            dbConn = null;

        }

        CuboidFaultException faultEx = new CuboidFaultException();

        faultEx.Reason = "Failed while performing Delete";

        faultEx.Source = "DeleteTM";

        faultEx.Detail = eX.Message;

        throw new FaultException<CuboidFaultException>(faultEx);

    }

Method void DeleteTA(int nID)

business logic for method DeleteTA is same as for DeleteTM. 

Method  public  Dictionary<int, CuboidData> GetList()

This method simply reads the list all the Cuboid from the database. all inside a try block.

get connection string from app.config file.

Find application’s Settings through config file.

System.Configuration.Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);

AppSettingsSection appSettings = (AppSettingsSection)config.GetSection("appSettings");

string[] keys = appSettings.Settings.AllKeys;

// Get the connection string.

string strConnStr = appSettings.Settings["ConnectionString"].Value;

Open Connection

dbConn = new SqlConnection(strConnStr);

dbConn.Open();

Create Query to read ID, Length, Width, Height from CuboidInfo Table, while as SurfaceArea and Volume is fetched from CuboidDetail table. 

string strQuery = "select CuboidInfo.*, CuboidDetail.SurfaceArea, CuboidDetail.Volume from CuboidInfo join CuboidDetail on CuboidDetail.ID=CuboidInfo.ID";`

execute query and collect the result in the list.

//  Create Command for Query 1

string strQuery = "select CuboidInfo.*, CuboidDetail.SurfaceArea, CuboidDetail.Volume from CuboidInfo join CuboidDetail on CuboidDetail.ID=CuboidInfo.ID";

SqlCommand dbCmd = new SqlCommand(strQuery, dbConn);

dbReader = dbCmd.ExecuteReader();

while (dbReader.HasRows)

{

     bool bReaded = dbReader.Read();

     if (bReaded == false) break;

     CuboidData cInfo = new CuboidData();

     cInfo.ID = int.Parse(dbReader["ID"].ToString());

     cInfo.Length = int.Parse(dbReader["Length"].ToString());

     cInfo.Width = int.Parse(dbReader["Width"].ToString());

     cInfo.Height = int.Parse(dbReader["Height"].ToString());

     cInfo.SurfaceArea = int.Parse(dbReader["SurfaceArea"].ToString());

     cInfo.Volume = int.Parse(dbReader["Volume"].ToString());

     cuboidList.Add(cInfo.ID, cInfo);

}

Close reader and Connection.

dbReader.Close();   //  Close Reader

dbConn.Close();     //  Close Connection.

dbConn = null;

as everything was inside try block, if there is any error throw a fault exception encapsulating error information.

catch (Exception eX)

{

     //  Close Connection.

     if (dbConn != null)

     {

          if (dbReader.IsClosed == false) dbReader.Close();

          if (dbConn.State == ConnectionState.Open) dbConn.Close();

          dbConn = null;

     }

     CuboidFaultException faultEx = new CuboidFaultException();

     faultEx.Reason = "Failed while fetching QuboidData";

     faultEx.Source = "GetList";

     faultEx.Detail = eX.Message;

     throw new FaultException<CuboidFaultException>(faultEx);

}

otherwise simply return the populated CuboidList, and close the function block.

    return cuboidList;

}

Second Module: Service Host Application (TransactionLibHost.dll)

Add a new Project to the workspace, To create this project, i have taken a simple Console based application , while choosing from project wizard option. let’s name it "TransactionLibHost", this application will host our WCF Service library.

Adding application configuration

  • Add an Application configuration (App.config) file to the project.

Defining configuration

before we write any code for the host, let’s define the configuration.

Assumption

The Host application will expose the following endpoints for the service.

  • CuboidService will expose HTTP endpoint at Port 9011
  • Corresponding mex End Point (IMetadatExchange) for the HTTP end point.

Defining configuration for CuboidService

Defining Service base address and Service Endpoints

<services>

     <service behaviorConfiguration="TransactionLib.CuboidServiceBehavior" name="TransactionLib.CuboidService">

       <host>

         <baseAddresses>

           <add baseAddress="http://localhost:9011/CuboidService/" />

         </baseAddresses>

       </host>

      
       <endpoint address="" binding="wsHttpBinding" contract="TransactionLib.ICuboidService" bindingConfiguration ="transBinding" />

       <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />

     
     </service>

   </services>

Explanation

behaviorConfiguration="TransactionLib.CuboidServiceBehavior

specifies that Service behavior of service has been defined in <serviceBehaviors> section,  which is named as "CuboidServiceBehavior".

<baseAddresses>  </baseAddresses>

Represents a collection of <baseAddress> elements, which are base addresses for a service host in a self-hosted environment. If a base address is present, endpoints can be configured with addresses relative to the base address.

<add baseAddress="http://localhost:9011/CuboidService/" />

Represents a configuration element that specifies the base addresses used by the service host.

<endpoint address="" binding="wsHttpBinding" contract="TransactionLib.ICuboidService" bindingConfiguration ="transBinding" />

<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />

The service defines, one endpoint with wsHttpBinding, another endpoint is defined as mex, are defined for publication of metadata, IMetadataExchange is standard contract for mex endpoints.

Defining behaviors

<behaviors>

     <serviceBehaviors>

       <behavior name="TransactionLib.CuboidServiceBehavior">

         <!– To avoid disclosing metadata information,

         set the value below to false and remove the metadata endpoint above before deployment –>

        <serviceMetadata httpGetEnabled="True"/>

         <!– To receive exception details in faults for debugging purposes,

         set the value below to true.  Set to false before deployment to avoid disclosing exception information –>

         <serviceDebug includeExceptionDetailInFaults="true" />

       </behavior>

     </serviceBehaviors>

   </behaviors>

Explanation

<serviceBehaviors> section represents all the behaviors defined for a specific service.

service behaviour defines two attributes,  

<serviceMetadata httpGetEnabled="true" /> 

Gets or sets a value that indicates whether to publich service medtadata for retrieval using an HTTP/GET request, true means yes, metadata is available for retrieval using a HTTP/GET request.

<serviceDebug includeExceptionDetailInFaults="true "/>

Set IncludeExceptionDetailsInFaults to true to enable clients to obtain information about internal service method  exceptions; it is only recommended as a way of temporarily debugging a service application. this property must be set to false on production servers.  

Defining Bindings

<bindings >

    <wsHttpBinding>

       <binding name ="transBinding" transactionFlow="true" >

          <reliableSession enabled ="true"/>

       </binding>

    </wsHttpBinding>

</bindings>

Explanation

<bindings> section used to configure system provided bindings.

<binding name ="transBinding" transactionFlow="true" >

By default, transaction aware bindings do not propagate transactions. You can specify whether or not client transaction is propagated to service by changing Binding and operational contract configuration. as transactionFlow="true".

<reliableSession enabled ="true"/>

Enabling reliability will decrease the likelihood of aborted transactions, means that the transactions will be less likely to abort due to communication problems.

Writing Code to Host the service.

As the configuration for the service has been defined, before we write any code 

  • add reference of System.ServiceModel to the project. 

Let’s write code to host the service.

try

{

     ServiceHost host = new ServiceHost(typeof(TransactionLib.CuboidService));

     host.Open();

     Console.WriteLine("\nService is Hosted as http://localhost:9011/CuboidService&quot;);

     Console.WriteLine("\nPress  key to stop the service.");

     Console.ReadLine();

     host.Close();

}

catch (Exception eX)

{

     Console.WriteLine("There was en error while Hosting Service [" + eX.Message + "]");

     Console.WriteLine("\nPress  key to close.");

     Console.ReadLine();

}

Explanation

ServiceHost class provides a host for services.

Build and Execute the Host.

Third Module, Creating Client (TransactionClient.exe)

Creating the base project 

Take a Windows Form based based application. name it TransactionClient.

Creating GUI for the Client Project.

I have designed the GUI for the client project, to test all the aspects of the service as shown below.

ClientGUI

Control Control Name Purpose
TextBox txtID ID of the Cuboid has to be entered here.
TextBox txtLength Length of the Cuboid has to be entered here.
TextBox txtWidth Width of the Cuboid has to be entered here.
TextBox txtHeight Height of the Cuboid has to be entered here.
Button btnDeleteTMYes Calls DeleteTM method, with transaction
Button btnDeleteTMNo Calls DeleteTM method, without transaction
Button btnDeleteTAYes Calls DeleteTA method, with transaction
Button btnDeleteTANo Calls DeleteTA method, without transaction
Button btnAddTMYes Calls AddTM method, with transaction
Button btnAddTMNo Calls AddTM method, without transaction
Button btnAddTAYes Calls AddTA method, with transaction
Button btnAddTANo Calls AddTA method, without transaction
Button btnUpdateTMYes Calls UpdateTM method, with transaction
Button btnUpdateTMNo Calls UpdateTM method, without transaction
Button btnUpdateTAYes Calls UpdateTA method, with transaction
Button btnUpdateTANo Calls UpdateTA method, without transaction
Button btnRefresh Calls GetList method
Button btnClose Closes the Application.
LuistView lvOutput Displays Output.

 

Add service Reference to the client application

Make sure Host Application (TransactionLibHost.exe)  is running,

RunningTransactionLibHost

right click on the project, select Add Service Reference

Add Service Reference, a dialog box will be displayed as shown below.

GenerateProxyCuboidService

for the Address, enter http://localhost:9011/CuboidService/mex

for the Namespace, enter CuboidServiceReference.

press OK.

  • Service References folder will be created under Client project. under which a new node CuboidServiceReference will be created.
  • A configuration file (app.config) will be added to the client project.

Little house keeping

  • On the Form_Load event just initialize the controls.,

    private void frmTransactionLibClient_Load(object sender, EventArgs e)

    {

               txtID.Text = "1";

               txtLength.Text = "400";

               txtWidth.Text = "300.0";

               txtHeight.Text = "200" ;

               lvOutput.View = View.Details;

               lvOutput.FullRowSelect = true;

               lvOutput.GridLines = true;

               lvOutput.Columns.Clear();

               lvOutput.Columns.Add( "ID", 43);

               lvOutput.Columns.Add("Length", 52);

               lvOutput.Columns.Add("Width", 52);

               lvOutput.Columns.Add("Height", 52);

               lvOutput.Columns.Add("Area", 70);

               lvOutput.Columns.Add("Volume", 88);

    }

     

  • Create an Helper function to fetch the numeric values from Input Text Boxes.

    //  Common Helper Function.

    private void ParseInputs(ref int nID, ref double dblL, ref double dblW, ref double dblH)

    {

         nID = 0;    dblL = 0;  dblW = 0;   dblH = 0;

         int.TryParse(txtID.Text, out nID);

         double.TryParse(txtLength.Text, out dblL);

         double.TryParse(txtWidth.Text, out dblW);

         double.TryParse(txtHeight.Text, out dblH);

    }

    note that, no input is  validated at this side, i just fetched the values, as we have implemented a FaultContract on the service side, to handle invalid input errors. although i would recommend, if possible,  always validate parameters at the client side, before passing them to service.

Calling Methods on the Service.

Calling Method GetList

enclose every thing in a try block, Creates an object of the Service Proxy, and calls the service method.

try

{

    CuboidServiceClient objClient = new CuboidServiceClient();

    Dictionary<int, CuboidData> list = objClient.GetList();

    if (list != null)  
    {

       and if the return value is not null (there was no error). ListView control is populated.

       int nCount = list.Keys.Count;

       lvOutput.Items.Clear();

       foreach (int nID in list.Keys)

       {

            CuboidData cInfo = list[nID];

            ListViewItem lvItem = lvOutput.Items.Add(cInfo.ID.ToString());

            lvItem.SubItems.Add(cInfo.Length.ToString("F2"));

            lvItem.SubItems.Add(cInfo.Width.ToString("F2"));

            lvItem.SubItems.Add(cInfo.Height.ToString("F2"));

            lvItem.SubItems.Add(cInfo.SurfaceArea.ToString("F2"));

            lvItem.SubItems.Add(cInfo.Volume.ToString("F2"));

       }

    }

Close try block, and see if you got any fault exception, if yes, then display the error message.

}

catch (FaultException<CuboidFaultException> eX)

{

    strMsg = "Source [" + eX.Detail.Source + "]" + "\nReason [" + eX.Detail.Reason + "] \nDeatil [" + eX.Detail.Detail + "]";

}

catch (Exception eX)

{

    strMsg = eX.Message;

}

MessageBox.Show(strMsg);

Calling Method AddTM, Adding a cuboid, inside a client’s transaction

declare and initialize variables.

int nID = 0;

double dblLength = 0, dblWidth = 0, dblHeight = 0;

Parse input values from controls.

ParseInputs(ref nID, ref dblLength, ref dblWidth, ref dblHeight);

enclose every thing in a try block

try

{

execute the service method in client’s transaction

   using (TransactionScope ts = new TransactionScope(TransactionScopeOption.RequiresNew))

    {

create procy object and call the method.

        CuboidServiceClient objClient = new CuboidServiceClient();

        objClient.AddTM ( nID, dblLength, dblWidth, dblHeight);

Client’s transacton is complete.

       ts.Complete();

close client’s Transaction scope block       
    }

close try block

}

in case if there is any problem. store the error message from service.

catch (FaultException<CuboidFaultException> eX)

{

    strMsg = "Source ["+eX.Detail.Source+"]"+"\nReason ["+ eX.Detail.Reason+"] \nDeatil ["+eX.Detail.Detail+ "]";

}

catch (Exception eX)

{

    strMsg = eX.Message;

}

display error message

MessageBox.Show(strMsg);

Calling Method AddTM, Adding a cuboid, without a client’s transaction

declare and initialize variables.

int nID = 0;

double dblLength = 0, dblWidth = 0, dblHeight = 0;

Parse input values from controls.

ParseInputs(ref nID, ref dblLength, ref dblWidth, ref dblHeight);

enclose every thing in a try block

try

{

create proxy object and call the method.

        CuboidServiceClient objClient = new CuboidServiceClient();

        objClient.AddTM (  nID, dblLength, dblWidth, dblHeight);

Note: same method is called for Updating a cuboid, passing first parameter as false.

close try block

}

in case if there is any problem. store the error message from service.

catch (FaultException<CuboidFaultException> eX)

{

    strMsg = "Source ["+eX.Detail.Source+"]"+"\nReason ["+ eX.Detail.Reason+"] \nDeatil ["+eX.Detail.Detail+ "]";

}

catch (Exception eX)

{

    strMsg = eX.Message;

}

display error message

MessageBox.Show(strMsg);

Calling Method UpdateTM, updating a cuboid, inside a client’s transaction

declare and initialize variables.

int nID = 0;

double dblLength = 0, dblWidth = 0, dblHeight = 0;

Parse input values from controls.

ParseInputs(ref nID, ref dblLength, ref dblWidth, ref dblHeight);

enclose every thing in a try block

try

{

execute the service method in client’s transaction

   using (TransactionScope ts = new TransactionScope(TransactionScopeOption.RequiresNew))

    {

create procy object and call the method.

        CuboidServiceClient objClient = new CuboidServiceClient();

        objClient.UpdateTM ( nID, dblLength, dblWidth, dblHeight);

Note: same method is called for Updating a cuboid, passing first parameter as false.

Client’s transacton is complete.

       ts.Complete();

close client’s Transaction scope block       
    }

close try block

}

in case if there is any problem. store the error message from service.

catch (FaultException<CuboidFaultException> eX)

{

    strMsg = "Source ["+eX.Detail.Source+"]"+"\nReason ["+ eX.Detail.Reason+"] \nDeatil ["+eX.Detail.Detail+ "]";

}

catch (Exception eX)

{

    strMsg = eX.Message;

}

display error message

MessageBox.Show(strMsg);

Calling Method UpdateTM, Updating a cuboid, without a client’s transaction

declare and initialize variables.

int nID = 0;

double dblLength = 0, dblWidth = 0, dblHeight = 0;

Parse input values from controls.

ParseInputs(ref nID, ref dblLength, ref dblWidth, ref dblHeight);

enclose every thing in a try block

try

{

create proxy object and call the method.

        CuboidServiceClient objClient = new CuboidServiceClient();

        objClient.UpdatingTM (  nID, dblLength, dblWidth, dblHeight);

close try block

}

in case if there is any problem. store the error message from service.

catch (FaultException<CuboidFaultException> eX)

{

    strMsg = "Source ["+eX.Detail.Source+"]"+"\nReason ["+ eX.Detail.Reason+"] \nDeatil ["+eX.Detail.Detail+ "]";

}

catch (Exception eX)

{

    strMsg = eX.Message;

}

display error message

MessageBox.Show(strMsg);

Calling Method DeleteTM, Adding a cuboid, inside a client’s transaction

declare and initialize variables.

int nID = 0;

Parse input values from controls.

int.TryParse(txtID.Text, out nID);

enclose every thing in a try block

try

{

execute the service method in client’s transaction

   using (TransactionScope ts = new TransactionScope(TransactionScopeOption.RequiresNew))

    {

create procy object and call the method.

        CuboidServiceClient objClient = new CuboidServiceClient();

        objClient.DeleteTM ( nID);

Client’s transacton is complete.

       ts.Complete();

close client’s Transaction scope block       
    }

close try block

}

in case if there is any problem. store the error message from service.

catch (FaultException<CuboidFaultException> eX)

{

    strMsg = "Source ["+eX.Detail.Source+"]"+"\nReason ["+ eX.Detail.Reason+"] \nDeatil ["+eX.Detail.Detail+ "]";

}

catch (Exception eX)

{

    strMsg = eX.Message;

}

display error message

MessageBox.Show(strMsg);

Calling Method DeleteTM, Deleting a cuboid, without a client’s transaction

declare and initialize variables.

int nID = 0;

Parse input values from controls.

int.TryParse(txtID.Text, out nID);

enclose every thing in a try block

try

{

create procy object and call the method.

        CuboidServiceClient objClient = new CuboidServiceClient();

        objClient.DeleteTM ( nID);

close try block

}

in case if there is any problem. store the error message from service.

catch (FaultException<CuboidFaultException> eX)

{

    strMsg = "Source ["+eX.Detail.Source+"]"+"\nReason ["+ eX.Detail.Reason+"] \nDeatil ["+eX.Detail.Detail+ "]";

}

catch (Exception eX)

{

    strMsg = eX.Message;

}

display error message

MessageBox.Show(strMsg);

Calling Method AddTA, Adding a cuboid, inside a client’s transaction

declare and initialize variables.

int nID = 0;

double dblLength = 0, dblWidth = 0, dblHeight = 0;

Parse input values from controls.

ParseInputs(ref nID, ref dblLength, ref dblWidth, ref dblHeight);

enclose every thing in a try block

try

{

execute the service method in client’s transaction

   using (TransactionScope ts = new TransactionScope(TransactionScopeOption.RequiresNew))

    {

create procy object and call the method.

        CuboidServiceClient objClient = new CuboidServiceClient();

        objClient.AddTA ( nID, dblLength, dblWidth, dblHeight);

Client’s transacton is complete.

       ts.Complete();

close client’s Transaction scope block       
    }

close try block

}

in case if there is any problem. store the error message from service.

catch (FaultException<CuboidFaultException> eX)

{

    strMsg = "Source ["+eX.Detail.Source+"]"+"\nReason ["+ eX.Detail.Reason+"] \nDeatil ["+eX.Detail.Detail+ "]";

}

catch (Exception eX)

{

    strMsg = eX.Message;

}

display error message

MessageBox.Show(strMsg);

Calling Method AddTA, Adding a cuboid, without a client’s transaction

declare and initialize variables.

int nID = 0;

double dblLength = 0, dblWidth = 0, dblHeight = 0;

Parse input values from controls.

ParseInputs(ref nID, ref dblLength, ref dblWidth, ref dblHeight);

enclose every thing in a try block

try

{

create proxy object and call the method.

        CuboidServiceClient objClient = new CuboidServiceClient();

        objClient.AddTA ( nID, dblLength, dblWidth, dblHeight);

close try block

}

in case if there is any problem. store the error message from service.

catch (FaultException<CuboidFaultException> eX)

{

    strMsg = "Source ["+eX.Detail.Source+"]"+"\nReason ["+ eX.Detail.Reason+"] \nDeatil ["+eX.Detail.Detail+ "]";

}

catch (Exception eX)

{

    strMsg = eX.Message;

}

display error message

MessageBox.Show(strMsg);

Calling Method UpdateTA, updating a cuboid, inside a client’s transaction

declare and initialize variables.

int nID = 0;

double dblLength = 0, dblWidth = 0, dblHeight = 0;

Parse input values from controls.

ParseInputs(ref nID, ref dblLength, ref dblWidth, ref dblHeight);

enclose every thing in a try block

try

{

execute the service method in client’s transaction

   using (TransactionScope ts = new TransactionScope(TransactionScopeOption.RequiresNew))

    {

create procy object and call the method.

        CuboidServiceClient objClient = new CuboidServiceClient();

        objClient.UpdateTA ( nID, dblLength, dblWidth, dblHeight);

Note: same method is called for Updating a cuboid, passing first parameter as false.

Client’s transacton is complete.

       ts.Complete();

close client’s Transaction scope block       
    }

close try block

}

in case if there is any problem. store the error message from service.

catch (FaultException<CuboidFaultException> eX)

{

    strMsg = "Source ["+eX.Detail.Source+"]"+"\nReason ["+ eX.Detail.Reason+"] \nDeatil ["+eX.Detail.Detail+ "]";

}

catch (Exception eX)

{

    strMsg = eX.Message;

}

display error message

MessageBox.Show(strMsg);

Calling Method UpdateTA, Updating a cuboid, without a client’s transaction

declare and initialize variables.

int nID = 0;

double dblLength = 0, dblWidth = 0, dblHeight = 0;

Parse input values from controls.

ParseInputs(ref nID, ref dblLength, ref dblWidth, ref dblHeight);

enclose every thing in a try block

try

{

create proxy object and call the method.

        CuboidServiceClient objClient = new CuboidServiceClient();

        objClient.UpdatingTA (  nID, dblLength, dblWidth, dblHeight);

close try block

}

in case if there is any problem. store the error message from service.

catch (FaultException<CuboidFaultException> eX)

{

    strMsg = "Source ["+eX.Detail.Source+"]"+"\nReason ["+ eX.Detail.Reason+"] \nDeatil ["+eX.Detail.Detail+ "]";

}

catch (Exception eX)

{

    strMsg = eX.Message;

}

display error message

MessageBox.Show(strMsg);

Calling Method DeleteTA, Adding a cuboid, inside a client’s transaction

declare and initialize variables.

int nID = 0;

Parse input values from controls.

int.TryParse(txtID.Text, out nID);

enclose every thing in a try block

try

{

execute the service method in client’s transaction

   using (TransactionScope ts = new TransactionScope(TransactionScopeOption.RequiresNew))

    {

create procy object and call the method.

        CuboidServiceClient objClient = new CuboidServiceClient();

        objClient.DeleteTA ( nID);

Client’s transacton is complete.

       ts.Complete();

close client’s Transaction scope block       
    }

close try block

}

in case if there is any problem. store the error message from service.

catch (FaultException<CuboidFaultException> eX)

{

    strMsg = "Source ["+eX.Detail.Source+"]"+"\nReason ["+ eX.Detail.Reason+"] \nDeatil ["+eX.Detail.Detail+ "]";

}

catch (Exception eX)

{

    strMsg = eX.Message;

}

display error message

MessageBox.Show(strMsg);

Calling Method DeleteTA, Deleting a cuboid, without a client’s transaction

declare and initialize variables.

int nID = 0;

Parse input values from controls.

int.TryParse(txtID.Text, out nID);

enclose every thing in a try block

try

{

create procy object and call the method.

        CuboidServiceClient objClient = new CuboidServiceClient();

        objClient.DeleteTA ( nID);

close try block

}

in case if there is any problem. store the error message from service.

catch (FaultException<CuboidFaultException> eX)

{

    strMsg = "Source ["+eX.Detail.Source+"]"+"\nReason ["+ eX.Detail.Reason+"] \nDeatil ["+eX.Detail.Detail+ "]";

}

catch (Exception eX)

{

    strMsg = eX.Message;

}

display error message

MessageBox.Show(strMsg);

Output

Calling Method GetList

 

Calling Method Client Transaction
GetList N/A GetListOutput
 

AddTM

Yes

image
     
AddTM No image


AddTA
Yes image
AddTA No image
UpdateTM Yes image
UpdateTM No image
UpdateTA Yes image
UpdateTA No image
DeleteTM Yes image
DeleteTM No image
DeleteTA Yes image
DeleteTA No image
Advertisements

November 12, 2013 Posted by | .NET, SOA, WCF | , , | Leave a comment