Page #85 (Chapter 12 - IIS Applications and Microsoft Transaction Server)

Chapter 12 - IIS Applications and Microsoft Transaction Server

Visual Basic Developers Guide to ASP and IIS
A. Russell Jones
  Copyright 1999 SYBEX Inc.

How Do You Use MTS Components?
You approach building MTS components differently than components for stand-alone applications. You need to understand how MTS initializes the components and how you tell MTS that you're done with the component. First, you can't create an MTS object by using the New keyword. When you use New (for example, Set myObj = New obj), VB bypasses MTS and provides an object pointer that points directly to the new object. To create an MTS object, you can use either the Server.CreateObject method, which MTS will intercept, or you can use the ObjectContext.CreateInstance method. If you already have an ObjectContext reference, use CreateInstance.
The object reference that you create via CreateObject or CreateInstance isn't a direct pointer to the object at all (although your program can't tell the difference). Instead, it's a pointer to a context object, which MTS creates as an intermediary. The context object's job is to fool your program into thinking that it can control the target object directly. Therefore, you can treat the object as if you had a "real" object pointer. Behind the scenes, MTS manages one or more instances of the object, but those few instances may be connected to many clients.
Whenever you call a method of your object, MTS looks for the first free instance and passes the call to that instance. That means that the instance that handles your second method call may not be the same instance that handled the first. At this point, you should begin to see why MTS improves scalability. If there were five clients, and each needed to use a certain component, you would normally have five copies of the component in memory. With MTS though, you might only need one or two. MTS uses the time between method calls to recycle a component for many clients.
Because many clients share the components, you can't set a property on a component and then run a method that depends on the component "remembering" the property. For example (and I promised in Chapter 11 that I'd tell you about this), if you set a database access component's ConnectionString property, then use that property to make multiple method calls to the database, the property may not be available. That's the primary reason that I set up the CRepositoryData class so that you have to pass the ConnectionString with each method call—so it would be easier to move that class into MTS.
A client program can use an MTS object pointer over many method calls and everything will work fine, but the component needs to inform MTS each time it completes a unit of work so that MTS can recycle it. Components do this via the ObjectContext object. Here's a typical sequence:
  1. The client requests an MTS component instance—an object.
  2. MTS provides a handle to an ObjectContext object instead.
  3. The client accepts that handle as a "real" object reference.
  4. The client makes a method call to the component.
  5. MTS locates an idle instance of the component. If none are available, it creates a new instance.
  6. MTS passes the method call to the component.
  7. The component uses the getObjectContext method to obtain a reference to its object context.
  8. The component performs the method. If the method is successful, the component calls the ObjectContext.SetComplete method; if the method is unsuccessful, it calls ObjectContext.SetAbort.
  9. In either case, success or failure, the call to SetComplete or SetAbort informs MTS that the component has completed its work and is ready to be recycled.
  10. When the client calls another method on the component, the cycle begins from step 5, not from step 1. A different instance of the component is likely to perform the method.
You've seen how to use ADO to retrieve data inside a WebClass, and you've seen how to move database method calls out of a WebClass and into an external DLL. If you move the database access component into MTS, what changes must you make?
First, you'll need to isolate the methods. Build the methods of a component so that each one could be a separate component if necessary. Next, add the get-ObjectContext calls for each method. Finally, add the calls to SetComplete and SetAbort, depending on the outcome of the method call.
Here's an example. In Chapter 11, the CRepositoryData component had a deleteCodeByID method:
Public Sub deleteCodeByID(aConnectionString As String, _
       aCodeID As Long)
    Dim conn As New Connection
    Dim methodName As String
    Dim sd As CStoredDictionary
    methodName = Classname & "deleteCodeByID"
    On Error GoTo ErrDeleteCodeByID
    Call mADO.openConnection(conn, aConnectionString, _
       adModeReadWrite)
    Set sd = New CStoredDictionary
    sd.Add "CodeID", aCodeID
    Call mADO.executeSQL(conn, "deleteCodeByID", _
       adCmdStoredProc, sd)
    Call mADO.closeConnection(conn)
ExitDeleteCodeByID:
    Exit Sub
ErrDeleteCodeByID:
    Err.Raise Err.Number, Err.Source & ": " & _
    methodName, Err.Description & vbCrLf & _
    "Unable to delete the code with ID " & aCodeID & _
    " from the repository."
    Resume ExitDeleteCodeByID
End Sub
Here's the same method rewritten so it runs in MTS. The highlighted lines show the changes to the code from the non-MTS version:
Public Sub deleteCodeByID(aConnectionString As String, _
       aCodeID As Long)
    On Error Goto ErrDeleteCodeByID
    Dim conn As New Connection
    Dim methodName As String
    Dim sd As CstoredDictionary
    Dim oc as ObjectContext
    Set oc = getObjectContext()
    methodName = Classname & "deleteCodeByID"
    On Error GoTo ErrDeleteCodeByID
    Call mADO.openConnection(conn, aConnectionString, _
       adModeReadWrite)
    Set sd = New CStoredDictionary
    sd.Add "CodeID", aCodeID
    Call mADO.executeSQL(conn, "deleteCodeByID", _
       adCmdStoredProc, sd)
    Call mADO.closeConnection(conn)
    oc.SetComplete
ExitDeleteCodeByID:
    Exit Sub
ErrDeleteCodeByID:
    oc.SetAbort
    Err.Raise Err.Number, Err.Source & ": " & _
    methodName, Err.Description & vbCrLf & _
    "Unable to delete the code with ID " & aCodeID & _
    " from the repository."
    Resume ExitDeleteCodeByID
End Sub
Note that you do not need to check whether the call to getObjectContext returns a valid object. You need to check only when you actually use the methods of the ObjectContext object. If getObjectContext fails, the object is still set to Nothing. Therefore, you can easily write a component so that it can run either inside an MTS context or outside a context by checking whether the Object-Context object is Nothing. For example:
If Not oc Is Nothing then
   oc.SetComplete
End If
If Not oc Is Nothing then
   oc.SetAbort
End If
Writing a getObjectContext call in every method quickly becomes tedious. A simpler way is to implement the ObjectControl interface in your class. The ObjectControl interface requires that you support three methods in your class. These methods are explained in Table 12.2.
Table 12.2: ObjectControl Interface Methods
ObjectControl Interface Method Name
Description
Activate
Called just before MTS activates your component. In MTS, this event substitutes for the Class_Initialize event. You should remove code from the Class_ Initialize event if possible.
Deactivate
Called just before MTS deactivates your component. In MTS, this event substitutes for the Class_Terminate event. You should remove code from the Class_Terminate event if possible.
CanBePooled
MTS calls this function to determine whether your component can be pooled. Although MTS does not currently support pooling, you must implement the method. Apartment-threaded DLLs like those created with VB cannot be pooled, so you should always return False.
Remember, MTS keeps one or more copies of your components "alive," so your component won't receive Class_Initialize or Class_Terminate events except when it is truly loaded or unloaded. Instead, MTS fires the ObjectControl_ Activate event just before it activates a component. You can use the event to perform any required initializations. Similarly, use the ObjectControl_Deactivate event to clean up.
The big advantage of implementing the ObjectControl interface is that you can declare a class-level variable to reference the ObjectContext object. That way you don't have to write the getObjectContext call in each method. Instead, you obtain the reference during the ObjectControl_Activate event and release it during the ObjectControl_Deactivate event:
Private Sub ObjectControl_Activate()
    On Error GoTo ErrActivate
    Dim methodname As String
    methodname = Classname & ".ObjectControl_Activate"
    Set MTSContext = GetObjectContext()
ExitActivate:
    Exit Sub
ErrActivate:
    Err.Raise Err.Number, Err.Source & ": " _
       & methodname, "Error retrieving an object context."
    Resume ExitActivate
End Sub
Private Sub ObjectControl_Deactivate()
    On Error GoTo ErrDeactivate
    Dim methodname As String
    methodname = Classname & ".ObjectControl_Deactivate"
    Set MTSContext = Nothing
ExitDeactivate:
    Exit Sub
ErrDeactivate:
    Err.Raise Err.Number, "ObjectControl_Deactivate", _
       "Unable to release the object context reference."
    Resume ExitDeactivate
End Sub
Even though MTS does not support object pooling, the ObjectControl_CanBePooled method is part of the ObjectControl interface specification; therefore, you must implement it. A future version of MTS will manage pools of components, but not apartment-threaded objects such as those you create with VB. You should always return False from this function:
Private Function ObjectControl_CanBePooled() As Boolean
    ObjectControl_CanBePooled = False
End Function
From a WebClass, use the Server.CreateObject method to create MTS objects. If you use the New keyword, the objects don't run inside MTS. Alternately, you can obtain an ObjectContext object reference and use the CreateInstance method to create the MTS object. To obtain an ObjectContext object, you must compile the WebClass and run it inside MTS as well. Either approach works, but the first one works better during the design and debugging phase. Note that you can use the New keyword to create objects inside your WebClass project—those objects do run inside of MTS if your WebClass runs in MTS.
You cannot perform the following actions without getting fatal errors:
  It doesn't seem possible to persist an object via a PropertyBag object unless you create the object with the New keyword or unless you create the object by instantiating it via the PropertyBag.ReadProperty method. This makes sense, because MTS components should be stateless.
  You can't implement the ObjectControl interface on a WebClass. When you do that, MTS shuts down the calling IIS process.
  You can't implement MTS security on objects you want to persist via the PropertyBag.
Other actions seem to work just fine:
  You can run your objects in MTS in either a Library Package or a Server Package.
  You can change the package activation (Library to Server) or security while the application is running with no apparent adverse effects (I haven't tried this under load).
I've rewritten the ADO and CRepositoryData classes so that they run both inside and outside MTS. I've also provided a second MTS-enabled copy of the CodeRepository Web application named CodeRepositoryMTS so that you can easily compare the two approaches. You can run both versions on your server to compare response time and scalability.



Visual Basic Developer[ap]s Guide to ASP and IIS
Visual Basic Developer[ap]s Guide to ASP and IIS
ISBN: 782125573
EAN: N/A
Year: 2005
Pages: 98

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net