场景1: 选择首页上的宠物图标,查询出该宠物的品种的过程。假设选择宠物――鸟。
< 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效率高。 Part 2 Code Behide代码,主要是SimplePager控件的事件onpageindexchanged,这段代码首先去缓存中查找结果,如果没有才去数据库查询,运用缓存API,提高执行效率。去数据库查询时,调用了业务层的类Product和方法GetProductsByCategory(categoryKey)。最后把结果绑定到SimplePager控件。关于SimplePager后面会单独讲解。Part 3 业务层的Product中的GetProductsByCategory方法是怎么实现的呢?关键代码是用PetShop.DALFactory中的Product类Create一个Iproduct接口。然后拿这个利用这个接口的GetProductsByCategory(category)方法。显然,运行过程中产生各种符合IProduct接口的对象,然后调用这个对象的GetProductsByCategory方法。Part 4 DALFactory中的Product如何进行Create的呢?这里利用DotNet的反射功能,运行时从配置文件中读取要创建的类的全名,然后利用反射创建出这个类。Part 5 以上我们见过n次IProduct,这是个什么东西呢?答案是仅仅是一个接口,一个空架子。
< 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 >
// 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;
// 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);
}
/**//// 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);
}
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使用。 针对Oracle的代码与针对SQL server的代码非常类似,不同的地方是:SQL语句和DAAB。Oracle的DAAB是仿照SQL server写的调用了Oracle的驱动API进行数据库操作。因为非常类似,以后的代码分析中就只分析针对SQLserver代码。
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;
}
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; }
}
}
}
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授权,后面会单独分析。
// 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);
}
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 2 AccountController放在表示层有两个作用,其一作为Session容器,其二作为工作流控制器。这里的代码分为两部分,前半部分调用业务层的Account依据用户名和密码取用户信息。后半部分用FormsAuthentication进行授权,然后把页面重定向到相应的页面。Form授权跟配置文件有关,相关代码列在下面。Authentication元素指出授权方式是Forms,location元素指出哪些页面是受保护页面。
// 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;
}
}
< 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 >
// 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;
}
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;
}
}
// 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();
}
< 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 >
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;
}
}
}
// 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));
}
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);
}
}
}
Part 1 Checkout页面的Load过程中从Session中取得当前得购物车。Simple控件分页事件中,把购物车里的宠物列出来。
ProcessFlow.CartController cartController = new ProcessFlow.CartController();
// Fetch the cart state from the controller
myCart = cartController.GetCart( false );
cart.DataSource = myCart.GetCartItems();
cart.DataBind();
AccountInfo myAccount = accountController.GetAccountInfo( true );
if (myAccount != null ) {
Account account = new Account();
billAddr.Address = account.GetAddress(myAccount.UserId);
}
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);
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();
[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;
}
}
public OrderInfo GetOrder( int orderId) {
// Use the order component optimized for reads
OrderRead orderWS = new OrderRead();
return orderWS.GetOrder(orderId);
}
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;
}
}
}
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;
}