Step 5: Create Emissaries to Issue Business Operation Requests

Emissaries exist to help clients issue perfectly formatted business operation requests. Creating emissaries involves:

Provide Data that Clients Need to Issue Valid Requests

Emissaries are responsible for helping Windows DNA application clients issue perfectly formatted requests for executants to perform various business operations. This responsibility includes providing clients with any supplemental information they need from the application to formulate valid business operation requests. To identify such supplemental data, developers must analyze each business operation request. For example, the DNA PurchaseOrder application requires clients issuing purchase order creation requests to provide several pieces of information, including the order's destination shipping address and the inventory ID of each item they wish to purchase as part of the order. While the user doesn't require any supplemental information from the application to supply the order's destination shipping address, the user does need the application to provide the inventory IDs of the various items the user wants to purchase. The DNA PurchaseOrder application provides users with this information by presenting a list that includes each inventory item's ID along with its description, cost, and current quantity on hand.

Figure 14. Inventory selection

Emissaries are responsible for helping Windows DNA application clients issue perfectly formatted requests for executants to perform various business operations, which includes providing clients with any supplemental information they need from the application to formulate valid business operation requests. The Windows DNA PurchaseOrder application requires clients issuing purchase order creation requests to provide several pieces of information, including the order's destination shipping address and the inventory ID of each item that they wish to purchase as part of the order.

The DNA PurchaseOrder application provides special iterator objects to assist emissaries in providing clients with the supplemental information they need to issue business operation requests. The DNA PurchaseOrder application's iterator objects are designed to behave like ADO recordsets and even provide similar properties and methods.

Table 17. DNA PurchaseOrder Application Iterator Object Properties and Methods

Property/method Data type Description
(P) AbsolutePage Long Specifies which page to move for a new current record.
(P) AbsolutePosition Long Specifies the ordinal position of the internal recordset's current record.
(P) BOF Boolean Indicates whether the current record position is before the first record in the internal recordset.
(P) Bookmark Variant Specifies a bookmark that uniquely identifies a record in the internal recordset.
(P) CacheSize Long Specifies the number of records that are cached locally in memory.
(M) Clone Recordset Creates a duplicate recordset.
(P) EOF Boolean Indicates that the current record position is after the last record in the internal recordset.
(P) Filter String Indicates the selection criteria for selectively screening out records.
(M) Initialize Boolean Initializes the iterator object by instructing it to issue a request to obtain an ADO disconnected recordset for internal usage.
(P) Item Object Returns an emissary that represents the record identified by the current record position.
(P) MaxRecords Long Indicates the maximum number of records to return from a query.
(M) Move N/A Moves the current record position a user-specified number of records forward or backward.
(M) MoveFirst N/A Moves the current record position to the first record in the internal recordset.
(M) MoveLast N/A Moves the current record position to the last record in the internal recordset.
(M) MoveNext N/A Moves the current record position one record forward, toward the bottom of the internal recordset.
(M) MovePrevious N/A Moves the current record position one record backward, toward the top of the internal recordset.
(P) PageCount Long Indicates how many pages of data the internal recordset contains.
(P) PageSize Long Indicates how many records constitute one page in the internal recordset.
(P) RecordCount Long Indicates the current number of records in the internal recordset.
(P) Sort String Sorts the records in the internal recordset.
(P) State Long Indicates the current state of the internal recordset.

However, unlike an ADO recordset, an iterator is designed to return an emissary that represents the record identified by the current record position. The emissary returned by an iterator can be used like any other emissary to prepare and issue requests for executants to perform various business operations. Before a client can use an iterator, the iterator must be initialized. Clients initialize iterators by providing the selection criteria that describes the records they're interested in. The iterator uses the user-provided selection criteria to formulate and issue a request to the appropriate executant, which responds by returning an ADO disconnected recordset to the iterator. The iterator caches the disconnected recordset and uses it to perform the various operations defined by the iterator. The following code snippet illustrates how the DNA PurchaseOrder application uses an inventory iterator to display the list of inventory items available for purchase:

Private Sub DisplayInventory()
    Dim objInventory As Inventory
    Dim objListViewItemAdded As ListItem
    
    ' Clear any existing contents
    lvInventory.ListItems.Clear
    Set mobjInventoryIterator = New InventoryIterator
    mobjInventoryIterator.Initialize
    ' Sort the items
    mobjInventoryIterator.Sort = "Description ASC"
    ' Display the Inventory Items
    Do While Not mobjInventoryIterator.EOF
        Set objInventory = mobjInventoryIterator.Item
        Set objListViewItemAdded = lvInventory.ListItems.Add(, "Key=" & CStr(objInventory.ID), CStr(objInventory.ID))
        ' Description
        objListViewItemAdded.SubItems(1) = objInventory.Description
        ' Price
        objListViewItemAdded.SubItems(2) = Format$(objInventory.Price, "Currency")
        ' Quantity on Hand
        objListViewItemAdded.SubItems(3) = CStr(objInventory.QOH)
        mobjInventoryIterator.MoveNext
    Loop
    ' Enable the Done button if necessary
    cmdDone.Enabled = Not (lvInventory.SelectedItem Is Nothing)
    
    ' Clean Up
    Set objListViewItemAdded = Nothing
    Set objInventory = Nothing
End Sub

Because the DNA PurchaseOrder application's iterators are built on top of ADO disconnected recordsets, they are capable of being downloaded and executed on the client's machine.

The DNA PurchaseOrder application relies on four different iterators to provide clients with the supplemental data they need to issue properly formatted business operation requests: the AccountIterator, the InventoryIterator, the LineItemIterator, and the PurchaseOrderIterator, each of which is designed to return a specific type of emissary.

To create the iterators used by the DNA PurchaseOrder application

  1. Start Visual Basic if it's not already running.

  2. From the File menu, select New Project.

  3. Select ActiveX DLL from the New Project window and click OK.

  4. From the Project menu, select Project1 Properties, type POEmissaries in the Project Name text box, and click OK.

  5. From the Project menu, select References, place check marks next to POInterfaces and Microsoft ActiveX Data Access Objects 2.1 Library in the Available References list box, and click OK.

  6. Create three additional class modules (for a total of four) to contain the iterators, and name them AccountIterator, InventoryIterator, LineItemIterator, and PurchaseOrderIterator.

  7. Implement each iterator using the code in Listings 43-46.

Validate Each Request Prior to Issuing to an Executant

To minimize the network traffic associated with issuing invalid business operation requests, emissaries should validate each client request as thoroughly as possible before actually issuing the request to an executant. For example, in order to create a new customer account, the DNA PurchaseOrder application requires the customer to provide their five-digit United States Postal Service (U.S.P.S) ZIP code. Unless the Account emissary has access to the entire list of valid U.S.P.S. ZIP codes, there is no way for the emissary to completely validate whether the ZIP code supplied by the customer is valid. However, the Account emissary can surely validate whether the ZIP code supplied by the customer is exactly five numeric characters.

Private Property Let IAccount_Zip(ByVal RHS As String)
    Dim intCharacterCode As Integer
    Dim intPos As Integer
    Dim blnInvalid As Boolean
    
    ' Perform any data validation that does not require
    ' access to the data services
    
    ' Zip must be 5 numeric characters
    If Len(RHS) = 5 Then
        blnInvalid = False
        For intPos = 1 To 5
            intCharacterCode = Asc(Mid$(RHS, intPos, 1))
            If intCharacterCode < 48 Or intCharacterCode > 57 Then
                blnInvalid = True
                Exit For
            End If
        Next intPos
        ' If the new value is valid then save it
        If Not blnInvalid Then
            mstrZip = RHS
        End If
    Else
        blnInvalid = True
    End If
    ' Track whether or not the property is invalid
    mobjInvalidProperties.Track IDS_INVALID_ACCOUNT_ZIP, blnInvalid
    ' Notify the client of any error
    If blnInvalid Then
        Err.Raise vbObjectError + IDS_INVALID_ACCOUNT_ZIP, LoadResString(IDS_PROJECTNAME), LoadResString(IDS_INVALID_ACCOUNT_ZIP)
    End If
End Property

So, while emissaries may not always be able to completely validate each client request, they should always attempt to validate client requests as thoroughly as possible in order to minimize the unnecessary network traffic associated with issuing invalid requests.

When validating client data, emissaries should perform property-level validation as well as object-level validation. Property-level validation is the process of ensuring an individual property is within a predefined range of acceptable values. Supporting property-level validation allows emissaries to provide immediate feedback to interactive clients (for example, clients with dynamic HTML or Win32-based presentation services).

Figure 15. Supporting property-level validation allows emissaries to provide immediate feedback to interactive clients like clients that rely on dynamic HTML or Win32-based presentation services.

Object-level validation is the process of ensuring all of an object's properties are within their respective predefined ranges of acceptable values. Supporting object-level validation allows emissaries to provide feedback to non-interactive clients (for example, clients with HTML 3.2-based presentation services supported via Active Server Pages (ASP) or Internet Server API (ISAPI) filters or extensions).

Figure 16. Supporting object-level validation allows emissaries to provide feedback to non-interactive clients like clients that rely on HTML 3.2-based presentation services supported via Active Server Pages (ASP) or Internet Server API (ISAPI) filters or extensions.

Figure 17. Supporting object-level validation allows emissaries to provide feedback to non-interactive clients like clients that rely on HTML 3.2-based presentation services supported via Active Server Pages (ASP) or Internet Server API (ISAPI) filters or extensions.

Each DNA PurchaseOrder emissary supports both property-level and object-level validation. To provide object-level validity, each DNA PurchaseOrder emissary relies on a special InvalidProperties helper object to track each invalid property individually:

' Track whether or not the property is invalid
    mobjInvalidProperties.Track INVALID_ACCOUNT_ZIP, blnInvalid

By tracking invalid properties, the InvalidProperties object makes it easy for the emissary to provide object-level validation:

Private Property Get IAccount_Valid() As Boolean
    ' The entire object cannot be valid unless
    ' all of its properties are valid
    IAccount_Valid = (mobjInvalidProperties.Count = 0)
End Property

To implement the DNA PurchaseOrder emissaries and related support objects

  1. Create six new class modules to contain the various emissaries and helper objects, name them InvalidProperties, Account, Inventory, LineItem, LineItems, and PurchaseOrder, and use the Properties window to set their Persistable property to 1-Persistable.

  2. Implement each object using the code in Listings 47-52.

  3. Create a new class module and name it Constants.

  4. Use the code in Listing 53 to define constants used for the various "invalid property" error messages that are capable of being validated.

  5. Create a resource file to contain the "invalid property" error messages defined in the following table. (See "Working with Resource Files" in the section "More about Programming" included in the Visual Basic documentation for more information on using resources files with Visual Basic.)

Table 18. "Invalid Property" Error Messages

Value Description
101 DNA Purchase Order.
102 The account must have a first name.
103 The account must have a last name.
104 The account must have an address.
105 The account must have a city.
106 The account must have a two-character state abbreviation.
107 The account must have a five-digit ZIP code.
108 The account could not be destroyed. Make sure that the account exists, and that you have the proper authorization to delete an account.
109 The account could not be saved. Make sure that you have the proper authorization to save an account.
110 The inventory item must have a description.
111 The inventory item cannot have a negative price.
112 The inventory item cannot have a negative quantity on hand.
113 The inventory item could not be destroyed. Make sure that the item exists, and that you have the proper authorization to delete an inventory item.
114 The inventory item could not be saved. Make sure that you have the proper authorization to save an inventory item.
115 The line item must have a valid inventory item.
116 The line item cannot have a negative price.
117 The line item cannot have a negative quantity.
118 The line item could not be destroyed. Make sure that the item exists, and that you have the proper authorization to delete a line item.
119 The line item could not be saved. Make sure that you have the proper authorization to save a line item.
120 The purchase order must have a valid account number.
121 The purchase order cannot have a negative shipping charge.
122 The purchase order must have a "ShipToFirstName."
123 The purchase order must have a "ShipToLastName."
124 The purchase order must have a "ShipToAddress."
125 The purchase order must have a "ShipToCity."
126 The purchase order must have a two-character "ShipToState" abbreviation.
127 The purchase order must have a five-digit numeric "ShipToZip" code.
128 The purchase order cannot have a negative tax rate.
129 The purchase order cannot have a negative total.
130 The purchase order could not be cancelled. Make sure that you have the proper authorization to cancel a purchase order.
131 The purchase order could not be destroyed. Make sure that the purchase order exists, and that you have the proper authorization to delete a purchase order.
132 The purchase order could not be saved. Make sure that you have the proper authorization to save a purchase order.

Once all of the emissaries and support objects have been completely implemented, save your work, and compile the ActiveX DLL into POEmissaries.dll.