This article may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. To maintain the flow of the article, we've left these URLs in the text, but disabled the links.


April 1998

Microsoft Systems Journal Homepage

Build MTS Components with Visual Basic for Deployment in Your ASP-Based Apps

Download MTS_ASPcode.exe (53KB)

Ted Pattison owns Subliminal Systems (http://www.sublimnl.com), a consulting and mentoring firm in Raleigh, NC. He works with Visual Basic, C++, Java, and COM to create client-server and three-tier database systems. Ted is also working on a book for Microsoft Press.

Many developers have tapped into the power of Active Server Pages (ASP) by creating database-driven Web sites. Using ASP together with ActiveX® Data Objects (ADO) makes it much easier to access all kinds of databases from server-side ASP scripts. Once you know how to write ASP code using VBScript or JavaScript, it's a snap to extract data from a DBMS like SQL Server and present it to a Web client in a standard HTML page. Using the same technique, it's also fairly painless to submit a simple action query to insert or update a database record.
      These database-driven Web sites are solving many of the problems associated with two-tier systems. Running your business logic and data access code on the server simplifies the tasks of maintaining and versioning your applications. Creating systems that run the client-side application as a pure-HTML solution eliminates costly configuration issues associated with the desktop. It also lets you reach new users you couldn't get to by any other means.
      I just described the critical first step in the evolution of today's information systems. Taking this one step further, you can integrate Microsoft Transaction Server (MTS) into your ASP development to create sophisticated information systems that go far beyond simple data publishing. You can create a distributed architecture that will allow you to put a browser front-end around a powerful online transaction processing (OLTP) system. Using ASP and the latest integration features in Internet Information Server (IIS) 4.0 and MTS 2.0 you can build dependable data entry applications that can be accessed from standard HTML-based clients.
      In this article, I'll discuss building MTS components with Visual Basic 5.0 for deployment within an ASP-based application. There are several benefits to moving your business logic and data access code out of your ASP pages and into a COM-based DLL. First, ASP is great for small apps, but it becomes increasingly difficult to manage an application that contains thousands of lines of code. Using a class-based language like Visual Basic, Java, or C++ will offer much higher levels of encapsulation and code reuse. Second, today's multitier designs encourage the use of business objects to separate the user interface code from the data access code. One of the benefits of this separation is that it eliminates the need for SQL in your ASP code. Third, COM-based DLLs created with Visual Basic can take full advantage of the integration between Internet Information Server (IIS) and the MTS runtime environment. Using MTS components in an ASP-based application gives you an infrastructure for building a reliable OLTP system.
      I'll also address how and why you should use MTS components in any large-scale ASP application that requires complex or reliable database transactions. I'll use Visual Basic to create MTS components, but you can also create them with Visual C++® or Visual J++. One of the best things about COM-based systems such as MTS is that any COM-capable development tool can be used to write your code. I chose Visual Basic, but you can use any COM-aware development tools that you're comfortable with.

Creating MTS Components with Visual Basic
      MTS is a distributed runtime environment for COM objects (see Figure 1). MTS provides a sophisticated infrastructure for activating and running objects from across the network. It also offers facilities for managing connections, pooling threads, sharing resources, and monitoring transactions within a distributed application. The majority of this support is transparent to your Visual Basic-based DLL. I'll present a quick overview of the MTS architecture, but for a more complete picture you should read David Chappell's article, "How Microsoft Transaction Server Changes the COM Programming Model" (MSJ, January 1998).

Figure 1  MTS Architecture
Figure 1  MTS Architecture


      MTS objects are created from components that have been installed into the MTS runtime environment. Every MTS component must be deployed within the context of an MTS package. MTS allows for both server packages and library packages. MTS will activate and run objects from each server package in a separate instance of the MTS runtime process, mtx.exe. Objects created from MTS components in a library package will be loaded into the address space of the client application that created them. Both types of package rely on the services of the MTS Executive, which lives in mtxex.dll. The MTS Executive oversees the MTS runtime environment. Whenever you want to deploy a component in the MTS environment, you must serve it up with a COM-based DLL. The MTS Executive is responsible for loading your DLL and creating MTS objects requested by clients.
      You can create MTS components with Visual Basic by adding public MultiUse classes to an ActiveX DLL. Each one of these classes will become a createable MTS component. After building your DLL, you must install it into the MTS runtime environment with the MTS Explorer by dragging and dropping its file onto a package's component view. When you do this, MTS changes some entries in the registry to redirect activation requests to a session of the MTS Executive running within a package-specific instance of the MTS runtime process. After your DLL has been installed, you should be able to see your MTS components in the MTS Explorer (see Figure 2).

Figure 2 MTS Explorer
Figure 2 MTS Explorer


      The IIS 4.0 Web server application is launched from InetInfo.exe. ASP applications are initially configured to run in the same process as the Web server application. Installing IIS 4.0 and MTS 2.0 automatically creates an MTS library package named IIS In-Process Applications. If you are running your ASP application in the same process as the Web server, you can install your MTS DLL into this package. This lets you run your ASP code in the same process as your MTS objects, offering a performance advantage when compared to running your ASP code and MTS objects in separate processes. In this scenario, your ASP code and your MTS objects run in the same process as the Web server, and potentially in the same process as other ASP applications.
      IIS 4.0 also allows you to configure an ASP application (for example, a virtual directory) to run in its own dedicated process. This option lets you isolate your ASP application from other ASP apps, as well as from the Web server itself. You can configure your application with the Internet Service Manager by selecting "Run in separate memory space (isolated process)" in the Property dialog for a virtual directory. When your ASP application is configured to run in its own process, IIS automatically launches it in a new instance of mtx.exe.
      IIS also creates a new MTS package dedicated to the ASP application. For example, if you mark a virtual directory named Market to run in isolation, there will be a package named IIS-Default Web Site//Root/Market created for you automatically. You can install your MTS DLL into this package to run your MTS objects in the same process as your ASP code. In addition, IIS/MTS integration makes it possible for your Visual Basic-based objects to access intrinsic ASP objects directly (for example, Request, Response, Session, and Application). You can read more about configuring your ASP-based MTS application for process isolation in "Virtually Crash-Proof Your Web Site with IIS 4.0 Applications," by Mai-lan Tomsen (Microsoft Interactive Developer, October 1997).
      Let's revisit one of my high-level objectives. My goal is to encapsulate data access code inside a Visual Basic DLL. My DLL should expose a model of business objects that eliminates the need for an ASP client to access the database directly. In other words, your user interface code should not include any SQL. I'll use a sales transaction system as an example. This application allows customers to purchase products by submitting order requests. Let's start by creating a method in a Visual Basic DLL to conduct a read-only query. It will be helpful to download the full source code and setup instructions.
      Before users can order a product, you must supply them with a list of available products. To hide the data access logic from the ASP client, the Visual Basic DLL might define a class CProducts that exposes the following method:

 ' CProducts component lives in Market.dll
 Function GetList() As Variant
  ' queries database for a list of products
  ' returns a variant array of products to client
 End Function
An ASP client that uses this business object (for example, a CProducts instance) does not concern itself with the underlying database schema because the data access code is maintained entirely inside the DLL.
      If you have written ADO code in ASP, you'll find that writing the equivalent code in the Visual Basic IDE is much easier. Once you add Visual Basic IntelliSense®, the object browser, compile-time type checking, and a robust debugging environment to the equation, you might never write ADO code with ASP again. If you are new to writing ADO code, you will be able to get up to speed fairly quickly. Any programmer who's used Visual Basic and is comfortable with either DAO or RDO can become productive with ADO fairly quickly. Here's the Visual Basic-based implementation of the previous method definition:

 'code in Market.dll
 Function GetList() As _  
  Variant
  Dim sSQL As String _
   sSQL = "SELECT Product _  	  FROM Products"
  Dim rs As New ADODB.Recordset
  rs.CursorType = adOpenStatic
  Const sConnect = "DSN=Market;UID=sa;PWD=;"
  rs.Open sSQL, sConnect
  GetList = rs.GetRows()
  rs.Close
 End Function
      By examining this method you can see the benefits of encapsulating your data access code in a DLL. If some aspect of the database—such as the schema or connection information—changes after your Web application has already gone into production, you can supply a new version of your DLL without rewriting any of your ASP code. This gives you a plug and play approach to evolving both your business logic and your data access code. As things change you can simply replace the DLL on the production server. What's more, multiple GUI applications can be built against a single set of business objects. You can even leverage your business model by building both Web-based applications and standard COM client applications at the same time. The potential to achieve high levels of code reuse is one of the greatest selling points of an n-tier architecture.
      MTS clients are called base clients. You can develop an MTS client application using any COM-capable tool such as Visual Basic, Visual C++, Visual J++, or VBScript. A base client creates an MTS object in the same manner as it creates any other COM object. The syntax for creating objects differs among different tools and languages. IIS and ASP make it easy to run these objects on the Web server. The following code shows how to create an MTS object in ASP using VBScript and the intrinsic Server object:

 <%
 Dim Products, ProductList, Product
 ' assume ProgID for your class is "Market.CProducts"
 Set Products = Server.CreateObject("Market.CProducts")
 ' function returns variant array
 ProductList = Products.GetList()
 For Each Product In ProductList
   Display = Display & Product & "<br>"
 Next
 Response.Write Display
 %>
      In many cases, a base client runs outside of the MTS environment. Some people argue that an MTS base client must run outside the MTS environment. An ASP-based MTS application is a hybrid situation. Your ASP code is an MTS client, yet it runs inside the same process as your MTS object. In some ways your ASP code is a base client, and in other ways it is not. For the rest of this article, I will simply refer to MTS clients. This will include typical base client applications as well as ASP clients that create and use MTS objects.
      The previous code demonstrates how to extract a read-only set of data from the business component CProducts. If you don't need anything beyond read-only queries in your data publishing applications, this article might not prove to be very useful. If your application needs to conduct reliable transactions, then this article will be essential. The remainder of this article will discuss the creation of transactional MTS components. These components will give you the building blocks needed to create a powerful OLTP system. Using Visual Basic to write the code that defines your transactions gives you an easy way to do something that has previously been very tough: managing distributed state across heterogeneous data sources.

Life in an OLTP Environment
      The four essential requirements of a transaction can be summed up with the term ACID. A transaction must be atomic, consistent, isolated, and durable. Consistency guarantees that data does not lose integrity in the presence of multiple, concurrent users. Isolation guarantees that the changes made during a transaction are never seen until the transaction has been committed. Changes that are rolled back should never be seen by other users.
      An OLTP system such as MTS must place locks on various resources to meet the requirements of consistency and isolation. While locking is essential to the integrity of every transaction, it often requires one or more users to wait for other locks to be released. The best way to optimize concurrency and throughput is to minimize the time that these locks are held. A get-in/get-out (GIGO) mindset is required when creating a scalable OLTP system.
      The goal when writing code to conduct a transaction is to get your work done and release whatever locks you acquired as soon as possible. MTS assists you by providing an infrastructure that makes it easy to obtain and release your locks as quickly as possible. MTS uses declarative transactions to synchronize commit/abort behavior across multiple objects. MTS acts as a transaction monitor enforcing the ACID requirements on every transaction. A single transaction can span multiple data sources even if they are of different types. MTS takes care of the tricky timing and synchronization of the two-phase commit protocol. The code is fairly simple; just use the four methods supplied by MTS to control the flow and clean up after a transaction.
      MTS eliminates the need to write code against proprietary transactioning APIs by providing three components: a universal transaction API, the Distributed Transaction Coordinator (DTC), and resource managers (see Figure 3). Resource managers are plug-in modules that encapsulate proprietary transactioning APIs for various data sources such as relational databases and mainframe applications. Objects in a transaction read and write to data sources through resource managers. The MTS Executive and the DTC work together to enlist and monitor resource managers in a transaction. This architecture allows MTS to synchronize commit/rollback behavior and the release of all acquired locks across multiple data sources.

Figure 3  MTS Transaction
Figure 3 MTS Transaction

      At the time of this writing, resource managers are available for SQL Server, Oracle, and Microsoft Message Queue only. Many more are expected to be available in the near future. A transaction that defines modifications to both a relational database and a mainframe application, the creation of a file, and the transmission of an email message as an all-or-nothing proposition is a pretty powerful paradigm. In a year or two, these types of transactions will be commonplace in MTS applications.

Creating Objects in a Transaction
      Each MTS component has a transactional attribute that you can set through the MTS Explorer. (Some languages, like C++ and Java, allow you to set this attribute in the component's type library.) Whenever the MTS Executive creates a new object, it examines the component's setting to determine whether the new object should be associated inside the context of a transaction. Every MTS object is created either inside a transaction or without one. After an object is created, this association cannot be changed. A transactional object spends its entire life inside the transaction it was created in. When the transaction dies, all the associated objects die along with it. It's important to understand that MTS must destroy the objects when it releases the locks. This cleanup activity is critical to ensuring the proper semantics of a transaction.
      An MTS object can be created by either an MTS client or another MTS object. When a new object is created, the MTS Executive looks to see if the creator is running in an existing transaction and looks at the component's transaction setting. From these two pieces of information, the MTS Executive knows whether to associate the new objects with a transaction. There are four possible transaction settings.
      The first setting is "Requires a transaction." The component will always be created inside the context of a transaction. The component will inherit the transaction of its creator if one exists. If the creator is not running in a transaction, a new one is created.
      The second possible setting is "Requires a new transaction." The component will always be created inside a new transaction.
      The next setting is "Supports transactions." The component will inherit the transaction of its creator if one exists. If the creator is not running in a transaction, the component will be created without a transaction.
      The final setting is "Does not support transactions." The component will never be created inside the context of an MTS transaction.
      An MTS client can initiate a transaction by creating a new object from a component marked "Requires a transaction." An object whose creation causes the system to create a new transaction is called the root of the transaction. The methods of the root object can create or enlist other objects in the transaction by creating objects from components that are marked either "Requires a transaction" or "Supports transactions." In either case, the new object will be associated with the root object's transaction.
      For the root object to create other objects inside its transaction properly, it must call the CreateInstance method of the ObjectContext interface. ObjectContext allows an MTS object to get information about the calling context in which it is executing. It also allows an MTS object to communicate with the MTS Executive. To use this interface, you must include a reference to the MTS type library (MtxAs.dll) in your Visual Basic DLL project and call GetObjectContext. The following demonstrates how to create two objects inside the same transaction as the root:


 ' CRoot component created by MTS client
 ' setting: requires a transaction
 Sub EnlistOtherObjects()
  Dim ObjCtx As ObjectContext
  Set ObjCtx = GetObjectContext()
  Dim Object1 As CComponent1, Object2 As CComponent2
  ' settings: requires or supports transaction
  Set Object1 =_
      ObjCtx.CreateInstance("MyDLL.CComponent1")
  Set Object2 =_
      ObjCtx.CreateInstance("MyDLL.CComponent2")
 End Sub
Releasing Your Transaction
      In most cases, the enlisted objects read and write data to and from their associated data sources. The objects in the sample application do this by reading and writing database records with ADO. Since the ODBC driver for SQL Server is a resource manager, MTS knows to monitor all database access with the DTC. The root object calls upon each of the enlisted objects to write their changes. If all the changes are made successfully, the root object must inform the MTS Executive that it wants to commit the transaction by calling SetComplete. If any object in the transaction cannot complete its work it should call SetAbort. It is not absolutely necessary for enlisted objects to call SetComplete, but it's a good idea to do it anyway. Typically, the root object will have a method that looks something like this:

 Sub RunMyTransaction()
  On Error GoTo WriteError
  Dim ObjCtx As ObjectContext
  Set ObjCtx = GetObjectContext()
  ' create a few objects
  ' call methods to read and write data
  ' if everything completes successfully
  ObjCtx.SetComplete
  Exit Sub
 WriteError:
  ' if there is a problem
  ObjCtx.SetAbort
  Err.Raise vbObjectError + 1, , _
   "Transaction unsuccessful"
 End Sub
The methods of the enlisted objects usually look something like this:

 ' class CComponent1
 Function WriteSomeChanges()
  Dim ObjCtx As ObjectContext
  Set ObjCtx = GetObjectContext
  If(ChangesCanBeMade=True)
   ' Visualize ADO code that writes a few changes here
   CtxObj.SetComplete
  Else
   CtxObj.SetAbort
   Err.Raise vbObjectError + 1, , "CComponent1 Failure"
  End If
 End Sub
      You can see that all paths of execution through these methods result in a call to either SetComplete or SetAbort. When an object in a transaction calls one of these methods, it's voting on whether the transaction should succeed. If any object calls SetAbort, the transaction cannot succeed. If every object calls SetComplete, then the MTS Executive will commit the transaction as soon as the root object's method completes and returns control back to the MTS client. As you can see, the basis of MTS commit/abort behavior works on the concept of passive consent. If all the objects vote to have their changes committed, then the MTS Executive and the DTC make sure the transaction is committed. However, any object can prevent the transaction from succeeding by calling SetAbort.
      The WriteSomeChanges method demonstrates a common pattern in the method of an enlisted object. If the changes are written successfully, it calls SetComplete; if not, it calls SetAbort. If an enlisted object calls SetAbort, the transaction will not succeed. In fact, the root object will experience a runtime error if it tries to call SetComplete after another object in the transaction has called SetAbort. This implies that enlisted objects must communicate with the root object to indicate that the entire transaction should be rolled back. The best way to do this is to raise an error after calling SetAbort. When the root object catches this exception, it knows to halt any further attempts to write other changes. At that point, the root object should call SetAbort and exit as soon as possible. In most cases, the root should throw an exception back to the MTS client to indicate the failure and send a message indicating a remedy (if one is possible).
      The call to SetComplete is really necessary only in the root object. You don't have to call it from any of the enlisted objects for the transaction to be committed. However, it's a good habit to call either SetComplete or SetAbort in any method that obtains locks. This lets you use a component to create root objects or enlisted objects without having to worry about whether you called SetComplete, giving your components a higher level of reuse.
      If the root object doesn't call SetComplete or SetAbort, the transaction will be left open in a pending state. If any object acquired locks on resources such as a page of records in a SQL Server database, these locks will be held after control is returned to the MTS client. The locks will be held until the MTS client initiates another transaction or releases the object. This is a situation you want to avoid like the plague. The bottom line is that you should release a transaction whenever you return control to the MTS client. This rule is part of the GIGO philosophy you must follow when working in an OLTP environment.

An Object-Oriented Curve Ball
      You release a transaction by calling SetComplete or SetAbort in the root object. This ensures that all the locks are released. It also has a very interesting side effect: MTS destroys all the objects associated with a transaction as part of the cleanup process. This implies that the root object and any enlisted objects will live only for the duration of a single method call. This requirement to create and destroy sets of objects in a fraction of a second leads to a style of programming that is very new to developers who are already familiar with classic OOP and COM.
      OOP and COM do not address scenarios where state is discarded at the completion of each method call. An object-oriented client assumes it can obtain a persistent reference to a long-lived object. If a client modifies some state within an object, an object-oriented programmer can assume that these changes will be held by the object across multiple method calls. You cannot make the same assumption in a transactional object in the MTS environment. Every object must die as part of the transaction's cleanup and, unfortunately, all its state must go along too.
      MTS encourages the destruction of transactional objects at the completion of each method. However, if MTS gave this responsibility to the MTS client, you would have a very tedious work environment. The MTS client would be responsible for creating and destroying an object each time it needed to invoke a method. Fortunately, MTS doesn't require the client to do this. Instead, the MTS Executive makes things much easier by using a little behind-the-scenes trickery. The MTS Executive is able to create and destroy root objects in the MTS environment while providing the MTS client the perception of a persistent connection to a single long-lived object. In reality, the system is creating, activating, and destroying new instances of your component each time a method is called.

Figure 4  Context Wrapper
Figure 4 Context Wrapper

      The MTS Executive holds onto the client's connection by using a transparent layer called the context wrapper (see Figure 4). When an MTS client creates an object in the MTS environment, the MTS Executive connects the client to the context wrapper and activates the actual MTS object inside the context of the wrapper. The context wrapper forwards method calls from the MTS client to the object. When a method call on the root object completes, the object can be destroyed without dropping the client's connection to the context wrapper. When the client makes another method call, the MTS Executive transparently activates another new object inside the context wrapper. The client is totally oblivious to what's going on. It's a wonderful illusion because it facilitates OLTP-style programming while cutting down on client-side coding requirements.

Much Ado About Statelessness
      Stateless programming has become a hot topic of conversation among developers. But all this talk has confused the meaning of statelessness. I've even heard it said that statelessness is a requirement of scalability. This is not true. Stateless programming is just part of the semantics of a transaction. Statelessness and object destruction are side effects of the cleanup that must occur after a transaction. Although some people suggest that you should always call SetComplete and destroy an object whether or not it is in a transaction, this couldn't be further from the truth. Statelessness is not a requirement of scalability. Once you understand this, MTS programming will seem far more natural.
      Take a moment to consider the disadvantages of statelessness. First, if you can't maintain state in an object, you must pass initialization parameters in every method call. For example, if you are using a customer object, you must pass the logical ID of the customer in each method call. Not only is this a tedious part of method definition, but it can actually decrease a system's scalability. Method calls require more parameters that result in more network traffic. Every call requires additional processing cycles from the server for the creation, initialization, and activation of a physical object. If just-in-time activation requires complex calculations or database access, statelessness can have a negative impact on scalability.
      As you can see, you need to carefully weigh the pros and cons to decide if you really need a stateless object. Objects with state definitely have a place in MTS programming as long as they are not part of a transaction. Objects with state can retain their logical identity and save processing cycles required to create and initialize new instances. They can also be used to accomplish many programming tasks that are impossible with stateless objects. If you want to use object state in your ASP applications, you must use the Session and Application objects. Remember that you can access these ASP objects directly from your Visual Basic-based DLL.

A Sales Transaction Sample
      In most cases, a transaction involves several transactional objects working together to accomplish one logical unit of work. The sample application for this article conducts a sales transaction that writes changes to three different tables. A products object queries and modifies inventory level. A customers object checks the available credit line and charges the purchase price to a customer account. An orders object adds a new record to the orders table that will be used for shipping and billing.
      You can see the full implementation by looking at the source code. Figure 5 shows a partial implementation of the Purchase method of the CProducts component.
      In the Purchase method, all possible execution paths result in a call to either SetAbort or SetComplete. Also, the product ID must be passed in each method call because a stateless object cannot hold any interim data that could be used to store the object's logical identity.
      While each of the three enlisted objects has a specific role in the transaction, some high-level object must be commissioned to create these objects and manage the transaction as a whole. A CBroker component composes a sales transaction by creating a products object, a customers object, and an orders object. The broker object should always be created in the context of a new transaction. You can accomplish this by setting the CBroker component's transaction setting to "Requires a transaction." The other components can be set to either "Requires a transaction" or "Supports transactions." The MTS client initiates a transaction by creating an instance of the CBroker component and invoking the SubmitOrder method as shown in Figure 6.
      In this example, the broker object defines the high-level business semantics for the transaction. Remember that an MTS transaction is a democratic community in which each object gets a vote in determining the ultimate success of the transaction. If any component calls SetAbort, the transaction is doomed to failure. If no object calls SetAbort, then the broker object must call SetComplete to commit the changes and cleanup the transaction. If one of the enlisted objects calls SetAbort, it must inform the broker object to not call SetComplete. The broker object handles errors by calling SetAbort and throwing another error back to the MTS client.
      It's easy to write the code for a successful transaction. Handling the flow of execution after a failure and propagating intelligent messages back to the caller requires a little more thought. You must decide how your components interact with one another. Once one of your objects realizes that its mission cannot be completed (for example, the products object sees that the requested quantity is not in inventory), it should call SetAbort and throw an error back to the root object with a domain-specific error message. As you can see from the code in the SubmitOrder method (see Figure 6), the root object can simply call SetAbort and forward the same error number and message back to the MTS client.

Creating a Broker Object in ASP
      ASP code can act as an MTS client by creating objects from the components in your DLL. Your ASP code can initiate a transaction by creating a new instance of the CBroker component through the Server object's CreateObject method. Figure 7 shows an example of creating a broker object and invoking the SubmitOrder method using VBScript. You can see that the ASP code required to run a transaction is minimal. Most of the work has been done in the DLL.
      The code in Figure 7 also shows how to handle Visual Basic errors thrown by the broker object. Error handling in VBScript is less elegant because you must use On Error Resume Next syntax and the errors must be handled inline. It's not as nice as error handling using Visual Basic or Visual Basic for Applications, yet it leads to a straightforward style of programming.
      Every time the page is called from a Web client, a set of transactional MTS objects are created and destroyed. The sample doesn't use any persistent state between order submissions, but you could add that to your ASP application. For instance, you could use the ASP session object to hold state information about the user. Perhaps you could even maintain a validated logon for each user. Maintaining state is fine, and it's often desirable in ASP applications. Just make sure that you don't do anything that would leave a transaction pending across multiple calls from a Web client.
      While this sample app demonstrates how to encapsulate both the business logic and the data access code in a Visual Basic-based DLL, there is another option for writing your business logic. An ASP page can serve as the root object in an MTS transaction. This means that the ASP can hold the high-level business logic for your transactions. Next, I'll replace the CBroker component in the DLL with transactional ASP code. However, all the data access code will remain in the Visual Basic-based DLL.

Using ASP as a Transaction Root
      The integration between IIS 4.0 and MTS 2.0 allows an ASP page to be recognized as the root object in an MTS transaction. You can accomplish this by placing a new @ directive at the top of an ASP page. Here's an example:


 <%@ TRANSACTION=Required LANGUAGE="VBScript" %>
 <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
This gives you the ability to compose high-level transaction logic (and, therefore, your business logic) in ASP. ASP code running in a transaction provides an ObjectContext object that exposes the SetComplete and SetAbort methods. It also recognizes two new events, OnTransactionCommit and OnTransactionAbort. These events are useful because they assist you with the conditional logic of your transaction.
       Figure 8 shows the ASP code required to compose a transaction from a products object, a customers object, and an orders object. Error handling is again somewhat awkward in VBScript because you must check the Err object after each method call. The bAbort variable is used to determine whether each subsequent call should be executed. If any of the enlisted objects calls SetAbort and raises an error, the flow of execution will be directed to a call to SetAbort, followed by the OnTransactionAbort event. This event is used to display the error message to the user. If all the enlisted objects complete their work successfully, the ASP code explicitly calls ObjectContext.SetComplete. This commits the transaction and triggers execution of the OnTransactionCommit event. You should notice one important thing in this example: even though the ASP page defines the business-level semantics of the transaction, it doesn't include any data access code. The data access code is still encapsulated inside the DLL.

Where Should You Go from Here?
      I showed you two examples of using MTS components written with Visual Basic in an ASP-based application. In both cases, Visual Basic was used to write the data access layer and ASP was used to define the presentation layer. That is an important characteristic of a three-tier system. The presentation logic never mingles with the data access code. What changed between the first and second examples is where the business logic was defined—what it means to submit a sales order. In the first example, this business logic was defined in the CBroker class of the Visual Basic-based DLL. In the second example it was defined in ASP code. Both examples used the MTS runtime environment to guarantee the ACID requirements of their transactions. The ASP code for these examples can be seen in SubmitOrder1.asp and SubmitOrder2.asp in the source code.
      The fact that IIS/MTS integration allows you to create transactional ASP pages doesn't mean that you have to use them. You might elect to maintain your business logic in a DLL as opposed to ASP code. There are a few reasons you might do this. Using classes available in Visual Basic or C++ will make it easier to maintain and extend your business logic with much better type checking and debugging capabilities. Compiled DLLs give you much better versioning control. Furthermore, you can use these DLLs from standard COM client applications as well as ASP clients.
      You might choose to maintain your business logic in ASP because of the advantages of a compile-free development environment. You might find it easier to make revisions without having to rebuild and replace a DLL. Now that you've seen the issues you'll need to consider, you're ready to make that choice.

From the April 1998 issue of Microsoft Systems Journal..