摘要:本章介绍了 ConsolidatedRetail.com 应用程序的功能,同时提供了伪代码和实际代码示例来详细说明处理流程。
现在,您对解决方案的各个组成部分已经有了总体了解,可以开始检查站点提供的具体功能了。本章探讨了解决方案的以下几个方面,并着重讨论了为每个功能区编写代码时所面临的内在问题:
阅读本章的各个小节时,您将了解具体功能的实现方法,以及如何在您自己的企业对消费者 (B2C) 解决方案中重新利用 ConsolidatedRetail.com 应用程序的部分或全部内容。
ConsolidatedRetail.com 站点中的表示服务由 XSLISAPI 过滤器提供。面临的主要开发问题在于:如何在每个 PASP 脚本中生成合适的 XML,以及如何创建合适的 XSL 样式表将 XML 表示为 HTML(或其它表示格式)。
在 ConsolidatedRetail.com 站点中,每个 PASP 文件都生成格式相同的 XML 文档。这是通过在站点的每一页中加入对 Common.asp 文件的 Include 引用,并调用其 PageStart 过程实现的。
Common.asp 文件中的 PageStart 过程包含以下代码:
Sub PageStart(strPageNameWithExtension)
' 除去 pasp 文件扩展名
Dim strPageName
strPageName = Trim(Left(strPageNameWithExtension, _
InstrRev(strPageNameWithExtension, _
".") - 1))
'XML 标头
Response.Write _
"<?xml-stylesheet type=""text/xsl" _
" server-config=""" & _
strPageName & "-Config.xml"" href=""" & _
strPageName & "-IE5.xsl""?>"
'页元素的根。
'结束标记在“PageEnd”子例程中生成。
Response.Write "<page pagename="" _
" & strPageNameWithExtension & """>" & vbcrlf
End Sub
此过程为 PASP 文件将要生成的文档创建 XML 标头。在每个 PASP 脚本的末尾,将调用 PageEnd 过程来生成 <page> 的结束标记并完成 XML 文档。下面列出了 PageEnd 过程中的相关代码:
Sub PageEnd()
'为了表达得更为清楚,这里省略了有关目录、注销和错误消息的代码
Call XMLEndTag("page")
End Sub
XMLEndTag 过程是 Common.asp 中的众多 XML Helper 过程之一。(有关详细信息,请参考本章中的“XML Helper 过程”一节。)它使用以下代码生成 XML 结束标记:
Sub XMLEndTag(strTagName)
Response.Write mc_strStartTag & mc_strForwardSlashTag & _
Lcase (Replace(strTagName, mc_strBlank,_
mc_strUnderScore)) & _
mc_strEndTag & vbCrLf
End Sub
使用 PageStart 和 PageEnd 过程生成的 XML 文档类似于以下代码示例:
<?xml-stylesheet type="text/xsl"
server-config="filename-Config.xml"
href="filename-IE5.xsl"?>
<page pagename="filename.pasp">
<!-- XML 内容 -->
</page>
除了上面提到的 XMLEndTag 过程之外,Common.asp 还包含多个与 XML 有关的实用程序例程。使用这些过程可以生成一些标记,这些标记将用于 ConsolidatedRetail.com 站点中的 PASP 页所生成的 XML 文档。
虽然在许多情况下,对所需的 XML 字符(例如 "<" 和 ">",请注意不包括引号)进行硬编码更为有效,但是,在各种 XML 生成过程中,大量 XML 字符常量均在 Common.asp 中进行声明。这样可增加代码的可读性,同时有助于调试。这些常量是:
Const mc_strStartTag = "<"
Const mc_strEndTag = ">"
Const mc_strForwardSlashTag = "/"
Const mc_strUnderScore = "_"
Const mc_strBlank = " "
以下 XML 生成过程使用了这些常量:
此外,还有许多其它过程(统称为 xxxWithDsplyNm)用于生成包含 displayname 属性的 XML 标记,例如:
<f_name displayname="First Name">Joe</f_name>
标记的显示名称基于标记名,从 CatalogDefinitionProperties 应用程序级字典对象变量中检索。
要全面了解 XML Helper 过程提供的功能,请查看 Common.asp 中的源代码。
如前所述,每页都有一个相关联的“文件名”-Config.xml 文件,该文件列出了要应用于每种客户端浏览器或设备类型的 XSL 样式表。在本实现方案中,只支持 Microsoft® Internet Explorer 5.5,尽管可以创建替代样式表并将添加到站点来解决这一问题。
每个 PASP 页的 XSL 文件被命名为“页名”-IE5.xsl(其中“页名”是 PASP 页的非版本特定名称),该文件包含该页数据专用的 XSL 代码。但是,为了使站点保持统一的外观,需要在一个单独的 XSL 文件(名为 UI_layout-IE5.xsl)中定义所有页的公共用户界面 (UI) 元素。该文件存储在 Include 目录下。每一页专用的 XSL 文件使用 XSL Include 指令将 UI_layout-IE5.xsl 中的表示逻辑合并到当前页的呈现形式中,如以下代码段所示:
<xsl:include href="include\UI_layout-IE5.xsl"/>
UI_layout-IE5.xsl 文件包含几个模板。page 模板将应用于每个 PASP 页所生成的 XML 文档中的 <page> 元素。该模板引用 Stylesheet.css 级联样式表,并创建一个包含五行的 HTML 表,总体页将基于该 HTML 表。系统调用 UI_layout-IE5.xsl 文件中的其它模板来填充这些行。
表的第一行包含对 pageheader 模板的调用(该模板在后面的脚本中定义)。该模板呈现页首标题,包括链接到“购物篮”、“配置文件”和“主页”的图象。
第二行用于创建距第三行的间距,而第三行包含对 main 模板的调用。该模板包含呈现页左侧菜单面板(包括搜索表单)所需的逻辑。main 模板依次调用 getCatalogsForUser 模板、Exceptions 模板以及 advertising 或 profilemenu 模板(这取决于是否存在 advertising 或 profilemenu XML 元素)。
表的第四行与第二行类似,用于创建距第五行的间距;第五行则调用了 pagefooter 模板。该模板呈现页的底部面板,包括版权声明。
UI_layout-IE5.xsl 中的 page 模板如以下代码所示。可在 UI_layout-IE5.xsl 中查看 pageheader、main、pagefooter 和其它用于呈现站点中的页的模板。
<?xml version="1.0" ?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:template match="*|/">
<xsl:apply-templates/></xsl:template>
<xsl:template match="text()|@*"><xsl:value-of select="."/></xsl:template>
<xsl:template match="page">
<html>
<head>
<title>ConsolidatedRetail.com</title>
<link rel="stylesheet" type="text/css" href="stylesheet.css"/>
</head>
<body bgcolor="#ceb6d5" leftmargin="0" marginwidth="0" topmargin="0" marginheight="0" onload="Focus()">
<table width="100%" cellpadding="0" cellspacing="0" border="0">
<tr>
<td>
<xsl:call-template name="pageheader"/>
</td>
</tr>
<tr>
<td height="10"/>
</tr>
<tr>
<td>
<xsl:call-template name="main"/>
</td>
</tr>
<tr>
<td height="10"/>
</tr>
<tr>
<td>
<xsl:call-template name="pagefooter"/>
</td>
</tr>
</table>
</body>
</html>
</xsl:template>
<!-- 为了表达得更为清楚,此处省略了其它模板的代码 -->
</xsl:stylesheet>
您可以检查 Index.pasp 页,以查看站点中各个页之间的组合关系。Index.pasp 页是站点的默认页(即主页)。Index.pasp 包含执行以下任务的代码:
以下代码执行上述任务:
<!--#include file = "include/Site_Const.asp" -->
<!--#include file = "include/Common.asp"-->
<%
Const mc_strPageName = "Index.pasp"
Sub Main()
Call PageStart(mc_strPageName)
XMLEmptyTag(mc_strAdvertisingMenu)
Call PageEnd()
End Sub
Call Main()
%>
此代码生成以下 XML 文档:
<?xml-stylesheet type="text/xsl"
server-config="Index-Config.xml"
href="Index-IE5.xsl"?>
<page pagename="Index.pasp">
<advertising/>
<getcatalogsforuser>
<selectiontitle>浏览目录:</selectiontitle>
<catalog>
<catalogname>书</catalogname>
</catalog>
<catalog>
<catalogname>硬件</catalogname>
</catalog>
</getcatalogsforuser>
<profile/>
<exceptions></exceptions>
</page>
这些 XML 代码将被传递到 XSLISAPI 过滤器,该过滤器确定在 Index-Config.xml 中指定相应的样式表。XSLISAPI 过滤器检查 HTTP 请求标头信息以确定浏览器的类型和版本。然后,对浏览器信息与 Index-Config.xml 中的相应样式表进行比较。如果未找到匹配条目,则应用默认样式表 (Index-IE5.xsl)。
Index-Config.xml 类似于以下代码:
<?xml version="1.0" ?>
<server-styles-config>
<!-- 对于 HDML 3.0 浏览器 -->
<device target-markup="HDML 3.0">
<stylesheet href="Index-HDML3.xsl"/>
</device>
<!-- 对于 WML 1.1 浏览器 -->
<device target-markup="WML1.1">
<stylesheet href="Index-WML11.xsl"/>
</device>
<!-- 对于 IE 4.0 浏览器 -->
<device browser="IE" version="4.0">
<stylesheet href="Index-IE5.xsl"/>
</device>
<!-- 对于 IE 5.0 浏览器 -->
<device browser="IE" version="5.0">
<stylesheet href="Index-IE5.xsl"/>
</device>
<!-- 对于 MME 浏览器 -->
<device browser="MME">
<stylesheet href="Index-WML11.xsl"/>
</device>
</server-styles-config>
从 Internet Explorer 5.0 浏览器收到请求时,使用 Index-IE5.xsl 样式表将页作为 HTML 呈现。(Index-Config.xml 文件中的其它条目只是作为例证;ConsolidatedRetail.com 站点中并不提供相应的样式表。)
Index-IE5.xsl 类似于以下代码:
<?xml version="1.0" ?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:include href="include\UI_layout-IE5.xsl"/>
<xsl:template match="*|/"><xsl:apply-templates/></xsl:template>
<xsl:template match="text()|@*"><xsl:value-of select="."/></xsl:template>
<xsl:template name="pageleft"/>
<xsl:template name="pagecenter">
<script language="JavaScript">
<![CDATA[
function Focus(){
document.formSearch.txtSearchPhrase.focus()
}
]]>
</script>
<table width="100%" cellpadding="0" cellspacing="0" border="0" bgcolor="#ffffff">
<tr>
<td width="11">
<img src="/china/msdn/images/spacer.gif" width="1" height="1" border="0"/>
</td>
<td valign="top" class="content-text">
<table width="100%" cellpadding="0" cellspacing="0" border="0">
<tr>
<td valign="top" colspan="3" class="content-text">
<p class="headline-text-purple">欢迎来到 ConsolidatedRetail.com</p>
<p>欢迎来到本站点,您在这里可以方便地购买到所需的任何商品。</p>
<p align="center"><a href="Registration.pasp"><img src="/china/msdn/images/home_freeshipping.gif" width="271" height="46" vspace="5" border="0"/></a><br/><br/></p>
</td>
</tr>
<tr>
<td valign="top" class="content-text">
<p><b> </b>
<br/> <img src="/china/msdn/images/spacer.gif" width="200" height="1" border="0"/></p>
</td>
<td width="1" bgcolor="#ceb6d5">
<img src="/china/msdn/images/spacer.gif" width="1" height="1" border="0"/>
</td>
<td width="125" align="right" valign="top" class="content-text">
<img src="/china/msdn/images/giftregistries.gif" width="118" height="30" border="0"/><br/>
<p>在不久的将来就可实现此功能。</p>
</td>
</tr>
</table>
</td>
<td width="11">
<img src="/china/msdn/images/spacer.gif" width="1" height="1" border="0"/>
</td>
</tr>
</table>
</xsl:template>
<xsl:template name="pageright"/>
</xsl:stylesheet>
此样式表包含上文介绍的 UI_layout-IE5.xsl 样式表。UI_layout-IE5.xsl 样式表用于呈现页的公共 UI 元素。在此示例中,XML 文档包含 <advertising/> 标记。因此,UI_layout-IE5.xsl 也使用 advertising 模板来呈现页的右侧面板。所得到的索引页类似于图 7-1。
图 7-1:索引页
ConsolidatedRetail.com 站点按照上文所述的方法呈现 PASP 页。使用这种方法(在 PASP 文件中生成 XML 内容,在 XSL 样式表中定义表示形式),您可以轻松更改用于显示页面的样式表,而不会影响 PASP 文件中的业务逻辑。因此,这个解决方案十分灵活,便于重用。
Microsoft® Commerce Server 2000 支持可扩展的配置文件系统,可以存储大量用户数据。该用户数据(或用户配置文件)可以包含收货人地址和联系人信息。客户可以使用这些配置文件来存储个人数据,其中包括收货人地址和联系人信息,这样他们就不必在每次访问站点时都要重新输入此类信息。公司则可以将配置文件信息用于商业分析和开展有针对性的广告宣传活动。
要创建和维护配置文件,用户必须登录到站点并在站点上注册。这将创建一个唯一用户 ID,用于标识用户以及从数据库中检索相应的配置文件信息。用户的 ID 存储在客户端浏览器上的 cookie 中。如果用户未登录,cookie 将包含与匿名用户相关的用户 ID,此时配置文件信息不可用。当用户登录时,将检索相应的用户 ID 并将其写入 cookie,这样,在后面的会话过程中就能够使用配置文件信息。
请注意,此站点要求将浏览器配置为允许 cookie。如果用户使用配置为允许每会话 cookie(即不存储到用户硬盘上的 cookie)的浏览器来访问此站点,而不存储 cookie,则用户将能够登录到站点,也能使用站点,但是不能将产品放到购物篮中。如果根本不允许 cookie,则用户将不能访问此站点。
用户可以使用 Registration.pasp 页注册到此站点。使用 Registration-IE5.xsl 样式表呈现该页时,该页包含一个向自身发回数据的表单。Registration.pasp 页包含执行以下任务的代码:
strPageMode = _
Application("MSCSAppFrameWork").RequestString( _
"Mode", Null, , , True, True, 0, Null)
strProcessingAction = _
Application("MSCSAppFrameWork").RequestString( _
"ProcessAction", Null, , , True, True, 0, Null)
Mode 通常为空,这表示用户已成功注册,应重定向到帐户管理页 (Acct.pasp)。在某些情况下,Mode 包含其它页的名称,应将用户重定向到该页。例如,如果匿名用户在注册前将产品添加到了购物篮,您可能希望在用户注册后将用户重定向到“结帐”页。
ProcessAction 参数用于确定用户是从站点中的另一页进入注册页的,还是从注册页本身进入的。如果是前一种情况,将会呈现注册表单;如果是后一种情况,则会使用注册表单的内容来注册用户。如果 ProcessAction 参数是 EditUserObject,则可以使用该页上表单中的数据来注册用户。
Set objMSCSProfile = _
Application("MSCSProfileService").GetProfile(strUserName, _
_ mc_strUserObject, blnReturnCode)
If Not (objMSCSProfile Is Nothing) Then
Call AddException(m_varrExceptions, 1, _
"用户名已存在。", mc_strPageName)
Set objMSCSProfile = Nothing
strUserID = GenerateGUID()
Set objMSCSProfile = _
Application("MSCSProfileService").CreateProfile( _
strUserName, mc_strUserObject)
objMSCSProfile.Fields(mc_strGeneralInfo).Value _
(mc_strUser_ID) = cstr(strUserID)
objMSCSProfile.Fields(mc_strAccountInfo).Value _
(mc_strAccount_Status) = CInt(1)
objMSCSProfile.Fields(mc_strGeneralInfo).Value _
("user_type") = strUserType
objMSCSProfile.Fields(mc_strGeneralInfo).Value _
(mc_strUser_Security_Password) = strPassword
objMSCSProfile.Update
Set objMSCSProfile = Nothing
PutUserObject = True
该函数运行完毕后,新用户就注册到了站点数据库。
If blnRegistered Then
If IsNull(strPageMode) Then
Response.redirect "Acct.pasp?Mode=" & strPageMode
Else
Response.Redirect strPageMode & "?Mode=" & strPageMode
End If
End If
已注册的用户必须登录并通过身份验证,才能访问其配置文件信息。如上文中所述,Registration.pasp 中的代码将使新注册的用户自动登录。但是,用户再次登录时,必须提供用户名和口令才能通过身份验证。
注意:在本应用程序示例中,登录详细信息是使用 HTTP 协议以纯文本格式传递的。但在实际站点中,应使用安全超文本传输协议 (HTTPS) 对安全凭据进行加密。
用户可以使用 Login.pasp 页登录。与 Registration.pasp 一样,此页向自身发送数据并使用 Profile.asp 的功能来处理数据。Login.pasp 页包含执行以下任务的代码:
blnLoginSuccessful = Login(strUserName, strPassword)
Set objMSCSAuthMgr = _
Server.CreateObject(mc_strAuthManager)
Call objMSCSAuthMgr.Initialize _
(Application("MSCSDictConfig").s_SiteName)
If objMSCSAuthMgr.IsAuthenticated Then
call objMSCSAuthMgr.SetAuthTicket _
("", blnCookieSupport, _
Application("MSCSDictConfig")._
i_FormLoginTimeOut)
End If
Set objMSCSProfileObject = Application("MSCSProfileService").GetProfile(strUserName,
mc_strUserObject, blnReturnCode)
strProfilePassword = _
objMSCSProfileObject(mc_strGeneralInfo).Value _
(mc_strUser_Security_Password)
strUserID = objMSCSProfileObject(mc_strGeneralInfo). _
Value (mc_strUser_ID)
If CStr(strProfilePassword) = CStr(strPassword) Then
...
strProfileUserID = _
objMSCSAuthMgr.GetUserID(mc_bytProfileTicketType)
Call objMSCSAuthMgr.SetAuthTicket _
(strUserID, blnCookieSupport, _
Application("MSCSDictConfig"). _
i_FormLoginTimeOut)
Call objMSCSAuthMgr.SetUserID(mc_bytAuthTicketType, _
strUserID)
Call objMSCSAuthMgr.SetUserID(mc_bytProfileTicketType, "")
Call objMSCSAuthMgr.SetProfileTicket("", blnCookieSupport)
If (Len(strProfileUserID) > 0) Then
' 从匿名会话获取配置文件
' 对象
Set objMSCSUnRegProfileObject = Application( _
"MSCSProfileService").GetProfilebykey( _
mc_strUser_ID, strProfileUserID, _
mc_strUserObject, blnReturnCode)
' 如果返回匹配的匿名配置文件
If Not (objMSCSUnRegProfileObject Is Nothing) Then
' 将购物篮内容从匿名 ID 传输到已注册的
' ID
Call MoveBasketItems(strProfileuserid, strUserID)
' 从配置文件存储区中删除匿名配置文件
Call Application("MSCSProfileService"). _
DeleteProfileByKey(mc_strUser_ID, _
strProfileUserID, mc_strUserObject)
End If
Set objMSCSUnRegProfileObject = Nothing
End if
' 返回表示成功登录的值
Logon = True
在之后的会话过程中,用户为每个请求提供身份验证 cookie,这样,代码就可以检索到用户的配置文件信息。
此站点允许用户在多个页上查看和编辑其配置文件信息。由于各页均使用类似的代码更新用户配置文件中的字段,因此本章只详细讨论 UserProfile.pasp 页。您也可以查看 EditAddressBook.pasp 和 ChangePasswd.pasp 中的代码,它们执行类似的功能。
UserProfile.pasp 页包含一个表单,用户可在该表单中查看和更改名字、姓氏、电子邮件地址和电话号码的配置文件值。其设计思路类似于上文所述的 Registration.pasp 页。UserProfile.pasp 页包含执行以下任务的代码:
Function GetUserID()
Dim objMSCSAuthMgr '身份验证管理器
Dim strUser_ID ' 从身份验证管理器中
' 检索到的用户 ID
' 将 AuthManager 对象实例化和初始化。
Set objMSCSAuthMgr = _
Server.CreateObject(mc_strAuthManager)
Call objMSCSAuthMgr.Initialize _
(Application("MSCSDictConfig").s_SiteName)
strUser_ID = Null
' 如果用户已通过身份验证且身份验证单未
' 超时
If objMSCSAuthMgr.IsAuthenticated Then
' 获取进行身份验证时所用的唯一登录 ID。
strUser_ID = _
objMSCSAuthMgr.GetUserID(mc_bytAuthTicketType)
' 否则,如果用户以匿名方式进行浏览
Else
' 获取配置文件用户 ID(即 GUID)。
' 如果 getuserid 方法返回空字符串
' 将该字符串转换为 Null
strUser_ID = _
objMSCSAuthMgr._
GetUserID(mc_bytProfileTicketType)
If not isNull(strUser_ID) Then
If len(trim(strUser_ID)) = 0 Then
strUser_ID = Null
End If
End If
End If
' 返回新的 GUID 或当前已通过身份验证的
' 用户名
GetUserID = strUser_ID
Set objMSCSAuthMgr = Nothing
End Function
Set objMSCSProfile = Application("MSCSProfileService").GetProfilebyKey( _
mc_strUser_ID, strUserID, mc_strUserObject, blnReturnCode)
objMSCSProfile.Fields(mc_strGeneralInfo).Value( _
mc_strFirst_Name) = strFirstName
objMSCSProfile.Fields(mc_strGeneralInfo).Value( _
mc_strLast_Name) = strLastName
objMSCSProfile.Fields(mc_strGeneralInfo).Value( _
mc_strEmail_Address)= strEmailAddress
objMSCSProfile.Fields(mc_strGeneralInfo).Value( _
mc_strTel_Number) = strTelNumber
objMSCSProfile.Fields(mc_strGeneralInfo).Value( _
mc_strWork_Number) = strWorkNumber
objMSCSProfile.Fields(mc_strGeneralInfo).Value( _
mc_strWork_Extension) = strWorkExtension
objMSCSProfile.Fields(mc_strProfileSystem).Value( _
mc_strDate_Last_Changed) = Now
objMSCSProfile.Update
Sub GetUserObjectXML()
Dim objMSCSProfile
Dim strUserID
Dim Field
Dim Group
Dim blnReturnCode
Const c_strProfile = "userobject"
strUserID = GetUserID
If Not IsNull(strUserID) Then
' 使用指定架构初始化配置文件服务并连接到配置文件存储区
If Not Application("MSCSProfileService") Is Nothing Then
' 使用配置文件服务检索用户的配置文件对象,并将该对象指派
' 给函数的返回值
Set objMSCSProfile = Application("MSCSProfileService") _
.GetProfilebyKey(mc_strUser_ID, GetUserID, _
mc_strUserObject, blnReturnCode)
If Not objMSCSProfile Is Nothing Then
Call XMLBegTag(c_strProfile)
For Each Group In objMSCSProfile.Fields
Call XMLBegTag(Group.Name)
For Each Field In Group.value
Call XMLTag(Field.Name, Field.Value)
Next
Call XMLEndTag(Group.Name)
Next
Call XMLEndTag(c_strProfile)
End If
Set objMSCSProfile = Nothing
End If
End If
End Sub
此过程将生成以下 XML 输出:
<?xml-stylesheet type="text/xsl"
server-config="UserProfile-Config.xml"
href="UserProfile-IE5.xsl"?>
<page pagename="UserProfile.pasp">
<profilemenu/>
<pagemode/>
<userobject>
<accountinfo>
<org_id/>
<account_status>1</account_status>
<user_catalog_set/>
<date_registered/>
</accountinfo>
<advertising>
<campaign_history/>
</advertising>
<businessdesk>
<partner_desk_role/>
</businessdesk>
<generalinfo>
<user_id>
{8A7D56E9-DABA-499A-96B8-1F8DD93D032B}
</user_id>
<logon_name>Kim</logon_name>
<user_security_password>
password
</user_security_password>
<email_address>[email protected]</email_address>
<user_type>1</user_type>
<user_title/>
<last_name></last_name>
<first_name></first_name>
<tel_number></tel_number>
<tel_extension/>
<fax_number></fax_number>
<fax_extension></fax_extension>
<user_id_changed_by/>
</generalinfo>
<profilesystem>
<date_last_changed>
2/8/2001 11:57:13 PM
</date_last_changed>
<date_created>2/8/2001 11:57:13 PM</date_created>
</profilesystem>
</userobject>
<!—该页的其余内容由 Common.asp 生成 -->
<getcatalogsforuser>
<selectiontitle>浏览目录:</selectiontitle>
<catalog>
<catalogname>书籍</catalogname>
</catalog>
<catalog>
<catalogname>硬件</catalogname>
</catalog>
</getcatalogsforuser>
<profile auth="auth"/>
<exceptions></exceptions>
</page>
ConsolidatedRetail.com 站点提供了有效的配置文件功能,并演示了创建、检索和更新用户配置文件信息的基本方法。使用 Commerce Server Management Desk,您可以扩展此功能以创建自定义配置文件属性,并且可以使用配置文件信息为各个用户提供站点个性化服务。有关使用 Commerce Server 2000 配置文件功能的详细信息,请参考 Commerce Server 2000 文档。
为用户提供浏览产品目录的简单方法,是设计电子商务站点时最重要的设计目标之一。ConsolidatedRetail.com 站点通过以下三种方式实现这一目标:
当前用户相关目录集中的目录始终列在用户界面的左侧窗格中。这是通过在 Common.asp 的 PageEnd 过程中加入确定并显示可用目录的代码来实现的:
Set objCatalogSets = _
Server.CreateObject(mc_strCatalogSets)
Call objCatalogSets.Initialize _
(Application("MSCSDictConfig"). _
s_CatalogConnectionString, _
Application("MSCSDictConfig")._
s_TransactionsConnectionString)
Set rsCatalogs = objCatalogSets.GetCatalogsForUser( _
Application("MSCSProfileService"), GetUserID & "", _
GetDefaultCatalogSet)
Do While Not rsCatalogs.Eof
Call XMLBegTag(c_strCatalog)
Call getXMLFromRSwithdsplynm(rsCatalogs)
Call XMLEndTag(c_strCatalog)
rsCatalogs.MoveNext
Loop
<catalog>
<catalogname>书籍</catalogname>
</catalog>
<catalog>
<catalogname>硬件</catalogname>
</catalog>
ConsolidatedRetail.com 解决方案中的目录是以分层结构实现的。“书籍”和“硬件”这两个目录分别包含若干类别。“书籍”目录还包含一个子类别层。产品可以存储在目录的任何一层上。
ConsolidatedRetail.com 站点中用于浏览目录数据的页是 Category.pasp。可以在两种模式下使用该页:根级模式或类别模式。在根级模式下,该页从指定目录的根来检索产品和类别。在类别模式下,该页从指定的类别来检索产品、子类别和父类别。
该页包含执行以下任务的代码:
在使用 Commerce Server 目录对象检索目录信息前,该页使用以下代码来创建 <searchscope> 元素:
Call XMLTag(c_strSearchScope, strCatalogName)
UI_layout-IE5.xsl 样式表使用此元素将当前目录名传递到搜索功能,然后限定搜索范围。(本章稍后将对搜索功能进行详细讨论。)
实际目录数据是使用 Commerce Server 自动化对象的分层结构进行检索的。分层结构的顶层是 CatalogManager 对象,用于对目录系统进行所有程序访问。CatalogManager 对象包含一些 ProductCatalog 对象,这些对象代表站点中的目录。在 Category.pasp 中,MSCSCatalogManager 应用程序级变量的 GetCatalog 方法用于检索指定的目录,如以下代码段所示:
Set objMSCSPrdCat = Application("MSCSCatalogManager"). _
GetCatalog(strCatalogName)
使用 GetCatalogAttributes 方法,可以将 ProductCatalog 对象的属性作为 ADO 记录集进行检索。Category.pasp 中的代码使用此方法将记录集中的每一行传递到 Common.asp 中的 GetXMLFromRSWithDsplyNm 例程,该例程将行转换为 XML 格式:
Set rsProperties = _
objMSCSPrdCat.GetCatalogAttributes
If Not (rsProperties.Eof And rsProperties.Bof) Then
Call XMLBegTag(c_strGetCatalogAttributes)
Do While Not rsProperties.Eof
'获取记录集行的 xml 版本
Call GetXMLFromRSWithDsplyNm(rsProperties)
rsProperties.MoveNext
Loop
Call XMLEndTag(c_strGetCatalogAttributes)
End If
这将生成以下格式的 XML 代码段:
<getcatalogattributes>
<catalogname>书籍</catalogname>
<locale>8</locale>
<startdate>12/8/1999</startdate>
<enddate>12/8/2006</enddate>
<variantid>ISBN</variantid>
<productid>标题</productid>
<currency>USD</currency>
<weightmeasure>lbs</weightmeasure>
<catalogid>1</catalogid>
<customcatalog>False</customcatalog>
<freetextindexcreated>2/8/2001 11:56:22 PM</freetextindexcreated>
<producttableupdated>2/8/2001 11:53:37 PM</producttableupdated>
</getcatalogattributes>
也可以使用记录集对象来代表目录根中的类别。RootCategories 方法用于从 Category.pasp 中检索这些类别,如以下代码段所示(请注意,Fields 集合属性用于从记录集中检索指定的数据字段):
Set rsCategories = objMSCSPrdCat.RootCategories
'为了表达得更为清楚,此处省略了一些代码
Do While Not rsCategories.Eof
Call XMLBegTag(c_strRootCategory)
Call XMLTagWithDsplyNm("catalogname", objMSCSPrdCat.catalogname)
Call XMLTagWithDsplyNm("categoryname", _
rsCategories.fields("CategoryName").Value)
Call XMLEndTag(c_strRootCategory)
rsCategories.MoveNext
Loop
这将生成一个 XML 代码段,如下所示:
<rootcategory>
<catalogname>书籍</catalogname>
<categoryname>商业软件</categoryname>
</rootcategory>
<rootcategory>
<catalogname>书籍</catalogname>
<categoryname>开发工具</categoryname>
</rootcategory>
<rootcategory>
<catalogname>书籍</catalogname>
<categoryname>特色产品</categoryname>
</rootcategory>
...
与之类似,对于目录根中的产品,可以使用 RootProducts 方法对包含这些产品的记录集进行检索:
Set rsProducts = objMSCSPrdCat.RootProducts
为 rsProducts 记录集中的每个产品生成的 XML 类似于以下代码:
<book>
<oid>64</oid>
<definitionname>SDKBook</definitionname>
<cy_list_price displayname="价格">19.99</cy_list_price>
<originalprice displayname="购买价">19.99</originalprice>
<i_classtype>4</i_classtype>
<parentoid>-1</parentoid>
<productid>
Microsoft Age of Empires II: The Age of Kings: Inside Moves
</productid>
<variantid/>
<title displayname="标题">Microsoft Age of Empires II: The Age of Kings: Inside Moves</title>
<isbn displayname="ISBN">0-7356-0513-0</isbn>
<description>在令人激动的新版 Microsoft Age of Empires 中,您将掌握能够帮助您夺取胜利的所有关键性战略策略、技巧和计谋!MICROSOFT AGE OF EMPIRES II: AGE OF KINGS: INSIDE MOVES 向您展示在从罗马帝国灭亡到中世纪的数千年中如何为生存、富强而奋斗。</description>
<image_filename>boxshots/press/2388.gif</image_filename>
<image_height>120</image_height>
<image_width>120</image_width>
<author displayname="作者">Microsoft Corporation</author>
<name displayname="名称">Microsoft Age of Empires II: The Age of Kings: Inside Moves</name>
<pagecount displayname="页数">280</pagecount>
<producturl displayname="产品信息Url">a href=http://mspress.microsoft.com/prod/books/2388.htm target =_a http://mspress.microsoft.com/prod/books/2388.htm /a</producturl>
<publication_year displayname="出版年份">1999</publication_year>
<publisher displayname="出版者">Microsoft Press</publisher>
<reading_level displayname="阅读级别:">所有级别</reading_level>
<catalogname>书籍</catalogname>
</book>
如果代码需要下钻到更深的目录并检索其中某个类别的内容,可以使用 Category 对象。Category.pasp 页使用 Category 对象访问指定类别的内容。该对象使用 ProductCatalog 对象的 GetCategory 方法进行实例化,如以下代码段中所示:
Set objMSCSCategory = _
objMSCSPrdCat.GetCategory(strCategoryName)
您可以使用 Products 属性将类别中的产品作为记录集检索:
Set rsProducts = objMSCSCategory.products
Category 对象还提供 ChildCategories 属性来检索子类别的记录集,提供 ParentCategories 属性返回父类别的记录集。
无论它们在分层结构中的位置如何,您都可以使用 Commerce Server Business Desk 来创建产品目录中类别和产品间的关系。Category 对象提供 RelatedCategories 和 RelatedProducts 属性来检索包含相应内容的记录集。您在 Category.pasp 中可以看到有关如何使用这些对象的示例。
用于呈现目录数据的 Category-IE5.xsl 样式表会生成 HTML,这样,在用户单击特定产品的“获取详细资料”链接时,将请求 Product.pasp 页。该页将显示所选产品的特定数据。
Product.pasp 中的代码开头部分与 Category.pasp 很相似。该代码在请求字符串中检索目录、产品 ID 和可选的产品变量值。如果没有目录或产品 ID 参数,则将用户重定向到 Index.pasp。然后,代码调用 PageStart 开始为该页生成 XML。
使用 GetProduct 方法从目录对象检索 Commerce Server 产品对象时,代码变得有趣起来:
Set objMSCSPrd = objMSCSPrdCat.GetProduct(strProductID)
您可以使用 GetProductProperties 方法在记录集对象中检索产品的一些属性,如产品名和价格:
Set rsProduct = objMSCSPrd.GetProductProperties
Commerce Server 目录中的产品支持变体(如颜色或大小不同的产品)。您可以使用 Variants 属性将特定产品的变量列表作为记录集检索。此外,还可以使用 RelatedProducts 和 RelatedCategories 属性来检索任何相关的产品或类别。利用这些属性可以创建链接,获得交叉销售机会。Product.pasp 页中使用所有这些属性来为产品生成 XML 数据,下面列出了一段 XML 代码。然后使用 Product-IE5.xsl 样式表来呈现这些数据。
<getproduct>
<book>
<catalogname>书籍</catalogname>
<definitionname>SDKBook</definitionname>
<cy_list_price displayname="价格">19.99</cy_list_price>
<originalprice displayname="购买价">19.99</originalprice>
<i_classtype>4</i_classtype>
<productid>Microsoft Age of Empires II: The Age of Kings: Inside Moves</productid>
<variantid/>
<author displayname="作者">Microsoft Corporation</author>
<description>在令人激动的新版 Microsoft Age of Empires 中,您将掌握能够帮助您夺取胜利的所有关键性战略策略、技巧和计谋!MICROSOFT AGE OF EMPIRES II: AGE OF KINGS: INSIDE MOVES 向您展示在从罗马帝国灭亡到中世纪的数千年中如何为生存、富强而奋斗。</description>
<image_filename>boxshots/press/2388.gif</image_filename>
<image_height>120</image_height>
<image_width>120</image_width>
<isbn displayname="ISBN">0-7356-0513-0</isbn>
<name displayname="名称">Microsoft Age of Empires II: The Age of Kings: Inside Moves</name>
<pagecount displayname="页数">280</pagecount>
<producturl displayname="Product Info. Url">a href=http://mspress.microsoft.com/prod/books/2388.htm target =_a http://mspress.microsoft.com/prod/books/2388.htm /a</producturl>
<publication_year displayname="出版年份">1999</publication_year>
<publisher displayname="出版者">Microsoft Press</publisher>
<reading_level displayname="阅读级别:">所有级别</reading_level>
<title displayname="标题">Microsoft Age of Empires II: The Age of Kings: Inside Moves</title>
</book>
</getproduct>
除了提供用户用于浏览目录的页面之外,一个高效的站点还应提供搜索功能。在 ConsolidatedRetail.com 站点中,用户可以输入搜索标准,在目录中搜索特定产品。
使用 Commerce Server 2000 中的搜索功能可以在当前目录中进行搜索,也可以在基于当前用户的目录集中进行搜索。搜索功能是在 SearchResults.pasp 中实现的,SearchResults.pasp 包含执行以下任务的代码:
搜索目录的实际代码使用应用程序级 MSCSCatalogManager 对象的 FreeTextSearch 方法来检索记录集中的搜索结果。此方法接受以下参数:
如果用户未指定目录,将把当前用户的默认目录集作为记录集进行检索,并将每个目录名连接成以逗号分隔的字符串:
Set objCatalogSets = Server.CreateObject(mc_strCatalogSets)
Call objCatalogSets.Initialize _
(Application("MSCSDictConfig").s_CatalogConnectionString, _
Application("MSCSDictConfig").s_TransactionsConnectionString)
Set rsCatalogs = objCatalogSets.GetCatalogsForUser _
(Application("MSCSProfileService"), GetUserID & "", _
GetDefaultCatalogSet)
strCatalogsToSearch = ""
If Not (rsCatalogs.EOF And rsCatalogs.BOF) Then
Do While Not rsCatalogs.EOF
strCatalogsToSearch = strCatalogsToSearch & "," & _
rsCatalogs.Fields("CatalogName").Value & ""
rsCatalogs.MoveNext
Loop
strCatalogsToSearch = Trim(Mid(strCatalogsToSearch, 2))
End If
或者,如果指定了目录,只需将目录名赋给 strCatalogsToSearch 变量即可:
strCatalogsToSearch = strCatalogName
最后,调用 FreeTextSearch 方法:
Set rsProducts = _
Application("MSCSCatalogManager").FreeTextSearch _
(strSearchPhase, strCatalogsToSearch, , _
"CatalogName, CategoryName, DefinitionName, _
OriginalPrice, cy_list_price, i_ClassType,
ProductID, Description, image_filename, _
image_width, image_height, Name", _
"i_ClassType, CatalogName", _
True, _
lngSearchStartPos, _
lngSearchRowToReturn, _
lngTotalRecordsInQuery)
该页上其余的代码只是将 FreeTextSearch 返回的记录集中的行转换为 XML 格式,这样,XSLISAPI 应用程序就可以呈现搜索结果,以便进行显示。为搜索结果生成的 XML 具有以下格式:
<searchscope>书籍</searchscope>
<searchstring>Age of Empires</searchstring>
<searchcount>4</searchcount>
<searchrowstoreturn>15</searchrowstoreturn>
<searchstartpos>1</searchstartpos>
<searchresults>
<book>
<catalogname>书籍</catalogname>
<definitionname>SDKBook</definitionname>
<originalprice displayname="购买价">19.99</originalprice>
<cy_list_price displayname="价格">19.99</cy_list_price>
<i_classtype>4</i_classtype>
<productid>Microsoft Age of Empires II: The Age of Kings: Inside Moves</productid>
<description>在令人激动的新版 Microsoft Age of Empires 中,您将掌握能够帮助您夺取胜利的所有关键性战略策略、技巧和计谋!MICROSOFT AGE OF EMPIRES II: AGE OF KINGS: INSIDE MOVES 向您展示在从罗马帝国灭亡到中世纪的数千年中如何为生存、富强而奋斗。</description>
<image_filename>boxshots/press/2388.gif</image_filename>
<image_width>120</image_width>
<image_height>120</image_height>
<name displayname="名称">Microsoft Age of Empires II: The Age of Kings: Inside Moves</name>
</book>
<!-- 为了表达得更为清楚,此处省略了其它结果 -->
<selectiontitle>根据搜索标准 'Age of Empires' 找到的产品。</selectiontitle>
</searchresults>
与大多数 B2C 站点一样,ConsolidatedRetail.com 解决方案使用购物车或购物篮的概念将用户选定要购买的产品集中在一起。无论是已登录的用户还是匿名用户,ConsolidatedRetail.com 都允许他们向购物篮添加产品;但是匿名用户在结帐前必须登录。
当用户在 Product.pasp 页上单击“添加到购物车”链接时,会将产品和数量信息发送给 _additem.asp 页。在将用户重定向到显示购物篮内容的 Basket.pasp 页之前,该页包含执行以下任务的代码:
_additem.asp 页调用 Basket.asp 头文件中的 LoadBasket 函数来检索包含当前用户购物篮的 OrderGroup 对象。LoadBasket 函数类似于以下代码:
Function LoadBasket(strUserID)
Dim objMSCSOrderGroup
Set objMSCSOrderGroup = Server.CreateObject( _
mc_strOrderGroup)
Call objMSCSOrderGroup.Initialize(Application( _
"MSCSDictConfig").s_TransactionsConnectionString, _
strUserID)
Call objMSCSOrderGroup.LoadBasket()
Set LoadBasket = objMSCSOrderGroup
Set objMSCSOrderGroup = Nothing
End Function
请注意:OrderGroup 对象是通过传递 MSCSDictConfig 应用程序变量中定义的事务连接字符串和当前用户的用户 ID 来创建和初始化的。(用户 ID 取自 Profile.asp 头文件中的 GetGuaranteedUserID 函数。对于已登录的用户,将返回通过身份验证的用户 ID,对于匿名用户,则返回配置文件用户 ID。)
然后,调用 OrderGroup 对象的 LoadBasket 方法来检索与当前用户相关联的购物篮内容。
加载购物篮后,_additem.asp 中的代码搜索购物篮中的明细项目,查看是否列出了请求的产品。如果产品已在购物篮中,则在订单上增加指定的数量,如下所示:
blnSkuMatched = False
'如果明细项目存在
If objMSCSOrderGroup.Value("total_lineitems") > 0 Then
'循环搜索每个明细项目,查找匹配项
For Each colItem in objMSCSOrderGroup.Value _
("OrderForms").Value("default").Items
If IsNull(strVariantID) Then
If (Trim(Cstr(colItem.product_id)) = _
Trim(Cstr(strProductID))) And _
IsNull(colItem.product_variant_id) Then
blnSkuMatched = True
Exit For
End If
Else
If (Trim(Cstr(colItem.product_id)) = _
Trim(Cstr(strProductID))) And _
Trim(Cstr(colItem. _
product_variant_id)) = _
Trim(Cstr(strVariantID))) Then
blnSkuMatched = True
Exit For
End If
End If
Next
If blnSkuMatched Then
' 如果项目已在购物篮中,则将新的项目数量加到
' 现有项目数量上
colItem.Quantity = colItem.Quantity + intProductQty
'保存新的购物篮
Call objMSCSOrderGroup.SaveAsBasket()
...
如果产品未在购物篮中列出,则代码调用 AddItemToBasket 局部函数,将其添加到购物篮中。AddItemToBasket 函数创建一个字典对象来表示该项目,并使用 OrderGroup 对象的 AddItem 方法将该项目添加到购物篮中。然后,使用 SaveAsBasket 方法保存购物篮。AddItemToBasket 函数的代码类似于以下代码:
Function AddItemToBasket(objMSCSOrderGroup, _
strProductID, _
strCatalogName, intProductQty, _
strVariantID, strCategoryName)
Dim objMSCSProductDictionary
'创建产品字典
Set objMSCSProductDictionary = _
Server.CreateObject(mc_strDictionary)
objMSCSProductDictionary.lineitem_uid = GenerateGUID()
objMSCSProductDictionary.product_id = strProductID
objMSCSProductDictionary.product_catalog = strCatalogName
objMSCSProductDictionary.Quantity = intProductQty
If Not IsNull(strVariantID) Then
objMSCSProductDictionary.product_variant_id = _
strVariantID
End If
If Not IsNull(strCategoryName) Then
objMSCSProductDictionary.product_category = _
strCategoryName
End If
Call objMSCSOrderGroup.AddItem(objMSCSProductDictionary)
Call objMSCSOrderGroup.SaveAsBasket()
Set objMSCSProductDictionary = Nothing
End Function
请注意:添加项目是通过创建一个表示该项目的字典对象,然后将该项目传递给 OrderGroup(表示购物篮)的 AddItem 方法来实现的。
更新购物篮后,_additem.asp 中的代码将用户重定向到 Basket.pasp。
用户可以使用 Basket.pasp 页查看和编辑购物篮的内容。Basket.pasp 包含执行以下任务的代码:
该页通过使用以下代码加载用户的购物篮并检查购物篮中是否包含产品:
Set objMSCSOrderGroup = LoadBasket(strUserID)
'检查购物篮中是否包含项目
If Not IsBasketEmpty(objMSCSOrderGroup) Then
blnBasketIsEmpty = False
End If
Include/basket.asp 中的 IsBasketEmpty 函数检查 OrderGroup 中是否包含项目。如果购物篮包含产品,则将购物篮传递给 PAGBasket 管道以便为显示做准备:
Set objMSCSPipelines = Application("MSCSPipelines")
intErrorLevel = _
RunMtsPipeline(objMSCSPipelines.PAGBasket, _
objMSCSPipelines.LogFolder & strUserID & _
".log", _
objMSCSOrderGroup)
管道用于配置一系列组件,这些组件将以固定顺序对一个业务对象进行操作。管道的操作分为几个“阶段”。在本示例中,PAGBasket 管道包含的组件对 OrderGroup 对象进行操作,该对象表示用户的购物篮。您可以使用 Commerce Server 管道编辑器来查看 PAGBasket 管道的配置,只需要打开站点上“管道”文件夹中的 PAGBasket.pcf 文件即可。PagBasket 管道如图 7-2 所示。
图 7-2:PAGBasket 管道
每次显示购物篮时,PAGBasket 管道收集显示购物篮所需的所有数据并进行必要的计算。除了在显示购物篮之前运行该管道之外,在结帐过程的最后阶段也要运行它,以便进行必要的计算来创建最终订单总计。
产品信息阶段
该管道从“产品信息”阶段开始执行。此阶段用于管理产品信息,涉及以下两个组件:
订单初始化阶段
然后进入“订单初始化”阶段,初始化 OrderGroup 中的相应值。此阶段只涉及一个组件:RequiredOrderInitCy。首先,RequiredOrderInitCy 确保 order_id 键有值。如果没有值,RequiredOrderInitCy 将生成一个唯一订单 ID,并将其赋给此键。然后,为了确保订单完整性,该组件将把 NULL 值赋给各 total 键,将其初始化。最后,对于 items 集合中的每个项目,RequiredOrderInitCy 将存储在 quantity 中的值复制到 _n_unadjusted 键,初始化未打折的项目数量,并将 _oadjust_adjustedprice (项目的总费用)初始化为零。
订单检查阶段
“订单检查”阶段校验显示的订单是否有效以及是否包含后续处理所需的所有条目。此阶段只涉及一个组件:RequiredOrderCheck。RequiredOrderCheck 组件确保 OrderForm 的项目列表不为空。
项目定价阶段
“项目定价”阶段为订单表单中的每个项目设置 _iadjust_regularprice。此阶段涉及以下两个组件: