Search This Blog

2012/04/29

Transactions for Web services-Part I


What is Transaction?

A transaction is a unit of work that involves one or more resources and is either completed in its entirety or is not done at all. Participating resources are locked for the duration of the transaction. Depending on the readiness of all the participating resources, the changes made to the resources are either committed or rolled back, and the lock is released.

   To further understand the transaction let’s take a look at real life example of Net Banking, suppose you want to transfer some money from your own account to your
mother’s account this process of transferring money from one account to another
can be viewed as made up of following main tasks
1)      Debiting specified amount from your bank account ledger 
2)      Crediting same amount into your mother’s  bank account ledger 
Transferring process will be complete only if both task completed successfully not otherwise. This means transfer of money from one account to another fulfill criteria of being a transaction.

   Most of the ecommerce sites rely on other third-party services for as a part of there business process e.g. consider an example of air ticket booking sites like
www.yatra.com or www.makemytrip.com usually to get best air ticket deal to there customer these site need to interact with different airlines having there own ticket booking facility through there own systems. Ticket for given seat in given airline on given date time should be booked at most once. Money transaction from customer to
air ticket booking site should be completed successfully that also depend on third party payment gateways like say PayPal.

      In such scenario we need to ensure that process completed in its entirety or is not done at all. Locking of resources of one party from other will concede control of once resources to other, making participating parting susceptible to Denial of Services attack situation demand transaction that may not necessary fit into ACID Property that we usually associate with Database transactions.
  
Compensation-based transactions
Let’s imagine a service composition that invokes a decision service to validate request data, proceeds to update a customer entity via an entity service, and then sends a message on a queue for an external process to consume. Consider the three steps as part of a single unit of work – a service transaction that execute in sequence.
   If runtime exceptions associated with the composition are unhandled there is an increased likelihood of compromising data and business integrity. The compensating service transaction pattern introduces additional compensation steps in order to handle runtime exceptions without locking system resources. These additional steps can be included as part of the composition logic or made available as separate undo service capabilities. Continuing the earlier example, a call to undo the customer update (essentially resetting the data back to the earlier state) can be made to place the underlying data in a valid state.
The compensation-based transactions have the following salient features:
  • Ability to manage consistency of data across applications without locking resources
  • Coordination of various transaction participants without giving complete authority to a central transaction manager
  • Ability to work in scenarios where participants' availability and/or response is not guaranteed
The above features are based on Web Services-Coordination/Web Services-Transaction (WS-C/T) specifications.
It is necessary to ensure that the compensating steps themselves execute successfully. Error alerts/notifications may need to be sent in case compensations fail and manual interventions are needed.
The Compensating Service Transaction pattern provides several benefits:
  • Eliminates the need for executing distributed service invocations within a transactional context.
  • Provides a mechanism for services/resources that don’t support atomic transactions to participate in service compositions.
  • Reduces load on underlying systems and databases by only invoking compensation routines when failure occurs. Instead of locking resources in case failures happen, the idea is to handle runtime exceptions when they actually occur.
  • Allows reuse of compensation steps across service compositions. E.g. two business services that update a data entity can reuse the undo Update capability.
Reference:

2012/04/22

Caching SOAP Response of Web Service


The caching is capability to maintain an in memory store where data, objects and various items are stored for reuse. Caching improves the response time of an application there by increasing the performance of application.
   Web services also have caching capability; The CacheDuration   is an attribute of webmethod that let us to specify duration of time for which SOAP response is stored in memory


[WebMethod(CacheDuration=50)]
Public string GetCurrentDate()
{
      return DateTime.Now.ToString(“yyyy-MM-dd hh:mm:ss”);
}

Here CacheDuration attribute set to 50 seconds, the value of CacheDuration should always be an integer no fractional values are allowed.

Now when we call this webmethod first time the system’s current date time will be our response, this response is cached for 50 seconds, within 50 seconds of last request if another request comes then lastly cached response is provided but after 50 seconds this cached response get automatically discarded the caller will get recent system date time.
   Point to note is that cached response is dependent on set of input parameters i.e.
If your webmethod input parameter combination differ from last requested input parameter combination then even if last request was within cache duration you will get response from cache.
e.g.


[WebMethod(CacheDuration=60)]
Public int AddInt(int num1,int num2)
{
      Return num1 + num2;
}

On first call of AddInt(10,20) will cache response but immediate next call to AddInt(20,10) will not be from cached data as both call differ in input parameter combination even if there time lag is within Cache duration of web method.

Helper Library: MSSQL QUERY Builder


      Many times we need to build a sql query using concating strings according to situation, quite often than not we go with the flow and create function each time to suit our current need. Then why not create a class that will be useful in most of this situation can further augmented to as new requirement pops in that can’t be fulfilled by our current class. Bellow is class that I build keeping in this mind I will like to share with you.

Code snipet


DBManager dbManager = new DBManager();
            dbManager.ConnectionString = ConfigurationManager.ConnectionStrings["MSSQL"].ToString();
            try
            {
                dbManager.Open();
                AffectedRows = dbManager.ExecuteNonQuery(System.Data.CommandType.Text, Query.ToString());
                return (AffectedRows > 0) ? true : false;
            }
            catch (Exception ex)
            {

            }
            finally
            {
                dbManager.Close();
            }
            return false;
 }
 in each of this method uses my data access layer that can be modified to suit your requirement.  

This class basically deals with SELECT; INSERT and UPDATE SQL statements modus operandi is to build a valid sql statement using input parameters
is either string or string array.


Here is My Class

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Conflux.DLL;
using System.Configuration;
using System.Data;

namespace Conflux.Helper
{
    public class MssqlQueryBuilder
    {
     public static bool Update(string TableName, string[] ColoumNames, string[] ColoumValues, 
                                              string[] WhereKey, string[] WhereValue)
        {
            string ExcludeQuotes=string.Empty;
            StringBuilder Query = new StringBuilder("update ");
            int AffectedRows = int.MinValue;
            if (ColoumNames.Length != ColoumValues.Length)
            {
                return false;
            }
            if (WhereKey.Length != WhereValue.Length)
            {
                return false;
            }
            Query.Append(TableName);
            Query.Append(" set ");
            for (int i = 0; i < ColoumNames.Length; i++)
            {
                Query.Append(ColoumNames[i]);
                Query.Append(" = ");
                Query.Append("'");
               
                ExcludeQuotes = ColoumValues[i];
                if (ColoumValues[i] != "")
                {
                    if ((ColoumValues[i].Substring(0, 1) == "'") && 
                      (ColoumValues[i].Substring(ColoumValues[i].Length - 1, 1) == "'"))
                    {
                        ExcludeQuotes = ColoumValues[i].Substring(1, ColoumValues[i].Length - 2);
                    }
                }
                Query.Append(ExcludeQuotes);
                Query.Append("'");
                if (i != ColoumNames.Length - 1)
                {
                    Query.Append(" , ");
                }

            }
            Query.Append(" where ");
           
             ExcludeQuotes = string.Empty;
            for (int i = 0; i < WhereKey.Length; i++)
            {
                Query.Append(WhereKey[i]);
                Query.Append(" = ");
                Query.Append("'");
               
                ExcludeQuotes = WhereValue[i];
                if (WhereValue[i] != "")
                {
                    if ((WhereValue[i].Substring(0, 1) == "'") && 
                          (WhereValue[i].Substring(WhereValue[i].Length - 1,1) == "'"))
                    {
                        ExcludeQuotes = WhereValue[i].Substring(1, WhereValue[i].Length - 2);
                    }
                }
                Query.Append(ExcludeQuotes);
                Query.Append("'");
                if (i != WhereKey.Length - 1)
                {
                    Query.Append(" and ");
                }
            }

            DBManager dbManager = new DBManager();
            dbManager.ConnectionString = ConfigurationManager.ConnectionStrings["MSSQL"].ToString();
            try
            {
                dbManager.Open();
                AffectedRows = dbManager.ExecuteNonQuery(System.Data.CommandType.Text, Query.ToString());
                return (AffectedRows > 0) ? true : false;
            }
            catch (Exception ex)
            {

            }
            finally
            {
                dbManager.Close();
            }
            return false;
        }
     public static bool Update(string TableName, string[] ColumnNames, string[] ColumnValues, 
                                                  string[] WhereKey, string[] WhereValue, string[] SkipColumns)
     {
         int NewArrayLength = ColumnNames.Length - SkipColumns.Length;
         string[] NewColumnNamesArray = new string[NewArrayLength];
         string[] NewColumnValuesArray = new string[NewArrayLength];
         int l = 0;
         for (int i = 0; i < SkipColumns.Length; i++)
         {
             for (int j = 0; j < ColumnNames.Length; j++)
             {
                 if (SkipColumns[i] != ColumnNames[j])
                 {
                     try
                     {
                         NewColumnNamesArray[l] = ColumnNames[j];
                         NewColumnValuesArray[l] = ColumnValues[j];
                         l++;
                     }
                     catch (Exception ex)
                     {

                     }
                 }
             }
         }
         return Update(TableName, NewColumnNamesArray, NewColumnValuesArray, WhereKey, WhereValue);
     }
    
     public static bool Insert(string TableName, string[] ColumnNames, string[] ColumnValues, string[] SkipColumns)
     {
         int NewArrayLength = ColumnNames.Length - SkipColumns.Length;
         string[] NewColumnNamesArray = new string[NewArrayLength];
         string[] NewColumnValuesArray = new string[NewArrayLength];
         int l=0;
         for (int i = 0; i < SkipColumns.Length; i++)
         {
             for (int j = 0; j < ColumnNames.Length; j++)
             {
                 if (SkipColumns[i] != ColumnNames[j])
                 {
                     NewColumnNamesArray[l] = ColumnNames[j];
                     NewColumnValuesArray[l] = ColumnValues[j];
                     l++;
                 }
             }
         }
         return Insert(TableName, NewColumnNamesArray, NewColumnValuesArray);
     }
     public static bool Insert(string TableName, string[] ColoumNames, string[] ColoumValues)
     {
         int AffectedRows = int.MinValue;
         StringBuilder Query = new StringBuilder("insert into ");
         if (ColoumNames.Length != ColoumValues.Length)
         {
             return false;
         }
         Query.Append(TableName + "(");
         for (int i = 0; i < ColoumNames.Length; i++)
         {
             Query.Append(ColoumNames[i]);
             if (i != ColoumNames.Length - 1)
             {
                 Query.Append(" , ");
             }
         }
         Query.Append(")values(");
         for (int i = 0; i < ColoumValues.Length; i++)
         {
             Query.Append("'");
             Query.Append(ColoumValues[i]);
             Query.Append("'");
             if (i != ColoumNames.Length - 1)
             {
                 Query.Append(" , ");
             }
         }
         Query.Append(")");

         DBManager dbManager = new DBManager();
         dbManager.ConnectionString = ConfigurationManager.ConnectionStrings["MSSQL"].ToString();
         try
         {
             dbManager.Open();
             AffectedRows = dbManager.ExecuteNonQuery(System.Data.CommandType.Text, Query.ToString());
             return (AffectedRows > 0) ? true : false;
         }
         catch (Exception ex)
         {

         }
         finally
         {
             dbManager.Close();
         }
         return false;

     }
    
     public static DataSet Select(string TableName)
     {
         StringBuilder Query = new StringBuilder("Select * from ");
         Query.Append(TableName);
       
         DBManager dbManager = new DBManager();
         dbManager.ConnectionString = ConfigurationManager.ConnectionStrings["MSSQL"].ToString();
         try
         {
             dbManager.Open();
             return dbManager.ExecuteDataSet(System.Data.CommandType.Text, Query.ToString());
         }
         catch (Exception ex)
         {
             throw new ArgumentException("Dynamically Generated Query Has Some Error");
         }
         finally
         {
             dbManager.Close();
         }
     }
     public static DataSet Select(string TableName, string[] ColoumNames, string[] WhereKey, string[] WhereValue)
     {
         StringBuilder Query = new StringBuilder("Select ");
         if (WhereKey.Length != WhereValue.Length)
         {
             throw new ArgumentException("WhereKey & WhereValue Array Size Differ");
         }

         for (int i = 0; i < ColoumNames.Length; i++)
         {
             Query.Append(ColoumNames[i]);
             if (i != ColoumNames.Length - 1)
             {
                 Query.Append(" , ");
             }

         }
         Query.Append(" from ");
         Query.Append(TableName);
         Query.Append(" where ");
         for (int i = 0; i < WhereKey.Length; i++)
         {
             Query.Append(WhereKey[i]);
             Query.Append(" = '");
             Query.Append(WhereValue[i]);
             Query.Append("'");
             if (i != WhereKey.Length - 1)
             {
                 Query.Append(" and ");
             }
         }

         DBManager dbManager = new DBManager();
         dbManager.ConnectionString = ConfigurationManager.ConnectionStrings["MSSQL"].ToString();
         try
         {
             dbManager.Open();
             return dbManager.ExecuteDataSet(System.Data.CommandType.Text, Query.ToString());
         }
         catch (Exception ex)
         {
             throw new ArgumentException("Dynamically Generated Query Has Some Error");
         }
         finally
         {
             dbManager.Close();
         }
     }
     public static DataSet Select(string TableName, string ColoumNames, string WhereKey, string WhereValue)
     {
         string[] whereselect = WhereKey.Split(',');
         string[] WhereselValue = WhereValue.Split(',');
         string[] wheresel = ColoumNames.Split(',');
         return MssqlQueryBuilder.Select(TableName, wheresel, whereselect, WhereselValue);
     }
    }
}


If anybody find it useful feel free to use it,it is nice of you if you provide me with any further addition into it.

Web Method Overloading


     We usually do method overloading with our normal csharp classes yet when we casually try to overload a web method we come across an error while consuming it.Today we will take this matter to logical end as we are not in hurry to complete the task.

Lets add a webservice to your website add two method to add integers as follows

[WebMethod]
    public int AddInt(int num1,int num2) {
        return num1 + num2;
    }

[WebMethod]
    public int AddInt(int num1, int num2,int num3)
    {
        return num1 + num2+ num3;
    }

Code in your aspx page should look similar to

[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class Math : System.Web.Services.WebService {

    public Math () {

        //Uncomment the following line if using designed components
        //InitializeComponent();
    }

    [WebMethod]
    public int AddInt(int num1,int num2) {
        return num1 + num2;
    }
    [WebMethod]
    public int AddInt(int num1, int num2,int num3)
    {
        return num1 + num2+ num3;
    }
}
Now compile code you will not get any error, make this asmx page as start page and run it you will be greeted with following error

Both Int32 AddInt(Int32, Int32, Int32) and Int32 AddInt(Int32, Int32) use the message name 'AddInt'.  Use the MessageName property of the WebMethod custom attribute to specify unique message names for the methods.


Now let’s find what went wrong

Error ask us to give different MessageName to overloaded methods lets do the same

[WebMethod(MessageName="AddTwoInts")]
    public int AddInt(int num1,int num2) {
        return num1 + num2;
    }
[WebMethod(MessageName = "AddThreeInts")]
    public int AddInt(int num1, int num2,int num3)
    {
        return num1 + num2+ num3;
    }

Modify your code to accommodate different MessageName to overloaded methods re run

Again we got some error message this time bit long

Service 'Math' does not conform to WS-I Basic Profile v1.1. Please examine each of the normative statement violations below. To turn off conformance check set the ConformanceClaims property on corresponding WebServiceBinding attribute to WsiClaims.None.
R2304: Operation name overloading in a wsdl:portType is disallowed by the Profile. A wsdl:portType in a DESCRIPTION MUST have operations with distinct values for their name attributes. Note that this requirement applies only to the wsdl:operations within a given wsdl:portType. A wsdl:portType may have wsdl:operations with names that are the same as those found in other wsdl:portTypes.
 -  Operation 'AddInt' on portType 'MathSoap' from namespace 'http://tempuri.org/'.
To make service conformant please make sure that all web methods belonging to the same binding have unique names.

After reading this bulky error message it can be concluded that finger is pointed toward following line in our code

[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]

WS-I-BASIC PROFILE 1.0 specification seems to not allowing our beloved method overloading lets not confirm to it.
Modify this line as bellow

[WebServiceBinding(ConformsTo = WsiProfiles.None)]

Now rerun now you should be able to consume this webservice without any error which has method overloading.

Now let’s analyze why we have to improvise our code?

    The WS-I organization (Web Services Interoperability Organization) publishes non-proprietary Web services specifications to promote the interoperability of Web services across platforms. WsiProfiles enumeration is used by the ConformsTo property of the WebServiceBindingAttribute attribute to indicate the WSI specification to which the Web service claims to conform.
     WS-I Basic Profile 1.0 specification relates to conformant use of WSDL to describe a web service. To go deep into this standard read reverences bellow.

References:

2012/04/21

SOAP for Webservices- Quick Introduction


Quite often than not IT infrastructure of an organization is made up of multiple vendor providing solutions using multiple technologies, Lot of Replication of data stores and functionality is required for communication between these multiple vendor systems to alleviate this XML standard was introduced.
   Though XML provide flexibility to structure data for developer but for better communication between disparate systems some rules for structuring data in xml document are needed, such rules will pave a way higher degree of automation in communication. Simple Object Access Protocol (SOAP) provides the rule that will
Help to standardize the communication using xml document.
    SOAP let us to expose and consume complex data structures e.g. dataset or table.


More about SOAP
     "Simple Object Access Protocol" was designed to be a platform and language-neutral alternative to previous middleware technologies like CORBA and DCOM. SOAP was also designed from the ground up to be extensible, so that other standards could be integrated into it
A SOAP message is an ordinary XML document containing the following elements.
·         Envelope: (Mandatory)
defines the start and the end of the message.
·         Header: (Optional )
Contains any optional attributes of the message used in processing the message, either at an intermediary point or at the ultimate end point.
·         Body: (Mandatory)
Contains the XML data comprising the message being sent.
·         Fault: ( Optional )
An optional Fault element that provides information about errors that occurred while processing the message
For details about of SOAP please refer links provide in reference section of article.
Here is a link to a site that let you to see by naked eye how soap request and soap response may look like

A SOAP Example


If a request is send to web service to invoke GetAccountBalance method having AccountCode as input parameter which returns Balance and the namespace for the function is defined in http://www.conflux.org/accountbalance then SOAP request and SOAP Response may look like

A SOAP request:


POST /URL_FromWhichCallMade HTTP/1.1
Host: www. conflux.org
Content-Type: application/soap+xml; charset=utf-8
Content-Length: nnn

<?xml version="1.0"?>
<soap:Envelope
xmlns:soap="http://www.w3.org/2001/12/soap-envelope"
soap:encodingStyle="http://www.w3.org/2001/12/soap-encoding">

<soap:Body xmlns:m=" http://www.conflux.org/accountbalance ">
  <m: GetAccountBalance >
    <m: AccountCode >00888786555</m: AccountCode >
  </m: GetAccountBalance >
</soap:Body>
</soap:Envelope>

The SOAP response:

HTTP/1.1 200 OK
Content-Type: application/soap+xml; charset=utf-8
Content-Length: nnn

<?xml version="1.0"?>
<soap:Envelope
xmlns:soap="http://www.w3.org/2001/12/soap-envelope"
soap:encodingStyle="http://www.w3.org/2001/12/soap-encoding">

<soap:Body xmlns:m="http://www.conflux.org/accountbalance">
  <m: GetAccountBalance >
    <m: Balance >4523</m: Balance >
  </m: GetAccountBalance >
</soap:Body>

</soap:Envelope>

Here we have seen how soap request & soap response look like point to note that both are XML document with additional soap related nodes.

<soap:Envelope> act as root just like <html> in any html page,   it contain <soap:Body>
Just like <body> tag in html. <soap:Envelope> can also contain <soap:Header>
 <soap:Fault>.
References: