對於Microsoft .net PetShop程序中的購物車和訂單處理模塊,文中主要分析兩種技術的應用:
1. Profile技術在PetShop程序中用於三處:
1) 購物車ShoppingCart -下面的例子圍繞購物車流程進行
2) 收藏WishList
3) 用戶信息AccountInfo
註冊新用戶 NewUser.aspx :使用的是CreateUserWizard 控件,基於MemberShip機制,在數據庫MSPetShop4Services的表aspnet_Users中創建用戶
修改用戶註冊信息 UserProfile.aspx: 基於Profile技術,在數據庫MSPetShop4Profile的表Profiles和Account中創建用戶信息
2. 異步消息處理技術運用於訂單處理
4.1 Web.config配置
Profile可以利用數據庫存儲關於用戶的個性化信息,有點象session對象,但session對象是有生存期的,在生存期後,session對象自動失效了。而profile不同,除非顯式移除它。要實現profile功能,必須先在web.config中進行定義。
在web.congfig中,將會定義一些屬性/值,分別存貯將要保存的變量和值,比如language屬性,定義其值是string類型,如此類推。而<group>標籤,則是將一些相同或類似功能的變量值放在一起。
程序中使用方法:Profile.language = ddlLanguage.SelectedItem.Value;
<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>
4.2 購物車程序流程-Profile技術
1. 點擊“加入購物車”: http://localhost:2327/Web/ShoppingCart.aspx?addItem=EST-34
2. ShoppingCart.aspx文件處理:在init方法之前處理
protected void Page_PreInit(object sender, EventArgs e) {
if (!IsPostBack) {
string itemId = Request.QueryString["addItem"];
if (!string.IsNullOrEmpty(itemId)) {
Profile.ShoppingCart.Add(itemId); //注意ShoppingCart的類型是PetShop.BLL.Cart
//Save 方法將修改後的配置文件屬性值寫入到數據源,如ShoppingCart屬性已經改變
Profile.Save();
// Redirect to prevent duplictations in the cart if user hits "Refresh"
//防止刷新造成 多次提交
Response.Redirect("~/ShoppingCart.aspx", true);//將客戶端重定向到新的 URL。指定新的 URL 並指定當前頁的執行是否應終止。
}
}
3. PetShop.BLL.Cart類
// Dictionary: key/value
private Dictionary<string, CartItemInfo> cartItems = new Dictionary<string, CartItemInfo>();
/// <summary>
/// Add an item to the cart.
/// When ItemId to be added has already existed, this method will update the quantity instead.
/// </summary>
/// <param name="itemId">Item Id of item to add</param>
public void Add(string itemId) {
CartItemInfo cartItem;
//獲取與指定的鍵相關聯的值TryGetValue(TKey key,out TValue value)
if (!cartItems.TryGetValue(itemId, out cartItem)) {
Item item = new Item();
ItemInfo data = item.GetItem(itemId);
if (data != null) {
CartItemInfo newItem = new CartItemInfo(itemId, data.ProductName, 1, (decimal)data.Price, data.Name, data.CategoryId, data.ProductId);
cartItems.Add(itemId, newItem);
}
}
else
cartItem.Quantity++;
}
4. 更新Profile
//Save 方法將修改後的配置文件屬性值寫入到數據源,如ShoppingCart屬性已經改變
Profile.Save();
如何更新:
根據配置的ShoppingCartProvider類型 PetShop.Profile.PetShopProfileProvider。
ASP.NET 配置文件提供對用戶特定屬性的持久性存儲和檢索。配置文件屬性值和信息按照由 ProfileProvider 實現確定的方式存儲在數據源中。
每個用戶配置文件在數據庫的 Profiles 表中進行唯一標識。該表包含配置文件信息,如應用程序名稱和上次活動日期。
CREATE TABLE Profiles
(
UniqueID AutoIncrement NOT NULL PRIMARY KEY,
Username Text (255) NOT NULL,
ApplicationName Text (255) NOT NULL,
IsAnonymous YesNo,
LastActivityDate DateTime,
LastUpdatedDate DateTime,
CONSTRAINT PKProfiles UNIQUE (Username, ApplicationName)
)
5. PetShop.Profile. PetShopProfileProvider類, 繼承自ProfileProvider
// 創建 PetShop.SQLProfileDAL.PetShopProfileProvider類-數據庫操作
private static readonly IPetShopProfileProvider dal
= DataAccess.CreatePetShopProfileProvider();
/// <summary>
/// 設置指定的屬性設置組的值
/// </summary>
public override void SetPropertyValues(SettingsContext context, SettingsPropertyValueCollection collection) {
string username = (string)context["UserName"];
CheckUserName(username);
bool isAuthenticated = (bool)context["IsAuthenticated"];
int uniqueID = dal.GetUniqueID(username, isAuthenticated, false, ApplicationName);
if(uniqueID == 0)
uniqueID = dal.CreateProfileForUser(username, isAuthenticated, ApplicationName);
foreach(SettingsPropertyValue pv in collection) {
if(pv.PropertyValue != null) {
switch(pv.Property.Name) {
case PROFILE_SHOPPINGCART: //ShoppingCart
SetCartItems(uniqueID, (Cart)pv.PropertyValue, true);
break;
case PROFILE_WISHLIST:
SetCartItems(uniqueID, (Cart)pv.PropertyValue, false);
break;
case PROFILE_ACCOUNT:
if(isAuthenticated)
SetAccountInfo(uniqueID, (AddressInfo)pv.PropertyValue);
break;
default:
throw new ApplicationException(ERR_INVALID_PARAMETER + " name.");
}
}
}
UpdateActivityDates(username, false);
}
// Update cart
private static void SetCartItems(int uniqueID, Cart cart, bool isShoppingCart) {
dal.SetCartItems(uniqueID, cart.CartItems, isShoppingCart);
}
6. PetShop.SQLProfileDAL. PetShopProfileProvider類
使用事務:包含兩個sql動作,先刪除,再插入
/// <summary>
/// Update shopping cart for current user
/// </summary>
/// <param name="uniqueID">User id</param>
/// <param name="cartItems">Collection of shopping cart items</param>
/// <param name="isShoppingCart">Shopping cart flag</param>
public void SetCartItems(int uniqueID, ICollection<CartItemInfo> cartItems, bool isShoppingCart) {
string sqlDelete = "DELETE FROM Cart WHERE UniqueID = @UniqueID AND IsShoppingCart = @IsShoppingCart;";
SqlParameter[] parms1 = {
new SqlParameter("@UniqueID", SqlDbType.Int),
new SqlParameter("@IsShoppingCart", SqlDbType.Bit)};
parms1[0].Value = uniqueID;
parms1[1].Value = isShoppingCart;
if (cartItems.Count > 0) {
// update cart using SqlTransaction
string sqlInsert = "INSERT INTO Cart (UniqueID, ItemId, Name, Type, Price, CategoryId, ProductId, IsShoppingCart, Quantity) VALUES (@UniqueID, @ItemId, @Name, @Type, @Price, @CategoryId, @ProductId, @IsShoppingCart, @Quantity);";
SqlParameter[] parms2 = {
new SqlParameter("@UniqueID", SqlDbType.Int),
new SqlParameter("@IsShoppingCart", SqlDbType.Bit),
new SqlParameter("@ItemId", SqlDbType.VarChar, 10),
new SqlParameter("@Name", SqlDbType.VarChar, 80),
new SqlParameter("@Type", SqlDbType.VarChar, 80),
new SqlParameter("@Price", SqlDbType.Decimal, 8),
new SqlParameter("@CategoryId", SqlDbType.VarChar, 10),
new SqlParameter("@ProductId", SqlDbType.VarChar, 10),
new SqlParameter("@Quantity", SqlDbType.Int)};
parms2[0].Value = uniqueID;
parms2[1].Value = isShoppingCart;
SqlConnection conn = new SqlConnection(SqlHelper.ConnectionStringProfile);
conn.Open();
SqlTransaction trans = conn.BeginTransaction(IsolationLevel.ReadCommitted);
try {
SqlHelper.ExecuteNonQuery(trans, CommandType.Text, sqlDelete, parms1);
foreach (CartItemInfo cartItem in cartItems) {
parms2[2].Value = cartItem.ItemId;
parms2[3].Value = cartItem.Name;
parms2[4].Value = cartItem.Type;
parms2[5].Value = cartItem.Price;
parms2[6].Value = cartItem.CategoryId;
parms2[7].Value = cartItem.ProductId;
parms2[8].Value = cartItem.Quantity;
SqlHelper.ExecuteNonQuery(trans, CommandType.Text, sqlInsert, parms2);
}
trans.Commit();
}
catch (Exception e) {
trans.Rollback();
throw new ApplicationException(e.Message);
}
finally {
conn.Close();
}
}
else
// delete cart
SqlHelper.ExecuteNonQuery(SqlHelper.ConnectionStringProfile, CommandType.Text, sqlDelete, parms1);
}
4.3 訂單處理技術
訂單處理技術:――分佈式事務
1) 同步:直接在事務中 將訂單 插入到數據庫中,同時更新庫存
2) 異步:訂單-》消息隊列(使用MSMQ)-》後臺處理
4.3.1 使用Wizard組件
4.3.2 分佈式事務處理技術
開啓MSDTC 服務支持分佈式事務. To start the MSDTC service, open Administrative Tools | Services and start the Distributed Transaction Coordinator service
4.3.3 MSMQ 消息隊列簡介
1)引用隊列
引用隊列有三種方法,通過路徑、格式名和標籤引用隊列,這裏我只介紹最簡單和最常用的方法:通過路徑引用隊列。隊列路徑的形式爲 machinename/queuename。指向隊列的路徑總是唯一的。下表列出用於每種類型的隊列的路徑信息:
如果是發送到本機上,還可以使用”.”代表本機名稱。
2)消息的創建
不過要使用MSMQ開發你的消息處理程序,必須在開發系統和使用程序的主機上安裝消息隊列。消息隊列的安裝屬於Windows組件的安裝,和一般的組件安裝方法類似。
往系統中添加隊列十分的簡單,打開[控制面板]中的[計算機管理],展開[服務和應用程序],找到並展開[消息隊列](如果找不到,說明你還沒有安裝消息隊列,安裝windows組件),右擊希望添加的消息隊列的類別,選擇新建隊列即可。
消息接收服務位於System.Messaging中,在初始化時引用消息隊列的代碼很簡單,如下所示:
MessageQueue Mq=new MessageQueue(“.//private$//jiang”);
通過Path屬性引用消息隊列的代碼也十分簡單:
MessageQueue Mq=new MessageQueue();
Mq.Path=”.//private$//jiang”;
使用Create 方法可以在計算機上創建隊列:
System.Messaging.MessageQueue.Create(@"./private$/jiang");
3) 發送和接收消息
過程:消息的創建-》發送-》接收-》閱讀-》關閉
簡單消息的發送示例如下:
Mq.Send(1000); //發送整型數據
Mq.Send(“This is a test message!”); //發送字符串
接收消息由兩種方式:通過Receive方法接收消息同時永久性地從隊列中刪除消息;通過Peek方法從隊列中取出消息而不從隊列中移除該消息。如果知道消息的標識符(ID),還可以通過ReceiveById方法和PeekById方法完成相應的操作。
接收消息的代碼很簡單:
Mq.Receive(); //或Mq.ReceiveById(ID);
Mq.Peek(); // 或Mq.PeekById(ID);
閱讀消息
接收到的消息只有能夠讀出來纔是有用的消息,因此接收到消息以後還必須能讀出消息,而讀出消息算是最複雜的一部操作了。消息的序列化可以通過Visual Studio 和 .NET Framework 附帶的三個預定義的格式化程序來完成:XMLMessageFormatter 對象( MessageQueue 組件的默認格式化程序設置)、BinaryMessageFormatter 對象、ActiveXMessageFormatter 對象。由於後兩者格式化後的消息通常不能爲人閱讀,所以我們經常用到的是XMLMessageFormatter對象。
使用XMLMessageFormatter對象格式化消息的代碼如下所示:
string[] types = { "System.String" };
((XmlMessageFormatter)mq.Formatter).TargetTypeNames = types;
Message m=mq.Receive(new TimeSpan(0,0,3));
將接收到的消息傳送給消息變量以後,通過消息變量m的Body屬性就可以讀出消息了:
MessageBox.Show((string)m.Body);
關閉消息隊列
消息隊列的關閉很簡單,和其他對象一樣,通過Close函數就可以實現了:
Mq.Close();
4.3.4 PetShop程序中訂單處理-使用同步消息
默認程序使用同步消息 處理,直接操作數據庫插入訂單,更新庫存類
4.3.5 PetShop程序中訂單處理-使用異步消息
1) Web程序中調用PetShop.BLL.Order類方法: Insert(OrderInfo order);
2) PetShop.BLL.Order類
//IOrderStrategy接口中只有一個插入訂單方法:void Insert(PetShop.Model.OrderInfo order);
//得到PetShop.BLL. OrderAsynchronous類
private static readonly PetShop.IBLLStrategy.IOrderStrategy orderInsertStrategy = LoadInsertStrategy();
//IOrder接口中有兩種方法:Send()與Receive() -消息隊列
private static readonly PetShop.IMessaging.IOrder orderQueue
= PetShop.MessagingFactory.QueueAccess.CreateOrder();
public void Insert(OrderInfo order) {
// Call credit card procesor,採用隨機化方法設置訂單認證數字
ProcessCreditCard(order);
// Insert the order (a)synchrounously based on configuration
orderInsertStrategy.Insert(order); //調用PetShop.BLL.OrderAsynchronous類
}
3) PetShop.BLL. OrderAsynchronous類
// CreateOrder()方法得到PetShop.MSMQMessaging .Order類的實例
private static readonly PetShop.IMessaging.IOrder asynchOrder
= PetShop.MessagingFactory.QueueAccess.CreateOrder();
public void Insert(PetShop.Model.OrderInfo order) {
asynchOrder.Send(order); //調用PetShop.MSMQMessaging.Order類
}
4) PetShop.MSMQMessaging項目 -關鍵(發送/接收消息)
PetShopQueue基類:創建消息隊列,發送和接收消息
Order類:繼承自PetShopQueue類
public new OrderInfo Receive() { //從隊列中接收消息
base.transactionType = MessageQueueTransactionType.Automatic;
return (OrderInfo)((Message)base.Receive()).Body;
}
public void Send(OrderInfo orderMessage) { //發送消息到隊列
base.transactionType = MessageQueueTransactionType.Single;
base.Send(orderMessage);
}
5) PetShop.OrderProcessor項目-後臺處理訂單,將它們插入到數據庫中
Program類:多線程後臺訂單處理程序,可寫成一個控制檯程序,作爲windows服務開啓