April 1998
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
|
Figure 1 MTS Architecture |
|
Figure 2 MTS Explorer |
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: |
|
By examining this method you can see the benefits of encapsulating your data access code in a DLL. If some aspect of the databasesuch as the schema or connection informationchanges 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: |
|
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
|
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
|
|
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: |
|
The methods of the enlisted objects usually look something like this: |
|
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
|
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
A Sales Transaction Sample
Creating a Broker Object in ASP
Using ASP as a Transaction Root
|
|
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?
From the April 1998 issue of Microsoft Systems Journal..
|