Active Server Page Examples

One of the toughest things about getting started with Active Server Pages is just that ”getting started. When I became interested in ASP, I looked around to see if I could find one area for a first attempt that met the following criteria:

  • The application should be required by a fair number of people, making a compelling argument for the ease of deployment possible with ASP applications.
  • The application should be a modest user of database resources ” ideally a read-only application.
  • The application should be modest in scope.

A Simple Phone Directory

The first application that met these criteria was a phone directory. While a phone directory could be created as a simple static set of HTML pages, that solution was awkward and didn't allow the kind of clever searching I wanted. For instance, I might want to find all the folks in a particular department, or I might be looking for "Joe" without knowing Joe's last name . The client that was to use this application had tried several approaches to this problem. First they tried Microsoft Word documents and Microsoft Excel spreadsheets. These items were distributed to those who requested them, but they were often outdated before reaching their intended users. Additionally, changes to document formats created support nightmares for the help desk. The same was true for Help files. Paper lists were even worse with regard to timeliness, but at least they did not cause support calls. An ASP application seemed like the perfect solution.

The number of names in the address book was not large (under 1000), so I created a lookup that would search for the entered text anywhere in the record. The record had fields for last name, first name, department, and facility. Thus, if I wanted to see the numbers for all the M.I.S. staff, I could just enter M.I.S. in the query field and get the results. Figure 9-4 shows the screen that allows the user to search for phone numbers , and Figure 9-5 shows the results screen. The complete code, along with a sample database, is included on the companion CD-ROM.

click to view at full size.

Figure 9-4 The simple entry screen for the phone lookup.

click to view at full size.

Figure 9-5 The results screen for the phone lookup.

The entry screen in Figure 9-4 contains two simple HTML controls: a text entry box and a submit button. The ASP code in Listing 9-8 generates this screen, which is actually pure HTML. It makes sense to keep this as an ASP page rather than an HTML page. With IIS 5, the overhead associated with processing the file as an ASP rather than an HTML file is minimal, and if future enhancements require ASP code (for instance, to display the current date or time), no references to the page will need to change. The HTML form described in this ASP has as its action Show.asp, the ASP file in Listing 9-9. When the submit button is clicked, control is transferred to Show.asp.

Listing 9-8

PhoneBook.asp

 <html> <head> <title>TEST</title> <metaNAME="FORMATTER"CONTENT="MicrosoftVisualInterDev1.0"> </head> <body> <CENTER> <h1><aNAME="top">Test</a></h1> <hr> <FORMmethod=POSTaction=show.asp> <p> Searchforphonenumber... <p><p> <INPUTTYPE=textNAME=showit> <p> <INPUTtype=submitVALUE="OK"> </CENTER> <hr> <h5>SimpleASPPhoneDirectoryTest<br> Copyright1998-99AccessMicrosystemsInc..Allrights reserved.<br> </h5> </body> </html> 

Figure 9-5 shows the table that results from the search for entries with "Reilly" anywhere in the record. Take a look at the code in Listing 9-9. The first line is a scripting directive letting ASP know that the file is using VBScript. Next is an include statement that defines many symbolic constants that make using ADO easier. This is followed by some fairly straightforward HTML code that sets up some metatags and the title, as well as the start of the body of the HTML page that will be generated. The "<%" is the beginning of the script on this page.

Listing 9-9

Show.asp

 <%@LANGUAGE="VBSCRIPT"%> <!--#includefile="adovbs.inc"--> <HTML> <HEAD> <METANAME="GENERATOR"Content="MicrosoftVisualInterDev1.0"> <METAHTTP-EQUIV="Content-Type"content="text/html;charset=iso-8859-1"> <TITLE>PhoneResults</TITLE> </HEAD> <BODY> <% dimrstPhones dimdb dimstrSQL dimlookFor lookFor=Request.Form("ShowIt") setdb=Session("db") setrstPhones=Server.CreateObject("ADODB.Recordset") 'ConstructSQLstatement. strSQL="SelectLName,FName,Dept,Facility,PhonefromtblPhone" strSQL=strSQL&"WHERESearchStringlike'%"&lookFor strSQL=strSQL&"%'ORDERBYlName,fName,Dept" 'OpenrecordsetbasedontheSQLstatement. rstPhones.OpenstrSQL,db,adOpenDynamic,adLockReadOnly 'Checkrecordcount,andletthemknowifnothingfound. ifrstPhones.RecordCount=0then Response.Write("<CENTER><H2>Sorry!"&_ "Nomatchfound!</H2></CENTER><es>") else 'Recordsfound?Movetofirstrecord. rstPhones.MoveFirst 'Writetableheader. Response.Write("<CENTER>") Response.Write("<P><TABLEBORDER=1><TR>") Response.Write("<TDBGCOLOR=aqua><B>LastName</B></TD>") Response.Write("<TDBGCOLOR=aqua><B>FirstName</B></TD>") Response.Write("<TDBGCOLOR=aqua><B>Dept</B></TD>") Response.Write("<TDBGCOLOR=aqua><B>Facility</B></TD>") Response.Write("<TDBGCOLOR=aqua><B>Phone</B></TD>") Response.Write("</TR>") Response.Write("<H3>") 'Loopthroughresultstowritetabledetails. DoWhileNotrstPhones.EOF Response.Write("<TRBGCOLOR=WHITE>") Response.Write("<TDBGCOLOR=white><B>"&_ rstPhones("LName")&"</B></TD>") Response.Write("<TDBGCOLOR=white><B>"&_ rstPhones("FName")&"</B></TD>") Response.Write("<TDBGCOLOR=white><B>"&_ rstPhones("DEPT")&"</B></TD>") Response.Write("<TDBGCOLOR=white><B>"&_ rstPhones("Facility")&"</B></TD>") Response.Write("<TDBGCOLOR=white><B>"&_ rstPhones("Phone")&"</B></TD>") Response.Write("</TR>") RstPhones.MoveNext loop Response.Write("</H3>") Response.Write("</CENTER)") endif RstPhones.Close 'Freeresourcesassociatedwiththerecordset. setRstPhones=nothing %> </BODY> </HTML> 

The first thing the script does is use dim statements to declare the variables to be used. This is not required, and the dim statements are a little different from standard Visual Basic or even Visual Basic for Applications (VBA) in that no type is declared. A local variable, lookfor , is set to the ShowIt element of the form (which is the text box from the initial screen), explicitly using the Forms collection of the Request object.

After the script creates an ADODB.Recordset object, it constructs a standard SQL SELECT statement and opens the recordset. The count of the recordset is checked, and then based upon whether any records were returned, the code either tells the user that no records were found or creates the table that will contain the results. The bulk of the work is done simply using the Response.Write method to write standard HTML to the client browser. Once all the records are written to the browser, the recordset is closed and then set to nothing, destroying the object.

The next step is not a suggested technique, but is here for illustrative purposes. The Global.asa for this application (shown in Listing 9-10) actually creates an ADODB.Connection object and saves it in the Session object. This is not ideal and will hurt the ability to use the IIS's build in connection pooling. This is not a great loss in this case, as the database for this simple example is a Microsoft Access database, and the Access ODBC driver is not thread-safe and therefore should be operated with IIS's thread pooling turned off. The Global.asa file also redirects the user to the Default.asp file.

Listing 9-10

Global.asa

 <SCRIPTLANGUAGE="VBScript"RUNAT="Server"> 'Youcanaddspecialeventhandlersinthisfilethatwillgetrun 'automaticallywhenspecialActiveServerPageseventsoccur.To 'createthesehandlers,justcreateasubroutinewithanamefrom 'thelistbelowthatcorrespondstotheeventyouwanttouse. 'Forexample,tocreateaneventhandlerforSession_OnStart,you 'wouldputthefollowingcodeintothisfile(withoutthe 'comments): SubSession_OnStart Dimdb 'Thisisjustanexample.ItwouldbeMUCHbettertouse 'connectionpooling,creatinganddestroying 'connectionsasneeded. Setdb=Server.CreateObject("ADODB.Connection") db.Open("UID=;PWD=;DSN=PHONES") setsession("db")=db response.redirect"default.ASP" EndSub SubSession_OnEnd Dimdb setdb=session("db") ifisObject(db)then setdb=nothing endif EndSub 'Application_OnStart'Runsoncewhenthefirstpageofyour 'applicationisrunforthefirsttime 'byanyuser. 'Application_OnEnd'RunsoncewhentheWebservershutsdown. </SCRIPT> 

A More Complex ASP Example: Troubleshooter

While the preceding example was a useful exercise, it was a bit too simple. In the real world, the structure of the data we must use is often more complex than a single, simple table. In addition, we often need to write the date as well as read it. And we sometimes need to move beyond scripting. The following example will allow us to do that.

In the life of a project, there is often a time when the specialized knowledge of a few people needs to be shared with many. So it was recently when I became involved in the rollout of a fairly significant update of a company's major product. This was the company's first significant 32-bit Windows product. It was designed to replace an MS-DOS product, but it needed to coexist with the MS-DOS product, as not all users at all customer sites were ready for a 32-bit Windows program. This led to added complexity in the final product and required that the specialized knowledge of the MS-DOS programmers and the Windows programmers be available to the support staff.

There are many ways to do this, but creating a Web application knowledge base was the option we selected. This choice circumvented the problems with using an application like Microsoft Access. (Not all users had Access installed, and there were at least four different versions of Access running on the support staff's machines in support of end users.) In addition, field installers could dial into the network and gain access to the latest product notes and suggestions from the road. This type of remote access was a key advantage for this rapidly evolving product: since the product was a bit overdue, the installers might be on the road for weeks at a time, making physical distribution of updated databases awkward.

The solution created to meet these requirements is the application shown in Figure 9-6. The first screen is much like the first screen in the phone search application ”it is only used to enter criteria for a search. This screen is a bit more complex because it has three controls for entry: a text box, a check box, and a combo box. In addition, the combo box is populated from a table ”in this case, a table stored in a Microsoft Database Engine (MSDE) database. The MSDE is a freely distributable data engine, fully compatible with SQL Server 7.0, used to build desktop and shared applications. It is available on the Microsoft Web site at http://msdn.microsoft.com/vstudio/msde/download.asp .

click to view at full size.

Figure 9-6 The search screen for the Troubleshooter system.

Depending upon the criteria entered, when the Search button is clicked, you will see either a screen telling you that no records met the criteria or a screen with one or more records displayed. Figure 9-7 is an example of the latter screen. From this screen, you can either return to the search screen or click one of the hyperlinks in the Problem Description field and be brought to a screen that shows all the details of the problem. Alternatively, you can go directly from the search screen to an Add New Problem screen. In the real world, the problems added through this screen are placed in a pending status, but the working example on the companion CD-ROM writes out new records with a ready status that will therefore be displayed immediately. The search screens are fairly self-explanatory, but I will discuss some of the finer points of the results screen shortly.

click to view at full size.

Figure 9-7 The results screen for the Troubleshooter system.

Integrating ActiveX components

One significant problem existed with the initial implementation. When records from the SQL database were first displayed, it was apparent that the formatting of the entered memo fields was lost in the HTML presentation. Of course, the reason is obvious in retrospect: HTML does not rely upon actual CR/LF pairs to determine where line breaks exist, but rather ignores literal CR/LF pairs and formats line breaks only when " <es> " literals are seen. The solution seemed clear: create a function that will add literal " <es> " strings within the memo fields. The VBScript code fragment below does the trick.

 functionAddBreaks(strIn) dimtloop dimstrInLen dimstrRet dimch strInLen=len(strIn) fortloop=1tostrInLen ch=mid(strIn,tloop,1) ifch=chr(10)then strRet=strRet&"<es>" else strRet=strRet&ch endif next addBreaks=strRet endfunction 

While this did solve the problem of line breaks not really appearing in the formatted output, it was not an ideal solution because of the time it took to format the text for some of the longer descriptions. If I entered a query that would return 20 or so records, displaying the results might take between four and six seconds. This lag was due in part to the performance of scripting languages in general and VBScript's string handling in particular.

The solution was to create a simple Active Template Library (ATL) object in Visual C++ that would handle this particular task, as well as any others that might appear. ATL is a library that is ideally suited for creating ASP components. ATL is not as suitable for creating user-interface types of components, but this is not a problem. Wizards for creating objects within the ATL DLL make the task reasonably easy.

The first step is to create a new ATL project using the ATL COM AppWizard. Figure 9-8 shows the wizard's one and only step. Select Dynamic Link Library for the Server Type to create an in-proc server. Check the Support MTS box so if needed the component can be managed by Microsoft Transaction Server (or Component Services in Windows 2000). Click Finish, and then click OK on the confirmation dialog, and the wizard creates the project.

click to view at full size.

Figure 9-8 The Visual C++ ATL COM AppWizard.

The next step is to create a new ATL object. From the Insert menu, select New ATL COM Object. This brings up the dialog in Figure 9-9. Select the ActiveX Server Component from the Objects category, and click the Next button. This results in the dialog shown in Figure 9-10. As you enter a name in the Short Name field in the C++ panel, the balance of the names are filled in with default values based upon the short name. Select the name carefully because it will be used as part of the ProgID, which will be used to create the object within scripting languages, such as ASP. The ProgID is created in the form of FILENAME.OBJNAME, so if we create the ASP project as HTMLUtil and insert an ATL object named TextUtil , the ProgID will be HTMLUtil.TextUtil , optionally with a dot and a version number appended.

click to view at full size.

Figure 9-9 The Visual C++ ATL Object Wizard.

click to view at full size.

Figure 9-10 The Visual C++ ATL Object Wizard Properties.

The other two tabs allow you to customize the object to be inserted. The Attributes tab allows you to select the threading model and several other minor options. Change the threading model to "Both" because the component we're building will not contain any state information and will be reentrant. The wizard does not allow you to change the interface from Dual to Custom because ASP requires a Dual interface. The ASP tab allows you to set some ASP-specific options. The component can get OnStartPage and OnEndPage notifications, and can have access to five of the native objects discussed in this chapter (all but the ObjectContext object). Select all the options on this page (more for educational purposes than any real necessity).

Once the object is added, right-click on the interface in the ClassView. (The interface will have the standard COM lollipop image.) Select Add Method from the context menu, and the dialog pictured in Figure 9-11 appears. Fill it in as it appears in Figure 9-11. The references in the square brackets are used to describe the interface, with the first parameter an in parameter and the second an out parameter. The retval means that the second parameter is what will be returned by the function. Both of the strings are BSTR types, meaning that they are Visual Basic-compatible strings. Clicking the Attributes button allows you to change the ID or help string value. Click OK, and a method will be added to the interface. You can then expand the interface tree in the CTextUtil class and double click on the AddBreaks method. The default code is a bare implementation that returns S_OK with the standard Wizard generated TODO comments.

Figure 9-11 The Add Method To Interface dialog.

Listing 9-11 contains the final AddBreaks method. The source for the entire object is on the companion CD-ROM. The actual code is mostly self-explanatory, with a couple of exceptions. First, the BSTR type is used throughout, but internally we use wchar_t buffers to do the actual manipulation of the elements of the array. Second, when dealing with BSTRs, you'll generally use the CComBSTR class. This utility class makes many of the operations a bit easier ”for instance, providing methods for the length of the underlying BSTR, as well as ways to get the BSTR into the BSTR pointer that is returned.

Listing 9-11

TextUtil.cpp

 STDMETHODIMPCTextUtil::AddBreaks(BSTRbstr,BSTR*pbstrRetval) { inttloop; //Usewchar_ttypebecauseBSTRsusewidecharacters. wchar_t*ptr; wchar_t*dst; wchar_ttemp[255]; //Justreturnifthepointersarenotvalid. if(bstr==NULLpbstrRetval==NULL) { returnE_POINTER; } //CreateatemporaryCComBSTRthatprovidesforbetter //manipulationofBSTRs. CComBSTRbstrTemp(bstr); if(!(bstrTemp)) { returnE_OUTOFMEMORY; } //CreatebufferstomanipulatemoreeasilythantheBSTRs. ptr=newwchar_t[bstrTemp.Length()+2]; dst=newwchar_t[bstrTemp.Length()+1024]; wcscpy(ptr,bstrTemp); dst[0]=0; for(tloop=0;ptr[tloop];tloop++) { temp[0]=0; if(ptr[tloop]==L'\n') { wcscpy(temp,L"<es>"); } else { temp[0]=ptr[tloop]; temp[1]=0; } wcscat(dst,temp); } CComBSTRResult(dst); if(!(Result)) { returnE_OUTOFMEMORY; } *pbstrRetval=Result.Detach(); deleteptr; deletedst; returnS_OK; } 

The adjustment necessary to use the HTMLUtil object's AddBreaks method was minimal. I simply created the object, used it instead of calling the VBScript function listed in the section "Integrating ActiveX components," and then destroyed the object by setting it equal to nothing. The improvement in performance was dramatic. What had previously taken up to six seconds was now virtually instantaneous. While implementing this component in Visual Basic might have been somewhat easier, I expect the speed improvement would have been less dramatic, as C's string handling tends to be quicker than Visual Basic's. Using the ATL COM wizards made this a reasonably easy task, even with the complexity of dealing with the BSTR type.

The rest of the application

Listings 9-12, 9-13, and 9-14 contain Global.asa, Default.asp, and Show.asp, respectively.

Listing 9-12

Global.asa

 <SCRIPTLANGUAGE="VBScript"RUNAT="Server"> 'Youcanaddspecialeventhandlersinthisfilethatwillgetrun 'automaticallywhenspecialActiveServerPageseventsoccur. 'Tocreatethesehandlers,justcreateasubroutinewithaname 'fromthelistbelowthatcorrespondstotheeventyouwantto 'use.Forexample,tocreateaneventhandlerforSession_OnStart, 'youwouldputthefollowingcodeintothisfile(withoutthe 'comments): SubSession_OnStart session("TroubleDSN")="UID=sa;PWD=;DSN=TroubleshootSQL" EndSub 'SubSession_OnEnd 'EndSub 'Application_OnStart'Runsoncewhenthefirstpageofyourapplication 'isrunforthefirsttimebyanyuser. 'Application_OnEnd'RunsoncewhentheWebservershutsdown. </SCRIPT> 

Listing 9-13

Default.asp

 <!--#includefile="client.inc"--> <% Response.Buffer=true %> <html> <head> <title>TroubleshooterSearch</title> <metaNAME="FORMATTER"CONTENT="MicrosoftVisualInterDev1.0"> </head> <BODY> <CENTER> <h1><aNAME="top">Troubleshooter</a></h1> <% dimcbStr dimrstSystems dimdb dimstrSQL Response.Write("<H3>Searchforproblemresolution.") Response.Write("<br>EnterwordsfromproblemDescription") Response.Write("and/orDetails,andthenclickonSearch.</H3><p>") Response.Write("<hr><FORMmethod=POSTaction=show.asp") Response.Write("id=form1name=form1><p>") Response.Write("<P><TABLEBORDER=0cellspacing=0><TR>") Response.Write("<TDwidth=""50%%""><B></B></TD>") Response.Write("<TD><B></B></TD>") Response.Write("</TR>") Response.Write("<TRBGCOLOR=WHITE>") Response.Write("<TDalign=rightwidth=""50%%"">") Response.Write("<B>Searchfor:</B></TD>") Response.Write("<TD><B><INPUTTYPE=text") Response.Write("<TD><B><INPUTTYPE=textNAME=Description>"+_ "</B></TD></TR><TRBGCOLOR=WHITE>") Response.Write("<TDalign=rightwidth=""50%%"">") Response.Write("<B>SearchDetails(slowerbutmore") Response.Write("completesearch):</B></TD>") Response.Write("<TD><B><INPUTTYPE=""checkbox""") Response.Write("id=SearchDetailsname=SearchDetails") Response.Write("value=SearchDetails></B></TD></TR>") Response.Write("<TRBGCOLOR=WHITE>") Response.Write("<TDalign=rightwidth=""50%%"">") Response.Write("<B>SystemInvolved(leaveblankforANY):"+_ "</B></TD>") cbStr="<SELECTid=cbSystemname=cbSystem>" Setdb=Server.CreateObject("ADODB.Connection") db.Open(session("TroubleDSN")) setrstSystems=Server.CreateObject("ADODB.Recordset") strSQL="SELECTSystemName,SystemIDFROMtblSystems" strSQL=strSQL&"ORDERBYSystemName" rstsystems.openstrSQL,db,1,4 cbStr=cbStr+"<OPTIONvalue=0></OPTION>" whilerstSystems.EOF<>true cbStr=cbStr+"<OPTIONVALUE="+CStr(rstSystems("SystemID"))+">"+_ rstSystems("SystemName") rstSystems.MoveNext wend cbStr=cbStr+"</SELECT>" setrstSystems=nothing setdb=nothing Response.Write("<TD><B>"+cbStr+"</B></TD>") Response.Write("</TR></TABLE><es>") Response.Flush %> <Center><INPUTtype=submitVALUE="Search"id=submit1name=submit1> </Center> </CENTER> </FORM> <FORMaction="EnterAdd.Asp"method=POSTid=frmAddname=frmAdd> <Center><INPUTtype=submitVALUE="AddProblem"id=Addname=Add> </Center> </FORM> </body> </html> 

Listing 9-14

Show.asp

 <%@LANGUAGE="VBSCRIPT"%> <!--#includefile="adovbs.inc"--> <% Response.Buffer=true %> <HTML> <HEAD> <METANAME="GENERATOR"Content="MicrosoftVisualInterDev1.0"> <METAHTTP-EQUIV="Content-Type"  content="text/html;charset=iso-8859-1"> <TITLE>SearchResults</TITLE> </HEAD> <body> <CENTER> <h1><aNAME="top">TroubleshooterResults</a></h1> <FORMaction="default.asp"method=POSTid=form1name=form1> <CENTER> <INPUTtype=submitVALUE="ReturnToSearch"id=Closename=Close> <es> </CENTER> </FORM> <% dimshowDescription dimshowDetails showDescription=Request.Form("Description") showDetails=Request.Form("Details") dimrstProblems dimdb dimstrSQL dimfinalDescription dimfinalDetails dimlookFor dimtloop dimwhereAdded dimtitle dimSearchDetails dimtstr dimhtmlUtil whereAdded=0 fortloop=1tolen(showDescription) ifmid(showDescription,tloop,1)="'"then finalDescription=finalDescription&"''" else finalDescription=finalDescription&mid(showDescription,tloop,1) endif next Setdb=Server.CreateObject("ADODB.Connection") db.Open(session("TroubleDSN")) setrstProblems=Server.CreateObject("ADODB.Recordset") strSQL="Select*from[QryProblemsReady]" ifRequest.Form("SearchDetails")<>"SearchDetails"then iffinalDescription<>""then strSQL=strSQL& "WHERE([Description]like'%"&_ finalDescription&"%')" whereAdded=1 endif SearchDetails=False else iffinalDescription<>""then strSQL=strSQL& "WHERE([Description]like'%"&finalDescription&_ "%'OR[details]like'%"&finalDescription&"%')" whereAdded=1 endif SearchDetails=True endif ifCInt(Request.Form("cbSystem"))<>0then ifwhereAdded=0then strSQL=strSQL&"WHERE" whereAdded=1 else strSQL=strSQL&"AND" endif strSQL=strSQL&"SystemID="&Cstr(Request.Form("cbSystem")) endif strSQL=strSQL&"ORDERBY[DateEntered]DESC" rstProblems.OpenstrSQL,db,adOpenStatic,adLockReadOnly ifrstProblems.recordCount=0then Response.Write("<CENTER><H2>Sorry!"&_ "Nomatchfound!</H2></CENTER><es>") else ifrstProblems.RecordCount>1then Response.Write(CStr(rstProblems.RecordCount)+&_ "RecordsFound...<es>") else Response.Write("ASingleRecordFound...<es>") endif ifSearchDetails=falsethen Response.Write("SearchingShortDescriptionOnly.") else Response.Write("SearchingShortDescription"&_ "<b>and</B>Details") endif Response.Write("<es>Scrolltoviewallrecords."&_ "ClickontheProblemDescriptionformoredetails.<es>") sethtmlutil=server.CreateObject("asp.htmlutil") rstProblems.MoveFirst Response.Write("<BODY>") Response.Write("<CENTER>") Response.Write("<P><TABLEBORDER=1><TRbgcolor=cyan>") Response.Write("<TD><B>ProblemDescription</B></TD>") Response.Write("<TD><B>DetailsofProblem</B></TD>") Response.Write("<TD><B>SolutiontoProblem</B></TD>") Response.Write("</TR>") Response.Write("<H3>") DoWhileNotrstProblems.EOF Response.Write("<TR>") title="details.ASP?ID="&rstProblems("ProblemID") Response.Write("<TD><B><AHREF="&title&">"&_ rstProblems("Description")&"</A></B></TD>") Response.Write("<TD><B>"&htmlUtil.AddBreaks(rstProblems("Details"))&"</B></TD>") Response.Write("<TD><B>"&htmlUtil.AddBreaks(rstProblems("Solution"))&"</B></TD>") Response.Write("</TR>") rstProblems.MoveNext loop Response.Write("</H3>") Response.Write("</CENTER)") endif rstProblems.Close setrstProblems=nothing Response.Flush %> </BODY> </HTML> 

Global.asp has a single event handler, Application_OnStart , which simply sets an application-level variable to hold the DSN that the entire application will use. Default.asp exhibits an interesting technique for aligning controls that is useful in HTML as well as in ASP applications. HTML is not designed as a page description language. One of the consequences of this is that precise placement of text or controls on a page is not possible. Dynamic HTML allows for greater precision but is implemented in ways that do not allow cross-browser support.

The solution to this problem that is used in Default.asp is the placement of controls and labels by using a table. Hiding the borders and using alignment properties of the table cells can align the controls to look as if they were placed using a grid, as in Visual Basic. In addition, Default.asp contains code to load the systems available for troubleshooting into a combo box.

Show.asp creates a table similar to the phone application's table, with a significant difference. Each problem description is added to the table as a hyperlink, with the ProblemID used as an argument to the Details.asp script shown in Listing 9-15. Details.asp uses a SQL Server view to display all details of the problem without requiring the logic to convert the various IDs to the text representations. This is done on the server through joins from the Problems table to the Systems and Server OS tables.

Listing 9-15

Details.asp

 <!--#includefile="adovbs.inc"--> <HTML> <HEAD> <METANAME="GENERATOR" Content="MicrosoftVisualInterDev1.0"> <METAHTTP-EQUIV="Content-Type" content="text/html;charset=iso-8859-1"> <TITLE>SearchResults</TITLE> </HEAD> <body> <CENTER> <h1><aNAME="top">TroubleshooterSearchDetails</a></h1> <FORMaction="default.asp" method=POSTid=form1name=form1> <INPUTtype=submitVALUE="ReturntoSearch" id=Closename=Close> </FORM> </CENTER> <% dimrstProblems dimrstComments dimdb dimstrSQL dimfinalTitle dimfinalSynopsis dimlookFor dimtloop dimwhereAdded dimtitle dimretStr dimhtmlUtil session("ID")=request("ID") Setdb=Server.CreateObject("ADODB.Connection") db.Open(session("TroubleDSN")) setrstProblems=Server.CreateObject("ADODB.Recordset") strSQL="Select*FROM[QryProblemSysOSJoin]" strSQL=strSQL&"WHEREProblemID="&request("ID") rstProblems.OpenstrSQL,db,adOpenDynamic,adLockOptimistic ifrstProblems.RecordCount=0then Response.Write("<CENTER><H2>Sorry!") Response.Write("Nomatchfound!</H2></CENTER><es>") else Response.Write("<CENTER>") Response.Write("<TABLEwidth=""80%""border=1cellpadding=1") Response.Write("BGCOLOR=darkslateblue>") Response.Write("<TR><TD><CENTER><H2>ProblemID:") Response.Write(Cstr(rstProblems("ProblemID"))) Response.Write("<es></H2></TD></TR>") Response.Write("<TR><TD><H3>EnteredBy") Response.Write(Cstr(rstProblems("EnteredBy"))&"On") Response.Write(Cstr(rstProblems("DateEntered"))) Response.Write("</H3>"&"</TD></TR>") ifisDate(rstProblems("DateModified"))<>falsethen Response.Write("<TR><TD><H3>ModifiedBy") Response.Write(Cstr(rstProblems("ModifiedBy"))&"On") Response.Write(Cstr(rstProblems("DateModified"))) Response.Write("</H3>"&"</TD></TR>") endif Response.Write("<TR><TD><H3>System:") Response.Write(rstProblems("SystemName")) Response.Write("Version"&rstProblems("VersionInfo")) Response.Write("</H3></CENTER>"&"</TD></TR>") Response.Write("<TR><TD><H3>ServerOS:") Response.Write(rstProblems("ServerOS")) Response.Write("</H3></CENTER>"&"</TD></TR>") Response.Write("<TR><TD><H3>Description:") Response.Write(rstProblems("Description")) Response.Write("</H3></CENTER>"&"</TD></TR>") Response.Write("<p>") 'Justintimecreation... sethtmlutil=server.CreateObject("htmlutil.textutil") Response.Write("<TR><TD><CENTER><H2>Details</H2></CENTER><P>") Response.Write(htmlUtil.AddBreaks(rstProblems("Details"))) Response.Write("</TD></TR><p>") Response.Write("<TR><TD><CENTER><H2>Solution") Response.Write("</H2></CENTER><P><P>") Response.Write(htmlUtil.AddBreaks(rstProblems("Solution"))) Response.Write("</TD></TR><p>") Response.Write("</TABLE>") Response.Write("</CENTER>") 'Freethecomponent sethtmlUtil=nothing endif %> <es> </BODY> 

One more ASP page is used by this simple system. This is the screen that allows entry of new problems into the system. Listing 9-16 shows the EnterAdd.asp script. Some of the same code used to fill in the System combo box on the search screen is used (an ideal candidate for becoming a callable function). Additionally, a file called Client.inc is included in this script. This is an include that implements a client-side JavaScript that ensures fields are properly filled in. Why JavaScript? Unlike the rest of the script code presented, this needs to operate on the client browser. While support for JavaScript is not perfect, it is far more universal than browser support for VBScript.

Listing 9-16

EnterAdd.asp

 <%@Language=VBScript%> <!--#includefile="adovbs.inc"--> <!--#includefile="client.inc"--> <% 'Writeoutthevalidationcode,fromclient.inc writeValidate() %> <HTML> <HEAD> <METANAME="GENERATOR"Content="MicrosoftVisualInterDev1.0"> <METAHTTP-EQUIV="Content-Type" content="text/html;charset=iso-8859-1"> <TITLE>TroubleshooterProblemEntry</TITLE> </HEAD> <body> <CENTER> <H1><aNAME="top">TroubleshooterProblemEntry</a></H1><P> <H3>Pleaseentertherequestedinformationandclick <I>'OK'</I>below.Theproblemwillbereviewedandmade activeassoonaspossible.Click<I>'Cancel'</I> toabandontheadditionofthisrecord. </H3> <FORMaction="Process.ASP?Action=ADD"method=POST id=AddFormname=AddFormonSubmit="returnvalidate(this);"> <H1> <TABLEborder=0cellPadding=0cellSpacing=0width="75%"> <TR> <TDalign=rightwidth="50%"><H4>EnteredBy:</B></TD> <TD><INPUTid=EnteredByname=EnteredBy></TD></TR></TABLE></H1> <H4> <TABLEborder=0cellPadding=0cellSpacing=0width="75%"> <TR> <TDwidth="50%"align=Right><B>System:</B></TD> <% dimcbStr dimdb dimrstSystems dimrstServerOS cbStr="<SELECTid=cbSystemname=cbSystem>" setdb=Server.CreateObject("ADODB.Connection") db.Open(session("TroubleDSN")) setrstSystems=Server.CreateObject("ADODB.Recordset") strSQL="SELECTSystemName,SystemIDFROMtblSystems" strSQL=strSQL&"ORDERBYSystemName" rstsystems.openstrSQL,db,1,4 cbStr=cbStr+"<OPTIONvalue=0>ANY</OPTION>" whilerstSystems.EOF<>true cbStr=cbStr+"<OPTIONVALUE="+CStr(rstSystems("SystemID"))+">"+_ rstSystems("SystemName") rstSystems.MoveNext wend cbStr=cbStr+"</SELECT>" setrstSystems=nothing Response.Write("<TD><B>"+cbStr+"</B></TD>") Response.Write("</TR></TABLE></H4>") Response.Write("<TABLEborder=0cellPadding=0") Response.Write("cellSpacing=0width=""75%"">") Response.Write("<TR><TDwidth=""50%""align=Right>" Response.Write("<B>ServerOS:</B></TD>") cbStr="<SELECTid=cbServerOSname=cbServerOS>" setrstServerOS=Server.CreateObject("ADODB.Recordset") strSQL="SELECTServerOS,ServerOSIDFROMtblServerOS" strSQL="ORDERBYServerOS" rstServerOS.openstrSQL,db,1,4 whilerstServerOS.EOF<>true cbStr=cbStr+"<OPTIONVALUE="+_ CStr(rstServerOS("ServerOSID"))+">"+_ rstServerOS("ServerOS") rstServerOS.MoveNext wend cbStr=cbStr+"</SELECT>" setrstServerOS=nothing setdb=nothing Response.Write("<TD><B>"+cbStr+"</B></TD>") 'ThebalanceisnativeHTMLasaconvenience.Iused 'VisualStudiotohelpformatthetextareas. %> </TR></TABLE></H4> <H4>Description:</H4> <TEXTAREAid=Descriptionname=Description style="HEIGHT:48px;WIDTH:434px"></TEXTAREA> <H4>Details:</H4> <TEXTAREAid=Detailsname=Details style="HEIGHT:105px;WIDTH:434px"></TEXTAREA> <H4>Solution:</H4> <TEXTAREAid=Solutionname=Solution style="HEIGHT:119px;WIDTH:434px"></TEXTAREA> <H1><INPUTtype=submitVALUE="OK"id=OKname=OK> </CENTER> </FORM> <FORMid=form1name=form1action=DEFAULT.ASP> <CENTER> <INPUTtype=submitVALUE="Cancel"id=Cancelname=Cancel>&nbsp;</H1> </FORM> </CENTER> </body></HTML> 

The WriteValidate function does the actual work, concatenating a string containing all of the JavaScript code to sense if fields are properly filled in. The major function is Validate. This text is then sent to the browser by a single Response.Write call. This code is visible on the browser (use the View/Source Code menu option in Microsoft Internet Explorer), but it has the advantage of saving a round-trip to the server for obviously invalid entries. The resulting code as seen by the browser is shown in Listing 9-17. Validate could be expanded to allow detection of other kinds of problems that could be detected on the client side. The OnSubmit handler of the form is set to Validate(this) , meaning that Validate should be called with this as a parameter. If the form is submitted with any of the TEXT or TEXTAREA fields blank, an error dialog is presented. This dialog comes from the browser and was generated without the form actually being posted to the server, thus cutting out one round-trip to the server.

Listing 9-17

Output.htm

 <SCRIPTLANGUAGE="JavaScript1.1"> //Allblanks? functionallblanks(s) { for(varloop=0;loop<s.length;loop++){ varch=s.charAt(loop); if((ch!='')&&(ch!='\n')&&(ch!='\t')) returnfalse; } returntrue; } //Validateentry. functionvalidate(f) { varmsg; varmissing=""; for(varloop=0;loop<f.length;loop++){ varcomp=f.elements[loop]; if(((comp.type=="text")(comp.type=="textarea"))&& (comp.optional!=true)){ //firstcheckifthefieldisempty if((comp.value==null)(comp.value=="") allblanks(comp.value)){ missing+="\n"+comp.name; continue; } } } if(!missing)returntrue; msg="____________________________________________________\n\n" msg+="Fieldslistedbelowweremissing.\n"; msg+="____________________________________________________\n\n" if(missing){ msg+="-Thefollowingrequiredfield(s)arenotfilledin:" +missing+"\n"; } alert(msg); returnfalse; } </SCRIPT> <HTML> <HEAD> <METANAME="GENERATOR"Content="MicrosoftVisualInterDev1.0"> <METAHTTP-EQUIV="Content-Type"content="text/html; charset=iso-8859-1"> <TITLE>TroubleshooterProblemEntry</TITLE> </HEAD> <body> <CENTER> <H1><aNAME="top">TroubleshooterProblemEntry</a></H1><P> <H3>Pleaseentertherequestedinformationandclick<I>'OK'</I>below. Theproblemwillbereviewedandmadeactiveassoonaspossible. Click<I>'Cancel'</I>toabandontheadditionofthisrecord. </H3> <FORMaction="Process.ASP?Action=ADD"method=POST id=AddFormname=AddFormonSubmit="returnvalidate(this);"> <H1> <TABLEborder=0cellPadding=0cellSpacing=0width="75%"> <TR> <TDalign=rightwidth="50%"><H4>EnteredBy:</B></TD> <TD><INPUTid=EnteredByname=EnteredBy></TD></TR></TABLE></H1> <H4> <TABLEborder=0cellPadding=0cellSpacing=0width="75%"> <TR> <TDwidth="50%"align=Right><B>System:</B></TD> <TD><B><SELECTid=cbSystemname=cbSystem><OPTIONvalue=0>ANY</OPTION> <OPTIONVALUE=5>DosAPGL<OPTIONVALUE=8>DosEBill <OPTIONVALUE=4>DosPayroll<OPTIONVALUE=3>DosRX <OPTIONVALUE=2>DosSkilled<OPTIONVALUE=6>Vscheduling <OPTIONVALUE=1>Vskilled<OPTIONVALUE=7>WindowsEBill </SELECT></B></TD></TR></TABLE></H4> <TABLEborder=0cellPadding=0cellSpacing=0width="75%"><TR> <TDwidth="50%"align=Right> <B>ServerOS:</B></TD><TD><B> <SELECTid=cbServerOSname=cbServerOS> <OPTIONVALUE=1>ANY<OPTIONVALUE=3>Citrix1.7 <OPTIONVALUE=4>Citrix1.8 <OPTIONVALUE=5>NetWare3.x<OPTIONVALUE=6>NetWare4.x <OPTIONVALUE=7>NetWare5.x <OPTIONVALUE=2>NT4.x<OPTIONVALUE=8>Windows2000Server</SELECT> </B></TD> </TR></TABLE></H4> <H4>Description:</H4> <TEXTAREAid=Descriptionname=Description style="HEIGHT:48px;WIDTH:434px"> </TEXTAREA> <H4>Details:</H4> <TEXTAREAid=Detailsname=Details style="HEIGHT:105px;WIDTH:434px"></TEXTAREA> <H4>Solution:</H4> <TEXTAREAid=Solutionname=Solution style="HEIGHT:119px;WIDTH:434px"></TEXTAREA> <H1><INPUTtype=submitVALUE="OK"id=OKname=OK> </CENTER> </FORM> <FORMid=form1name=form1action=DEFAULT.ASP> <CENTER> <INPUTtype=submitVALUE="Cancel"id=Cancelname=Cancel>&nbsp;</H1> </FORM> </CENTER> </body></HTML> 

The most significant code in the application is the code used on the Process.asp page, shown in Listing 9-18, that actually adds the records to the database. While it is sometimes convenient to simply create SQL UPDATE statements on the fly, doing so here is problematic , since the memo fields often contain embedded line breaks. Instead, the code uses the AddNew method of the ADODB.Recordset , sets the various fields, and then calls the Update method of the recordset.

Listing 9-18

Process.asp

 <%@Language=VBScript%> <!--#includefile="adovbs.inc"--> <% Response.Buffer=true %> <HTML> <HEAD> <METANAME="GENERATOR"Content="MicrosoftVisualStudio6.0"> </HEAD> <body> <% dimdb dimrstProblems dimrstCheckout dimstrSQL dimOBJdbConnection dimfinalTitle ifrequest("Cancel")="Cancel"then Response.Redirect("Default.ASP") endif setdb=Server.CreateObject("ADODB.Connection") db.Open(session("TroubleDSN")) setrstProblems=server.CreateObject("ADODB.Recordset") rstProblems.CursorType=adOpenKeyset rstProblems.Open"tblProblems",db,adOpenDynamic,adLockOptimistic ifrequest("Action")="ADD"then rstProblems.AddNew rstProblems("StatusID")=2 ifrequest("cbSystem")<>0then rstProblems("SystemID")=request("cbSystem") endif ifrequest("cbServerOS")<>0then rstProblems("ServerOSID")=request("cbServerOS") endif rstProblems("VersionInfo")="ANY" rstProblems("EnteredBy")=request("EnteredBy") rstProblems("Description")=request("Description") rstProblems("Details")=request("Details") rstProblems("Solution")=request("Solution") rstProblems.Update Response.Write("<h2><Center>Problementered") Response.Write("intodatabaseas'Pending'Willbeadded") Response.Write("toactivedatabaseASAP</Center></H2>") endif Response.Write("<CENTER><h2>") Response.Write("<FORMaction=""DEFAULT.ASP""method=POST") Response.Write("id=form1name=form1>") Response.Write("<INPUTtype=""submit""value=""Return") Response.Write("toSearch""id=button1name=button1>") Response.Write("</H2></FORM></CENTER></BODY></HTML>") %> 

Of course, improvements could be made to this system. Obviously, it might be useful to allow comments on problems to be entered and then perhaps searched and displayed. Editing existing entries might also be useful as well, though obviously this would require authentication to ensure that only authorized users could edit the database.



Inside Server-Based Applications
Inside Server-Based Applications (DV-MPS General)
ISBN: 1572318171
EAN: 2147483647
Year: 1999
Pages: 91

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