The context object that MTS provides for each component instance has the two methods we've mentioned already, SetComplete
and SetAbort
. In this part of the chapter we'll see how we can use these within the simple WCCFinance
component that we created in the previous chapter. Then we'll install the component into Transaction Server and get our first glimpse of MTS in action.
As we suggested earlier, the changes required to a component so that it can take advantage of MTS vary from 'none at all' to 'loads of work'. It really comes down to whether the component was originally designed to maintain state (which isn't what we really want) or not. Our sample component has only a single method, and three write-only properties. Generally, using properties in a component creates state, because the code that uses it will usually expect the values to remain the same after they've been set. This isn't, as we've seen, always the case within MTS.
So its better to use methods in your components that accept parameters, and provide the values for the parameters when you call the method each time. That way the component can become stateless, and operate more efficiently within MTS. However, we're going to live with properties in this example. You'll see how we use parameters instead of properties in the other components of the Wrox Car Co application.
One particular point to remember is the one we mentioned earlier about changing the interface of your component as you adapt it for use with MTS. To get the best performance you should call the SetComplete
or SetAbort
methods as often as possible within the component, However, each time one of these method calls is made, MTS assumes that the component's state (internal values) can be disposed of. Therefore simply adding either of these calls to a component's methods will change the way the component behaves.
For example, if our application sets some property values inside the component and then calls a method in that component, which contains SetComplete
or SetAbort
, MTS will reclaim the component instance and the property values are lost. If our application then comes back to the component again—through the context object that it thinks is a real component—and reads one of these values, it will get the wrong result. In this case, the application is treating the object as if it were still holding its state, whereas the SetComplete
and SetAbort
methods tell MTS that it is stateless at that point.
So even though we haven't changed any of the names or parameters of the component's members, or the way they work under the hood, we have changed the interface definition. Calling a method in it, or reading the value of a property, could well produce a different (and unexpected) result. To get round this, you may want to change the interface definition more obviously—by changing the names of the members for example. In C++ or other languages that permit it, you may prefer to implement another separate interface instead.
Before we start to work with MTS components in VB, it's useful to add a reference to the MTS interfaces type library to VB. This way it can provide information about the properties, methods and events supported by each object—including pop-up syntax assistance.
In the Project | References dialog click Browse and locate the file mtxas.dll
(probably in the Program Files\Mts
folder on your server). If you are developing on a separate machine (always a good idea) you can copy the DLL into a folder on your local machine and select it there instead. This adds the Microsoft Transaction Server Type Library entry to the References dialog:
Once referenced within VB, we can then use the Object Browser to examine the members of the MTS interface objects as we work. Be sure to select the library MtxAS in the upper-left combo box:
The changes we need to make to our code are minimal, and only affect the GetNumberPayments
method. We're not implementing the ObjectControl
interface, because we have no processing requirements for Activate
or Deactivate
. Instead, we get a reference to the context object as we enter the method. Here are the changes to the code:
Public Function GetNumberPayments() As Integer
'get reference to the context object
Dim objOContext As ObjectContext
Set objOContext = GetObjectContext()
'rest of function code goes here
...
The next part of the original code calculated the number of payments, and placed the result in the variable intNumberMonths
. If the monthly payment was insufficient to pay the interest and reduce the balance, it set intNumberMonths
to zero. The function also returned zero if there was an error. We'll do the same in the new version, but before we end execution of the method code we'll call either SetComplete
or SetAbort
as appropriate:
...
GetNumberPayments = intNumberMonths
'complete or abort the MTS context
If intNumberMonths > 0 Then
objOContext.SetComplete
Else
objOContext.SetAbort
End If
Exit Function
...
To complete the method code we add a call to SetAbort
in the error handler, so that an error will prevent a transaction from taking place:
...
GNP_Error:
GetNumberPayments = 0 'indicates an error
objOContext.SetAbort 'abort the MTS context
Exit Function
End Function
And that's it. We just recompile the component and copy it to the server. You will have to stop and restart the server before you can replace an existing component if you installed the sample from the previous chapter.
Once the component is on the server and properly registered we can add it to MTS. The process is referred to as installing it in MTS because it permanently changes the way the component is referenced within Windows (until you delete it from MTS again).
The main administration for MTS is the Transaction Server Explorer. In Windows NT Server this is a snap-in to the Microsoft Management Console (MMC), while in Windows 95 it is a separate executable file. MMC provides a one-stop-shop for working with several services at once, such as Internet Information Server and Index Server as well as MTS. If you don't see the Microsoft Transaction Server entry in MMC after installing MTS, you can add it using the Console menu.
The following screen shot shows Transaction Server Explorer in the MMC, together with some of the default components that are installed from the NT4 Option Pack. Notice that there is an entry for each computer, because MMC can be configured to administer remote servers as well as the local one. For each computer there is a set of packages that are installed within MTS on that machine. A package can contain one or more components, and is simply a way of setting up and managing them all together. Each package has a set of roles. Roles are part of the security mechanism implemented within MTS, and we'll be looking at this in more detail in Chapter 8:
Creating a new package is as easy as right-clicking the Packages Installed entry and selecting New then Component. The Package Wizard opens, with a choice of two ways of creating a package:
Like most other tasks that you can perform with the MMC, you also can do this from the drop-down list marked Action. In Windows 95, Transaction Server Explorer does not support right-click menus so the standard menu bar is used instead.
We want an empty package rather than a pre-built one (components and their package can be exported, and then installed as pre-built packages package on other machines). The next screen (not shown) just allows us to enter a name for the package. The third screen is where we set up the identity for the package, i.e. which user account it uses to access other services on the machine. We'll use the default of the current user:
While we are running the components from a Web browser with anonymous access, the current user will be IIS. However there are often situations where this is not the case, and so selecting Interactive User is the best plan unless you can be sure that you can validate the user directly. You'll find a detailed discussion of users and identities in Chapter 8.
Finally, clicking Finish creates the new package, which we named WCCFinanceTest, and we're ready to add our component to it.
To add our component to the new empty package we right-click the Components entry and select New | Component (or use the Action menu):
This starts the Component Wizard, and the first screen allows us to install a new (unregistered) component or import a previously registered one. We've already registered our WCCFinance
component, so we choose the second option:
Importing previously registered components has the minor disadvantages in that you can’t set the properties for any individual interfaces within that component. In this case you should un-register it and install it as a new component.
Clicking Next means coffee-time unless you have a fast machine. The Wizard trawls through the Registry building a list of available components. If you turn on the Details checkbox, you can get useful details about the components that are available. In the screen shot, we've found our WCCFinance
component in the list:
When you install a component in MTS, the registry entries for it are changed. MTS swaps its own class ID for your component's class ID, so that references to the component are redirected to MTS with a parameter added that identifies which package and component was referenced. For this reason, you won't find any components that are already installed in MTS in the list.
Selecting the component from the list and clicking Finish places it in the current package:
You can also install components by dragging them from Explorer into the right-hand pane of MMC when the appropriate package's Components entry is selected.
The final step is to set the MTS properties for the component, by right-clicking it and selecting Properties. The General tab provides useful information about the component, and allows us to enter a description for it:
However, the most important property is the Transaction Support entry. We need 'Supports transactions' for our component:
We discussed the options available earlier in the chapter If a component has been designed to be used either within a multi-component transaction or by itself, we'll usually choose 'Supports transactions'. However, if it is designed to be the parent of several components, and it always initiates a transaction, we would probably choose 'Requires a new transaction'.
The MTS snap-in for Microsoft Management Console provides some useful other extras. One is the ability to switch to Status View. This shows information about the number of cached and activated objects in use at any one time:
In a previous section of this chapter we talked about the way that Windows Distributed Transaction Coordinator (DTC) controls data store transactions. The DTC, like many other parts of our application's environment, is a service that can be started and stopped. This can be done from within MMC by right-clicking the appropriate Computers entry:
If your applications use the DTC, you should arrange for it to be started automatically each time Windows starts up. This can be done in the Services applet available from Control Panel, or from within the Server Manager program. The monitor screen of the My Computer icon changes color to reflect the state of the DTC—dark green shows that it is not running, yellow that it is starting to run, and light green indicates that it is running.
So far we've only been discussing components that are installed on the server and referenced directly by applications that are also running there. However, COM allows an application to use a component that is stored on a remote machine. This means, for example, that we can place the component on the server inside MTS, but create and work with an instance of it remotely from an application running on the client—including client-side scripting in a browser. This is Distributed COM (DCOM) at work
DCOM uses proxy and stub objects to provide transport remote activation and invocation. The proxy object is loaded on the client, and the stub is loaded in the process of the component. MTS makes it easy to create install routines that will set up components to be used in this way.
Remote components are useful in MTS where the performance or other issues dictate that a component should be installed on a remote machine, but still take part in a transaction with other components on the local machine. For example, a component that processes large sets of data but returns no result (other than indicating the result via SetComplete
or SetAbort
) might provide better performance if it was installed on the same machine as the data store. However, performance considerations must include the fact that invoking a remote object is an order of magnitude slower than invoking a method on a local object.
DCOM allows servers running MTS to share their local components. In MMC you add the remote computer to the Computers folder by right-clicking on it and selecting New | Computer, or by using the Action menu. You can let MTS list all networked machines, and select the one you want to use:
In the MMC entry for the source (local) machine, right-click the package you want to export and enter a name for the .pak
file that MTS will create. This file can then be installed on the remote machine:
Then, within the MMC, select the remote machine (remember MMC can administer remote servers as well as the local one). Alternatively you can move to the remote machine and open the MMC there. To install the package, right-click the Remote Packages folder and select the machine, package, and components that you want to install. MTS doesn’t copy the component itself, just the files required to access it via DCOM over the network:
Microsoft's currently evolving Remote Scripting technology also allows a client-side script to interact with objects via a server-side ASP page. We aren’t covering Remote Scripting in this book, as the technology is too young to be of real value yet. However, it will provide extra opportunities to use MTS components as it matures and stabilizes.
Rather than install all our components as separate items in MTS, we must, as you've seen, create packages and install them within a package. It's a similar concept to storing disk files in separate sub-directories, rather than all in the root directory of your disk.
Each package should contain a set of components that between them perform related tasks within an application. Packages allow us to encapsulate components for installation as a group either locally or remotely. They also provide a way to allocate properties and security permissions to all the components within a package in one go, in the same way as we can allocate security permissions to users within a group in Windows NT. In a later chapter of this book, we'll look at the security properties in more detail. For the time being, we'll see what other features packages provide.
When we discussed how MTS deactivates and caches components after use, we said that the default was to hold the component in memory for three minutes. This behavior can be changed in the Activation page of the Properties dialog for each package separately:
The setting you choose depends on the way the components in that package will be used—in general the default gives reasonable performance under average conditions. The shut down delay is the time-out period in minutes (between 0 and 1440) before the component will be removed from memory after use.
In Transaction Server Explorer there are also two permission-related check boxes: Disable Deletion, which prevents users from deleting the package from the Explorer without first clearing the box; and Disable Changes, which prevents changes to the package’s attributes and contained components unless the check box is clear. In the MMC, these features are handled by Windows own native security features.
The Activation page of the Properties dialog controls where the component instances will be created and executed. In general, you will execute them in the MTS environment—in other words in a 'dedicated server process'. If you want to execute them within the memory space of the 'creator' process, you can choose this option. It provides higher performance if there are multiple calls from the creator application to the component, but means that a failure of the component can crash the creator process as well.
If you are using roles to control access permissions to your components, you should always choose the default Server Package option. In general, we will use this setting for all our components.
Our component is now nestling warm and safe inside MTS, and we can start to use it in an application. We're only going to show you one example here, using an updated version of the Active Server Pages file that we used in the previous chapter with the un-transacted component.
So, what do we need to do that's different? In the component, we added code that will call the MTS SetComplete
method if everything goes OK in the calculation, and SetAbort
if not. However this will only happen if the component is running under a transaction. Because we set its Transaction Support property to 'Supports transactions', it will use an existing one but not start a new one if there isn't one running already.
This means that if we want to benefit from the transactional features of our component we have to start a transaction running before we activate it. Of course, we could have set the Transaction Support property to 'Requires a transaction' but that would mean that we could never run it without one.
In fact, as you saw when we looked at ways of starting a transaction earlier in the chapter, we can effectively override the Transaction Support property setting in our ASP script using the Transaction
statement at the head of the page. This only affects the current page, and hence the instances of components that we create within the page. Once we start a transaction, all the instances of the components we create will join in this transaction unless they have a Transaction Support property of 'Requires a new transaction' or 'Does not supports transactions'.
So we just have to add the statement Transaction
=
Required
to the head of our ASP page to make sure that a transaction is running for this component instance—here we've added it to the existing LANGUAGE
statement:
<%@ LANGUAGE = VBScript Transaction = Required %>
...
Now we can create our component instance, and set the properties as we did in the previous chapter's example. As soon as we access the component for the first time, i.e. to set the TotalPrice
property, MTS creates a context object and activates and holds onto the component for us. It won't be deactivated until SetComplete
or SetAbort
is called within the component's code, so we can continue setting the property values with no fear of it disappearing.
The final line of this section of code calls the GetNumberPayments
method of our component:
...
<%
Set objFinance = Server.CreateObject("WCCFinance.PaymentTerms")
objFinance.TotalPrice = Request.Form("txtTotalPrice")
objFinance.InterestRate = Request.Form("txtInterestRate")
objFinance.MonthlyPayment = Request.Form("txtMonthlyPayment")
intResult = objFinance.GetNumberPayments
%>
...
Because we are using a transaction, we don't know what's going on now until MTS tells us. Although we can guess that when our method call returns the task will be complete, we can't always guarantee this in a more general case. We really only know that it will happen here because we're using a single component that we built ourselves—so we know how it works.
In the real world, we may not know what's going on in the component, and of course there could well be several different ones used in the page anyway. It's a bit like tossing the values into a black hole then waiting to see if anything comes out. The upshot of all this is that the only thing we can reliably do here is tell the user that something's happening:
...
<BODY>
Your inquiry is being processed, please wait...<P>
...
Thankfully you have a good deal more chance of something coming back from your component than you do with a black hole (even of you have only limited experience of creating components yourself). This is what we're depending on, but we need to know when MTS actually does return the result. We do this by handling the two MTS context object events OnTransactionCommit
and OnTransactionAbort
:
...
<%
Sub OnTransactionCommit()
strResult = "<P>A total price of <B>$" & Request.Form("txtTotalPrice") _
& "</B> at a monthly interest rate of <B>" _
& Request.Form("txtInterestRate") & "%</B>, and paying <B>$" _
& Request.Form("txtMonthlyPayment") & "</B> per month, " _
& "will require <B>" & intResult & "</B> payments.</P>"
Response.Write strResult
End Sub
Sub OnTransactionAbort()
strResult = "<P>Sorry, the calculation could not be completed. " _
& "Either the monthly payment amount you entered " _
& "is not sufficient to pay off the loan, " _
& "or another error occurred.</P>"
Response.Write strResult
End Sub
%>
</BODY>
</HTML>
And here's the result. Isn't it wonderful when a plan comes together like this?
Note that aborting a transaction does not rollback changes to any ASP session-level variables. The OnTransactionAbort
event handler should be used to reset the session variables if this is appropriate in your application.