摘要:本章介绍了 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 strPageNamestrPageName = 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 & """>" & vbcrlfEnd 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 & vbCrLfEnd 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 SubCall 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.UpdateSet objMSCSProfile = NothingPutUserObject = 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 IfEnd If' 返回新的 GUID 或当前已通过身份验证的' 用户名GetUserID = strUser_IDSet objMSCSAuthMgr = NothingEnd Function
Set objMSCSProfile = Application("MSCSProfileService").GetProfilebyKey( _ mc_strUser_ID, strUserID, mc_strUserObject, blnReturnCode)
objMSCSProfile.Fields(mc_strGeneralInfo).Value( _ mc_strFirst_Name) = strFirstNameobjMSCSProfile.Fields(mc_strGeneralInfo).Value( _ mc_strLast_Name) = strLastNameobjMSCSProfile.Fields(mc_strGeneralInfo).Value( _ mc_strEmail_Address)= strEmailAddressobjMSCSProfile.Fields(mc_strGeneralInfo).Value( _ mc_strTel_Number) = strTelNumberobjMSCSProfile.Fields(mc_strGeneralInfo).Value( _ mc_strWork_Number) = strWorkNumberobjMSCSProfile.Fields(mc_strGeneralInfo).Value( _ mc_strWork_Extension) = strWorkExtensionobjMSCSProfile.Fields(mc_strProfileSystem).Value( _ mc_strDate_Last_Changed) = NowobjMSCSProfile.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.MoveNextLoop
<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.GetCatalogAttributesIf 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.MoveNextLoop
这将生成一个 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 = NothingEnd 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 = strProductIDobjMSCSProductDictionary.product_catalog = strCatalogNameobjMSCSProductDictionary.Quantity = intProductQtyIf Not IsNull(strVariantID) Then objMSCSProductDictionary.product_variant_id = _ strVariantIDEnd IfIf Not IsNull(strCategoryName) Then objMSCSProductDictionary.product_category = _ strCategoryNameEnd If Call objMSCSOrderGroup.AddItem(objMSCSProductDictionary) Call objMSCSOrderGroup.SaveAsBasket()Set objMSCSProductDictionary = NothingEnd Function
请注意:添加项目是通过创建一个表示该项目的字典对象,然后将该项目传递给 OrderGroup(表示购物篮)的 AddItem 方法来实现的。
更新购物篮后,_additem.asp 中的代码将用户重定向到 Basket.pasp。
用户可以使用 Basket.pasp 页查看和编辑购物篮的内容。Basket.pasp 包含执行以下任务的代码:
该页通过使用以下代码加载用户的购物篮并检查购物篮中是否包含产品:
Set objMSCSOrderGroup = LoadBasket(strUserID)'检查购物篮中是否包含项目If Not IsBasketEmpty(objMSCSOrderGroup) Then blnBasketIsEmpty = FalseEnd 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。此阶段涉及以下两个组件:
项目调价阶段
“项目调价”阶段为订单表单中的每个项目设置 _iadjust_currentprice。此阶段只涉及一个组件:RequiredItemAdjustPriceCy。RequiredItemAdjustPriceCy 校验项目列表中每个项目的当前价格 (_cy_iadjust_currentprice) 是否存在。如果此值不存在,则该组件创建它并将它初始化为常规价格 (_cy_iadjust_regularprice)。此外,该组件对照当前价格 (cy_iadjust_currentprice) 来检查放置价格 (cy_placed_price),看看自用户将产品放入购物篮后当前价格是否发生了更改。如果放置价格不存在,该组件创建它并将它设置为当前价格 (cy_iadjust_currentprice)。如果放置价格存在但是不等于当前价格,则 RequiredItemAdjustPriceCy 从 MessageManager 中检索无效放置价格的警告消息文本,并将消息写入订单表单中的 _Basket_Errors 集合。
订单调价阶段
“订单调价”阶段为订单表单中的每个项目设置 _oadjust_adjustedprice,以便将价格折扣的因素考虑进去。此阶段只涉及以下一个组件: RequiredOrderAdjustPriceCy:对于每个项目,RequiredOrderAdjustPriceCy 首先计算不打折数量的费用。方法是:将项目的当前价格 (_cy_iadjust_currentprice) 与项目不打折的数量 (n_unadjusted) 相乘,然后将乘积加到项目的总费用 (_cy_oadjust_adjustedprice) 上。然后,计算项目折扣 (cy_oadjust_discount)。方法是:用项目的当前价格 (_cy_iadjust_currentprice) 乘以总数量 (quantity) 得出项目的总费用,然后减去以前算出的不打折的项目费用,这样就计算出了项目折扣。
订单小计阶段
“订单小计”阶段在订单表单上设置 _oadjust_subtotal。此阶段涉及以下三个组件:
运行管道后,Basket.pasp 中的代码再次检查 OrderGroup 对象,以确保该对象仍包含一些项目(因为某些项目可能已在管道中删除)。如果购物篮包含项目,则使用 Commerce Server DictionaryXMLTransforms 对象将内容转换为 XML 并将其写入 Response 对象。DictionaryXMLTransforms 对象生成的 XML 类似于以下代码:
orderform_id="{0F111D4C-E79F-4615-B1AD-9193C811DE86}" saved_cy_oadjust_subtotal="24.99"> <Items quantity="1" product_id="Quick Course in Microsoft Office 2000" product_catalog="书籍" product_category="" description="QUICK COURSE IN MICROSOFT OFFICE 2000 提供一些速成教程,帮助您快速掌握此办公套件的基础知识,熟练使用 Microsoft Excel、Microsoft Word、Microsoft PowerPoint、Microsoft Outlook、Microsoft Access、Microsoft Internet Explorer 5、Microsoft FrontPage 和 Microsoft Publisher。" d_DateCreated="16/02/2001 18:36:06" cy_lineitem_total="24.99" cy_unit_price="24.99" lineitem_uid="{FC57A7EA-1420-40A8-8F55-569FE9B2BEDE}" _product_Name="Quick Course in Microsoft Office 2000" _cy_oadjust_adjustedprice="24.99"/></orderform>
然后,使用 Basket-IE5.xsl 样式表将购物篮页作为 HTML 呈现。
用户决定结帐时,必须执行以下步骤:
ConsolidatedRetail.com 站点使用 Commerce Server 对象和管道组件来处理这些流程。
用户通常使用 Shipping.pasp 来指定收货人地址。该页包含一个表单,该表单列出了用户配置文件中的所有收货人地址,还带有创建新地址或编辑现有地址的选项。用户只需要指定项目要发往的地址即可。然后,该页将表单发回给自身,用指定地址更新表示购物篮内容的 OrderGroup 对象。
用户指定收货人地址时,Shipping.pasp 中的代码使用 Common.asp 头文件中的 GetUserID 函数来检索当前用户的 ID。然后,Shipping.pasp 调用 Basket.asp 头文件中的 LoadBasket 例程填充 OrderGroup 对象,如下所示:
strUserID = GetUserIDSet objMSCSOrderGrp = LoadBasket(strUserID)
接着,代码从查询字符串中检索提供的地址 ID,用指定的地址 ID 更新购物篮中的每个项目:
<orderform For Each strOrderFormName In objMSCSOrderGrp.Value.OrderForms Set objMSCSOrderForm = objMSCSOrderGrp.Value.OrderForms _ (strOrderFormName) For each colItem in objMSCSOrderForm.Items colItem.value("shipping_address_id") = strAddressID NextNext
最后,设置整个 OrderGroup 的收货人地址;对于以前设置的地址,如果没有用到则将其删除;然后保存购物篮。将用户重定向到 ShippingMethod.pasp,为订单指定发货方法:
Call objMSCSOrderGrp.SetShippingAddress(strAddressID)Call objMSCSOrderGrp.SaveAsBasket()Set objMSCSOrderGrp = NothingResponse.Redirect "ShippingMethod.pasp"
但是,如果用户是首次访问 Shipping.pasp 页,则必须从用户配置文件中检索可能的地址列表以便在表单中显示该列表。Shipping.pasp 中的代码使用 ADO 查询在站点数据库的地址表中进行检索,如下所示:
strUserID = GetUserID() Set cnBizDesk = server.CreateObject(mc_stradodb_connection)Set rsAddress = server.CreateObject(mc_stradodb_recordset)cnBizDesk.Open _ Application("MSCSDictConfig").s_BizDataStoreConnectionStringrsAddress.Open "select g_address_id as 'address_id', i_address_type as 'address_type', u_description as 'description', u_address_name as 'address_name', u_address_line1 as 'address_line1', u_address_line2 as 'address_line2', u_city as 'city', u_region_name as 'region_code', u_region_name as 'region_name', u_postal_code as 'postal_code' from addresses where g_id = '" & strUserID & "' ORDER BY u_address_name", cnBizDesk
如果用户的配置文件中未列出地址,则使用 mode 参数将用户重定向到 EditAddressBook.pasp 页。该参数在添加地址后将用户返回到此页:
If rsAddress.EOF Then '没有为该用户 ID 输入任何地址 rsAddress.Close Set rsAddress = Nothing cnBizDesk.Close Set cnBizDesk = Nothing Response.Redirect "EditAddressBook.pasp?Mode=" & mc_strPageNameEnd If
如果用户配置文件包含一个或多个地址,则会将地址信息添加到购物篮(指定的最后一个地址是默认收货人地址)并将其作为 XML 呈现:
Call XMLBegTag(mc_strAddresses)Do While Not rsAddress.EOF strAddressID = rsAddress.Fields("address_id").value strDescription = rsAddress.Fields("description").value strAddressName = rsAddress.Fields("address_name").value strAddressLine1 = rsAddress.Fields("address_line1").value strAddressLine2 = rsAddress.Fields("address_line2").value strCity = rsAddress.Fields("city").value strRegionCode = rsAddress.Fields("region_code").value strRegionName = rsAddress.Fields("region_name").value strPostalCode = rsAddress.Fields("postal_code").value blnSaveSuccessful = PutOrderAddress(objMSCSOrderGrp, _ mc_lngShippingAddress, strAddressID, strAddressName, _ strAddressLine1, strAddressLine2, strCity, strRegionName, _ strPostalCode, strDescription) If blnSaveSuccessful Then Call XMLBegTag(mc_strAddress) Call XMLTag(mc_strAddress_ID, strAddressID) Call XMLTag(mc_strDescription, strDescription) Call XMLTag(mc_strAddress_Name, strAddressName) Call XMLTag(mc_strAddress_Line1, strAddressLine1) Call XMLTag(mc_strAddress_Line2, strAddressLine2) Call XMLTag(mc_strCity, strCity) Call XMLTag(mc_strRegion_Code, strRegionName) Call XMLTag(mc_strRegion_Name, strRegionName) Call XMLTag(mc_strPostal_Code, strPostalCode) Call XMLEndTag(mc_strAddress) '设置付款地址 If rsAddress.Fields("address_type").value = 2 Then objMSCSOrderGrp.Value("OrderForms").Value _ ("default").Value("billing_address_id") = strAddressID End If End If rsAddress.MoveNext LoopCall XMLEndTag(mc_strAddresses)End If
此代码所生成的 XML 格式如以下代码所示:
<addresses> <address> <address_id>{0243FFF8-E633-4DE4-AA2E-2083E9D5ABB4}</address_id> <description>家庭地址</description> <address_name>Kim Abercrombie</address_name> <address_line1>我的住宅号</address_line1> <address_line2>我所在的街道</address_line2> <city>Redmond</city> <region_code>Washington</region_code> <region_name>Washington</region_name> <postal_code>12345</postal_code> </address> <address> <address_id>{B87EDB27-8FBE-4BDA-83BE-ED037A0B7E4C}</address_id> <description>工作地址</description> <address_name>Kim Abercrombie</address_name> <address_line1>Microsoft Corp.</address_line1> <address_line2>1 Microsoft Way</address_line2> <city>Redmond</city> <region_code>Washington</region_code> <region_name>Washington</region_name> <postal_code>54321</postal_code> </address></addresses>
用户为订单指定收货人地址后,必须选择发货方法。Commerce Server 2000 支持配置多种发货方法,每个方法的价格范围各不相同,这取决于重量、项目数量或订单总费用。(有关定义发货方法的详细信息,请参考 Commerce Server 2000 文档。)
使用 ShippingMethod.pasp 页,用户可以从站点数据库中定义的众多发货方法中选择一个。该页包含一个表单,将各种发货方法作为选项列出。用户选择了一个选项后,将把所作的选择发回该页,进行处理。从应用程序级 ShippingMethodsXML 变量检索发货方法,以便显示。
可用发货方法列表是从应用程序级 ShippingMethodsXML 变量检索的,使用 Global.asa 中的 GetShippingMethodsXML 函数将该变量初始化,如下所示:
Function GetShippingMethodsXML(objMSCSDictConfig)Dim objMSCSShpMthMgrDim rsShippingMethodsDim strXMLStreamstrXMLStream = ""Set objMSCSShpMthMgr = _ Server.CreateObject("Commerce.ShippingMethodManager")objMSCSShpMthMgr.Initialize _ (objMSCSDictConfig.s_TransactionsConnectionString)Set rsShippingMethods = _ objMSCSShpMthMgr.GetInstalledMethodList _ ("", "shipping_method_name", Array("shipping_method_id", _ "shipping_method_name"))If Not (rsShippingMethods.EOF And rsShippingMethods.BOF) Then Do While Not rsShippingMethods.EOF strXMLStream = strXMLStream & vbCrLf & "<shipping_method>" strXMLStream = strXMLStream & vbCrLf & "<shipping_method_id>" & _ rsShippingMethods.Fields("shipping_method_id").Value & _ "</shipping_method_id>" strXMLStream = strXMLStream & vbCrLf & _ "<shipping_method_name>" & _ rsShippingMethods.Fields("shipping_method_name").Value & _ "</shipping_method_name>" strXMLStream = strXMLStream & vbCrLf & "</shipping_method>" rsShippingMethods.MoveNext LoopEnd IfSet objMSCSShpMthMgr = NothingGetShippingMethodsXML = strXMLStreamEnd Function
ShippingMethod.pasp 页包含可用发货方法列表,如以下 XML 格式所示:
<shipping_method> <shipping_method_id> {00000000-0000-0000-0000-003688009465} </shipping_method_id> <shipping_method_name>快递</shipping_method_name></shipping_method><shipping_method> <shipping_method_id> {00000000-0000-0000-0000-001140002642} </shipping_method_id> <shipping_method_name>标准</shipping_method_name></shipping_method>
用户选择发货方法后,必须将该方法的 ID 和名称存储在 OrderGroup 中。由于从表单传递的只是 ID,因此必须使用 Commerce Server ShippingMethodManager 对象从数据库中检索名称。此对象提供 GetInstalledMethodList 方法,使用该方法可以将匹配指定标准的方法作为记录集进行检索。根据提供的 ID 检索相关方法名称的代码类似于以下内容:
Set objMSCSShpMthMgr = _ Server.CreateObject(mc_strShippingMethodManager)objMSCSShpMthMgr.Initialize(Application("MSCSDictConfig"). _ s_BizDataStoreConnectionString)set rsShpMthName = objMSCSShpMthMgr.GetInstalledMethodList _ ("shipping_method_id = '" & strShippingMethodID & "'", "", _ Array(mc_strshipping_method_name))set objMSCSShpMthMgr = nothingIf Not (rsShpMthName.EOF and rsShpMthName.BOF) then strShippingMethodName = rsShpMthName.Fields(0).ValueEnd IfrsShpMthName.CloseSet rsShpMthName = Nothing
检索到发货方法名称后,将使用发货方法数据更新购物车中的项目,并将用户重定向到 Payment.pasp 以指定付款信息:
For Each strOrderFormName In objMSCSOrderGrp.Value.OrderForms Set objMSCSOrderForm = objMSCSOrderGrp.Value. _ OrderForms(strOrderFormName) For each colItem in objMSCSOrderForm.Items colItem.value(mc_strshipping_method_id) = strShippingMethodID colItem.value(mc_strshipping_method_name) = strShippingMethodName NextNextCall objMSCSOrderGrp.SaveAsBasket()Set objMSCSOrderGrp = NothingResponse.Redirect "Payment.pasp"
用户可以为购物车中的每个项目指定不同的收货人地址和发货方法。支持此功能的代码位于 MultiShipping.pasp 页中。该页包含一组 txtShippingMethodID 和 txtAddressID 输入字段(购物篮中的每个项目都有一个输入字段)。
用户请求每个项目的发货方法和地址时,将窗体发回 MultiShipping.pasp,同时更新代表购物篮的 OrderGroup。以下代码用于更新每个项目的发货方法和地址数据:
' 用发货方法 ID 填充每个项目For Each strOrderFormName In objMSCSOrderGrp.Value.OrderForms Set objMSCSOrderForm = _ objMSCSOrderGrp.Value.OrderForms(strOrderFormName) For each colItem in objMSCSOrderForm.Items intIndex = intIndex + 1 strShippingMethodID = _ Request.Form("txtShippingMethodID").Item(intIndex) strShippingMethodName = Null If Not IsNull(strShippingMethodID) Then ' 发货方法名称 Set objMSCSShpMthMgr = Server.CreateObject _ (mc_strShippingMethodManager) objMSCSShpMthMgr.Initialize (Application("MSCSDictConfig"). _ s_BizDataStoreConnectionString) Set rsShpMthName = objMSCSShpMthMgr.GetInstalledMethodList _ ("shipping_method_id = '" & strShippingMethodID & _ "'", "", Array(mc_strshipping_method_name)) Set objMSCSShpMthMgr = nothing If Not (rsShpMthName.EOF and rsShpMthName.BOF) then strShippingMethodName = rsShpMthName.Fields(0).Value else strShippingMethodID = Null End If rsShpMthName.Close Set rsShpMthName = Nothing End If strAddressID = Request.Form("txtAddressID").Item(intIndex) colItem.value(mc_strshipping_method_id) = strShippingMethodID colItem.value(mc_strshipping_method_name) = strShippingMethodName colItem.value("shipping_address_id") = strAddressID Next
作为结帐流程的一部分,用户必须为订单指定付款详细信息。Payment.pasp 页按订单处理顺序处理这一事项。
注意: 在本应用程序示例中,付款详细信息是使用 HTTP 以纯文本的形式进行传输的。在实际站点中,应使用 HTTPS 对付款数据进行加密。
Payment.pasp 页包含一个表单,允许用户从配置文件指定信用卡和付款地址详细信息。如果配置文件中未定义付款地址,则将用户重定向到可以添加此地址的 EditAddressBook.pasp 页。Payment.pasp 页读取该表单中的付款详细信息,然后将信息写入代表用户购物篮的 OrderGroup 对象。
strUserID = GetUserIDSet objMSCSOrderGrp = LoadBasket(strUserID)For Each strOrderFormName In objMSCSOrderGrp.Value.OrderForms Set objMSCSOrderForm = objMSCSOrderGrp.Value. _ OrderForms(strOrderFormName) ' 付款方法 - 站点只支持信用卡 objMSCSOrderForm.value("payment_method") = "credit_card" ' 信用卡全称 objMSCSOrderForm.value("cc_name") = strPaymentMethod ' 信用卡内部代码 objMSCSOrderForm.value("cc_code") = strPaymentMethod ' 信用卡持有人姓名 objMSCSOrderForm.value("cc_Account_Holder") = strNameOnAcct ' 信用卡号 objMSCSOrderForm.value("cc_number") = strCardNo ' 信用卡到期月份 objMSCSOrderForm.value("cc_expmonth") = strCardExpMth ' 信用卡到期年份 objMSCSOrderForm.value("cc_expyear") = strCardExpYrNext Call objMSCSOrderGrp.SaveAsBasket()Response.Redirect "OrderSummary.pasp"
填写完付款信息后,用户将被重定向到 OrderSummary.pasp 页。
在进入订单处理的最后一个步骤前,用户还有机会确认或更改订单详细信息。订单详细信息显示在 OrderSummary.pasp 页上。
代码使用两个管道来检索订单详细信息,以便显示该信息。首先,PAGBasket 管道检索购物篮内容。然后,PAGTotal 管道计算装运费用和小计。有关 PAGBasket 管道的说明,请参考本章中的“查看购物篮”一节。PAGTotal 管道将在下一节中介绍。
PAGTotal 管道如图 7-3 所示。使用 Commerce Server 管道编辑器打开 Total.pcf 就可以进行查看。
图 7-3:PAGTotal 管道
货物分拆阶段
“货物分拆”阶段准备发货字典。该阶段涉及以下两个组件:
发货阶段
“发货”阶段将发货折扣应用于订单。该阶段只涉及一个组件:ShippingDiscountAdjust。此组件检查 _shipping_discount_type 并调整订单的总装运费用。如果折扣为空白,则组件不执行任何操作。如果折扣类型为 1 或 2,则组件应用折扣。如果折扣既不为空白也不属于折扣类型 1 或 2,则组件返回一个错误。使用 OrderDiscount 对象确定 _shipping_discount_type 值。
经办阶段
“经办”阶段在订单表单上设置 _handling_total。此阶段涉及以下两个组件:
税金阶段
“税金”阶段在订单表单上设置 _tax_total 和 _tax_included 值。此阶段涉及以下两个组件:
订单合计阶段
“订单合计”阶段在订单表单上设置 _total_total 值。此阶段涉及以下三个组件:
修正阶段
最后一个阶段是“修正”阶段,它只涉及一个组件:Truncate Description。在 Commerce Server 2000 中,任何长度超过 255 个字符的字段将被归于 Text 类型;而此组件是用 VBScript 编写的,提供了解决这个问题的方法。
运行管道后,OrderSummary.pasp 中的代码为当前用户检索 ProfileObject,同时提取电子邮件地址以供订单确认消息使用。然后将地址添加到 OrderGroup 的默认 OrderForm,保存 OrderGroup:
'获取默认 orderformSet objMSCSOrderForm = objMSCSOrderGroup.Value("OrderForms"). _ Value("default")'设置电子邮件地址以供订单确认消息使用。Set objMSCSProfileObject = GetUserObject()objMSCSOrderForm.user_email_address = objMSCSProfileObject _ (mc_strGeneralInfo).Value(mc_strEmail_Address)Set objMSCSProfileObject = NothingCall objMSCSOrderGroup.SaveAsBasket()
最后,使用 Commerce Server DictionaryXMLTransforms 对象将 OrderGroup 的内容转换为 XML 并将其写入 Response 对象,如以下代码所示:
Set objXMLTransforms = _Server.CreateObject(mc_strXMLTransforms)Set objXMLOrderForm = _ objXMLTransforms.GenerateXMLForDictionaryUsingSchema _ (objMSCSOrderForm, Application("TransformSchema"))Set objMSCSOrderForm = NothingSet objXMLTransforms = NothingIf not isEmpty(objXMLOrderForm) Then Response.Write objXMLOrderForm.xmlElse Call AddException(m_varrExceptions, "1222", _ "将 orderform 转换为 XML 时出错。", _ "basket.asp")End if
这将生成用以下 XML 格式表示的 OrderGroup:
<orderform orderform_id="{CD9EABBE-B0BF-48FE-9DB9-58A35A8FEE9F}" payment_method="credit_card" billing_address_id="{0243FFF8-E633-4DE4-AA2E-2083E9D5ABB4}" saved_cy_oadjust_subtotal="44.98" saved_cy_total_total="49.98" cc_name="American Express" cc_expyear="2001" cc_expmonth="08" cc_number="1234" _cy_shipping_total="5" _cy_tax_total="0" user_email_address="[email protected]" cc_Account_Holder="Kim" cy_tax_total="0" cy_shipping_total="5"> <Addresses address_name="Kim Abercrombie" address_line1="我的住宅" address_line2="我的街道" city="我的城市" region_code="WA" postal_code="12345" country_code="US" description="家庭地址" AddressesDictKey="{0243FFF8-E633-4DE4-AA2E-2083E9D5ABB4}"/> <Items quantity="1" product_id="Quick Course in Microsoft Office 2000" product_Name="Quick Course in Microsoft Office 2000" product_catalog="书" product_category="" description="QUICK COURSE IN MICROSOFT OFFICE 2000 提供一些速成教程,帮助您快速掌握此办公套件的基础知识,熟练使用 Microsoft Excel、Microsoft Word、Microsoft PowerPoint、Microsoft Outlook、Microsoft Access、Microsoft Internet Exp" shipping_address_id="{0243FFF8-E633-4DE4-AA2E-2083E9D5ABB4}" shipping_method_id="{00000000-0000-0000-0000-003688009465}" shipping_method_name="快递" d_DateCreated="16/02/2001 18:36:06" cy_lineitem_total="24.99" cy_unit_price="24.99" lineitem_uid="{FC57A7EA-1420-40A8-8F55-569FE9B2BEDE}" _product_Name="Quick Course in Microsoft Office 2000" _cy_oadjust_adjustedprice="24.99"/> <shipments shipping_address_id="{0243FFF8-E633-4DE4-AA2E-2083E9D5ABB4}" shipping_method_id="{00000000-0000-0000-0000-003688009465}" _cy_shipping_total="5"/></orderform>
用户确认订单详细信息后,订单处理就完成了。有关订单处理的最后一个步骤的代码是在 Thankyou.pasp 页中实现的。
运行以前所述的 PAGBasket 和 PAGTotal 管道,即开始运行 Thankyou.pasp 页了。然后,运行 PAGFinal 管道完成订单处理。
使用 Commerce Server 管道编辑器打开 Final.pcf,就可以查看 PAGFinal 管道。PAGFinal 管道如图 7-4 所示。
图 7-4:PAGFinal 管道
订购流程中的唯一阶段名为 OrderTransfer。该阶段只涉及一个组件:QueueEmail Class。这是一个自定义管道组件,用于将订单确认消息以电子邮件的形式发送给默认 OrderForm 中的 user_e-mail_address 字段。
QueueEmail Class 是一个管道组件(COM 对象),用于实现 IPipelineComponent 接口。QueueEmail Class 组件从 OrderForm 获取信息,将信息转换为 XML,然后使用自定义的 QueuedEMailer.CMailer 排队组件将信息作为电子邮件消息发送。由于调用了排队组件来发送电子邮件消息,因此这一进程是异步进行的,这样可以防止电子邮件进程导致的延迟对用户结帐的响应时间产生不良影响。
QueueEmail Class 组件的自定义属性页用于在管道编辑器中配置该组件。这将允许您设置相应的消息队列、标记和 ProgID 以便将排队组件实例化,该组件将实际用于发送电子邮件,如图 7-5 所示。
图 7-5:QueueEmail Class 自定义属性页
实际发送电子邮件的过程由 QueuedEMailer.Cmailer 排队组件来处理。该组件安装在被标记为排队的 COM+ 应用程序中。此外,由于 _CMailer 接口也被标记为排队,从而允许通过消息队列异步调用该接口上的方法。_CMailer 接口只包含一个方法 (SendMail),该方法是使用以下方法签名来定义的:
HRESULT SendMail( [in] BSTR strXMLOrderForm, [in] VARIANT_BOOL blnUseHTMLMail);
请注意以上代码中的两个参数都被标记为 [in],这是因为排队组件不支持[out] 或 [retval] 参数。strXMLOrderForm 参数用于将订单表单的 XML 表示传递给该组件。blnUseHTMLMail 参数值是布尔值,用于确定是以 HTML 格式还是以纯文本格式发送电子邮件消息。
也可以将 QueuedEMailer.CMailer 组件配置为支持对象构建。这意味着该组件可以实现 IobjectConstruct 接口,该接口包含名为 Construct 的方法,当对象实例化时,COM+ 将调用这一方法。将构造函数字符串传递给 Construct 方法,该方法包含对象可以使用的配置信息。传送给 QueuedEMailer.CMailer 的构造函数字符串采用以下 XML 格式:
<config><from>[email protected]</from><subject>订单确认</subject><TextXSL>C:\Inetpub\b2cref\xml\emailtext.xsl</TextXSL><HTMLXSL>C:\Inetpub\b2cref\xml\emailhtml.xsl</HTMLXSL><SMTPServer></SMTPServer><SMTPPort></SMTPPort><SMTPTimeout></SMTPTimeout><UseSSL>False</UseSSL><SMTPUserName></SMTPUserName><SMTPPassword></SMTPPassword><SMTPAuthMethod></SMTPAuthMethod></config>
使用 Component Services Microsoft Management Console (MMC) 管理单元(如图 7-6 所示)来配置此构造函数。
图 7-6:Component Services MMC 管理单元
QueuedEMailer.CMailer 对象使用 MSXML3 和 CDOSYS 对象。XML 和 XSL 的加载是借助使用 MSXML3 对象的 XMLDomDocument 完成的。电子邮件消息的实际发送是由 IMessage 对象(CDO 协作数据对象)完成的,该对象要用到 CDOSYS 对象。用于 Windows 2000 的协作数据对象 (CDO) Cdosys.dll 实现了 CDO API 规范的 2.0 版本,它是一个 COM 组件,专用于简化创建或操纵 Internet 消息的程序编写工作。
现在,您应该已经了解了 ConsolidatedRetail.com 应用程序背后的基本功能和开发策略。文中的代码注释提供了更为详细的信息。
Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=5788