.Net PetShop 3.0中购物车总价计算的bug
当在购物车页面(ShoppingCart.aspx)中,更改了购物车中某一个商品的数量(Quantity栏)后,点击Update按钮来更新购物车的总价(Total),但是购物车的总价并没有被更新,这是一个bug。
在业务逻辑层(BLL)中,购物车由Cart类表示,而购物车中每个商品记录则由Mode模块的CartItemInfo类来表示。CartItemInfo类的Price、Quantity和Subtotal属性表示购物车中每个商品记录的单价、数量和价格小计(Subtotal=Price*Quantity),而Cart类的Total属性表示购物车的总价。购物车的总价应该是购物车中每个商品记录的价格小计的总和。
在什么时候计算购物车的总价,有两种可选的方式:
1)在向购物车中添加商品记录、删除商品记录和修改购物车中某商品记录的数量时,修改购物车总价
2)在要读取购物车总价时,遍历购物车中每个商品记录来计算购物车总价
从类的设计角度来讲,第二种方式明显要好于第一种方式。因为,第一种方式中,计算总价的逻辑被分散到多个地方(Cart类的Add、Remove、RemoveAt方法和UI层的Update按钮的事件处理方法)来实现。第二种方式中,只需要在Cart类的Total属性的代码中来实现计算总价的逻辑。
令人不解的是,.net PetShop使用的是第一种方式,在Cart类的Add、Remove、RemoveAt方法编写了计算总价的代码,但是在Update按钮的事件处理方法(PetShop.Web.ShoppingCart类的CommandClicked方法)中却没有修改购物车总价的代码,这就是Bug的所在。
CommandClicked方法的代码如下:
---------------------------------------------------------
/// <summary>
/// Function to control user clicking on a button on the page
/// 处理Remove和Update按钮的事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected void CommandClicked(object sender, RepeaterCommandEventArgs e)
{
// Check for update button
if (e.CommandName == CMD_UPDATE)
{
TextBox txt;
int qty;
int index;
// Go through each item on the page
// 遍历购物车中每一条商品记录,如果其数量在页面上被修改了,则更新之
// cart(ViewStatePager控件)和myCart(Cart对象)保持一致
for (int i = 0, j = cart.Items.Count; i < j; i++){
// lookup the control
txt = (TextBox)cart.Items[i].FindControl(ID_TXT);
try
{
// 得到商品记录的数量
qty = int.Parse(txt.Text);
// 得到商品记录在购物车中的下标
index = cart.CurrentPageIndex * cart.PageSize + i;
// If the new qty is zero, remove the item from the cart
if (qty <= 0)
myCart.RemoveAt(index);
// Update the item with the new quantity
else
//在购物车对象中更新指定商品记录的数量
//这时,购物车的总价发生了变化,但这里没有作相应的处理,是一个bug
myCart[index].Quantity = qty;
}
catch
{} //忽略所有异常
}
}
else
// otherwise the command is to remove the an item
myCart.Remove((string)e.CommandArgument);
// Refresh the contents of the cart page
Refresh();
// Update the page count if required
int pageCount = (myCart.Count - 1) / cart.PageSize;
cart.SetPage(Math.Min(cart.CurrentPageIndex, pageCount));
}
-------------------------------------------------------
相应的,可以用上述的两种方式来修补这个bug。第一种方式只需要在UI层的ShoppingCart类的CommandClicked方法中加上修改购物车总价的代码。第二种方式,需要删除Cart类的Add、Remove、RemoveAt方法中相关的代码,然后重新编写Cart类的Total属性的实现代码。
这里采用第二种方式,Cart的代码如下:
-----------------------------------------------------
using System;
using System.Collections;
//References to PetShop specific libraries
//PetShop busines entity library
using PetShop.Model;
namespace PetShop.BLL {
/// <summary>
/// An object to represent a customer's shopping cart
/// 购物车类
/// </summary>
[Serializable]
public class Cart : IEnumerable {
/// <summary>
/// Internal storage for a cart
/// 数组列表中保存的是CartItemInfo对象实例
/// </summary>
private ArrayList _items = new ArrayList();
//Fixbug bug001: 修改了属性Total,下面的一条语句就不需要了(被注释掉)
// private decimal _total=0;
/// <summary>
/// Returns an enumerator for the cart items in a cart
/// </summary>
/// <returns></returns>
public IEnumerator GetEnumerator() {
return _items.GetEnumerator();
}
// Properties
public decimal Total
{
//Fixbug bug001: Total属性应该是只读的,其值应该是自动计算的
// get { return _total; }
// set { _total = value; }
//Fixbug bug001: 重写的代码
get{
Decimal total = 0;
foreach(CartItemInfo cartItem in _items)
{
total += cartItem.Subtotal;
}
return total;
}
}
/// <summary>
/// Returns number of items in cart
/// 返回购物车中商品项的数目
/// </summary>
public int Count {
get { return _items.Count; }
}
/// <summary>
/// Return CartItem representation of object at a given address
/// 索引器。获取购物车中任一商品项的信息
/// </summary>
///
public CartItemInfo this[int index] {
get { return (CartItemInfo)_items[index]; }
}
/// <summary>
/// Add an item to the cart
/// 添加一个商品项到购物车
/// </summary>
/// <param name="ItemId">ItemId of item to add</param>
public void Add(string ItemId)
{
foreach (CartItemInfo cartItem in _items) //如果商品项已经存在于购物车中
{
if (ItemId == cartItem.ItemId) {
cartItem.Quantity++; // 该商品项的个数加一
// 判定该商品项是否还有库存
cartItem.InStock = (GetInStock(ItemId) - cartItem.Quantity) >= 0 ? true : false;
//Fixbug bug001: 修改了属性Total,下面的一条语句就不需要了(被注释掉)
// _total = _total+(cartItem.Price*cartItem.Quantity); //修改购物车中商品的总价
return;
}
}
Item item = new Item();
ItemInfo data = item.GetItem(ItemId);
CartItemInfo newItem = new CartItemInfo(ItemId,data.Name,(data.Quantity >= 1),1,(decimal)data.Price);
_items.Add(newItem);
//Fixbug bug001: 修改了属性Total,下面的一条语句就不需要了(被注释掉)
// _total = _total+(data.Price);
}
/// <summary>
/// Remove item from the cart based on itemId
/// 从购物车中移除一个商品项
/// </summary>
/// <param name="itemId">ItemId of item to remove</param>
public void Remove(string itemId) {
foreach (CartItemInfo item in _items) {
if (itemId == item.ItemId) //找到指定的商品项id
{
_items.Remove(item);
//Fixbug bug001: 修改了属性Total,下面的一条语句就不需要了(被注释掉)
// _total = _total-(item.Price*item.Quantity); //修改总价
return;
}
}
}
/// <summary>
/// Removes item from cart at specific index
/// 从购物车中移除一个商品项(指定索引)
/// </summary>
/// <param name="index">Element number of item to remove</param>
public void RemoveAt(int index)
{
CartItemInfo item = (CartItemInfo)_items[index];
//Fixbug bug001: 修改了属性Total,下面的一条语句就不需要了(被注释掉)
// _total = _total-(item.Price*item.Quantity);
_items.RemoveAt(index);
}
/// <summary>
/// Returs internal array list of cart items
/// </summary>
/// <returns></returns>
public ArrayList GetCartItems() {
return _items;
}
/// <summary>
/// Method to convert internal array of cart items to order line items
/// 将购物车中的商品项信息转换为订单明细信息
/// </summary>
/// <returns>New array list of order line items</returns>
public ArrayList GetOrderLineItems() {
ArrayList orderLineItems = new ArrayList();
int lineNum = 1;
foreach (CartItemInfo item in _items) {
// 购物车中每一个商品项就生成一条订单明细行
LineItemInfo lineItem = new LineItemInfo(item.ItemId,item.Name,lineNum,item.Quantity,item.Price);
orderLineItems.Add(lineItem);
lineNum++;
}
return orderLineItems;
}
/// <summary>
/// Internal method to get the stock level of an item
/// 获得指定商品项的库存数
/// </summary>
/// <param name="ItemId">Unique identifier of item to get stock level of</param>
/// <returns></returns>
private int GetInStock(string ItemId)
{
Inventory inventory = new Inventory();
return inventory.CurrentQuantityInStock(ItemId);
}
}
}