We don’t actually have to do anything to a component to use it in MTS, other than install it in a Package—as you'll see in a while. The component will benefit from the fact that MTS will run it in its own memory space separate from the Web server, and MTS will also control activation and deactivate to make it available to applications more quickly.
However, to feel the real benefits of MTS, we need to adapt our components and applications by adding MTS-specific code to them. This is easy enough to do, involving generally only a few lines. There are some rules that we need to abide by to get the best performance from MTS, and there are also two different MTS interface objects that we can work with.
The three main points to remember when you create applications or components for use in MTS are:
CreateObject
statement (the New
keyword should not be used to create instances of components that reside in MTS, as you'll see shortly). Because all you are holding is a reference to a context object, and not an instance of a real component, this is low on resource use. As you saw earlier, existing instances of the component will be dynamically shared between the applications or components that require it. SetComplete
method inside a component or application as often as possible to allow MTS to recover the component instance and use it with another application. The component reference you are holding on to remains valid, but the component instance is not held in memory waiting to be used. There are two main COM interface objects available in MTS that we can code to, and between them they allow us to control how our applications and components behave within the MTS environment.
The most useful interface the MTS provides for our components is that of the context object that is allocated to our application for this component. Every context object exposes the COM interface IObjectContext
. There is a global MTS method GetObjectContext()
that returns a reference (or interface pointer) to the context object. In a Visual Basic component, we normally use this as follows:
Dim objOContext As ObjectContext
Set objOContext = GetObjectContext()
Then we can use the methods of the context object to:
We'll be looking at the whole subject of security in a later chapter. For the meantime, we'll concentrate on how we use the other methods within our components. The SetComplete
and SetAbort
methods, as we've seen, are used to indicate to MTS whether our component is happy with the outcome of its operations, for example:
objOContext.SetComplete
tells MTS that we have completed all the operations we intended, and that we are happy that everything went well. As far as this component is concerned, the complete transaction can be committed. If something went wrong, however, we call:
objOContext.SetAbort
This tells MTS that we aren't happy with the outcome of the operations we performed, and that it is to abort the entire transaction and roll back all the changes made by all the other components within this transaction.
If we are embarking on a series of operations, which at certain points could leave data in an indeterminate state, we can tell MTS that the current transaction is not to be committed under any circumstances until we're ready. To do this we with:
objOContext.DisableCommit
Then, when we're ready we can call:
objOContext.EnableCommit
to indicate that MTS can commit the transaction, but should not release and reuse this object (if we called SetComplete
, MTS would destroy our object instance).
Whether the transaction is committed will depend, of course, on whether all the components within the transaction (including ours) have signaled that they are also happy to commit as well.
The context object also provides the CreateInstance
method, which we can use to create dependent instances of other objects, for use by our component. This doesn't stop us using the New
or CreateObject
methods, but it's important to understand the effects of each one as far as MTS is concerned.
The New
keyword in VB is used to create a new instance of a class as an object, for use within the current application. When used in a component within MTS, it creates a Private
instance of an object that MTS knows nothing about. It won't have its own context object, and won't be included within the current transaction. In other words, it has to look after itself with no help from MTS.
The CreateObject
method is almost exactly the opposite. When used in a component running within MTS, this creates a new instance of an object that MTS will treat as separate from the current component instance—and it will get its own context object. However, this will not contain any information from the context of the component that created the new instance, so it will run outside the current transaction.
The CreateInstance
method of the context object provides a solution to these two problems. It creates a new instance of the referenced component and provides it with a new context object. However, it also copies the transaction information from the context of the object that created it into the new context—thus making it part of the current transaction:
objOContext.CreateInstance("MyClasses.Customer")
The second COM interface, named IObjectControl
, is that of the component class factory, as stored in MTS when the component is installed. This is useful for carrying out tasks during activation and deactivation of the component. In Visual Basic, we implement this interface within our component using the Implements
keyword:
Implements ObjectControl
Our ObjectControl
interface must provide three methods, Activate
, Deactivate
and CanBePooled
. Together they provide a way for us to control how our object interacts with MTS. The Activate
method is called when an instance of our component is created—either for the first time or from the cached object pool if it has been deactivated after use. The Deactivate
method is called just before it is returned to the cache after use. The CanBePooled
method is used to tell MTS whether the component instance can be pooled or not; though remember that instance pooling is not currently supported—only a limited form of component caching is implemented at present.
Whether we implement it is entirely optional, but if we do need to carry out any processing when the component is activated or deactivated, we have to implement it so that MTS will provide the Activate
and Deactivate
events.
To see how we can use the ObjectControl
interface, look at the following code. It acquires a reference to the context object as soon as the component instance is activated, and tells MTS that it will be OK to pool this instance if Microsoft ever gets round to implementing support for it:
Implements ObjectControl
Private objOContext 'global variable to hold the object context
Private Function ObjectControl_Activate()
'get the object context as soon as instance is activated
Set objOContext = GetObjectContext()
End Function
Private Function ObjectControl_Deactivate()
'release the object context
Set objOContext = Nothing
End Function
Private Function ObjectControl_CanBePooled()
ObjectControl_CanBePooled = True
End Function
Of course, we can do any other initialization and clean-up we need to in the Activate
and Deactivate
methods. If the object needs to maintain state for any reason (though it generally shouldn't) we can save and reload values here. We can also use them to create and destroy instances of any other objects we need to use.
Implements ObjectControl
Private objOContext 'global variable to hold the object context
Private objCustomer 'global variable to hold customer object
Private Function ObjectControl_Activate()
'get the object context as soon as instance is activated
Set objOContext = GetObjectContext()
Set objCustomer = objOContext.CreateInstance("MyClasses.Customer")
objCustomer.LoadValues 'custom routine to load state from disk
End Function
Private Function ObjectControl_Deactivate()
'release the object context
objCustomer.SaveValues 'custom routine to save state to disk
Set objCustomer = Nothing
Set objOContext = Nothing
End Function
The example component we've been using so far, and which we'll continue to use in this chapter, is a stand-alone server component. It doesn't require a transaction, because only this component is used in the application. We're really only using MTS to get the benefit of its component instance management features. However, this isn't usually the case—we'll often have two or more components in use and these need to be part of a transaction.
MTS allocates a Transaction Support property to each component installed within it. This property has four possible settings, and defines how the component will behave within MTS when activated. The options are:
Requires a transaction | The component will run within an existing transaction if one already exists. If not MTS will start a new one. |
Requires a new transaction | MTS will start a new transaction each time an instance of the component is activated. |
Supports transactions | The component will run within an existing transaction if one exists. If not, it will run without a transaction. |
Does not support transactions | The component will always run outside any existing transactions. |
So, we could set the Transaction Support property for one component (let's call it A) to 'Requires a new transaction' and the other (B) to 'Requires a transaction' (or even 'Supports transactions'). The only thing now is that we would have to be sure to always instantiate them in the right order. If we instantiate A after B, it won’t be part of the same transaction as B—it will create its own new one. And if we had set B to just 'Supports transactions', we wouldn't get a transaction for this one at all unless the application had already started one for a different component.
On top of this, we also reduce the opportunities for reusing components if we have to run them in a particular order. We can’t use two components that have the 'Requires a new transaction' setting inside one transaction. What we need to do in this case is set all the components that actually achieve a task to 'Requires a transaction' or 'Supports transactions', so that we can freely use them together in any combination.
The usual way to get round this problem of the Transaction Setting property is to have a 'parent' component that creates instances of the other components and manages them. In the case of the Wrox Car Co application, which you'll be seeing a lot more of in the following chapter, placing an order involves two components. One handles local database updates, and the other handles remote database updates:
The parent component is responsible for initiating a transaction that includes the other two components. For this reason, the parent component has its Transaction Setting property set to 'Requires a new transaction' while the other two components, which do the real work, have their Transaction Setting properties set to 'Requires a transaction'.
The 'parent' component is part of the same transaction as any components that it creates using the CreateInstance
method of the ObjectContext
object. (Remember that this is not the case with New
or CreateObject
). Therefore it can control the outcome of the transaction using the SetAbort
and SetComplete
methods in just the same way as the components it creates can.
This isn’t usual, however, because the components that do the work should indicate success or failure to MTS by calling SetComplete
or SetAbort
as appropriate. The only circumstances that the parent component might use these methods are if it has to cancel the transaction on command for some reason. However, it can use the DisableCommit
and EnableCommit
methods to control when MTS can make the transaction changes permanent, as we saw earlier. This might be useful in a situation where the data could be in an inconsistent state during certain parts of the process.
We've seen how MTS automatically enlists components into an existing transaction when they are created with the CreateInstance
method of the parent's ObjectContext
object. However, this only works if the component either supports transactions or requires a transaction. If it requires a new transaction, it cannot be enlisted into an existing one.
In this case, MTS starts a new nested transaction for this component and any other components it enlists (unless they too require a new transaction). It is then up to this component to indicate to MTS whether it wants to commit or abort the original transaction (by calling SetComplete
or SetAbort
) based on the results of the nested transaction.
For example, the following diagram shows four components A, B, C and D, together with their transaction support property. RT means that this component requires (an existing) transaction or supports transactions, while RNT means that the component requires a new transaction:
In this case a single automatic transaction encompasses all the components, and will only complete if all four components agree to commit. However if component C was marked as requiring a new transaction, as in the next diagram, a nested transaction is created. Now component C can still commit the transaction (by calling SetComplete
) even if component D failed—if this was appropriate.
We generally think of components as being compiled DLLs created in languages like C++, J++, Delphi or Visual Basic. However Active Server Pages scripts can also be considered as server-side components, and ASP is an ideal tool for initiating and controlling a transaction within MTS. We can take advantage of the ObjectContext
interface in Active Server Pages just as we can in other components.
In Active Server Pages (under IIS 4) we use a different technique to reference the MTS context object. We add a line to the start of the ASP page that indicates how we want to use components within MTS in this page:
<%@ Transaction = value %>
Where value can be:
Required |
Indicates that MTS should either use the current transaction for this page, or start a new transaction if one is not already available. |
Requires_New |
Indicates that MTS should start a new transaction even if there is an existing one. |
Supported |
Indicates that MTS should use the current transaction if one exists, but not start a new one. |
Not_Supported |
Indicates that this page is not to be included in a transaction. |
These values are, of course, similar to the Transaction Property settings we listed earlier for compiled components.
Note that ASP only allows one '@
' line in a page. If you already use this to set the default script language for the page, you can add the Transaction
statement to the same line:
<%@ Language = VBScript Transaction = Required %>
MTS also provides our ASP scripts with events. This gives us an easy way to discover the outcome of a single component's execution, or the result of a multi-component transaction. Remember that components can initiate processes that occur concurrently as part of a complete transaction. So, simply calling the methods of each component within a transaction may not provide confirmation that they all completed properly unless you design the components specifically to do this.
All we have to do is create an event handler for the OnTransactionCommit()
and OnTransactionAbort()
events that MTS provides:
Sub OnTransactionCommit()
'code for when transaction completed OK
End
Sub OnTransactionAbort()
'code for when transaction failed
End
We'll see how these can be used when we adapt and use the WCCFinance
sample component in MTS in the next section of this chapter.