BizTalk Utilities CV ,   Jobs ,   Code library  
 
 

Washington, September 15-18, 1999 – London, November 21-24, 1999

The presentation assumes you're familiar with ASP and VB. A minimum knowledge of MTS and COM helps but is not required.

Component Programming 2

Brian Francis

This session will follow on to the Component Programming 1 session. We will look at how to build components that can take advantage of Microsoft Transaction Server. Next, we will look at the ACID properties that define a transaction. Finally, we will look at how to create a data access component using VB and then integrate that component with MTS.

Brian Francis (bfrancis@mindspring.com) Brian Francis is the technical evangelist for NCR's Self Service Solutions. From his office in Duluth, Georgia, Brian is responsible for enlightening NCR and their customers in the technologies and tools used for Self Service Applications. Brian was formerly the lead developer for NCR's Human Interface Technology Center in Atlanta, Georgia. At the HITC, Brian was responsible for prototyping and developing advanced applications that applied superior human interfaces as developed at the Center.

Brian is the author/co-author of numerous books from Wrox Press, including Professional ASP 2.0, Beginning ASP 2.0, Professional IE4 Programming and ASP Programmer's Reference. Brian was also a featured speaker at the first Professional ASP Developers Conference.

MTS Components

There are several areas that will cover the areas that make developing MTS components different from developing regular COM components. These areas include how to hold state in an object, issues surrounding the various threading models, and the scope at which objects are visible.

Holding State

In developing objects, whether they are standard COM objects or COM objects that work with MTS, an object can be one of two types - stateless or stateful. A stateless object does not retain any information from one method call to the next, whereas a stateful object has some internal storage mechanism that allows it to retain information between two separate method calls.

An application designer can take advantage of either type of component in their applications. The two types of components can even be mixed in one application. There may be times that it is more efficient from a coding standpoint to develop a component that holds state within the object. However, there are tradeoffs in using stateful components within the context of MTS.

Activation and Deactivation

MTS can manage the creation and utilization of components automatically. This allows applications to scale much more efficiently than using other means. To look at the effect that state has on this mechanism, we need to look at the deactivation of objects.

An object has two modes inside of MTS. When it is first created, it is deactivated. A deactivated object looks to the client as a real object, but it consumes very few resources on the server. Through a process known as just-in-time activation, MTS will activate the component when the client actually calls a method on the object. This allows the clients to not have to worry about when it should create an instance of an object in relation to when it is used. The client can create the instance and hold it as long as it wants before using it, as MTS will not activate the object until it is needed.

There are three ways that an object, once activated, can be deactivated. It can request deactivation through the ObjectContext interface by calling either SetComplete or SetAbort. If the object is participating in a transaction, and that transaction is committed or aborted, then the object will be deactivated. Finally, if all of the clients that are accessing the object release their references to that object, then it will be deactivated. When an object is deactivated, MTS can use the resources that were allocated to it for other objects. This means that any information that was stored inside of the object is lost. The recently deactivated object could also be reused if MTS detects another client requesting the same object.

Stateful Components

A stateful component is one that retains internal information from one method call to another. For an object to be able to do this, there are certain tradeoffs that must be made. MTS gains its efficiency from being able to automatically activate and then quickly deactivate objects. As we said earlier, when an object deactivates, it loses all information that is stored inside of it. In order for an object to be stateful, it must maintain internal information, which means it cannot be deactivated. If a stateful object were to be deactivated, it would lose all of its state, thus becoming a stateless object.

When an object decides to become stateful, MTS no longer has the ability to use its resources for any other objects. The stateful object has in effect locked a portion of the resources that MTS has to work with. MTS relies on its ability to dynamically manage resources in order to allow applications to effectively scale. Therefore, if a system is using a large number of stateful objects, then it will be much less efficient when scaling.

This does not mean to say that you should never use stateful objects when developing MTS applications. As we stated earlier, the use of stateful objects can make the development of client applications much easier. It is just important for the developer to understand the implications that using stateful objects will have on the scalability of their application.

ACID properties

When a transaction processing system creates a transaction, it will ensure that the transaction will have certain characteristics. The developers of the components that comprise the transaction are assured that these characteristics are in place, and as such do not need to manage these characteristics themselves. These characteristics are known as the ACID properties. ACID is an acronym for atomicity, consistency, isolation, and durability.

Atomicity

The atomicity property identifies that the transaction is atomic. An atomic transaction is either fully completed, or not completed at all. Any updates that a transaction might affect on a system are completed in their entirety. If for any reason an error occurs and the transaction is unable to complete all of its steps, the then system is returned to the state it was in before the transaction was started. An example of an atomic transaction is an account transfer transaction. The money is removed from account A then placed into account B. If the system fails after removing the money from account A, then the transaction processing system will put the money back into account A, thus returning the system to its original state. This is known as a rollback.

Consistency

A transaction enforces consistency in the system state by ensuring that at the end of any transaction the system is in a valid state. If the transaction completes successfully, then all changes to the system will have been properly made, and the system will be in a valid state. If any error occurs in a transaction, then any changes already made will be automatically rolled back. This will return the system to its state before the transaction was started. Since the system was in a consistent state when the transaction was started, it will once again be in a consistent state.

Looking again at the account transfer system, the system is consistent if the total of all accounts is constant. If an error occurs and the money is removed from account A and not added to account B, then the total in all accounts would have changed. The system would no longer be consistent. By rolling back the removal from account A, the total will again be what it should be, and the system back in a consistent state.

Isolation

When a transaction runs in isolation, it appears to be the only action that the system is carrying out at one time. If there are two transactions that are both performing the same function and are running at the same time, transaction isolation will ensure that each transaction thinks it has exclusive use of the system. This is important in that as the transaction is being executed, the state of the system may not be consistent. The transaction ensures that the system remains consistent after the transaction ends, but during an individual transaction, this may not be the case. If a transaction was not running in isolation, it could access data from the system that may not be consistent. By providing transaction isolation, this is prevented from happening.

Durability

A transaction is durable in that once it has been successfully completed, all of the changes it made to the system are permanent. There are safeguards that will prevent the loss of information, even in the case of system failure. By logging the steps that the transaction performs, the state of the system can be recreated even if the hardware itself has failed. The concept of durability allows the developer to know that a completed transaction is a permanent part of the system, regardless of what happens to the system later on.

Creating a Data Access Component using VB

Perhaps the most powerful technology in Active Server Pages is the ability to integrate web pages with server-side databases. This allows you to quickly and easily publish information from databases to a browser. The era of static web sites is long gone and database-driven web sites are becoming the norm – if not always expected.

You are probably already familiar with accessing databases directly from ASP scripts. In the previous session, we have seen how to extend ASP through the use of components written in Visual Basic. Now we will combine the two, and see how to utilize the database access facilities of ADO from a VB Component.

Referencing the Data Objects

Whenever you want to utilize some new component type in Visual Basic, the first step is to add a reference to that component into your VB Project. Once VB is aware of the component type, it can make the development much easier to do. With your VB Project open, you can select the References… selection from the Project menu.

You will need to add a reference to the Microsoft ActiveX Data Objects Library. As you can see from the screen shot, there are multiple versions of this library available. You should always select the version of the library that corresponds to the version of ADO that is installed on your web server.

Now that you have a reference added to Visual Basic, you can set about extending the component to work with databases. For this example, we have added an additional class module to the VB project. This new class is called DBTest. We have copied over some of the initialization code from the existing class module.

Option Explicit

Private m_OC As ObjectContext

Private Sub Class_Initialize()

    Set m_OC = GetObjectContext()

End Sub

Notice that we will still need to use the ObjectContext in this class as well. We will be using it to reference the ASP intrinsic components, as well as providing an interface to create the database components.

ActiveX Data Objects (ADO) is the friendly face of OLE-DB. This means that you don't actually have to know anything about OLE-DB, since ADO will hide all of the complexity from you, giving you a simple way of accessing data from any data store. ADO is the way we actually get data to and from a data store, allowing us to, amongst other things, read records, find specific records, and update data.

If you've done any database programming in Access or Visual Basic then you'll have come across Data Access Objects or Remote Data Objects. Well don't worry, because ADO is a superset of DAO and RDO and is much easier to understand. We will be using ADO in all of our transaction examples

We will not go into detail about how to work with ADO, as that is a topic for a different session. We will assume that you understand the basics of ADO – the relationship between Connection, Command, and Recordset objects – and how to use these object to access data.

We're not going to explain this in detail here but it's important to note that just because the Connection object is at the top, that doesn't necessarily mean you have to have a Connection object. You can work directly with a Command or Recordset without having first created a separate Connection. Having said that, we're going to start with the connection object, because if you understand how it works, you'll find the rest of the database work easier.

Here are some quick examples of using ASP and ADO to access a database. For this example, and for the examples that follow, we will be using the PUBS database that comes as part of SQL Server.

Open a Database Connection

In this example, we will be opening a connection to a database. This connection will be represented by a Connection object. There are two different ways of identifying a database to be opened:

ุ        DSN – Data Source Name for ODBC Connection

ุ        DNS-less – Connecting to a database without a DSN

We will be using a DSN-less connection in this example. One of the important things to note is that to take advantage of the Resource Manager capabilities of SQL Server, you have to connect to it using an OLE-DB connection. The DSN only works with ODBC connections, which will not allow you access to the transaction management capabilities of MTS when accessing the database.

<%

dim db, strConn

set db = Server.CreateObject("ADODB.Connection")

strConn = "PROVIDER=SQLOLEDB; DATA SOURCE=bftecra; DATABASE=pubs; USER ID=sa; PASSWORD=;"

db.Open strConn

%>

In this example, the critical part to look at is the connection string. We are creating this string, storing it in a local variable, and then passing that local variable to the Open method of the ADO Connection object. The connection string defines the characteristics of the connection. These values are:

PROVIDER

SQLOLEDB

This tells ADO to use the OLE DB connection to SQL Server. Remember that we need OLE DB in order to take advantage of the Resource Manager capabilities

DATA SOURCE

bftecra

The machine name of the SQL Server

DATABASE

pubs

The database name we are accessing

USER ID

sa

The user id to log into the database server with

PASSWORD

The password for the user id sa. In this case, the password is blank

One the connection string has been set, it is passed to the Open method of the Connection object, and if everything is set correctly, the connection to the database will be opened.

Open a Recordset

Now that we have successfully opened a connection to the database, the next step is to open a recordset that we can read information from. In this example, we will be opening the authors table as a recordset. Once it is opened, we can iterate through it and retrieve all of the author's last names from the table,

<%

dim db, strConn

set db = Server.CreateObject("ADODB.Connection")

strConn = "PROVIDER=SQLOLEDB; DATA SOURCE=bftecra;database=pubs;USER ID=sa; PASSWORD=;"

db.Open strConn

dim rsAuthors

set rsAuthors = Server.CreateObject("ADODB.Recordset")

rsAuthors.Open "authors", db

do while not rsAuthors.EOF

    Response.Write rsAuthors("au_lname") & "<BR>"

    rsAuthors.MoveNext

Loop

%>

As you can see, the code to open the database is the same as the previous example. In fact, for all of the examples in this section, we will use the exact same code to open the database connection. Since we will be using an ADO Recordset to view the information in the file, we will need a reference to a Recordset object. This reference is created by using the CreateObject method of the Server object and passing the class ID of the ADO Recordset component. This creates the Recordset component – to actually connect it to a table in the database, we need to use its Open method. This method takes as a parameter the name of the table we want to open, in this case the authors table, as well as a reference to a valid connection to a database.

Once we have the Recordset open and connected to the table that we want, we will retrieve all of the author's last names from the table and display it in the browser. The EOF property of the recordset will be set to True if you have tried to move past the end of the database. So while this property is False, we know that we are pointing at a valid record, so we can output information from that record to the browser and then move to the next record.

Query the Database

In addition to using a Recordset to open a table in a database, you can also use a SQL query to retrieve information from a database. This information is returned to you as a Recordset, but it only contains those fields and rows that match the results of the query.

<%

dim db, strConn

set db = Server.CreateObject("ADODB.Connection")

strConn = "PROVIDER=SQLOLEDB; DATA SOURCE=bftecra;database=pubs;USER ID=sa; PASSWORD=;"

db.Open strConn

dim rsTitles

set rsTitles = db.Execute ("SELECT title, price, notes FROM titles WHERE price > 5;")

%>

<TABLE BORDER=1>

<TR>

<TH width=50%>Title</TH><TH>Price</TH><TH>Notes</TH>

</TR>

<%

do while not rsTitles.EOF

    Response.Write ("<TR>")

    Response.Write "<TD>" & rsTitles("title") & "</TD>"

    Response.Write "<TD align=right>" & FormatCurrency(rsTitles("price")) & "</TD>"

    Response.Write "<TD>" & rsTitles("notes") & "</TD></TR>" & vbCRLF

    rsTitles.MoveNext

Loop

%>

To create our Recordset using a SQL query, we will use the Execute method of the Connection object. This method will accept a valid SQL query as a parameter and return a reference to a Recordset that contains the results of the query. We do not need to explicitly create a Recordset using the Server.CreateObject method since one is automatically created by the Execute method.

This time, since we are displaying more than one field, we will construct a table to display the information. The table header will only be displayed once, so it needs to appear before the beginning of the loop. We will use the same type of loop that we used in the previous example, except we will be outputting three fields from the recordset. The second file is a currency format field, so we will want to format it properly as a currency. To do this, we can use the VBScript FormatCurrency function.

Add data to a Table

The next step after being able to retrieve information from a database is to add or update data in a table. There are two ways that this can be done. First, a Recordset can be used not only retrieve information from a table, it can also be used to update existing information or add new information to that table.

<%

dim db, strConn

set db = Server.CreateObject("ADODB.Connection")

strConn = "PROVIDER=SQLOLEDB; DATA SOURCE=bftecra;database=pubs;USER ID=sa; PASSWORD=;"

db.Open strConn

dim rsAuthors

set rsAuthors = Server.CreateObject("ADODB.Recordset")

rsAuthors.Open "authors", db, adOpenDynamic, adLockOptimistic

rsAuthors.AddNew

rsAuthors("au_id") = "119-54-2834"

rsAuthors("au_lname") = "Francis"

rsAuthors("au_fname") = "Brian"

rsAuthors("contract") = True

rsAuthors.Update

rsAuthors.Close

Response.Write "Record Added Successfully"

%>

In this example, we will be adding information to the authors table by using a recordset. Since the table will be opened directly by the recordset, we will first need to create a recordset object, then open it for the authors table in the database. Since we will be adding new information to this table, we will need to add some additional parameters to the Open method.

The first of these new parameters defines the type of cursor we will use to access the information in the database. The adOpenDynamic cursor will allow all changes made to this table, by you or any other users, to be visible once the change is committed. The second new parameter the type of locking that will be used whenever a row is edited in the table. The adLockOptimistic lock type will only lock the record once it is ready to be updated. This allows other users full access to the database except at the moment when you add a record.

With the recordset now open, we can create a new record to add to the table by using the AddNew method of the recordset object. Once we call AddNew, we can set various fields in the record using relatively the same syntax as we used when retrieving information from the table. With the information properly added to the new record, we will use the Update method to store the information to the database.

The second method that we can use to update information in a table is to use the SQL INSERT command. This command allows us to define the table and fields that we want to insert, along with the values that should be inserted.

<%

dim db, strConn

set db = Server.CreateObject("ADODB.Connection")

strConn = "PROVIDER=SQLOLEDB; DATA SOURCE=bftecra;database=pubs;USER ID=sa; PASSWORD=;"

db.Open strConn

dim strSQL

strSQL = "INSERT INTO authors (au_id, au_lname, au_fname) VALUES ('119-54-3234', 'Francis', 'Brian');"

db.Execute strSQL

%>

The INSERT statement needs to know the table that the record is being added to – authors – the fields that will have values set – au_id, au_lname, & au_fname – and the list of values for each of these fields. This SQL statement is stored as a string and then passed to the Execute method of the Connection object. This is the same method we used to retrieve information from the database earlier, but since the INSERT query does not return any information, we do not get a recordset as a return value from this method.

Transacted Data Access Components

In this section, we will continue with our theme of using transactions to interact with databases from ASP. This time, instead of that database interaction taking place within an ASP file, we will move that interaction to an ASP Component. By building MTS-aware components, they can participate in determining the outcome of a transaction, enlist other objects to participate in the transaction, and handle events fired at the completion of a transaction based on its success or failure.

There are a set of straightforward steps to follow in making a component MTS-aware. We will take our banking example from the previous section and move the database access to an MTS-aware component, while retaining the business rules inside of the ASP page. The steps to follow in building this component are:

ุ        Getting the Context – we need to obtain a reference to the current transaction context in order to interact with MTS

ุ        Accessing the Data – the information that we need to work with is stored in a database, so we will need access to that data

ุ        Writing New Data – this component will be adjusting the balance in an account, so it will need to write the updated balance to the database

ุ        Completing the Transaction – if the database access works properly, then this component needs to indicate this by voting for a successful transaction

ุ        Aborting the Transaction – if the database could not be updated for some reason, then the component needs to abort the transaction

ุ        Installing into MTS – once the component is written, it needs to be properly installed into MTS

To build the component, we will be using Microsoft Visual Basic 6.0. We have also installed Service Pack 3 for Visual Studio 6.0. At the time of writing, this was the latest and greatest version of Visual Basic available. As a recap, the component that we are building will be responsible for interacting with our banking database. We will create two methods for this component:

ุ        CreditAccount – this method will take as parameters the customer ID, account number, and credit amount. It will add the specified amount to the balance in the specified account.

ุ        DebitAccount - this method will take as parameters the customer ID, account number, and debit amount. It will subtract the specified amount from the balance in the specified account.

Now, lets start out by creating the infrastructure for the component in Visual Basic. From the New Project dialog box in Visual Basic, we will choose to create an ActiveX DLL for our component. We will then need to create a name for the component as well as the class module that will hold our methods. For this example, let's name the component WroxBank and the class module Account. Finally, we need to add a reference to the MTS Type Library as well as the ADO 2.1 Type Library. Now, we are ready to begin implementing our component.

Adding MTS Support

The first step is to add the plumbing to the component to support MTS. We have referenced the necessary type libraries so now we need to implement the code to link up to MTS. We will also want to lay the foundation for the component we are creating which means that we will have a few global variables and constants.

Option Explicit

Implements ObjectControl

As a rule, all components developed in VB should use the Option Explicit statement. This forces you to always declare your variables. This is good programming practice, but unfortunately the default in Visual Basic does not require this. The ObjectControl is an interface that MTS-aware components can support. It is optional, but it does allow for support of better interaction with MTS. When a component implements this interface, MTS will call certain methods on the control. It is up to the developer to implement these methods.

Private oObjectContext As ObjectContext

Private vSql As Variant

Const vDbConn As Variant = "PROVIDER=SQLOLEDB; DATA SOURCE=bftecra;database=BankTest;USER ID=sa; PASSWORD=;"

Since we will be using the ObjectContext reference throughout this component, we are going to create a variable to hold it. This does not mean that we will be retaining the reference to this component from call to call and instance to instance, but it does make it easier than having to declare it every time you need to use it. Since we will be accessing a database in this component, we will store the connection string to that database in a constant. In some instances, you may want to have user-level validation of database access. In that case, you could store the first part of the connection string as a constant, and then append the username and password at the time of database access.

Private Sub ObjectControl_Activate()

    Set oObjectContext = Nothing

End Sub

Private Sub ObjectControl_Deactivate()

    Set oObjectContext = Nothing

End Sub

Private Function ObjectControl_CanBePooled() As Boolean

    ObjectControl_CanBePooled = True

End Function

These three methods comprise the ObjectControl interface. When MTS detects a component that supports this interface, it will call these three methods on the component. This means that if your component decides to implement this interface, you must support these three methods. Even if you don't do any processing in a method, you must have support for it. The Activate method is called by MTS just before the control is activated, allowing you to perform any context-specific initialization. We will be setting the reference to the ObjectContext to Nothing to ensure that the proper ObjectContext is used by the control.

The Deactivate method is called by MTS when a control is deactivated. This happens after the transaction completes or is aborted. Again we will delete the reference to the ObjectContext so that the next time the control is used, an incorrect value will not be found there. The CanBePooled method is called just after the object is deactivated to see if this instance of the component should be held in a pool for later reuse or destroyed by MTS. By returning True, we are telling MTS to hold onto this component and use it next time a similar component is requested.

For this to work, an object must be accessible on different threads each time it's activated. This is only possible for components that are marked as Both threaded. This means that recycling isn't possible under the apartment-threading model. As we discussed before, an Apartment-threaded object can be instantiated on any thread, but it can only be used by the thread on which it was instantiated. A Both threaded object tells MTS that the component's objects can be called from different threads.

In this version of MTS, all MTS objects run under the apartment-threading model. This means that even if they return True from the CanBePooled method they won't be recycled. However, if you build a component to support the Both threading model, the component will run under the current version of MTS and will also be able to take advantage of recycling as soon as it becomes available, without any changes to the code.

Public Interfaces and Helper Functions

Next we will add the public methods to this component. These will be the way that ASP and other applications or components can talk to this component. Since this is a banking component that will manipulate accounts, we will need a way to both credit money to an account and debit money from an account. I know that the accounting standard has debit meaning adding money and credit meaning subtracting, but for this interface, we will use the standard terminology that the general public is familiar with.

Public Function CreditAccount(custID As Variant, acctNum As Variant, amount As Variant) As Variant

    CreditAccount = ChangeBalance(custID, acctNum, amount)

End Function

Public Function DebitAccount(custID As Variant, acctNum As Variant, amount As Variant) As Variant

    DebitAccount = ChangeBalance(custID, acctNum, -amount)

End Function

Now the inner workings of a debit action and credit action are nearly identical. They are simply the change in balance of an account. It is just that a credit adds an amount to the balance and a debit subtracts an amount from the balance. This means that we should be able to use the same set of code for both debits and credits and just change the sign of the transaction. This is accomplished in the ChangeBalance method, which we will look at in just a bit.

Private Function ValidAccount(db As ADODB.Connection, custID As Variant, acctNum As Variant) As Boolean

    Dim oRs As ADODB.Recordset

   

    vSql = "SELECT custID FROM Accounts WHERE AcctNumber = " & acctNum & " AND CustID = '" & custID & "';"

    Set oRs = db.Execute(vSql)

    If oRs.EOF Then

        ValidAccount = False

    Else

        ValidAccount = True

    End If

    oRs.Close

End Function

There is one private helper function that we are also adding to the component. This helper function will let us determine if an account number for a particular user is valid. We will want to check the account numbers passed into the component to ensure that they are valid prior to making any changes to the account balances. To do this, we will use the existing connection to the database and try to select a field from the Accounts table that corresponds to the particular account we are validating. The account is uniquely identified by a combination of the account number, stored in the AcctNumber field, and the customer ID number, stored in the custID field.

We will execute a SQL SELECT statement on this table and check the recordset that this query returns. If it is empty, then we know that the account number is not valid. When we encounter this, we will return a False from this function. The caller can then decide what action to take. If there is a record present, this means that we have a valid account number, so we return True from this function. As always, we close the recordset and the connection to the database before the function ends. This is good programming practice and helps to make efficient use of the resources available to the system.

Updating the Accounts

The main part of this component is the code that manipulates that database to change the balance in an account. As we saw earlier, the public methods of this component will call a single private method to make the actual change to the database. The ChangeBalance method accepts three parameters, which are the customer ID number, the account number, and the amount to change the account balance by. The public methods that call this method are responsible for setting the sign on the balance change amount – positive for a credit and negative for a debit.

Private Function ChangeBalance(custID As Variant, acctNum As Variant, amount As Variant) As Variant

    Dim oDb As ADODB.Connection

    Dim oRs As ADODB.Recordset

    On Error GoTo ErrHandler

   

This method will also need to access the database, so it will need a local variable to hold a connection reference as well as a recordset reference. This method will also have an error handler in place. This error handler will be called if any statement generates an error. We will add this to the end of the method. It makes for a convenient place to clean up the component, pass an error message to the caller, but most importantly abort the transaction.

    Set oObjectContext = GetObjectContext()

    Set oDb = oObjectContext.CreateInstance("ADODB.Connection")

    oDb.Open vDbConn

   

To create a connection to the database, we need an instance of the ADO Connection object. In order to create this connection object, or any object inside of an MTS component, we need to use the CreateInstance method of the ObjectContext. In Visual Basic, there are three ways to create an instance of a component:  The New Operator, the CreateObject statement, and CreateInstance. Since we want any components we create to be part of the same transaction context as our component, we need a way to pass that context information to the component being created. The New and CreateObject statements are native Visual Basic statements and they don't have any knowledge of MTS or Object Contexts. The only way to create a component that will have knowledge of MTS and the current context is to use the CreateInstance method of the current ObjectContext.

To get this reference to the current object context, we will use the GetObjectContext method and save the reference it returns to the global variable we created earlier. Once we have this, we can use it to create an ADO Connection object. With that object, we can open the connection to the database using the connection string that we created as a constant. Remember that we set a global error handler for this method, so if there are any problems opening the database, the error will be handled by that part of the method.

    If Not ValidAccount(oDb, custID, acctNum) Then

        oObjectContext.SetAbort

        Exit Function

    End If

   

With the database now open, the first step is validate that the account number is valid. We will use the private ValidAccount helper function that we created earlier. Since we already have an open connection to the database, we will pass the reference to the database connection to this method.  Since Visual Basic passes by reference, all that is being passed to the function is a reference to the object, not a copy of the object itself. If this function returns False, then we know that we have an invalid account number. We will need to abort the transaction since we won't be able to complete it successfully. To do this, we will call the SetAbort method of the ObjectContext.

    vSql = "UPDATE Accounts SET Balance = (Balance + " & amount & ") WHERE AcctNumber = " & acctNum & " AND CustID = '" & custID & "';"

    oDb.Execute (vSql)

If we do have a valid account number, then we will need to change the balance of that account by the amount specified in the parameter. We will use a SQL UPDATE statement to change the value of the balance field in the record that matches the account number and customer ID. All that we need to do in this method is add the change amount to the current balance. The methods that call ChangeBalance are responsible for setting the sign of the change appropriately.

   

    vSql = "SELECT Balance FROM Accounts WHERE AcctNumber = " & acctNum & " AND CustID = '" & custID & "';"

    Set oRs = oDb.Execute(vSql)

The SQL UPDATE statement does not return any information – it simply makes the change. One of the things that we want our method to do is return the new balance to the calling function. To get this information, we will need to retrieve it from the recently updated database. This value is retrieved with a SQL SELECT statement and returned in a recordset. If this recordset is empty for some reason – the account could have been deleted by another user, but this is unlikely – then we will just return a balance of 0 to the calling function.

   

    Dim cBalance As Currency

    cBalance = 0

    If Not oRs.EOF Then cBalance = CCur(oRs("Balance"))

   

    oRs.Close

    ChangeBalance = cBalance

   

The value that we retrieve from the recordset will be converted to a currency data type, since it is a monetary amount, and then stored in a local variable. This local variable is initially set to 0 so that if no record is found by the SQL query, a value of 0 will be returned to the caller. Once we have retrieved the information, we can close the recordset and then set the return value for this method.

    oObjectContext.SetComplete

    oDb.Close

    Set oDb = Nothing

    Set oRs = Nothing

    Exit Function

With the return value for the function set, we can complete the work for this function. First, we need to vote on whether or not the transaction was successfully completed. If we have reached this point, then we know that no errors have occurred in the function, so then this function's part of the transaction was successful. To indicate this to MTS, we need to call the SetComplete method of the ObjectContext.

After that is done, we can clean up our database connections. To close the open connection to the database, we will use the Close method of the Connection object. We will then destroy this object, along with the Recordset object, by setting their references to Nothing. This ensures that if the component is pooled and then reused, each use will have its own database connection and recordset.

ErrHandler:

    oObjectContext("Response").Write "<P>Error = " & Err.Description

    oObjectContext("Response").Write "<P>ErrorLine = " & Err.Source

   

    oObjectContext.SetAbort

End Function

The final portion of this method is the error handler. We set this portion of the method to be the error handler at the top of the method. If any error occurred during the processing of the method, the execution would have been immediately transferred to this section of the code. There are two important things to do in this error handler. First, we will inform the user as to what the problem was. This is done by directly outputting the information to the browser. To do this, we need to access the intrinsic ASP Response Object. This is accessible through the Object Context. In fact, this is a great example of how the Object Context is used to relate the component that is executing with the environment it is executing within.

The Err object is a Visual Basic object that contains information about an error generated in VB. We will be sending two properties of this object back to the browser. The Description property contains a description of the error that caused the error handler to be invoked.  The Source property indicates the name of the module where the error occurred. After sending back the error information, we want to make sure to abort the transaction that is currently running. Obviously, if an error occurred while processing, we don't want to commit the data changes being made in the transaction. The SetAbort method is the component's way of voting against the transaction completing. And remember, in this system, it only takes one vote to abort to cause the transaction to abort.

Installing into MTS

Once we have written all of the code for the component and successfully compiled it, the next step is to install the component into MTS. When we install it into MTS, we are telling the COM subsystem on the system that we are installing to that any attempt to implement this component must be handled by MTS. At a technical level, the LocalServer32 registry entry for this component now points to the MTX.EXE program, which is the executable shell for MTS. This means that MTX.EXE will be invoked, rather than the component itself, when this component is instantiated.

The steps to installing a component into MTS are:

ุ        Register the component on the system

ุ        Create a new package in MTS or open an existing one

ุ        Add the component by select it from the list of installed components

ุ        Set the transaction properties for the component

ุ        Test it to make sure everything works.

The testing step is what we will look at next. We will be using an ASP page to interact with the component as well as providing the business logic to the application.

Using from ASP

Now that we have the component successfully installed in MTS, we can begin to use it from an ASP page. Remember that we have only included data access functionality in this component, so we need to make sure that any business rules are handled in ASP. For this example, we are just going to implement the pieces that you need to work with the component. Our data input and output capabilities will be limited, but that just leaves you with some homework to do.

 

<%@ TRANSACTION=REQUIRED %>

This directive tells ASP to enlist the services of MTS and have it build a transaction around this page. On a literal level, the REQUIRED value allows this page to operate within an existing transaction, or create its own. But in transacted ASP, a transaction can only span one page, so there is no way for transaction to already be in existence when this page is loaded. So a new transaction will be created.

<%

dim xferAmt

xferAmt = 150

Like we stated earlier, this example page has limited input and output. We are defining the amount to be transferred from one account to another in the xferAmt variable. In a real application, this value would probably be passed into the ASP page through a FORM element or on the QueryString.

dim oBank

set oBank = Server.CreateObject("WroxBank.Account")

Next, we will create an instance of the data component so that we can use it from within the ASP page. Even though we have not set Option Explicit at the top of this page, it is still proper programming practice to declare all variables before you use them. The CreateObject method will create an instance of this component. Since this component is installed into MTS, an instance of MTX.EXE will be assigned to handle this component for ASP.

dim cNewBalance

cNewBalance = oBank.DebitAccount (1001, 222222, xferAmt)

The first step in our funds transfer page is to remove the money from the source account. This is done via the DebitAccount method of our component. We will pass to it the customer ID, the account number, and the amount to debit. This method will then go off and do its work. It will return us the new balance in the account after the debit was made.

We will then pass this value to the CheckBalance method, which is shown here.

Function CheckBalance(amt)

    if amt < 100 or amt > 1000 then

        CheckBalance = False

    else

        CheckBalance = True

    end if

End Function

This method checks to see if the new balance in the account is within the valid ranges of balances. This is the enforcement of the business rules for the application. If the balance is between $100 and $1000 inclusive, then we have a valid balance. All other balances are invalid. This method will return a False if the balance is out of range.

if not CheckBalance(cNewBalance) then ObjectContext.SetAbort

If we receive a False return value from the CheckBalance method, we know that our balance is going to be out of range. Our business rules state that we should not allow any out-of-range balances, so we need to stop what we are doing and reset the account balances to where they were to start. This is easily done by aborting the current transaction through a call to SetAbort.

cNewBalance = oBank.CreditAccount (1001, 123456, xferAmt)

if not CheckBalance(cNewBalance) then ObjectContext.SetAbort

We will need to do the same steps with the destination account. This time though, we will be using the CreditAccount method instead of DebitAccount. Again, after the balance change is made, we need to ensure that the new balance in the destination account is valid. If it is not, we need to rollback the ENTIRE transaction - this is the only way to ensure that the balances are valid in both accounts.

Sub OnTransactionCommit()

    Response.Write "<P>Funds Transferred Successfully"

End Sub

Sub OnTransactionAbort()

    Response.Write "<P>Funds Transfer Rolledback"

End Sub

%>

Finally, we need to tell the user whether or not the transfer was successful. A successful transfer relies on all operations completing successfully and then voting to complete the transaction. If any one part of the transaction was not successful, then it will have voted to abort, and the entire transaction will be aborted. Depending on the outcome of the transaction, the corresponding transactional event will be fired and the success or rollback message will be displayed for the user.

 

Recent Jobs

A great opportunity to Digital Vide
here is a greate opportunity as a S
A great opportunity as a Network En
A Greate Opportunituy as a SQL Deve
An immediate job opportunity as a B

View all Jobs (Add yours)
View all CV (Add yours)



Information Online

swimming pool builder
chicago web site design
spfxmasks
Cheap Web Hosting
conference calling
Versace sunglasses
answering service


    Email TopXML  

Front Page Daily Stuff TopXML Forum XML blogs XML Newsgroups BizTalk Biztalk Utilities Biztalk Utilities Tutorial B2B SAP XML Microsoft .NET Dotnet System XML Soapformatter SQLXML XMLserializer XQuery PHP PHP SimpleXML PHP XML Dom PHP XML RPC PHP XSLT Java Java Java XML Xalan Microsoft ASP ASP Schemas XML SQL Server XML XMLDom XSL XSL Tutorial XSLT Stylesheets General Javascript CSS XHTML WAP