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.
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.
- Atomic : To qualify as atomic, when a transaction completes. all individual operations must be made, as if they were one indivisible operation.
- 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.
- 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).
- 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.
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.
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
ICuboidServiceinterface, 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");
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.
| 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,
right click on the project, select Add Service Reference
Add Service Reference, a dialog box will be displayed as shown below.
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 | |
|
AddTM |
Yes |
|
| AddTM | No | |
AddTA |
Yes | |
| AddTA | No | |
| UpdateTM | Yes | |
| UpdateTM | No | |
| UpdateTA | Yes | |
| UpdateTA | No | |
| DeleteTM | Yes | |
| DeleteTM | No | |
| DeleteTA | Yes | |
| DeleteTA | No |


