Designing and Implementing a Programmability Model

Duwamish Books, Phase 3.5: VBA

Ade Miller
Summit Software Company

December 1999

Summary: The Business Logic Layer (BLL) must expose a programmability object model against which the VBA programmer writes code. (6 printed pages) This article discusses:

Contents

Introduction
Exposing Events Within the BLL Method Workflow to VBA
Providing Methods and Properties
Automating a BLL Method
Raising Events in the MT Project
Raising Errors and Communicating with the Client
Exposing the Transaction Context to VBA
Conclusion

Introduction

Duwamish Books, Phase 3.5: VBA features two BLL methods that support customization using multithreaded (MT) VBA Projects and provides a programmability model for each. These are implemented as Microsoft® ActiveX® DLLs, ordermod.dll and buyermod.dll.

Exposing Events Within the BLL Method Workflow to VBA

Duwamish Books, Phase 3.5: VBA modifies the BLL to execute VBA code at various points during a BLL method. Two possible strategies for doing this exist. The BLL method could load a VBA MT Project and search for a named entry point, a public subroutine or function in the Designer module, and call it. Alternatively, the object model could expose events to VBA that could have code behind them. This phase of Duwamish Books takes the latter approach.

By exposing the workflow entry points to the VBA programmer as events, rather than named subroutines or functions, the entry points become self-documenting. The only disadvantage to this approach is that events do not return values but can modify In/Out parameters. Using public subroutines or functions would allow values to be returned but would rely on the VBA programmer using the documentation to create the correct routine name and parameter list.

The Order programmability model exposes two such events: InsertOrderStart and InsertOrderEnd. The InsertOrderStart event has a Cancel parameter. If the VBA code handling the event sets this to True, the BLL method will abort. The Buyer model only exposes one event, CalculatePerformance, which also has an In/Out parameter, PerformancePoints.

Providing Methods and Properties

The ADO Recordset passed into the InsertOrder BLL method is not exposed directly to the VBA programmer. Object models should be self-documenting, but the Recordset Fields collection is only populated at run time so the VBA programmer cannot browse the available fields. To provide the VBA programmer with a fully exposed object model the Recordset fields are exposed as read-only properties, so to access the Quantity field for the first record in the Recordset the VBA programmer can use Order.OrderItems(1).Quantity rather than Fields("Quantity").Value, which doesn't support Microsoft IntelliSense® at design time.

In addition to exposing the Recordset data as a collection of read-only properties, the object model also provides methods and properties to make common tasks easier to perform. For example, the TotalValue and TotalBooks properties save the VBA programmer from iterating over the OrderItems collection to sum the Price and UnitQuantity properties to work out the total value and number of books in an Order. In general, any computationally intensive methods that the VBA programmer might require frequently should be exposed through the object model to increase performance.

Automating a BLL Method

The first thing a BLL method must do is create an instance of the programmability model object and initialize the MT VBA Runtime, hooking up the programmability object in the process. The InsertOrder method includes the following code to initialize the application programmability object and the VBA MT Runtime:

On Error GoTo VbaStartupErrorHandler

If (AppHelper Is Nothing) Then
    Set AppHelper = New VbaOrderObjectModel.Helper
End If

If (MtRuntime Is Nothing) Then
    Set MtRuntime = New VbaMT
  
    '   VbaMt.Init expects the license key as a pointer to an array of
    '   single byte characters. Here's how to do this in VB.
    '
    Dim keystr() As Byte
    keystr = CommonConstants.scVBALICENSE
    keystr = StrConv(keystr, vbFromUnicode)

    MtRuntime.Init VarPtr(keystr(0)), _
                          CommonConstants.scMTREGPATH_ORDER, _
                          AppHelper.AppObj
    On Error Resume Next
    Set MTDlls = MtRuntime.LoadAllRegMTDlls
    
    '   Ignore the following errors for LoadAllRegMTDlls
    '
    '   E_FAIL (0x80004005) if the registry key doesn't exist
    '   0x800A02EE if a registry entry points to a DLL that doesn't exist
    '
    If ((Err.Number <> CLng(&H80004005)) And _
        (Err.Number <> 0) And _
        (Err.Number <> &H800A02EE)) Then GoTo VbaStartupErrorHandler
End If

Note how the InsertOrder code accesses the VbaOrderObjectModel.Application application programmability object through the friend object VbaOrderObjectModel.Helper. This approach is used to minimize the number of additional methods on the Application object and keep it as clean as possible. The Application object only exposes methods and properties for the VBA programmer. The Helper friend class supports all the functionality required to configure the Application object and pass information back to the hosting BLL.

The Helper object is responsible for creating an instance of the Application object and provides an AppObj property to allow the BLL method to gain access to the Application object.

Providing a separate error handler for the VBA-related code is good practice, especially during development. The MT Runtime can generate unexpected errors.

Raising Events in the MT Project

During the BLL method's workflow the method raises events in the MT Project DLL. The InsertOrder method uses the following code to raise the OrderStart event:

Dim Cancel As Boolean
On Error GoTo VbaEventErrorHandler
AppHelper.RaiseInsertOrderStart Cancel

The InsertOrderStart event takes a Cancel parameter. Cancel is an In/Out parameter; if the VBA code in the event handler sets Cancel to True, the BLL method aborts the transaction:

If (Cancel = True) Then
    GetObjectContext.SetAbort
    Set rsDetails = Nothing
    Set rsIns = Nothing
    Set m_oDal = Nothing
    InsertOrder = False
    Exit Function
End If

Raising Errors and Communicating with the Client

The only mechanism for the VBA programmer customizing Duwamish Books to communicate with the client is by raising an error from within the VBA code using the Application.RaiseError method. This error is passed back to the client by setting the out parameters on the InsertOrder method, which displays a dialog box. Aborting an MTS transaction changes the error information passed back to the client so it's not possible to simply raise an error from Visual Basic and use this to return information to the client.

The programmability model provides no other way of communicating with the client because, in general, Microsoft Transaction Server (MTS) objects should not communicate with the client. If the programmability model allowed the VBA programmer to write code that displayed (modal) dialog boxes on the client the MTS method would pause while waiting for the client's user to respond to the dialog box. MTS transactions expire if not completed within a certain period, after which the MTS transaction will automatically be aborted.

The VBA programmer should not use UserForms or message boxes to communicate with the client, because the BLL might be hosted on a MTS server instead of the client machine. In this case any forms or dialog boxes would not be visible to the client user. The Modifier client removes the Add | UserForm menu items to prevent the VBA programmer from adding forms to MT Projects. The VBA programmer could still use MsgBox to display dialog boxes—there is no way of preventing this.

Any UserForms or dialog boxes displayed on the client on behalf of a BLL method must occur as the BLL method ends, either as the result of an error being raised or by the client in response to information returned by the BLL method.

The Order programmability model contains an Application.RaiseError method to allow the VBA programmer to pass custom errors back to the client. The hosting BLL method uses the Helper.IsError, Helper.ErrorSource, and Helper.ErrorDescription properties to detect and pass back the error to the client. If an error is detected the MTS transaction is aborted:

If (AppHelper.IsError) Then
    GetObjectContext.SetAbort
    Set rsDetails = Nothing
    Set rsIns = Nothing
    Set m_oDal = Nothing
    InsertOrder = False
    ErrorNumber = AppHelper.ErrorNumber
    ErrorSource = AppHelper.ErrorSource
    ErrorDescription = AppHelper.ErrorDescription
End If

Exposing the Transaction Context to VBA

Neither of the VBA programmability models exposes the MTS Transaction Context. The programmability model could completely expose the Transaction Context by providing a GetObjectContext method, which returned an IObjectContext interface pointer. This approach is probably not desirable in most cases. The Transaction Context is very powerful and exposing it directly to the VBA programmer may allow them to break your application.

Aborting or completing a transaction can be thought of as a voting process. If the VBA code calls SetAbort and aborts the transaction there is no way for the hosting BLL method to reverse this. The Order model allows the VBA programmer to abort a transaction by calling the RaiseError method, which then calls SetAbort. This allows the hosting BLL method to clean up during the abort process. The Buyer programmability model doesn't expose the Transaction Context at all.

If Duwamish Books took advantage of the Microsoft Windows NT® security model to authenticate users, exposing IObjectContext::IsCallerInRole would make good sense. Similarly, if the BLL provided some additional objects or methods specifically for the VBA programmer, exposing IObjectContext::CreateInstance would allow the VBA programmer to create new objects inside the current Transaction Context.

In these cases, one approach would be to provide the VBA programmer with an object similar to ObjectContext, containing implementations of a subset of IObjectContext methods, without returning the interface itself.

Conclusion

As you have seen, VBA enabling a BLL method requires very little new code to be added to the BLL. The following points are of primary importance: