Page #57 (Chapter 7 - Controlling Program Flow)

Chapter 7 - Controlling Program Flow

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

Using Multiple WebClasses in an Application
After you have completed the code changes to the Signon WebClass, you should be able to sign on. You'll need to change what happens for a successful sign-on. In this case, you want execution to continue, but in a different WebClass. You can't use the NextItem property of the Signon WebClass, for example, Set NextItem= Reports, because the Signon WebClass doesn't know anything about the Reports WebClass.
Remember, the server instantiates WebClasses via a Server.CreateObject request from an ASP page, so to activate any particular WebClass, you need to execute the ASP page associated with that WebClass. Change the code in Signon_frmSignon as follows.
Original Code:
With Response
     .Write "You are signed on." & "<BR>"
     .Write "Signon=" & aSignon & "<BR>"
     .Write "Password=" & aPassword & "<BR>"
     .Write "Click <a href='" & URLFor(TestSecurity) _
          & "'>Test Security</a> to "
     .Write "go to the secured page."
End With
New Code:
With Response
     .Redirect "Reports.asp?WCI=SelectAccount"
As with any redirection, you lose some efficiency because the browser needs two round trips to the server to retrieve a page. The browser sends a request, the server responds with a redirect header, then the browser sends a second request to the new URL. Only then will the server respond with the page. You should keep that process in mind. It's not a problem unless you start redirecting between WebClasses often.
If you haven't tried it already, run the program. Try to sign on. If you can sign on properly, you'll get an error when the Reports WebClass receives the Select-Account request, because it doesn't exist yet. When everything seems to be working, create a new HTML template file and name it SelectAccount.htm.
  Tip Remember to create a Templates subdirectory of the AccountInfo directory. Save the original copies of any HTML template files there to keep VB from renaming them with numeric extensions as you import them into your WebClasses.
Listing 7.2 contains the HTML code for the SelectAccount HTML template.
Listing 7.2: Code for the SelectAccount HTML Template
<html><head><title>Select Account</title></head>
<body>
<center>
<h2>
Accounts List For: <WCACCOUNTHOLDER></WCACCOUNTHOLDER>
</h2>
</center>
<table align="center" width="90%" border="1" cols=2>
   <tr>
      <td colspan="2" align="middle"
      bgColor="lightblue"><b><font
      size=5>Directions</font></b>
      </td>
   </tr>
   <tr>
      <td colspan="2" align="left">
          Click on an account to view the account options
      </td>
   </tr>
</table>
<table align="center" width="90%" border="1">
   <tr>
      <td colspan="2" align="middle"
          background=""><b>Accounts</b>
      </td>
   </tr>
   <WCACCOUNT></WCACCOUNT>
</table>
</body>
</html>
The two replaceable markers hold the positions for the customer name and the list of accounts belonging to that customer. You'll replace the <WCACCOUNT> </WCACCOUNT> marker with table rows containing anchor tags that display the account type and number. Each anchor is a link to an AccountOptions options page that displays a list of actions a customer can take—in this case, inquire about the account balance or view the account history. The SelectAccount_Process-Tag event code handles the replacements:
Private Sub SelectAccount_ProcessTag _
    (ByVal TagName As String, TagContents As String, _
    SendTags As Boolean)
    Dim R As Recordset
    Dim i As Integer
    Select Case LCase(TagName)
    Case "wcaccountholder"
        TagContents = Session("FirstName") & " " _
             & Session("LastName")
    Case "wcaccount"
        ' show the list of accounts for the signed-on user
        ' create a table row for each account
        If getAccountList(R, _
            CLng(Session("CustomerID"))) Then
            While Not R.EOF
                URLData = R!AccountNumber.value
                TagContents = TagContents & _
                     "<tr><td><a href=" _
                     & Chr$(34) & _
                     URLFor("AccountOptions") _
                     & Chr$(34) & ">" _
                     & R!accountType & " (" & _
                     R!AccountNumber & ")</a>" _
                     & "</td></tr>"
                R.MoveNext
            Wend
            R.Close
        End If
    End Select
End Sub
You should notice several things in this code. Replacing the <WCACCOUNT-HOLDER> tag is straightforward, but replacing the <WCACCOUNT> tag with the list of accounts is not. Because it seemed likely that I might want to get the list of accounts in other places in the program, and because retrieving the list requires database access, I wrote a separate subroutine. The getAccountList subroutine takes a Recordset parameter and a CustomerID. The subroutine sets the Recordset parameter to a list of accounts by looking in the CustomerAccounts table to find which account numbers are associated with that CustomerID, then selecting those rows from the Accounts table.
Private Sub getAccountList(R As Recordset, _
    aCustomerID As Long) As Boolean
    Dim SQL As String
    Dim methodName As String
    methodName = "getAccountList"
    On Error GoTo Err_GetAccountList
    SQL = "SELECT * FROM Accounts "
    SQL = SQL & "INNER JOIN CustomerAccounts ON "
    SQL = SQL & "Accounts.AccountNumber = "
    SQL = SQL & "CustomerAccounts.AccountNumber "
    SQL = SQL & "WHERE CustomerAccounts.CustomerID = " _
        & aCustomerID
    SQL = SQL & " ORDER BY AccountType"
    Set R = conn.Execute(SQL)
    getAccountList = True
Exit_GetAccountList:
    Exit Function
Err_GetAccountList:
   Err.Raise Err.Number, Err.Source & ", " & _
   methodName, Err.Description & vbCrLf & _
   "Error retrieving account information " _
   & " for CustomerID '" & aCustomerID & "'."
   Resume Exit_GetAccountList
End Sub
The most important thing to notice in the SelectAccount_ProcessTag event code is that for each account, the program sets a WebClass property called URLData. The URLData property accepts a string, which the WebClass subsequently appends to all URLs generated by the URLFor method. When used in this way, URLData is equivalent to QueryString data on an ASP page. When a user clicks on an anchor tag created by URLFor, the browser navigates to the URL, and also returns the URLData that the WebClass appended to the href parameter in the anchor tag.
Here's how it works:
  1. You set the URLData property to a string, "XXX" for example.
  2. You use the URLFor method to create a URL for an item or method in your WebClass.
  3. The WebClass appends the string "&WCU=XXX" to the URL.
  4. The browser returns XXX to the WebClass when the user clicks the anchor tag containing the URL.
  5. You can retrieve the value by checking the URLData property.
Although this is by no means the only way to pass data from page to page, it is convenient, partly because it saves you from having to format the URL in code. You'll see more about passing information from page to page in Chapter 8, "Maintaining State in IIS Applications."
One other convenient side effect of using the URLData property rather than hand-formatting QueryString data is that the WebClass will append the URLData to every link that it finds in an HTML template when you call the WriteTemplate method that contains a WCU parameter. In other words, you neither have to know the data that you want to send in advance nor add it in the ProcessTag event for that template; the WebClass will do that for you. All you need to do is make sure that each URL for which you want to return the URLData ends with either ?WCU= or &WCU= (the equals sign is optional).
In this application, the URLData parameter serves to maintain a property page sequence. It ensures that the user must select an account before getting a report, because you can check the URLData property in the report pages and redirect to the AccountOptions WebItem if the property is blank. Here's the relevant code:
' check for out-of-sequence inquiry
If Len(URLData) = 0 Then
    Response.Redirect URLFor(AccountOptions)
End If
The report pages are custom WebItems. Both of them check to ensure that the user has selected an account before displaying the information.
An unfortunate side effect of using URLData to pass information from page to page is that the value is visible in the browser's address bar. For example, you would see a URL similar to this after selecting an account: http://localhost/ AccountInfo/Reports.asp?WCI=AccountOptions&WCU=451-586-67. Therefore, you can't use URLData to pass sensitive information unless you encrypt the information. In this case, you're passing an unencrypted bank account number—something you might not want to do in a real application.
To guard against the possibility of a person requesting information about an account that they do not own, the application also performs a database lookup in the Respond event for each report. If the user identified by the sign-on and password does not own the requested account, the report code returns a message stating that the user is not authorized to view the account.
Figure 7.4 shows the SelectAccount page for a typical customer.
Each of the links constructed in the SelectAccount WebItem points to the AccountOptions WebItem. As soon as the user selects an account, the WebClass fires the AccountOptions_Respond event. At that point, you can retrieve the value of the URLData property to find out which account the user selected.
Private Sub AccountOptions_Respond()
    On Error GoTo Err_AccountOptions_Respond
    methodName = "AccountOptions_Respond"
    Session("CurrentAccount") = URLData
    Dim R As Recordset
    Set R = conn.Execute("SELECT * FROM Accounts " & _
        WHERE AccountNumber='" & URLData & _
        "'", , adCmdText)
    Session("AccountNumber") = R!AccountNumber
    Session("AccountType") = R!accountType
    Session("Balance") = Format$(R!Balance, "currency")
    Session("CreatedOn") = R!CreatedOn
    Session("LastActivity") = R!LastActivity
    R.Close
    AccountOptions.WriteTemplate
Exit_AccountOptions_Respond:
    Exit Sub
Err_AccountOptions_Respond:
    Err.Raise Err.Number, Err.Source & methodName, Err.Description
    Resume Exit_AccountOptions_Respond
End Sub
The AccountOptions_Respond event performs a database lookup on the requested account number and caches the account information in Session variables. You'll use those Session values to display specific information about the selected account on the report pages. After caching the account information, the AccountOptions.WriteTemplate command causes the WebClass to process the AccountOptions template. Figure 7.5 shows the Account Options page for a typical customer.
The reports themselves are relatively straightforward. At this point, you've set up a sequence of three conditions that the request must pass before the WebClass will display either the Balance Inquiry or the Account History report:
  1. The user must have signed on with a valid sign-on and password.
  2. The user must have selected an account.
  3. The request must be for one of the accounts owned by the user.
If all the conditions have been met, the browser displays the requested report for the account number contained in the URLData property. Looked at another way, in pseudocode, the tests look like this:
If (the user is not signed on) Then
   Redirect to the signon page
End If
If (the user has not selected an account) Then
   Redirect to the account selection page
End If
If (the account requested does not belong to the user) Then
   Display an "unauthorized" message
End If
The first check is in the BeginRequest event for the WebClass, because users should not be able to reach any item in the Reports WebClass until they have signed on successfully. The other two checks are within the Respond events for the BalanceInquiry and AccountHistory. I chose to create the reports as custom WebItems, but there's no reason why they couldn't be HTML templates just as easily. Here's the code for the BalanceInquiry_Respond event:
Private Sub BalanceInquiry_Respond()
    On Error GoTo Err_BalanceInquiry_Respond
    methodName = "BalanceInquiry_Respond"
    Dim V As Variant
    Dim authorized As Boolean
    ' check for out-of-sequence inquiry
    If Len(URLData) = 0 Then
        Response.Redirect URLFor(AccountOptions)
    End If
    ' check for unauthorized inquiry
    For Each V In Session("AccountList")
        If V = URLData Then
            authorized = True
            Exit For
        End If
    Next
    If Not authorized Then
        Response.Write "You are not authorized " & _
            "to view this account."
        Exit Sub
    End If
    With Response
        .Write "<html><head><title>Balance " & _
             "Inquiry Report</title></head><body>"
        .Write "<table align='center' " & _
            "width='70%' border='1'>"
        .Write "<tr><td align='center' "& _
             "bgcolor='#80FFFF'><b>Account Balance " & _
             "</b></td></tr>"
        .Write "<tr><td align='left'>Current Balance " & _
             "for Account " & URLData & " is " & _
             Session("Balance")
        .Write "</td></tr>"
        .Write "</table></body></html>"
    End With
    ' add an Inquiry record
    Dim SQL
    SQL = "INSERT INTO Transactions (AccountNumber, " & _
        "CustomerID, TransactionDate, " & _
        "TransactionType, Amount, Balance) "
    SQL = SQL & "VALUES('" & _
      Session("AccountNumber") & "', "
    SQL = SQL & Session("CustomerID") & ", '" & _
        CStr(Now) & "','Inquiry', 0, " & _
        CDbl(Session("Balance")) & ")"
conn.Execute SQL
Exit_BalanceInquiry_Respond:
    Exit Sub
Err_BalanceInquiry_Respond:
    Err.Raise Err.Number, Err.Source & _
        methodName, Err.Description
    Resume Exit_BalanceInquiry_Respond
End Sub
Just to keep things realistic, querying the account balance writes a transaction inquiry record. The AccountHistory WebItem doesn't add a transaction record, it just writes all the applicable rows from the Transactions table to the browser, in reverse date order:
Private Sub AccountHistory_Respond()
    On Error GoTo Err_AccountHistory_Respond
    methodName = "AccountHistory_Respond"
    Dim R As Recordset
    Dim F As Field
    Dim dAccount As Dictionary
    Dim d As Dictionary
    Dim V As Variant
    Dim s As String
    Dim authorized As Boolean
    ' check for out-of-sequence inquiry
    If Len(URLData) = 0 Then
        Response.Redirect URLFor(AccountOptions)
    End If
    ' check for unauthorized inquiry
    For Each V In Session("AccountList")
        If V = URLData Then
            authorized = True
            Exit For
        End If
    Next
    If Not authorized Then
        Response.Write "You are not authorized to " & _
            "view this account."
        Exit Sub
    End If
    Set R = conn.Execute("SELECT * FROM Accounts WHERE " _
        & "AccountNumber='" & URLData & "'", , adCmdText)
    If Not isEmptyRecordset(R) Then
        Set dAccount = recordToDictionary(R)
    Else
        Response.Write "Invalid Account Number. Click " & _
           " the Back button on your browser to continue."
        Response.End
    End If
    Set R = conn.Execute("SELECT * FROM Transactions " & _
        "WHERE AccountNumber='" & URLData & "' ORDER " & _
        "BY TransactionDate DESC", , adCmdText)
    With Response
        .Write "<html><head><title>Account History " & _
            "</title></head>"
        .Write "<body><center>"
        .Write "<table width='90%' align='center' " & _
            "border='1'>"
        .Write "<tr><td align='center' bgcolor=" & _
            "'#80FFFF'><font size='5'><b>Account " & _
            "History</b></font><br>Account: " & _
            dAccount("AccountType") & "  " & _
            dAccount("AccountNumber") & "</td></tr>"
        .Write "<tr><td align='left'>This report " & _
            "shows your entire account history, " & _
            "beginning with the most recent " & _
            "transactions.</td></tr>"
        .Write "</table>"
        ' display a table
        s = RecordsetToTable(R, "90%", "center", 1, _
            True,TransactionID,TransactionDate, _
            TransactionType,Amount,Balance")
        s = Left$(s, Len(s) - Len("</table>"))
        s = s & "<tr><td colspan='5' align='right'>" & _
            "Starting Balance: " & "  " & _
            Format$(dAccount("StartingBalance"), _
            "Currency") & "</td></tr>"
        s = s & "</table>"
        .Write s
        R.Close
        Set R = Nothing
        Set d = Nothing
        .Write "</body></html>"
    End With
Exit_AccountHistory_Respond:
    Exit Sub
Err_AccountHistory_Respond:
    Err.Raise Err.Number, Err.Source & _
        methodName, Err.Description
    Resume Exit_AccountHistory_Respond
End Sub
When you run the program, the reports look similar to Figures 7.6 and 7.7.
Despite the convenience of URLData, it isn't a complete replacement for Query-String-formatted data, and it doesn't stop you from using QueryString data if you desire—even in conjunction with the URLFor method. It's perfectly acceptable to write code like this:
Response.Write <a href='" & URLFor("AccountOptions") & _
    "&AccountNumber=" & AccountNumber & "'>" & _
    AccountNumber & "</a>"
If you were to look at the result of that code by selecting View Source in the browser, the href parameter of the anchor tag would look something like the following:
'Reports.asp?WCI=AccountOptions&AccountNumber=110-67-4459'
Back on the server, you can retrieve all the QueryString parameters by using the Request.QueryString collection. Note that you can retrieve the URLData using WCU as the variable name, for example:
myVar=Request.QueryString("WCU")
After you've run the program and seen how it works, remember that there are several ways that users can get to a URL. Try saving one of the pages as a favorite. Then, close your browser, open a new instance of the browser, and try navigating to the favorite. What happens? The program should re-route you to the Signon page. You should also check to see what happens if you sign on as one person, but try to change the URL in the address bar to look at another person's account. You should get an unauthorized message, similar to Figure 7.8.
Using the URLData value and some authorization checks, you've controlled the flow in this program. Users cannot reach a report page without moving through the Signon, Account Selection, and Account Options pages. But what if you had a program, like a linear story, in which you wanted people to be able to move only one page at a time, either forward or backward. In other words, if you had 10 pages, a user should always start at page 1, then see page 2. From page 2, the user could either back up to page 1, or move forward to page 2, but could not access page 3.
To create such a program, write the program rules first, then implement them in code. The rules are as follows:
  All users must start on the first page.
  From the first page, users may quit or go to the next page.
  From the last page, users may quit or go to the previous page.
  For all other pages, users may move only to the next or the previous page, or quit.
You can visualize this programmatically as if the user's position were a pointer into an array. The array items are the pages. When the user first begins the program, the array pointer is empty; therefore you redirect to the first page, regardless of which page was requested. Figure 7.9 shows all the possible valid requests for the program.
You store the current page in a Session variable, in a cookie, in a URLData or QueryString variable, or in a hidden form field. Each time the user requests a page, you:
  1. Find the user's current position in the array, as stored in your variable.
  2. Find the position of the requested item in the array.
  3. If the two items are not contiguous—that is, if they're not next to one another, and the request is not a Quit request, you deny the request.
Exactly what happens when you deny a request is up to you and depends on the requirements of the program. The most likely scenario is that you simply ignore the request and redisplay the current page, but you might decide to display an invalid page message. Alternately, you could determine the direction from the relative positions of the user's current position and the request and move one page in the requested direction.
In this chapter, you've seen how to repurpose WebClasses, use more than one WebClass in an application by redirecting between WebClasses, and find and control the user's progress through your application. In the next chapter, you'll extend and generalize these concepts to maintain a user's state in your application.



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