- Here's the Scenario
- Atomicity, Consistency, Isolation, and Durability (ACID)
- Automatic Web Service Transactions Are Easy
- A Web Service That Uses An Automatic Transaction
- Using the Web Service from an ASP.NET Page
- Summary
Using the Web Service from an ASP.NET Page
Once the web service has been created and compiled, we need to create an ASP.NET application that uses it. With Visual Studio .NET, simply create an ASP.NET project using VB or C#.
It's easy to add a wrapper class that takes care of all of the plumbing that needs to happen to use a web service. The wrapper class performs all of the data conversions to a Simple Object Application Protocol (SOAP) payload, which is needed to communication with the web service. To create a web service wrapper class, start by right-clicking the References folder in the Visual Studio .NET Solution Explorer. Select Add Web Reference when the menu appears. In the Add Web Reference dialog box, type the URL to the web service .asmx file followed by ?WSDL as shown in Figure 3. The ?WSDL lets the web service know that you're requesting its contract. This contract describes the behavior of the web service, and is used to create the wrapper class.
Figure 3 To add a web service wrapper class, you must type the URL plus ?WSDL.
Once you hit Enter, the XML will be shown in the left pane (see Figure 4). This XML contains the contract description. All you have to do now is click the Add Reference button to add the wrapper class to your project.
Figure 4 The contract XML is shown in the left pane.
The user interface for the demo application is simple. It has some text, six TextBox objects, and two buttons (see Figure 5).
Figure 5 The user interface has six editable fields.
To use the application, you type a PIN and an amount and click the Deposit or Withdraw button to update the balance. If the web service throws an exception, it will be shown in a label below the editable fields.
NOTE
This application can be run from the URL http://www.aspnet-solutions.com/DemoWebServicesTransactions/Default.aspx.
TIP
When you're developing applications and web services at the same time, there will be occasions when the parameters to a web method might change. There's an easy way to update the web service wrapper class in an application without having to delete and re-add it. All you need to do in the application is right-click the web reference class (in the Web References folder of the Visual Studio .NET Solution Explorer window) and select Update Web Reference.
All of the transactions for a user are displayed in a DataList object. Listings 34 show the code behind the Default.aspx page (both the C# and VB versions).
Listing 3The Code Behind Default.aspx (C#)
private void Page_Load(object sender, System.EventArgs e) { if( !IsPostBack ) { // Create an account based on the system clock. Account.Text = Convert.ToString( DateTime.Now.Ticks ); // We need this for random numbers. Random rnd = new Random( (int) DateTime.Now.Ticks ); // Create a random balance. double dBalance = (double) ( rnd.Next( 2000 ) + 5000 ); // Convert the balance to a string. Balance.Text = dBalance.ToString( "$.00" ); // Start the charge off at zero. Charge.Text = "$0.00"; } } private void PopulateTransactionList() { // Create a database connection object. SqlConnection objConnection = new SqlConnection( "server=localhost;uid=sa;pwd=;database=WebServiceTransactions" ); try { // Open the connection. objConnection.Open(); // Create the SQL string. string strSql = string.Format( "select TD,Bal=CAST(Balance As varchar)," + "Amt=CAST(Amount As varchar) from Transactions " + "where Account='{0}' order by TD", Account.Text ); // Create a command object. SqlCommand objCommand = new SqlCommand( strSql, objConnection ); // Execute the SQL and get a SqlDataReader object back. SqlDataReader objReader = objCommand.ExecuteReader(); // Set the data source for the DataList. TransactionList.DataSource = objReader; // Bind the data source to the DataList. TransactionList.DataBind(); // Close the SqlDataReader object. objReader.Close(); } catch( Exception ex ) { // Let the user know of any errors. ErrorMessage.Text = ex.Message.ToString(); } finally { // If the connection is open, close it. if( objConnection.State == ConnectionState.Open ) { objConnection.Close(); } } } private string ExtractExceptionMessage( string strCompleteExceptionMessage ) { // Look for the start of the exception message that was thrown // from within the Web Service int nStartIndex = strCompleteExceptionMessage.IndexOf( "invocation. ---> " ) + 17; // Skip the exception type (such as System.Exception :) nStartIndex = strCompleteExceptionMessage.IndexOf( ":", nStartIndex ) + 2; // Find the end of the exception description // before the debug information. int nEndIndex = strCompleteExceptionMessage.IndexOf( " at ", nStartIndex ); // Return the substring that contains the actual exception message. return( strCompleteExceptionMessage.Substring( nStartIndex, nEndIndex - nStartIndex ) ); } private void DoTransaction( int nType ) { // Instantiate the Web Service wrapper class. com.aspnet_solutions.http://www.BankTrans bt = new com.aspnet_solutions.http://www.BankTrans(); double dBalance = 0, dAmount = 0, dCharge = 0; try { // Get the balance as a double. dBalance = Convert.ToDouble( Balance.Text.Substring( 1, Balance.Text.Length - 1 ) ); // Get the amount as a double. dAmount = Convert.ToDouble( Amount.Text ); // Get the charges as a double. dCharge = Convert.ToDouble( Charge.Text.Substring( 1, Charge.Text.Length - 1 ) ); } catch( Exception ex ) { // If there was a numeric conversion exception, // we'll catch it here, show the error, and // return to caller. ErrorMessage.Text = ex.Message.ToString(); return; } try { // Call the Transact() method. bt.Transact( nType, dBalance, dAmount, Account.Text, PIN.Text ); } catch( Exception ex ) { ErrorMessage.Text = ExtractExceptionMessage( ex.Message.ToString() ); return; } // Show the results. ErrorMessage.Text = "Transaction success"; // If a deposit, add the amount. if( nType == 1 ) { dBalance += dAmount; } // Otherwise subtract the amount. else { dBalance -= dAmount; } // Subtract the transaction charge from the balance. dBalance -= 0.15; // Add the transaction charge to the charge variable. dCharge += 0.15; // Set the user interface strings to the updated values. Balance.Text = dBalance.ToString( "$.00" ); Amount.Text = ""; Charge.Text = dCharge.ToString( "$.00" ); PopulateTransactionList(); } private void Deposit_Click(object sender, System.EventArgs e) { DoTransaction( 1 ); } private void Withdraw_Click(object sender, System.EventArgs e) { DoTransaction( 0 ); }
Listing 4The Code Behind Default.aspx (VB)
Private Sub Page_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.Load If Not IsPostBack Then ' Create an account based on the system clock. Account.Text = Convert.ToString(DateTime.Now.Ticks) ' We need this for random numbers. Dim rnd As New Random(DateTime.Now.Ticks) ' Create a random balance. Dim dBalance As Double = (rnd.Next(2000) + 5000) ' Convert the balance to a string. Balance.Text = dBalance.ToString("$.00") ' Start the charge off at zero. Charge.Text = "$0.00" End If End Sub Private Sub PopulateTransactionList() ' Create a database connection object. Dim objConnection As _ New SqlConnection(_ "server=localhost;uid=sa;pwd=;database=WebServiceTransactions") Try ' Open the connection. objConnection.Open() ' Create the SQL string. Dim strSql As String = _ String.Format("select TD,Bal=CAST(Balance As varchar)," + _ "Amt=CAST(Amount As varchar) from Transactions " + _ "where Account='{0}' order by TD", _ Account.Text) ' Create a command object. Dim objCommand As New SqlCommand(strSql, objConnection) ' Execute the SQL and get a SqlDataReader object back. Dim objReader As SqlDataReader = objCommand.ExecuteReader() ' Set the data source for the DataList. TransactionList.DataSource = objReader ' Bind the data source to the DataList. TransactionList.DataBind() ' Close the SqlDataReader object. objReader.Close() Catch ex As Exception ' Let the user know of any errors. ErrorMessage.Text = ex.Message.ToString() Finally ' If the connection is open, close it. If objConnection.State = ConnectionState.Open Then objConnection.Close() End If End Try End Sub Private Function ExtractExceptionMessage(_ ByVal strCompleteExceptionMessage As String) As String ' Look for the start of the exception message that was thrown ' from within the Web Service Dim nStartIndex As Integer = _ strCompleteExceptionMessage.IndexOf("invocation. ---> ") + 17 ' Skip the exception type (such as System.Exception :) nStartIndex = _ strCompleteExceptionMessage.IndexOf(":", nStartIndex) + 2 ' Find the end of the exception description before the ' debug information. Dim nEndIndex As Integer = _ strCompleteExceptionMessage.IndexOf(" at ", nStartIndex) ' Return the substring that contains the actual exception message. Return (strCompleteExceptionMessage.Substring(nStartIndex, _ nEndIndex - nStartIndex)) End Function Private Sub DoTransaction(ByVal nType As Integer) ' Instantiate the Web Service wrapper class. Dim bt as _ new com.aspnet_solutions.http://www.BankTrans() Dim dBalance As Double = 0, dAmount As Double = 0, _ dCharge As Double = 0 Try ' Get the balance as a double. dBalance = Convert.ToDouble(Balance.Text.Substring(1, _ Balance.Text.Length - 1)) ' Get the amount as a double. dAmount = Convert.ToDouble(Amount.Text) ' Get the charges as a double. dCharge = Convert.ToDouble(Charge.Text.Substring(1, _ Charge.Text.Length - 1)) Catch ex As Exception ' If there was a numeric conversion exception, ' we'll catch it here, show the error, and ' return to caller. ErrorMessage.Text = ex.Message.ToString() Return End Try Try ' Call the Transact() method. bt.Transact( nType, dBalance, dAmount, _ Account.Text, PIN.Text ) Catch ex As Exception ErrorMessage.Text = ExtractExceptionMessage(ex.Message.ToString()) Return End Try ' Show the results. ErrorMessage.Text = "Transaction success" ' If a deposit, add the amount. If nType = 1 Then dBalance += dAmount Else ' Otherwise subtract the amount. dBalance -= dAmount End If ' Subtract the transaction charge from the balance. dBalance -= 0.15 ' Add the transaction charge to the charge variable. dCharge += 0.15 ' Set the user interface strings to the updated values. Balance.Text = dBalance.ToString("$.00") Amount.Text = "" Charge.Text = dCharge.ToString("$.00") PopulateTransactionList() End Sub Private Sub Deposit_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles Deposit.Click DoTransaction(1) End Sub Private Sub Withdraw_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles Withdraw.Click DoTransaction(0) End Sub
The code in Listings 34 is fairly straightforward, but let's talk through it right now so that you understand what I've done.
The Page_Load() Method
The Page_Load() method initializes three of the user interface TextBox objects the first time that the page is loaded. The Account TextBox object is initialized with the system ticks, and this is used to simulate an account number. A Random object is then instantiated so that a random number can be obtained. This random number (plus 5,000) will be the starting bank balance, and the value will be converted to a text string and placed in the Balance TextBox object. Finally, the Charge TextBox object will be initialized to $0.00.
The PopulateTransactionList() Method
The PopulateTransactionList() method queries the database for all records that are in the currently displayed account. When a user first loads the page, no transactions will be shown, because the user account has just been created based on the system ticks.
The first thing that happens in this method is that a SqlConnection object is created. The only argument to its constructor is the connection string (with the password removed here in the text). A try/catch/finally construct handles any database errors that are thrown. Inside the try block, the connection is opened with the SqlConnection.Open() method. Figure 6 shows the Transactions table.
Figure 6 The Transactions table.
A SQL string is formed that will get all of the transactions for the currently displayed account number. The SQL is a little more complicated than it might need to be, but this is because we want to make sure that the money amounts have two decimal places. If we weren't concerned about the formatting, the following SQL statement would work for all records for the 12345 account:
Select TD,Balance,Amount from Transactions where Account='12345'
Even through the Balance and Amount fields are of the money type in the database, when they are bound to the DataList object they may not have two decimal places. For that reason, we add some extra T-SQL code as follows:
Bal=CAST(Balance As varchar)
and
Amt=CAST(Amount As varchar)
The statement, with these two additions, is now as follows:
Select TD, Bal=CAST(Balance As varchar), Amt=CAST(Amount As varchar) from Transactions where Account='12345'
Notice in the code, though, that the string.Format() method is used to prevent additional string objects from being created in the background.
Once we have the SQL string, we create a SqlCommand object. It has two arguments: the SQL string and the SqlConnection object. The SqlCommand object's ExecuteReader() method is called, which in turn returns a SqlDataReader object that contains all the records matching the account number.
The DataList object is named TransactionList. This object's DataSource property is set to the SqlDataReader, and then the DataBind() method is called. This will populate the records to the DataList object. Finally, the SqlDataReader object is closed.
The catch block takes the exception message and places it into the user interface Label named ErrorMessage. The finally block closes the database connection if it is open.
The ExtractExceptionMessage() Method
The ExtractExceptionMessage() method is needed because when the web service wrapper class throws an exception, there is a lot of information in addition to the actual exception message. This method extracts what's between "invocation ---> " and " at ". This is the effective exception that was thrown within the web service.
The DoTransaction() Method
This method instantiates the web service wrapper class, calls the Transact() web method, and then displays the results for the user.
The first thing that happens is the instantiation of the web service, as shown below:
C#:
com.aspnet_solutions.http://www.BankTrans bt = new com.aspnet_solutions.http://www.BankTrans();
VB:
Dim bt As New com.aspnet_solutions.http://www.BankTrans()
Three variables of the type double are declared, and their values are initialized to zero. A try/catch construct will take care of any numeric conversion errors. These will happen if the user interface TextBox objects contain non-numeric characters, or no characters at all. If everything goes well, though, the Convert.ToDouble() method converts the text in the TextBox objects to numeric values. If an exception was thrown, the user is notified of the error, with the exception message placed into the ErrorMessage Label object.
Another try/catch construct catches exceptions that the Transact() web method throws. The call to Transact() is made with five parameters: the transaction type, the balance, the amount, the account number, and the PIN. Possible exceptions that the web method might throw are that the PIN is not four characters, or the balance is below zero. If an exception is caught, the real exception message is extracted using the ExtractExceptionMessage() method, the ErrorMessage Label is set to this text, and a Return is executed.
If everything goes okay, though, the only thing left to do is to update the user interface objects. The dBalance variable is updated using the dAmount variable, and an additional 15 cents is subtracted for the transaction charge. The dCharge variable gets an additional 15 cents to reflect the total transaction charges. The Balance, Amount, and Charge TextBox objects are updated to reflect the new values. Finally, a call to PopulateTransactionList() is made to show the list of transactions for this user.
The Deposit_Click() Method
The Deposit_Click() method simply calls the DoTransaction() method with the argument of 1 so that a deposit is made.
The Withdraw_Click() Method
The Withdraw_Click() method calls the DoTransaction() method with the argument of 0 so that a withdrawal is made.