Steve Kirk
MSDN Content Development Group
March 1997
The BANKASP sample is associated with this article. For installation instructions and sample code associated with this article and related Banking articles, please go to Banking Samples: Installation Overview.
You can view client-side scripting and server-side scripting as two very different approaches to Internet application development, even though they share many similarities. In order to compare these approaches, I built a pair of applications that represent the opposing techniques while providing basically the same user function. Both of the applications process simple banking transactions over the Internet by using data services provided by a Component Object Model (COM) server, and they both use VBScript (Visual Basic® Scripting Edition) code within Internet documents as the application glue. The ActiveX™ scripting engine in Microsoft® Internet Explorer version 3.0 evaluates the script code contained in the client-side version, while the ActiveX scripting engine in Microsoft Internet Information Server (IIS) evaluates the scripts contained in the server-side version. The sample files include the data services provider as well as all of the Internet files for both applications. While I developed the sample using Microsoft Windows NT® Server and Workstation versions 4.0, other client platforms are compatible depending on the scripting technique used.
Scripting is a technique for embedding program code into Internet documents. While performing its tasks, a transaction processor sends requests for resources and submits results to the server. If the requested resource contains script code, the appropriate scripting engine in either the server or the client evaluates the script as it passes through. Figure 1 shows the resource request path and the locations of the scripting engines.
Figure 1. The HTTP resource request path and location of scripting engines
The script engine on either the browser or the server processes script commands that are embedded in its input stream. Script commands can use the following mechanisms to accomplish their ends:
Client-side scripting uses the power of the client computer to provide application services that augment the Hypertext Markup Language (HTML) part of the application. Software components are downloaded as needed and run on the client computer. The client-side script engine has access to these components through COM. A notable addition here is that COM in a distributed environment (DCOM) allows the client computer to use components that are located on computers across a local or wide area network, or even across the Internet. By using the computing power of the client, you can reduce the number of requests and the amount of data that is sent over the Internet.
Server-side scripting frees the developer from concerns over client platform differences. Any standard browser can provide basically the same user function when the stream that the client receives contains only standard HTML. The price for this universality is a greater number of client request/server response cycles, so your application interface is more apt to be affected by network latency.
Our two applications use data services provided by a COM server developed in Microsoft Visual Basic version 5.0 (see Table 1). Using a services model allows us to hide the details of data storage behind an object interface and to increase the security, maintainability, and scalability of our overall solution. We are using a Microsoft Access .MDB for this sample, but the services model would allow us to upgrade to an Open Database Connectivity (ODBC) source without changing the applications. We will also locate the data services server on the Internet server where the server-side application can access it locally through COM and the client-side application can access it across the Internet by using DCOM.
Table 1. Objects, properties, and methods for the data services
The application displays a list of banks in a vertical frame to the left side of the display. When you select a bank, the system updates the bank detail frame and the account list. You may add new accounts or select an account to edit in the account detail frame. Figure 2 shows the common interface for both applications.
Figure 2. Common user interface for both transaction processors
These are the client-side application files included in the BANKASP sample that accompanies this article:
You enter the application through default.htm. The code in default.htm defines names for the application frames so that the system can later access variables and objects across frames. The src attribute sets the initial Internet resource for the banks frame to banks.htm while it sets the remaining frame source files to empty.htm.
<frameset cols="30%,*">
<frame name="banks" src="banks.htm">
<frameset name="currentbank" rows="50%,*">
<frameset name="bankaccounts" cols="45%,*">
<frame name="accounts" src="empty.htm">
<frame name="acctdet" src="empty.htm">
</frameset>
<frame name="bankdet" src="empty.htm">
</frameset>
</frameset>
When the system first loads banks.htm, the first order of business is to create m_oAdmin of class CAdmin that provides the primary interface for the data services. The m_oAdmin.Init method makes the database connection, and FillListBanks prepares the Banks collection for use.
<script language="VBScript">
Dim m_oAdmin
Dim m_iCurrBank
Dim m_iCurrAccount
.
.
.
Set m_oAdmin = CreateObject("BankSrv.CAdmin")
If m_oAdmin.Init("pathname\banking.mdb") Then m_oAdmin.FillListBanks
The next section writes the list of banks to the frame document. Once a script uses document.writeln, the scripting engine ignores any display content in the file and the script assumes responsibility for generating all of the HTML necessary to fill the display. The <a> elements that we write to the frame each contain an index to a bank, which the system uses to fill the other frames.
For iIndex = 1 to m_oAdmin.Banks.Count
Set oBank = m_oAdmin.Banks(iIndex)
SBuff = ("<a href=""bankdet.htm"" target=""bankdet"""
SBuff = sBuff & “onClick=""m_iCurrBank = " & CStr(iIndex) & """"">"
SBuff = sBuff & oBank.Name & "</a><br>"
document.writeln(sBuff)
Next
Set oBank = Nothing
.
.
.
</script>
When the user clicks on a bank, the onClick handler assigns m_iCurrBank to the selected bank index and the bankdet frame loads bankdet.htm. The following script section from bankdet.htm updates the bankdet and accounts frames according to the selected bank. The system creates a CBank object, m_oBank, and sets it to the selected member of the m_oAdmin Banks collection. The program then walks through the m_oBank Accounts collection and writes a list of Accounts to the accounts frame.
iBank = top.banks.m_iCurrBank
Set m_oBank = top.banks.m_oAdmin.Banks.Item(iBank)
top.accounts.document.open
top.accounts.document.writeln("<strong>Accounts</strong>")
top.accounts.document.writeln("<hr>")
For iIndex = 1 to m_oBank.Accounts.Count
Set oAcct = m_oBank.Accounts(iIndex)
sBuff = "<a href=""detact.htm"" target=""acctdet"" "
sBuff = sBuff & "onClick=""top.banks.m_iCurrAccount = " & CStr(iIndex)
sBuff = sBuff & """>" & oAcct.Number & "</a><br>"
top.accounts.document.writeln(sBuff)
…
Next
Set oAcct = Nothing
top.accounts.document.close
The program now writes a bank detail report to the current frame:
document.writeln("Bank - " & m_oBank.Name & "<br>")
document.writeln("Code - " & m_oBank.Code & "<br>")
document.writeln("Address - " & m_oBank.Address1 & "<br>")
When you select an account from the account list, the system sets top.banks.m_iCurrAccount to the selected account index and loads detact.htm into the acctdet frame. Detact.htm includes the following script section that displays account detail as well as Edit and New anchors.
Set oAcct = oBank.Accounts.Item(iAccount)
document.writeln("Account Number - " & oAcct.Number & "<br>")
document.writeln("Account Type - " & oAcct.TypeID & "<br>")
document.writeln("Account Balance - " & FormatCurrency(oAcct.Balance) & "<br>")
document.writeln("<br>")
document.writeln("<a href=""detedt.htm"">Edit</a><br>")
document.writeln("<a href=""detnew.htm"">New</a><br>")
When the user selects the Edit anchor, the system loads detedt.htm. This time, instead of generating the interface elements, the script fills in static HTML elements and provides an On_Click procedure for the Commit button. The text box controls and the Commit button are defined using standard HTML.
Account Number<br>
<input type=text Name="AcctNumber"><br>
Account Type<br>
<input type=text Name="AcctTypeId"><br>
<br>
<input type=button value="Commit" OnClick="CommitAccount">
The script then fills in the account number and account type:
Set m_oAcct = oBank.Accounts.Item(iAccount)
m_oAcct.Refresh
AcctNumber.Value = m_oAcct.Number
AcctTypeId.Value = m_oAcct.TypeID
The script defines a subroutine for use when the user clicks on the Commit button. CommitAccount() gets user input from the text box controls and validates the input by attempting to convert it from text to the appropriate data type and monitoring the value of Err.
Sub CommitAccount()
.
.
.
Err = False
lNewType = CLng(AcctTypeId.Value)
If Err Then
MsgBox("Invalid account TypeID.")
Exit Sub
Else
m_oAcct.lNewType = lNewType
End if
If the input values are valid, pass oAccount, with the new values, to the Admin update method.
bResult = m_oAdmin.Update(, m_oAcct)
This completes the client-side application, which demonstrates how client-side scripting can work with software components through the client computer in order to provide function beyond what is possible with HTML alone. The server-side version will use the same data services, but it will access those services through the server's scripting engine and present the browser with a layer of standard HTML.
These are the server-side application files included in the BANKASP sample that accompanies this article:
Default.asp defines and names application frames.
Global.asa provides application and session level definitions.
Banks.asp creates the bank list.
Empty.asp reserves a place for dynamically created HTML.
Bankdet.asp displays bank detail and creates the accounts list.
Detact.asp displays the account detail.
Detedt.asp contains account editing interface.
Commit.asp handles account inserts and updates.
The first step toward creating an Active Server Pages (ASP) application in Internet Information Server (IIS) is to create a virtual root directory on the server using the Internet Service Manager. Add the directory to the WWW Service as a virtual directory with read and execute rights and then set the default document to default.asp. With the virtual root configured, IIS sees the files within the directory as an application and it will provide application and session objects and events. When IIS receives a request for one of the files within the application folder, it manages application and session states and fires the appropriate OnStart events if the session is new. You can create scripts to handle these events and define application and session variables within the reserved file global.asa. The following code from global.asa instantiates and inits the CAdmin object and instantiates CBank and CAccount.
<SCRIPT LANGUAGE=VBScript RUNAT=Server>
Sub Session_OnStart
Set Session("m_oAdmin") = Server.CreateObject("BankSrv.CAdmin")
Session("bInitResult") = Session("m_oAdmin").Init("pathname\banking.mdb")
Set Session("m_oBank") = Server.CreateObject("BankSrv.CBank")
Set Session("m_oAccount") = Server.CreateObject("BankSrv.CAccount")
Session("bNewAccount") = False
End Sub
</SCRIPT>
Since default.asp contains only HTML definitions for the application framesets and frames, the scripting engine sends it to the browser unchanged. The banks frame requests banks.asp, which contains this next script segment. Note that <% and %> delimit script segments in ASP files.
Refresh the Banks collection of the session-level CAdmin object with the FillListbanks method, and write the list of banks to the Response object as HTML anchor tags. The anchors include a bank index in the request to bankdet.asp when they are selected by the user.
Session("m_oAdmin").FillListbanks
For iIndex = 1 To Session("m_oAdmin").Banks.Count
sBuff = "<a href=""bankdet.asp" & "?iBankNdx=" & Cstr(iIndex)
sBuff = sBuff & " ""target=""bankdet"" >"
sBuff = sBuff & Session("m_oAdmin").Banks(iIndex).Name & "</a><br>"
Response.Write(sBuff)
Next
In bankdet.asp, the system retrieves the Bank index from the QueryString collection of the Request object and uses it to set the Session level CBank object:
iIndex = Cint(Request.QueryString("iBankNdx").Item(1))
Set Session("m_oBank") = Session("m_oAdmin").Banks(iIndex)
A quick walk through the accounts provides positive and negative totals for the bank detail report, which the system writes to the Response object:
For iIndex = 1 to Session("m_oBank").Accounts.Count
Set oAcct = Session("m_oBank").Accounts(iIndex)
If oAcct.Balance > 0 Then
cPos = cPos + oAcct.Balance
Else
cNeg = cNeg + oAcct.Balance
End If
Next
Set oAcct = Nothing
Response.Write("Bank - " & Session("m_oBank").Name & "<br>")
Response.Write("Code - " & Session("m_oBank").Code & "<br>")
Response.Write("Credits - " & FormatCurrency(cPos) & "<br>")
Response.Write("Debits - " & FormatCurrency(cNeg) & "<br>")
Response.Write("Net Val - " & FormatCurrency(cPos + cNeg) & "<br>")
The script in accounts.asp generates the accounts list by using the previously described technique of attaching an index number to an HTML anchor:
For iIndex = 1 to Session("m_oBank").Accounts.Count
Set oAcct = Session("m_oBank").Accounts(iIndex)
sBuff = "<a href=""detact.asp" & "?iActNdx=" & Cstr(iIndex) & """"
sBuff = sBuff & " target=""acctdet"" >"
sBuff = sBuff & oAcct.Number & "</a><br>"
Response.Write(sBuff)
Next
When you select an account from the account list, detact.asp handles the request and returns the account detail. The script also adds anchors for Edit and New, which are identical except for the way that they set the new account flag by passing QueryString data:
iIndex = Cint(Request.QueryString("iActNdx ").Item(1))
Set Session("m_oAccount") = Session("m_oBank").Accounts(iIndex)
Response.Write("Account Number - " & Session("m_oAccount").Number & "<br>")
.
.
.
Response.Write("<a href=""detedt.asp? bNewAccount =0"">Edit</a><br>")
Response.Write("<a href=""detedt.asp? bNewAccount =1"">New</a><br>")
When the user selects Edit or New, the request goes to detedt.asp, which stores the NewAccount flag to a session variable and then generates an HTML form that will be posted to commit.asp.
Session("bNewAccount") = cBool(Request.QueryString("bNewAccount ").Item(1))
SBuff = "<form name = ""account"" method=""post"" action=""commit.asp"">”
Response.Write(sBuff)
.
.
.
sBuff = "<input type=text Name=""AcctNumber"" "
If Not(Session("bNewAccount")) Then
sBuff = sBuff & "value=""" & Session("m_oAccount").Number & """"
End If
sBuff = sBuff & "><br>"
Response.Write(sBuff)
…
Response.Write("<input type=submit value=""Commit"" >")
Response.Write("</form>")
The system requests commit.asp when a user submits the form with the Commit button. Since this script handles both new and existing accounts, the script creates a new Account object if necessary.
If Session("bNewAccount") Then
Set Session("m_oAccount") = Server.CreateObject("BankSrv.CAccount")
Set Session("m_oAccount").Admin = Session("m_oAdmin")
Set Session("m_oAccount").Bank = Session("m_obank")
End if
The system retrieves input parameters from the Request.Form object and applies them to the Account object.
sNewNumber = Cstr(Request.Form("AcctNumber"))
Session("m_oAccount").Number = sNewNumber
lNewType = CLng(request.Form("AcctTypeId"))
Session("m_oAccount").TypeID = lNewType
Finally, the system either inserts or updates the Account object based on the value of NewAccount.
If Session("bNewAccount") Then
Result = Session("m_oAdmin").Insert(, Session("m_oAccount"))
Else
Result = Session("m_oAdmin").Update(, Session("m_oAccount"))
End If
The data services component, as well as the database file, is located on the server for both applications, so we can perform a reasonable comparison between client-side and server-side scripting.
Create the registry entries necessary for remote registration on your client machines by importing banking.reg with regedit.exe. Note that these registry entries include the SafeToScript and SafeToInitialize flags under implemented categories that you will have to create by hand if you register the servers using another method.
Use racmgr.exe, included with Visual Basic version 5.0, to configure the server on your clients as well as the server. Configure the data services classes for launching on the server via DCOM. Specify the server name if you are running on an intranet or the IP address of the server if you are running over the Internet.
Finally, use dcomcnfg.exe to configure your computers running Windows NT version 4.0 for DCOM security and launch permissions. The easiest way to get started with DCOM security is to open default security as wide as possible to get the system running and then make it progressively more restrictive until you have the desired level of security. Start with a default authentication level of none and a default impersonation level of anonymous. With default security configured, configure each data services class to use default security.
Although I've presented client-side and server-side scripting as distinct techniques that represent opposing design philosophies, they are not mutually exclusive. In fact, they work best when you use them together, along with ActiveX controls and components. In future projects, I'll cover the issues that stand between these introductory samples and real applications, and I'll explore related Internet and component software issues of interest to client/server developers.