创建服务器端的ASP搜索组件(二)


  ShowSearchFrame 方法
  ShowSearchFrame是唯一一个声明为Public 的主方法。它不接收自变量,动态生成搜索-输入域、导航条和标题列表。这是通过调用4个支持性Private 方法实现的。

  在ShowSearchFrame 方法中要声明多个变量,是为了引用我们的支持性Private 方法时进行传递。在应用它们的Private 方法的上下文中进行解释。

  调用的第一个Private 方法是 SearchInputField 函数。这个函数返回一个字符串,在我们这个例子中,字符串由HTML标记和文本填充,以产生一个文本输入区和搜索按钮。

  我们将这个HTML代码存储在字符串变量strHTML中:
Public Function ShowSearchFrame() As String
"''''' Local variables
Dim strHTML As String
"===== Search input field
strHTML = SearchInputField
End Function

SearchInputField 函数   SearchInputField 函数不接收自变量。但是它动态地设置一个由页面访问者决定的变量。它还设置源文件名和画面名,与在SearchFrameSet.htm 文件中使用的FRAME 标记名相匹配,这是与决定我们站点的画面特征的SearchFrameSet.htm 相同的文件。

  首先看看将要返回的HTML代码:
< FORM NAME="SearchForm" METHOD="GET" ACTION="SearchPage.asp" TARGET="SearchFrame" >
< INPUT TYPE="TEXT" NAME="Src" MAXLENGTH="50" SIZE=15 VALUE="" >
< INPUT TYPE=SUBMIT VALUE="Search" >
< /FORM >
  表单的NAME标记设置为 SearchForm。ACTION标记设置为SearchPage.asp, 这是mstrSearchFrameURL变量的默认值。
  任何从这个表单的输入域中输入的文本都将传送到源文件(SearchPage.asp )。我们的情况是,这个源文件自引用完全相同的asp 文件,其中容纳着包含SearchInputField 方法的组件。实质上,这个表单调用了它自己,这是设置默认的结构。

  在浏览器中,这个HTML代码生成下面的文本输入控件:

现在当访问者在输入域中键入文本并且点击Search 按钮时,文本就会被附在SearchPage.asp URL结尾处。这是因为FORM标记内的METHOD被设置为GET,而不是POST。

  POST当然是这两种方法中较好的一个,因为它在HTTP 头文件中隐藏了输入的文本并且允许更长的值。但是我们的例子中,是一个短的文本值,? T 也能允许通过将一个查询字符串附在任何URL的结尾来调用方法。它还有一个好处就是允许浏览器缓存查询结果,因为在进行GET 时,缓存在完整的URL上工作。这样就可以快速访问每个搜索页面,并且在后来离线浏览。

  当访问者输入"component" 作为他们的文本输入时,我们的表单发送下面的URL和查询字符串:
SearchPage.asp?Src=component
  你还可以从任何站点将这一声明作为一个连接包含在一个HREF标记内--SearchPage.asp 文件不需FORM标记就能工作。
  以下是完整的SearchInputField 函数,它生成以上的HTML 代码和浏览器输入控制:
Private Function SearchInputField() As String
Dim strHTML As String
"'''' Search input form start
strHTML = strHTML & "< FORM NAME=""SearchForm"" METHOD=""GET"" ACTION=""" _
& mstrSearchFrameURL & """ TARGET=""" & mstrSearchFrameName & """ >"
"'''' Search input field
strHTML = strHTML & "< INPUT TYPE=""TEXT"" NAME=""Src"" MAXLENGTH=""50""" _
& "SIZE=15 VALUE=""" & mobjRequest.QueryString("Src") & """ >"
"'''' Search input button
strHTML = strHTML & "< INPUT TYPE=SUBMIT VALUE=""Search"" >"
"'''' Search input form end
strHTML = strHTML & "< /FORM >"
SearchInputField = strHTML
End Function
  请注意我们是如何在HTML代码的连接片段内连接属性程序变量来使用它们的。还要注意HTML标记内要求的单引号(") 是如何被双引号("") 引用的,这样就可以避免VB把它作为一个字符串常量的开头或结尾而产生错误。
  还有一个需要注意的是,在INPUT标记内被设置为VALUE的变量。它从查询字符串mobjRequest.QueryString("Src") 中得到它的值,聪明的读者会认出它就是标记本身的名字:
VALUE=""" & mobjRequest.QueryString("Src")
  这个自引用查询字符串将在它的文本域内显示发送时在域中键入的任何文本。由于在文本输入域中键入"component" 将把?Src=component附在URL的结尾,INPUT 标记的值将在输入文本框中显示Src的值。

  假设在文本输入域内键入的是"component" ,则发送的URL是?/P >
SearchPage.asp?Src=component
  而下面的VB代码< INPUT TYPE=""TEXT"" NAME=""Src"" VALUE=""" & _
mobjRequest.QueryString("Src") & """ >
  将生成
< INPUT TYPE="TEXT" NAME="Src" VALUE="component" >
  而且将显示:

现在用户可以输入一个查询字符串,并将其发送到我们的组件看看查询字符串完成一次搜索。

回到ShowSearchFrame 方法第二部分
  回到我们的ShowSearchFrame 方法,它是在SearchInputField 方法中被调用的,我们将返回的HTML代码存储在strHTML变量中。但是在我们将它发送给浏览器之前,必须要确定访问者是否已经输入了一个搜索查询文本让我们在搜索数据库过程中使用。
  这是通过校验Src 查询字符串变量是否为空实现的。如果为空,就通知访问者现在他们可以输入查询字符串了。当访问者进入一个搜索站点,连接到一个没有Src 查询字符串的网页时通常就会看到这样的提示。
  ShowSearchFrame 调用 SearchInputField 方法并将HTML输入控制代码存储到strHTML 变量后,我们要查看一下这是不是一个新用户或查询请求:
"===== Search input field
strHTML = SearchInputField
If mobjRequest.QueryString("Src") = "" Then
"''''' No query was entered
strHTML = strHTML & "Enter a query"
Else
"[Search code here]
End If

  如果没有名为Src的查询字符串,连接来自我们的组件以外,我们可以将一个介绍性的信息附在strHTML 代码上(从这个组件发送一个空查询也会产生同样效果):
strHTML = strHTML & "Enter a query"
  现在我们的第一个HTML代码已经完整了,我们将它发送给 IIS在访问者的浏览器上显示:
Else
"[Search code here]
End If
"''''' Send html code to IIS for delivery to visitors browse r
mobjResponse.Write (strHTML )
产生下面的显示:


  访问者输入了要组件搜索的文本以后,以上的条件If 语句就会发现VBmobjRequest.QueryString("Src") 变量不为空,分支控制逻辑就会流向选择的Else 部分。然后连接到数据库并在数据库中搜索匹配的Title 和 Text 域。
  因为我们要搜索一个Access Memo 型域,或者说是SQL 服务器文本型域,我们不在SQL声明内使用LIKE 语句。相反,我们通过以下的SQL字符串返回一个记录集:
SELECT Title, Text, URL FROM SearchTable
  可以用一个帐号群ID、一个典型范围或者在过滤表中有相关内容的任何类别来限制初始查询。
  由于SQL声明恢复每个记录,我们就很快地搜索Title和 Text 域寻找与我们的访问者所提交的文本查询字符串相匹配的内容。这个字符串的搜索用VB InStr声明来实现。如果找到了匹配,并且我们在导航分界线的范围之内,就将标题和URL放置在动态字符串数列里。

  查看导航分界线限制实质上是从记录的子集中选择一个标题和UR列表L。然后,用户就可以用"next"来访问标题的下一个列表或用"back" 来访问已经看过的列表中的前一个。实际上就是在向浏览器进行显示时,限制从数据库搜索返回的文本数量。

  现在我们的数据库中充满了从SQL声明中提取出来的记录(如果你在SQL 声明中使用了WHERE 过滤器的话就可能是一个有限数量的记录)。然后用InStr 声明在每一个返回记录中搜索一个匹配。这就在SQL声明返回的数据库记录中创建了一个虚拟子集。如果这个子集比我们将要显示的大的话,就不将它存储在VB变量中。相反我们选择我们子集的另一个子集在web 页面上显示。我们将数据库域的变量分配限制在最后的子集中,从而节省系统资源并提高运行速度。


  为了收集与搜索查询相匹配的web 站点标题和URL,需要创建两个字符串数列。一个保存标题,另一个保存URL。因为我们不知道字符串数列最终将保存多少条目,所以将它们设置为动态数列,每次增加一个新条目时调整它的大小。我们还在不止一个方法中需要这些数列。我们在初始的包装方法ShowSearchFrame 中创建它们,而不是将它们设置为全局变量。然后就将其作为自变量向每个需要的方法传递。我们还需要创建另外两个整形变量,与字符串变量一起传递。

Public Function ShowSearchFrame() As String
"''''' Local variables
Dim strHTML As String
Dim astrTitleArray() As String
Dim astrURLarray() As String
Dim intRecordsDisplayed As Integer
Dim intRecordsMatched As Integer
  声明了两个动态字符串变量和两个整形变量之后,就可以将它们传递给第二个方法。GetRecords 函数用数据库中的标题和URL的值来填充字符串数列。用数列中条目的个数设置intRecordsDisplayed 变量。intRecordsMatched 变量中保存与访问者查询字符串相匹配的记录数。我们使mintRecordsSearched 成为一个属性程序,可从ASP主机文件中读取。尽管我们不在这个组件中使用它,用户可能想知道共搜索到多少条记录。

  这是ShowSearchFrame 方法中的方法调用。记住,GetRecords函数是有条件地被调用的,当有来自用户的搜索查询,并且在SearchInputField方法之后:
If mobjRequest.QueryString("Src") = "" Then
"''''' No query was entered
strHTML = strHTML & "Enter a query"
Else
"[Search code here]
mintRecordsSearched = GetRecords(astrTitleArray(),astrURLarray(), _
intRecordsDisplayed, intRecordsMatched)
End If

GetRecords 函数
  现在我认为对声明和传递方法变量的特殊方法的细节已经介绍得够多了。对于GetRecords方法的解释将主要是此方法的一般功能方面。
  如果你使用的是VB5的话,就需要安装Service Pack 3 以获得对ADO 的引用来使这些代码工作。
  首先要注意在声明之后,代码有一个错误控制声明。
 Private Function GetRecords(astrTitleArray() As String, astrURLarray() As _
String, intRecordsDisplayed As Integer, intRecordsMatched As _
Integer) As Integer
"''''' LOCAL VARIABLES
Dim intCount As Integer
Dim intRecordsSearched As Integer
Dim strHTML As String
Dim strSearchHold As String
Dim strTitleHold As String
Dim strTextHold As String
Dim strURLhold As String
Dim recSearch As New ADODB.Recordset
Dim objCmd As New ADODB.Command
Dim objConn As New ADODB.Connection
Dim strSQL As String
On Error GoTo ErrorCode
  如果在使用数据库时发生了错误,控制就在函数结尾处传递一个ErrorCode 标志。
  下面的6个声明用Data Source Name(数据源名)打开到数据库的连接、启动一个处理、设置SQL字符串、将SQL 字符串分配给ADO命令对象、告诉命令对象等待一个SQL字符串、设置到命令对象的连接。
  在这里我使用的处理是为了示范,而且因为它在转入命令之前用二进制存储代码,这样可以提高数据库处理的速度:
"''''' Open the connection with a data source name
objConn.Open "SearchExampleDSN"
"''''' Begin the transaction (only for speed in this case)
objConn.BeginTrans
"''''' Store the SQL command in a string
strSQL = "SELECT Title, Text, URL FROM SearchTable"
"''''' Set the Command object to the SQL string
objCmd.CommandText = strSQL
"''''' indicate that the command is a SQL string (for speed)
objCmd.CommandType = adCmdText
"''''' Set the Connection object to the Command object
Set objCmd.ActiveConnection = objConn
  一旦我们用命令对象得到了所需要的打开记录集的连接,我们就设置:
"'''''Open Recordset with the above Command settings
recSearch.Open objCmd
  在继续进行搜索之前,需要验证我们确实向搜索返回了一些记录。如果没有返回记录,记录集就既是在开头也是在结尾:
If recSearch.EOF And recSearch.BOF Then
"''''No records were returned by the SQL
GetRecords = 0
  但是,如果SQL声明返回了一些记录,就需要设置一些变量确保记录集处在开头:
Else
"''''Initialize Procedural Variables
intRecordsMatched = 0
intRecordsDisplayed = 0
intRecordsSearched = 0
mintSearchStart = CInt(mobjRequest.QueryString("Start"))
If mintSearchStart = 0 Then mintSearchStart = 1
recSearch.MoveFirst
  随着不同的整数计数器的增加,我们在记录集中循环,并将数据库中的标题、文本、URL数据存储在本地字符串变量中:
"''''Loop through the recordset
Do While Not recSearch.EOF
"Increment the total number of records to be searched
intRecordsSearched = intRecordsSearched + 1
"''''' Store the database field data in temparary strings
strTitleHold = recSearch.Fields("Title")
strTextHold = recSearch.Fields("Text")
strURLhold = recSearch.Fields("URL")
  连接文本和标题字符串后,从头至尾搜索字符串,首先将字符串变成大写字母,然后把我们的查询字符串片段也变成大写字母进行搜索:
"''''Concatenate the Title and URL into a String to Search
strSearchHold = strTitleHold & strTextHold
"''''' Determine if the database record meets the visitors query
If InStr(UCase(strSearchHold), _
UCase(mobjRequest.QueryString("Src"))) Then
"''''Increment the number of records that match the visitor"s query
intRecordsMatched = intRecordsMatched + 1
  如果我们的记录数据包含了查询字符串,就必须要确定它是否在导航列表界限范围内。这是一点技巧,
"Determine if records are within Start/Stop display range
If intRecordsMatched >= mintSearchStart And _
intRecordsDisplayed < mintMaxSearchReturn Then
  这个声明使用了mintSearchStart 变量,导航条把它作为一个查询字符串来获取,然后查看一下列表页是否达到了它的最大容量,而这个最大容量是由mintMaxSearchReturn变量决定的。如果通过了测试,就将控制传递给动态数列声明。
  每次传递动态数列保存内容之后,调整其大小。
"''''Increment number of records to display
intRecordsDisplayed = intRecordsDisplayed + 1
"''''Store Title in title array
ReDim Preserve astrTitleArray(intRecordsDisplayed)
astrTitleArray(intRecordsDisplayed) = strTitleHold
"''''Store URL in URL array
Redim Preserve astrURLArray(intRecordsDisplayed)
astrURLArray(intRecordsDisplayed) = strURLHold
End If
End If

下面我们移到下一个记录,重新开始:
"''''Move to next record
recSearch.MoveNext
Loop
End If
  因为这两个字符串数列是由引用传递的(VB的默认状态),在这个方法中所做的任何修改都会被保存。在结束之前,我们提交处理,关闭数据库连接,进行一些整理工作:
"------ Commit & Close
objConn.CommitTrans
objConn.Close
Set research = Nothing
Set objConn = Nothing
Set objCmd = Nothing

  最后要增加一个错误句柄,设置回到方法开头的方法:
Exit Function
ErrorCode:
objConn.RollbackTrans
strHTML = "Error in Accessing Database File< BR >< BR >"
strHTML = strHTML & "Search::GetRecords< BR >"
strHTML = strHTML & Err.Description & "< BR >"
strHTML = strHTML & strSQL
"''''' Write the error message to the web page
mobjResponse.Write (strHTML)
"''''' Send back a negative one to indicate that there was an error
GetRecords = -1
End Function

  在这里我们将数据库代码开始时的处理反转过来,向浏览器写一个HTML错误信息,然后返回一个-1,作为一个错误代码通知调用的方法有错误发生。

回到ShowSearchFrame 方法第三部分
  回到我们的调用方法,查看一下如果返回了-1,然后退出函数,如果是这种情况,-1就表明一个
数据库错误。不然的话,就转到其它方法。

Else
"[Search code here]
intRecordsSearched = GetRecords(strTitleArray(), strURLarray(), _
intRecordsDisplayed, intRecordsMatched)
"''''Exit of a database error occurred
If mintRecordsSearched = -1 Then Exit Function
End If
  现在确定至少返回了一个记录之后,就可以创建导航条了:
"''''Exit of a database error occurred
If intRecordsSearched = -1 Then Exit Function
"===== NARVIGATION BAR
If Not intRecordsDisplayed = 0 Then
strHTML = strHTML & NavigationBar(intRecordsMatched, _
intRecordsDisplayed) & "< BR >< BR >"
End If
End If
  这里我们调用了NavigationBar方法,该方法有两个自变量,保存与搜索查询字符串相匹配的记录数和标题、URL数列中的条目数。NavigationBar 方法返回一个字符串,其中是在浏览器上显示导航条的HTML代码。

NavigationBar 方法
  用这个方法生成组成导航条的三个独立部分。



  NavigationBar 方法有三个部分,每个用来生成上图的一部分。它接收匹配的记录数加上显示的记录数作为自变量:
Private Function NavigationBar(intRecordsMatched As Integer, _
intRecordsDisplayed As Integer)
Dim strHTML As String
  back按钮用来回到条目中的上一个列表。它发送一个查询字符串(Start) ,其中存储着上一个列表的第一条:
"''''Navigation Back
If mintSearchStart > 1 Then
strHTML = strHTML & "< A HREF=""" & mstrSearchFrameURL & "?Src=" & _
mobjServer.URLEncode(mobjRequest.QueryString("Src")) & _
"&Start=" & (mintSearchStart - (mintMaxSearchReturn)) & _
""" >back< /A > "
End If
  注意要被发送回组件包含在文本域中的搜索查询字符串编码为:
Server.URLEncode(mobjRequest.QueryString("Src"))
  如果不使用URLEncode ,浏览器就不能显示或发送有一定字符的查询字符串。
  然后需要计算列表中的条目数和找到了多少条目:
"'''''Naviagation numbers
strHTML = strHTML & " " & CStr(mintSearchStart) & "-" & _
mintSearchStart + (intRecordsDisplayed - 1) & " of " & _
CStr(intRecordsMatched) & " "

  next 按钮与 back按钮工作方法相同但方向相反:
"''''Navigation Next
If mintSearchStart + (intRecordsDisplayed - 1) < intRecordsMatched Then
strHTML = strHTML & " < A HREF=""" & mstrSearchFrameURL & "?Src=" & _
mobjServer.URLEncode(mobjRequest.QueryString("Src")) & _
"&Start=" & (mintSearchStart + intRecordsDisplayed) & _
""" >next< /A >"
End If

  最后将字符串自变量返回到调用代码中:
"''''Send Back the composed HTML code
NavigationBar = strHTML
End Function

回到ShowSearchFrame 方法第四部分
  最后我们要显示与查询字符串相匹配的URL的列表。这就需要调用我们的第四个私用方法ListRecords :
"===== Narvigation bar
If Not intRecordsDisplayed = 0 Then
strHTML = strHTML & NavigationBar(intRecordsMatched, _
intRecordsDisplayed) & "< BR >< BR >"
End If
"===== URL Title list
strHTML = strHTML & ListRecords(astrTitleArray(), astrURLarray(), _
intRecordsDisplayed)
End If

ListRecords 方法
  本方法接受两个字符串数列,以及它们保存的条目数作为自变量:
Private Function ListRecords(astrTitleArray() As String, _
astrURLArray() As String, _
intRecordsDisplayed As Integer)
Dim strHTML As String
Dim intCount As Integer

  首先要验证存在可以列出的条目,在格式化我们的HTML代码的时候在字符串数列中循环。在这种情况下,我们使用条目字符串作为可视文本,将URL字符串数列作为HREF 源:
If intRecordsDisplayed = 0 Then
"''''No records where found for this query
strHTML = strHTML & "Query not found"
Else
"''''List search titles with URL link results
For intCount = 1 to intRecordsDisplayed
strHTML = strHTML & "< A HREF=""" & astrURLarray(intCount) & """" _
& "TARGET=""" & mstrResultFrameName & """ >" & _
astrTitleArray(intCount) & "< /A >< BR >"
Next
End If

  注意以mstrResultFrameName 变量给TARGET 赋值。回想起来这个变量可以从ASP主机文件设置。它决定在哪个画面中显示HREF:
  然后所需要做的就是将HTML代码返回字符串变量中:
"'''''Send back HTML code of the Title/URL list
ListRecords = strHTML
End Function

回到ShowSearchFrame 方法第五部分
  实际上我们已经完成了这个方法,但是我还是想确保我们在同一页上,下面是本方法的完整清单:
Public Function ShowSearchFrame() As String
"''''' Local variables
Dim strHTML As String
Dim astrTitleArray() As String
Dim astrURLArray() As String
Dim intRecordsDisplayed As Integer
Dim intRecordsMatched As Integer
"===== Search input field
strHTML = SearchInputField
If mobjRequest.QueryString("Src") = "" Then
"''''' No query was entered
strHTML = strHTML & "Enter a query"
Else
"[Search code here]
mintRecordsSearched = GetRecords(astrTitleArray(), astrURLArray(), _
intRecordsDisplayed, intRecordsMatched)
"''''Exit of a database error occurred
If mintRecordsSearched = -1 Then Exit Function
"===== NARVIGATION BAR
If Not intRecordsDisplayed = 0 Then
strHTML = strHTML & NavigationBar(intRecordsMatched, _
intRecordsDisplayed) & "< BR >< BR >"
End If
"===== URL Title list
strHTML = strHTML & ListRecords(astrTitleArray(), astrURLArray(), _
intRecordsDisplayed)
End If
"''''' Send html code to IIS for delivery to visitors browser
mobjResponse.Write (strHTML)
End Function
 这就是全部了。现在可以编译DLL,继续下一步了。

进一步超越基础

  我使这些代码尽可能地简单,只有几个数据库处理和属性程序这些必需的内容。但是为了增强代码功能,还有许多可以做。

  能够想到并且也很好玩的一个功能是缩短构成列表的标题。尽管将标题列表放在一个表格内可以改善可视界面,但是过长的标题会削弱列表的可读性。以下是进入ListRecords 方法缩短长标题的程序,它在标题结尾处加3个句号,表明它被删节了:
Dim strTemp As String
strTemp = strTitleArray(intCount)
If Not Trim(Len(strTemp)) = intListTitleMax Then
strTemp = Trim(Left(strTemp, intListTitleMax))
If Trim(Len(strTemp)) = intListTitleMax Then
strTemp = strTemp & "..."
End If
End If
  以上代码将标题中超过一定字符长度的部分切断。字符长度是intListTitleMax 的值。
  另外一个增加包括了在指针窗口中查看web 站点描述的选择。在一个非画面版本中,站点可以在一个空白浏览器中启动,以避免那些要求顶部画面的浏览器、用户粘贴新站点的能力、站点类别过滤、以字母或搜索的匹配程度为顺序进行列表等等。

在ASP下重新编译DLL

  当我们第一次将组件存为DLL文件时,VB创建文件并在系统上注册它时是没有问题的。但是在ASP页上使用过DLL对象之后,如果不完全关掉ASP系统的话,VB就不允许重新编译DLL。
  用Windows 9x 你可以关闭ASP、重新编译DLL并用以下指令启动ASP:
pws /sto p
pws /star t
在NT系统,用下列指令:
net stop iisadmin net start w3sv c
我不能肯定你怎么样,但是当基本的代码完成后,我对这个增强功能的过程很感兴趣,因为我知道,即使它们不能正常工作,我的基本代码部分是不受影响的。从这一点上来说,很难让这个程序样本保留这个样子而不去做那些增强工作。所以如果你有兴趣,随便怎么使用这个程序都可以。

点击本处下载本文相关链接。