前言
购物篮模块是PetShop4中最最重的模块之一。在数据库中,与购物篮联系最紧密的就是Cart表了(关于Cart表请参见本系列博文{dy}篇相关部分)。
购物篮的数据访问层(DAL)
购物篮模块中设计了实体类,这样可以快速为数据库中各字段赋值,体现数据结构的整体性。PetShop4的所有的实体类都保存的项目Model中。
其中的CardItemInfo类与Card表基本对应,可以实现对Card表的快速存取。
购物篮的数据访问是通过Profile工厂模式实现的。工厂模式的基本原理以前面的文章中已有简介,这里用户只要知道如何调用Profile就行了。最终实现对数据库的访问是通过SQLProfileDAL项目中的PetShopProfileProvider类来完成的。该类中有关购物篮的部分方法如下:
/// Retrieve collection of shopping cart items
public IList<CartItemInfo> GetCartItems(string userName, string appName, bool isShoppingCart)
/// Update shopping cart for current user
public void SetCartItems(int uniqueID, ICollection<CartItemInfo> cartItems, bool isShoppingCart)
/// Update activity dates for current user and application
public void UpdateActivityDates(string userName, bool activityOnly, string appName)
/// Retrive unique id for current user
public int GetUniqueID(string userName, bool isAuthenticated, bool ignoreAuthenticationType, string appName)
/// Create profile record for current user
public int CreateProfileForUser(string userName, bool isAuthenticated, string appName)
下图保存购物篮信息的实现流程图:
购物篮的业务逻辑层(BLL)
在三层结构中,业务逻辑层处在中间层,是承上启下的一层,它为用户调用提供一个接口,并启动数据访问层来完成用户操作,由于DAL与业务逻辑无关,所以BLL是实现用户数据逻辑处理的关键层.当数据访问层的SQL语句发生变化时,只需要改变DAL层的部分内容,不会影响到BLL等上面层.BLL主要作用就是实现当前业务的操作功能.
系统中所有业务逻辑层的处理类类位于BLL项目下,PepShop4中购物篮功能的业务逻辑主要包括:添加商品到购物篮,移除购物篮中的商品,更新购物篮中的某件商品的数量,清空购物篮,获取购物篮中商品总数及总价格,获得购物篮中某商品的记录等.实现这些操作主要是通过Card类来完成的,Card类的结构如下图:
购物篮的界面层(UI)
界面层位于Web项目下,购物篮的界面层文件是:ShoppingCard.aspx.打开该文件的HTML视图,
首先,该页面应用了母版面:MasterPageFile="~/MasterPage.master"
其次,页面上注册了一个用户控件:ShoppingCardControl,注册代码如下:
<%@ Register Src="Controls/ShoppingCartControl.ascx" TagName="ShoppingCartControl" TagPrefix="PetShopControl" %>
接下来在页面上声明了这一控件:
<PetShopControl:shoppingcartcontrol id="ShoppingCartControl1" runat="server"></PetShopControl:shoppingcartcontrol>
打开ShoppingCartControl.ascx控件的设计界面,购物篮商品列表是由一个Reparter控件来呈现的。其中记录显示格式如下:
<ItemTemplate>
<tr class="listItem">
<td>
<asp:ImageButton ID="btnDelete" runat="server" BorderStyle="None" CausesValidation="false"
CommandArgument='<%# Eval("ItemId") %>' CommandName="Del" ImageUrl="~/Comm_Images/button-delete.gif"
OnCommand="CartItem_Command" ToolTip="Delete" />
</td>
<td style="width:{bfb};">
<a runat="server" href='<%# string.Format("~/Items.aspx?itemId={0}&productId={1}&categoryId={2}", Eval("ItemId"), Eval("ProductId"), Eval("CategoryId")) %>'><%# string.Format("{0} {1}", Eval("Name"), Eval("Type")) %></a>
</td>
<td>
<asp:TextBox ID="txtQuantity" runat="server" Columns="3" Text='<%# Eval("Quantity") %>' Width="20px"></asp:TextBox>
</td>
<td align="right"><%# Eval("Price", "{0:c}")%></td><td>
<asp:ImageButton ID="btnToWishList" runat="server" AlternateText="Move to wish list" CausesValidation="false" CommandArgument='<%# Eval("ItemId") %>' CommandName="Move" ImageUrl="~/Comm_Images/button-wishlist.gif" OnCommand="CartItem_Command" ToolTip="Move to wish list" />
</td>
</tr>
</ItemTemplate>
显示购物篮界面
需要注意的是,当用户打开购物篮界面时,系统会自动检索用户之前添加过的购物篮信息,并加载到界面上。这一功能是怎样实现的呢?
加载先前购物信息的功能是在页面的“Page_PreRender”事件中完成的。该事件调用“BindCart”方法,其中“BindCart”方法的代码如下:
private void BindCart() {
ICollection<CartItemInfo> cart = Profile.ShoppingCart.CartItems;
if (cart.Count > 0) {
repShoppingCart.DataSource = cart;
repShoppingCart.DataBind();
PrintTotal();
plhTotal.Visible = true;
}
else {
repShoppingCart.Visible = false;
plhTotal.Visible = false;
lblMsg.Text = "Your cart is empty.";
}
}
添加商品到购物篮
protected void Page_PreInit(object sender, EventArgs e) {
if (!IsPostBack) {
string itemId = Request.QueryString["addItem"];
if (!string.IsNullOrEmpty(itemId)) {
Profile.ShoppingCart.Add(itemId);
Profile.Save();
// Redirect to prevent duplictations in the cart if user hits "Refresh"
Response.Redirect("~/ShoppingCart.aspx", true);
}
}
}
其中语句:Profile.ShoppingCart.Add(itemId);是功能操作的关键语句,该语句的实现流程如下:
调用BLL项目中Cart类中的Add方法→调用Profile项目中PetShopProfileProvider类的SetPropertyValues方法
→调用同类中的SetCartItems方法向购物篮中添加商品信息
→调用SQLProfileDAL项目中的PetShopProfileProvider类的CreateProfileForUser方法添加用户的配置信息
更改商品数量
protected void BtnTotal_Click(object sender, System.Web.UI.ImageClickEventArgs e) {
TextBox txtQuantity;
ImageButton btnDelete;
int qty = 0;
foreach (RepeaterItem row in repShoppingCart.Items) {
txtQuantity = (TextBox)row.FindControl("txtQuantity");
btnDelete = (ImageButton)row.FindControl("btnDelete");
if (int.TryParse(WebUtility.InputText(txtQuantity.Text, 10), out qty)) {
if (qty > 0)
Profile.ShoppingCart.SetQuantity(btnDelete.CommandArgument, qty);
else if (qty == 0)
Profile.ShoppingCart.Remove(btnDelete.CommandArgument);
}
}
Profile.Save();
BindCart();
}
计算商品总价
private void PrintTotal() {
if (Profile.ShoppingCart.CartItems.Count > 0)
ltlTotal.Text = Profile.ShoppingCart.Total.ToString("c");
}
移除购物篮中的商品
protected void CartItem_Command(object sender, CommandEventArgs e) {
switch (e.CommandName.ToString()) {
case "Del":
Profile.ShoppingCart.Remove(e.CommandArgument.ToString());
break;
case "Move":
Profile.ShoppingCart.Remove(e.CommandArgument.ToString());
Profile.WishList.Add(e.CommandArgument.ToString());
break;
}
Profile.Save();
BindCart();
}
匿名用户到验证用户购物篮的转移
PetShop4中一个重要的特点是它允许用户不登录时就可以选择商品,并可以将商品添加到购物篮,然后当用户登录后,再将用户登录前选择的商品保存到验证过的个性配置中。
.Net 3.5提供了一种匿名登录的方法,它会为用户自动生成一个UserName,当匿名用户选择商品到购物篮中是地,系统自动在Profile表中保存这个匿名用户的名字,保存用户的购物篮,当用户登录后,只需要将表中的用户名替换掉就行了。
要实现此功能,首先,在Web.config中xx:
<anonymousIdentification enabled="true"/>
<profile automaticSaveEnabled="false" defaultProvider="ShoppingCartProvider">
<providers>
<add name="ShoppingCartProvider" connectionStringName="SQLProfileConnString" type="PetShop.Profile.PetShopProfileProvider" applicationName=".NET Pet Shop 4.0"/>
<add name="WishListProvider" connectionStringName="SQLProfileConnString" type="PetShop.Profile.PetShopProfileProvider" applicationName=".NET Pet Shop 4.0"/>
<add name="AccountInfoProvider" connectionStringName="SQLProfileConnString" type="PetShop.Profile.PetShopProfileProvider" applicationName=".NET Pet Shop 4.0"/>
</providers>
<properties>
<add name="ShoppingCart" type="PetShop.BLL.Cart" allowAnonymous="true" provider="ShoppingCartProvider"/>
<add name="WishList" type="PetShop.BLL.Cart" allowAnonymous="true" provider="WishListProvider"/>
<add name="AccountInfo" type="PetShop.Model.AddressInfo" allowAnonymous="false" provider="AccountInfoProvider"/>
</properties>
</profile>
其次是要正确处理匿名迁移,系统中使用Profile_MigrateAnonymous事件来处理,此事件一般在Global.asax文件中。
void Profile_MigrateAnonymous(Object sender, ProfileMigrateEventArgs e) {
ProfileCommon anonProfile = Profile.GetProfile(e.AnonymousID);
// Merge anonymous shopping cart items to the authenticated shopping cart items
foreach (CartItemInfo cartItem in anonProfile.ShoppingCart.CartItems)
Profile.ShoppingCart.Add(cartItem);
// Merge anonymous wishlist items to the authenticated wishlist items
foreach (CartItemInfo cartItem in anonProfile.WishList.CartItems)
Profile.WishList.Add(cartItem);
// Clean up anonymous profile
ProfileManager.DeleteProfile(e.AnonymousID);
AnonymousIdentificationModule.ClearAnonymousIdentifier();
// Save profile
Profile.Save();
}