由于本文是分析Pet Shop3.0的代码实现,所以首先按场景进行分析,接着按技术点进行分析,最后进行总结。
场景1: 选择首页上的宠物图标,查询出该宠物的品种的过程。假设选择宠物――鸟。
Part 1 页面代码分析,图片上做了“热区“,用的是map标签。超连接是Category.aspx,附加Get请求参数categoryId.
<map name="mainMap">
<area href="Category.aspx?categoryId=BIRDS" alt="Birds" coords="408,133,514,239" shape="RECT">
<area href="Category.aspx?categoryId=FISH" alt="Fish" coords="2,250,108,356" shape="RECT">
<area href="Category.aspx?categoryId=DOGS" alt="Dogs" coords="108,326,214,432" shape="RECT">
<area href="Category.aspx?categoryId=REPTILES" alt="Reptiles" coords="348,254,454,358" shape="RECT">
<area href="Category.aspx?categoryId=CATS" alt="Cats" coords="242,334,348,440" shape="RECT">
<area href="Category.aspx?categoryId=BIRDS" alt="Birds" coords="280,180,350,250" shape="RECT">
</map><img src="Images/splash.jpg" usemap="#mainMap" width="548" height="466" border="0">

Category.aspx页面的代码,利用SimplePager自定义控件,继承了Repeater控件,比DateSet效率高。
<controls:SimplePager id="products" runat="server" pagesize="4" emptytext="No products found." onpageindexchanged="PageChanged">
<HeaderTemplate>
<TABLE cellSpacing="0" cellPadding="0">
<TBODY>
<TR class="gridHead">
<TD>Product ID</TD>
<TD>Name</TD>
</TR>
</HeaderTemplate>
<ITEMTEMPLATE>
<TR class="gridItem">

<TD><%
# DataBinder.Eval(Container.DataItem, "Id") %></TD>

<TD><A href='Items.aspx?productId=<%# DataBinder.Eval(Container.DataItem, "Id") %>'><%
# DataBinder.Eval(Container.DataItem, "Name") %></A></TD>
</TR>
</ITEMTEMPLATE>
<FOOTERTEMPLATE></TBODY></TABLE></FOOTERTEMPLATE>
</controls:SimplePager>
Part 2 Code Behide代码,主要是SimplePager控件的事件onpageindexchanged,这段代码首先去缓存中查找结果,如果没有才去数据库查询,运用缓存API,提高执行效率。去数据库查询时,调用了业务层的类Product和方法GetProductsByCategory(categoryKey)。最后把结果绑定到SimplePager控件。关于SimplePager后面会单独讲解。
products.CurrentPageIndex = e.NewPageIndex;

// Get the category from the query string
// string categoryKey = Request["categoryId"];
string categoryKey = WebComponents.CleanString.InputText(Request["categoryId"], 50);

// Check to see if the contents are in the Data Cache

if(Cache[categoryKey] != null)
{
// If the data is already cached, then used the cached copy
products.DataSource = (IList)Cache[categoryKey];

}else
{
// If the data is not cached, then create a new products object and request the data
Product product = new Product();
IList productsByCategory = product.GetProductsByCategory(categoryKey);
// Store the results of the call in the Cache and set the time out to 12 hours
Cache.Add(categoryKey, productsByCategory, null, DateTime.Now.AddHours(12), Cache.NoSlidingExpiration , CacheItemPriority.High, null);
products.DataSource = productsByCategory;
}
// Bind the data to the control
products.DataBind();
// Set the label to be the query parameter
lblPage.Text = categoryKey;
Part 3 业务层的Product中的GetProductsByCategory方法是怎么实现的呢?关键代码是用PetShop.DALFactory中的Product类Create一个Iproduct接口。然后拿这个利用这个接口的GetProductsByCategory(category)方法。显然,运行过程中产生各种符合IProduct接口的对象,然后调用这个对象的GetProductsByCategory方法。

public IList GetProductsByCategory(string category)
{

// Return null if the string is empty
if (category.Trim() == string.Empty)
return null;

// Get an instance of the Product DAL using the DALFactory
IProduct dal = PetShop.DALFactory.Product.Create();

// Run a search against the data store
return dal.GetProductsByCategory(category);
}
Part 4 DALFactory中的Product如何进行Create的呢?这里利用DotNet的反射功能,运行时从配置文件中读取要创建的类的全名,然后利用反射创建出这个类。

public static PetShop.IDAL.IProduct Create()
{


/**//// Look up the DAL implementation we should be using
string path = System.Configuration.ConfigurationSettings.AppSettings["WebDAL"];
string className = path + ".Product";

// Using the evidence given in the config file load the appropriate assembly and class
return (PetShop.IDAL.IProduct)Assembly.Load(path).CreateInstance(className);
}
Part 5 以上我们见过n次IProduct,这是个什么东西呢?答案是仅仅是一个接口,一个空架子。
IList GetProductsByCategory(string category);
综合Part 3、Part 4和Part 5,正是利用了DotNet的反射技术和抽象工厂模式,在运行期确定要创建Oracle还是SQLserver数据层的类对象。
Part 6 那么数据层的Product如何实现GetProductsByCategory(category)方法呢?分别看看SQL server和Oracle实现代码:

public IList GetProductsByCategory(string category)
{

IList productsByCategory = new ArrayList();

SqlParameter parm = new SqlParameter(PARM_CATEGORY, SqlDbType.Char, 10);
parm.Value = category;
//Execute a query to read the products

using (SqlDataReader rdr = SQLHelper.ExecuteReader(SQLHelper.CONN_STRING_NON_DTC, CommandType.Text, SQL_SELECT_PRODUCTS_BY_CATEGORY, parm))
{

while (rdr.Read())
{
ProductInfo product = new ProductInfo(rdr.GetString(0), rdr.GetString(1), null);
productsByCategory.Add(product);
}
}

return productsByCategory;
}
首先,调用了ADO.NET和微软企业库的DAAB块,对数据库进行查询;其次,查询的方式是DataReader。ADO.NET是伴随DotNet出现,微软对ADO的升级版本,而DAAB的出现是为了简化对ADO.Net的调用和避免大量的重复编码,Pet Shop3.0中用的是DAAB1.0,现在的版本是3.0,API已经有了很大的变化。DataReader是Pet Shop 3.0进行数据库查询的主要方式,配合IList使用。

public IList GetProductsByCategory(string category)
{

IList productsByCategory = new ArrayList();

OracleParameter parm = new OracleParameter(PARM_CATEGORY, OracleType.Char, 10);
parm.Value = category;
//Execute a query to read the products

using (OracleDataReader rdr = OraHelper.ExecuteReader(OraHelper.CONN_STRING_NON_DTC, CommandType.Text, SQL_SELECT_PRODUCTS_BY_CATEGORY, parm))
{

while (rdr.Read())
{
ProductInfo product = new ProductInfo(rdr.GetString(0), rdr.GetString(1),null);
productsByCategory.Add(product);
}
}

return productsByCategory;
}
针对Oracle的代码与针对SQL server的代码非常类似,不同的地方是:SQL语句和DAAB。Oracle的DAAB是仿照SQL server写的调用了Oracle的驱动API进行数据库操作。因为非常类似,以后的代码分析中就只分析针对SQLserver代码。
Part 7 上面查询数据库后,返回的结果会被构造成一个ProductInfo,然后添加到ArrayList里面。所以我们看看ProductInfo的内容:
using System;


namespace PetShop.Model
{


/**//// <summary>
/// Business entity used to model a product
/// </summary>
[Serializable]

public class ProductInfo
{

// Internal member variables
private string _id;
private string _name;
private string _description;


/**//// <summary>
/// Default constructor
/// </summary>

public ProductInfo()
{}


/**//// <summary>
/// Constructor with specified initial values
/// </summary>
/// <param name="id">Product Id</param>
/// <param name="name">Product Name</param>
/// <param name="description">Product Description</param>

public ProductInfo(string id, string name, string description)
{
this._id = id;
this._name = name;
this._description = description;
}

// Properties

public string Id
{

get
{ return _id; }
}


public string Name
{

get
{ return _name; }
}


public string Description
{

get
{ return _description; }
}
}
}
由上面代码可以看出类非常简单,一个默认的构造函数,每个属性都由Set和Get构成。非常类似Java bean。另外实现[Serializable],为了在远程调用时,可以序列化对象。
对场景1的调用过程进行总结:共涉及到页面CodeBehide,业务对象,数据层工厂,数据层接口,数据层实现和瘦实体。业务层API提供页面的CodeBehide代码调用,而业务层中功能由数据层工厂决定该调用针对哪个数据库的对象。具体的数据层实现了数据层接口,并且调用DAAB和ADO.NET实现数据调用,而CodeBehide代码,业务层对象和数据层对象间所传递的对象时IList,该对象是可被序列化的。Ilist又由ProductInfo瘦实体填充,并且这个瘦实体也可以被序列化,保证可以进行分布式发布。调用过程如下图所示:

场景 2:选择鸟的品种Finch。与场景1的处理过程非常类似,这里不再分析。
场景 3: 选择Adult Male的Finch。与场景1和场景2类似,不同点在于,首先,层间传递的是ItemInfo,而不再是IList。其次,页面展示不再用SimplePager控件。调用过程和场景1场景2大同小异,不再讨论。
场景 4:宠物搜索,与场景1,场景2类似,难点是在数据访问实现中对SQL语句的组织,主要对Product表进行查询。由于调用机制与场景1,场景2一样,所以不讨论。
场景 5:添加新客户,填写表单信息,然后提交。
Part 1 页面代码没什么内容分析,主要用了两个用户控件以及数据验证,关于用户控件和输入验证后面分别会单独讲解。
Part 2 Code Behide代码,收集用户信息数据,构造一个AccountInfo瘦实体,最后把这个瘦实体交给ProcessFlow.AccountController去CreateAccount。

protected void SubmitClicked(object sender, ImageClickEventArgs e)
{

if (Page.IsValid)
{

string userId = WebComponents.CleanString.InputText(txtUserId.Text, 50);
string password = WebComponents.CleanString.InputText(txtPassword.Text, 50);
string email = WebComponents.CleanString.InputText(txtEmail.Text, 50);
AddressInfo address = addr.Address;
string language = prefs.Language;
string favCategory = prefs.Category;
bool showFavorites = prefs.IsShowFavorites;
bool showBanners = prefs.IsShowBanners;

// Store all the customers information in an account business entity
AccountInfo accountInfo = new AccountInfo(userId, password, email, address, language, favCategory, showFavorites, showBanners);

ProcessFlow.AccountController accountController = new ProcessFlow.AccountController();


if (!accountController.CreateAccount(accountInfo))
{

// Tell the user they have failed to create an account
valUserId.ErrorMessage = MSG_FAILURE;
valUserId.IsValid = false;
}
}
}
Part 3 上面的ProcessFlow.AccountController相当与一个流程处理工具兼Session容器。看看它的CreateAccount是怎么实现的?首先调用业务实体Account进行添加用户。然后把用户信息放到Session里面,最后进行Form授权。关于Form授权,后面会单独分析。

public bool CreateAccount(AccountInfo newAccountInfo)
{

try
{
// Creata a new business logic tier
Account account = new Account();

// Call the insert method
account.Insert(newAccountInfo);

// Store the data in session state and store the authenticated cookie
HttpContext.Current.Session[ACCOUNT_KEY] = newAccountInfo;
FormsAuthentication.SetAuthCookie(newAccountInfo.UserId, false);
//Finally forward to the welcome page
HttpContext.Current.Response.Redirect(URL_ACCOUNTCREATE, true);

}catch
{
return false;
}

return true;
}
Part 4 Account业务实体如何添加用户的呢?用工厂类创建数据访问对象,然后调用数据访问对象进行Insert操作,这项操作在前面已经分析过了。

public void Insert(AccountInfo account)
{

// Validate input
if (account.UserId.Trim() == string.Empty)
return;

// Get an instance of the account DAL using the DALFactory
IAccount dal = PetShop.DALFactory.Account.Create();

// Call the DAL to insert the account
dal.Insert(account);
}
Part 5 数据访问层的Account的Insert做了哪些工作? 与前面分析的数据访问实体操作类似,多出来的是利用ADO.NET的事务处理。关于事务处理,后面会单独分析。

public void Insert(AccountInfo acc)
{
SqlParameter[] signOnParms = GetSignOnParameters();
SqlParameter[] accountParms = GetAccountParameters();
SqlParameter[] profileParms = GetProfileParameters();

signOnParms[0].Value = acc.UserId;
signOnParms[1].Value = acc.Password;

SetAccountParameters(accountParms, acc);
SetProfileParameters(profileParms, acc);

using (SqlConnection conn = new SqlConnection(SQLHelper.CONN_STRING_NON_DTC))
{
conn.Open();

using (SqlTransaction trans = conn.BeginTransaction())
{

try
{
SQLHelper.ExecuteNonQuery(trans, CommandType.Text, SQL_INSERT_SIGNON, signOnParms);
SQLHelper.ExecuteNonQuery(trans, CommandType.Text, SQL_INSERT_ACCOUNT, accountParms);
SQLHelper.ExecuteNonQuery(trans, CommandType.Text, SQL_INSERT_PROFILE, profileParms);
trans.Commit();

}catch
{
trans.Rollback();
throw;
}
}
}
}
Part 6 添加客户过程中,在层与层之间传递的也是瘦实体数据AccountInfo,与ProductInfo类似,这里不在列出。
场景 6:登录Pet Shop。在这个场景主要分析Form授权的安全性和实现过程。
Part 1 Web的Code Behide代码,把用户名和密码交给AccountController去处理。

protected void SubmitClicked(object sender, ImageClickEventArgs e)
{

if (Page.IsValid)
{
// Get the user info from the text boxes
string userId = WebComponents.CleanString.InputText(txtUserId.Text, 50);
string password = WebComponents.CleanString.InputText(txtPassword.Text, 50);

// Hand off to the account controller to control the naviagtion
ProcessFlow.AccountController accountController = new ProcessFlow.AccountController();


if (!accountController.ProcessLogin(userId, password))
{

// If we fail to login let the user know
valUserId.ErrorMessage = MSG_FAILURE;
valUserId.IsValid = false;
}
}
}
Part 2 AccountController放在表示层有两个作用,其一作为Session容器,其二作为工作流控制器。这里的代码分为两部分,前半部分调用业务层的Account依据用户名和密码取用户信息。后半部分用FormsAuthentication进行授权,然后把页面重定向到相应的页面。Form授权跟配置文件有关,相关代码列在下面。Authentication元素指出授权方式是Forms,location元素指出哪些页面是受保护页面。

public bool ProcessLogin(string userId, string password)
{

// Use the account business logic layer to login
Account account = new Account();
AccountInfo myAccountInfo = account.SignIn(userId, password);

//If login is successful then store the state in session and redirect

if (myAccountInfo != null)
{
HttpContext.Current.Session[ACCOUNT_KEY] = myAccountInfo;
// Determine where to redirect the user back too
// If they came in from the home page, take them to a similar page

if (FormsAuthentication.GetRedirectUrl(userId, false).EndsWith(URL_DEFAULT))
{

FormsAuthentication.SetAuthCookie(userId, false);
HttpContext.Current.Response.Redirect(URL_ACCOUNTSIGNIN, true);


}else
{
// Take the customer back to where the came from
FormsAuthentication.SetAuthCookie(userId, false);

HttpContext.Current.Response.Redirect(FormsAuthentication.GetRedirectUrl(userId, false), true);
}

return true;

}else
{
// Login has failed so return false
return false;
}
}
<system.web>
<authentication mode="Forms">
<forms name="PetShopAuth" loginUrl="SignIn.aspx" protection="None" timeout="60" />
</authentication>
…
</system.web>
<location path="EditAccount.aspx">
<system.web>
<authorization>
<deny users="?" />
</authorization>
</system.web>
</location>
<location path="OrderBilling.aspx">
<system.web>
<authorization>
<deny users="?" />
</authorization>
</system.web>
</location>
<location path="OrderShipping.aspx">
<system.web>
<authorization>
<deny users="?" />
</authorization>
</system.web>
</location>
<location path="OrderProcess.aspx">
<system.web>
<authorization>
<deny users="?" />
</authorization>
</system.web>
</location>
<location path="MyAccount.aspx">
<system.web>
<authorization>
<deny users="?" />
</authorization>
</system.web>
</location>
Part 3
业务层的Account仍然利用工厂创建合适的数据访问层Account对象,最后调用Account对象的SignIn方法取AccountInfo。

public AccountInfo SignIn(string userId, string password)
{

// Validate input
if ((userId.Trim() == string.Empty) || (password.Trim() == string.Empty))
return null;

// Get an instance of the account DAL using the DALFactory
IAccount dal = PetShop.DALFactory.Account.Create();

// Try to sign in with the given credentials
AccountInfo account = dal.SignIn(userId, password);

// Return the account
return account;
}
Part 4 工厂方法略,直接看数据访问层的Account如何实现SignIn?其实也没有什么新东西,仍然调用DAAB从Account,Profile和Sigon三张表中获取客户信息,最后返回AccountInfo对象。

public AccountInfo SignIn(string userId, string password)
{

SqlParameter[] signOnParms = GetSignOnParameters();

signOnParms[0].Value = userId;
signOnParms[1].Value = password;


using (SqlDataReader rdr = SQLHelper.ExecuteReader(SQLHelper.CONN_STRING_NON_DTC, CommandType.Text, SQL_SELECT_ACCOUNT, signOnParms))
{

if (rdr.Read())
{
AddressInfo myAddress = new AddressInfo(rdr.GetString(1), rdr.GetString(2), rdr.GetString(3), rdr.GetString(4), rdr.GetString(5), rdr.GetString(6), rdr.GetString(7), rdr.GetString(8), rdr.GetString(9));
return new AccountInfo(userId, password, rdr.GetString(0), myAddress, rdr.GetString(10), rdr.GetString(11), Convert.ToBoolean(rdr.GetInt32(12)), Convert.ToBoolean(rdr.GetInt32(13)));
}
return null;
}
}
场景 7:修改客户信息,与添加客户的原理类似,这里不再分析。
场景 8: 注销登录。通过Sigout页面调用了AccountController的Logout方法。内部原理是:调用FormsAuthentication对授权信息清空,并且把HttpContext中的Session清空。

public void LogOut()
{

// Clear the authentication ticket
FormsAuthentication.SignOut();
// Clear the contents of their session
HttpContext.Current.Session.Clear();
// Tell the system to drop the session reference so that it does
// not need to be carried around with the user
HttpContext.Current.Session.Abandon();
}
场景 9:显示动态广告,这里用到用户控件,用户控件用到Html的服务器控件,用户控件自身用是否已登录,以及用户信息中是否显示广告来决定是否显示动态广告。
Part 1 用户控件的页面代码,给Div加上runat属性,它就是一个服务器端控件。
<div id="areaBanner" runat="server" visible="false">
<img src="Imags/space.gif" width="1" height="10"><br>
<table width="100%" cellspacing="0" border="0" background="images/footerStripe.gif">
<tr>
<td><img src="Images/space.gif" width="1" height="10"></td>
</tr>
<tr>
<td align="middle" border="0"><span id="areaImage" runat="server"></span></td>
</tr>
<tr>
<td><img src="Images/space.gif" width="1" height="10"></td>
</tr>
</table>
</div>
Part 2 CodeBehide代码,根据用户信息决定是否显示动态广告。这个广告图片地址放入缓存,如果缓存中不存在才取数据库取。到数据库取信息的过程就是业务层调用数据工厂创建数据访问层对象,然后调用该数据访问层对象取得图片地址信息,这个过程在前面已经分析过多次,这里不再分析。

private void ShowBanner()
{


if (Request.IsAuthenticated == true)
{

ProcessFlow.AccountController accountController = new ProcessFlow.AccountController();

// Retrieve the account information from the account controller
AccountInfo myAccount = accountController.GetAccountInfo(false);


if (myAccount != null)
{
areaBanner.Visible = myAccount.IsShowBanners;
string categoryKey = myAccount.Category;
string bannerKey = "Banner" + categoryKey;
string bannerPath = "";


if(Cache[bannerKey] != null)
{
// If the data is already cached, then used the cached copy
bannerPath = ( (string)Cache[bannerKey] );

}else
{
// If the data is not cached, then create a new profile object object and request the data
Profile profile = new Profile();

bannerPath = profile.GetBannerPath(categoryKey);

// Store the results of the call in the Cache and set the time out to 6 hours
Cache.Add(bannerKey, bannerPath, null, DateTime.Now.AddHours(6), Cache.NoSlidingExpiration , CacheItemPriority.High, null);
}

areaImage.InnerHtml = bannerPath;
}
}
}
现在对用户管理进行总结,这部分用了一个AccountController,主要功能是对用户操作提供一个统一的外观,另外对用户的Session进行统一管理。好处有两点,第一,代码清晰,便于维护;第二,保证Session关键字唯一,便于管理Session。
场景10:如何实现购物车?这部分比较复杂主要是要理解购物车的原理,所以仔细读Cart类的代码绝对是必要的。
Part 1 添加宠物到购物车的过程是把连接指向带有宠物ID参数的ShoppingCart页面,该页面含有两个只读视图的自定义控件:viewstatepager(该控件内容后面会详细讨论)。第一个控件绑定订单明细,第二个控件绑定用户喜欢的种类的所有宠物列表。
Part 2 CodeBehide代码通过CartController操作Session中的购物车。每次页面load的时候往购物车添加一个宠物,点击按钮从购物车里删除宠物或者修改宠物数量,并且随时刷新总价。这个页面用ViewState保持页面上的数据,而不是Session,减少了资源代价。

override protected void OnLoad(EventArgs e)
{

// Create an instance of the cart controller
ProcessFlow.CartController cartController = new ProcessFlow.CartController();

myCart = cartController.GetCart(true);


if (!Page.IsPostBack)
{

// Get the itemdId from the query string
string itemId = Request["itemId"];


if (itemId != null)
{
// Clean the input string
itemId = WebComponents.CleanString.InputText(itemId, 50);
myCart.Add(itemId);
cartController.StoreCart(myCart);
}
}

//Get an account controller
ProcessFlow.AccountController accountController = new ProcessFlow.AccountController();

//Get the user's favourite category
string favCategory = accountController.GetFavouriteCategory();

//If we have a favourite category, render the favourites list

if (favCategory != null)
{
favorites.Visible = true;
ViewState[KEY_CATEGORY] = favCategory;
}

Refresh();
}


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

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
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));
}
Part 3 Cart业务对象,用ArrayList对象装宠物,一个decimal变量记录总价格。Cart类扩展了Ienumerable,这样就可以把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
/// </summary>
private ArrayList _items = new ArrayList();

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
{

get
{ return _total; }

set
{ _total = value; }
}


/**//// <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;
_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);
_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)
{
_items.Remove(item);
_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];
_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);
}
}
}
场景 11:结帐过程是如何实现的?这个过程实现比较复杂,涉及到几步确认订单和填写送货地址等步骤。下面先拿顺序图列出调用顺序,需要注意的是Checkout由三个页面组成,Cart实际只的是CartController。

Part 1 Checkout页面的Load过程中从Session中取得当前得购物车。Simple控件分页事件中,把购物车里的宠物列出来。
// Create an instance of the cart controller
ProcessFlow.CartController cartController = new ProcessFlow.CartController();
// Fetch the cart state from the controller
myCart = cartController.GetCart(false);
cart.DataSource = myCart.GetCartItems();
cart.DataBind();
Part 2 OrderBilling页面从Session中获取AccountInfo,并且调用Account获取AddressInfo,最后把信息显示出来。
ProcessFlow.AccountController accountController = new ProcessFlow.AccountController();

AccountInfo myAccount = accountController.GetAccountInfo(true);


if (myAccount != null)
{
Account account = new Account();
billAddr.Address = account.GetAddress(myAccount.UserId);
}
Part 3 OrderBilling页面点击Continue后,把信用卡信息,地址信息,订单信息都存入Session。
CreditCardInfo creditCard = new CreditCardInfo(cardType, cardNumber, string.Format(FORMAT_EXPIRATION, cardMonth, cardYear));
cartController.StoreCreditCard(creditCard);

AddressInfo billingAddress = billAddr.Address;
// Now store the billing information
cartController.StoreBillingAddress(billAddr.Address);

// Continue with the order process
cartController.ContinueOrder(chkShipBilling.Checked);
Part 4 OrderBilling页面继续点击Continue后,显示OrderProcess页,在页面Load过程中,先进行数据持久化,然后显示订单详细信息。最后清空Session。
ProcessFlow.CartController cartController = new ProcessFlow.CartController();

OrderInfo newOrder = cartController.PurchaseCart();

//Display the order info to the user
lblOrderId.Text = newOrder.OrderId.ToString();
lblOrderDate.Text = newOrder.Date.ToLongDateString();;
lblUserId.Text = newOrder.UserId;
lblCardType.Text = newOrder.CreditCard.CardType;
lblCardNumber.Text = newOrder.CreditCard.CardNumber;
lblCardExpiration.Text = newOrder.CreditCard.CardExpiration;
statAddrBill.address = newOrder.BillingAddress;
statAddrShip.address = newOrder.ShippingAddress;
cart.DataSource = newOrder.LineItems;
cart.DataBind();
Part 5 在Order数据持久化过程中,采用了分布式事务处理。一方面插入订单信息,另一方面更新库存信息。这两方面的数据分别属于两个数据库。
[Transaction(System.EnterpriseServices.TransactionOption.Required)]
[ClassInterface(ClassInterfaceType.AutoDispatch)]
[ObjectPooling(MinPoolSize=4, MaxPoolSize=4)]
[Guid("14E3573D-78C8-4220-9649-BA490DB7B78D")]

public class OrderInsert : ServicedComponent
{

// These variables are used to demonstrate the rollback characterisitic
// of distributed transactions and would not form part of a production application
private const string ACID_USER_ID = "ACID";
private const string ACID_ERROR_MSG = "ACID test exception thrown for distributed transaction!";

// Instruct COM+ whether this object can be returned to the pool

protected override bool CanBePooled()
{

// Always return true
return true;
}


/**//// <summary>
/// A method to insert a new order into the system
/// The orderId will be generated within the method and should not be supplied
/// As part of the order creation the inventory will be reduced by the quantity ordered
/// </summary>
/// <param name="order">All the information about the order</param>
/// The new orderId is returned in the order object
[AutoComplete]

public int Insert(OrderInfo order)
{

// Get an instance of the Order DAL using the DALFactory
IOrder dal = PetShop.DALFactory.Order.Create();

// Call the insert method in the DAL to insert the header
int orderId = dal.Insert(order);

// Get an instance of the Inventory business component
Inventory inventory = new Inventory();
inventory.TakeStock( order.LineItems);
// As part of the sample application we have created a user
// you can tested distributed transactions with
// If the order has been created with the user 'Acid',
// then throw an exception which will rollback the entire transaction
if (order.UserId == ACID_USER_ID)
throw new ApplicationException(ACID_ERROR_MSG);

// Set the orderId so that it can be returned to the caller
return orderId;
}
}
Part 6 至于如何调用数据访问层来实现数据插入和更新,在前面已有类似的代码分析,这里不在分析。
场景 12:查询订单的Web Service。由于业务层对业务操作进行了封装,WebService只要直接调用OrderRead的GetOrder方法就可以了。至于OrderRead的GetOrder怎么实现?无非就是用DALFactory创建一个合适的DAL层的对象,然后进行数据库查询,最后返回一个OrderInfo对象。所以代码中就不列出业务层以下的代码。
[WebMethod]

public OrderInfo GetOrder(int orderId)
{

// Use the order component optimized for reads
OrderRead orderWS = new OrderRead();

return orderWS.GetOrder(orderId);
}