Petshop4学习
Petshop4 的研究分析 :http://blog.csdn.net/sjklove/archive/2006/11/10/1377405.aspx
1.项目概述与架构分析
微软刚推出了基于ASP.NET 2.0下的Pet Shop 4, 该版本有了一个全新的用户界面。是研究ASP.NET 2.0的好范例啊,大家都知道,一直以来,在.NET和Java之间争论不休,到底使用哪个平台开发的企业级应用性能最好、结构最优、生产力最高。为了用事实说话,通过对项目各方面的性能评估进而在比较.NET和Java的高下。用户做比较的这个项目就是Petshop。正因为Petshop肩负着上面所说的重任,各方面必须是最优的,架构设计应该是经过慎重考虑的。所以其一经推出,便成为了开发者、架构师等人学习、研究的典范。
日前微软推出了基于.NET Framework 2.0开发的Petshop 4。新的Petshop4实现了与Petshop 3相同甚至更多的特性,由于采用了Master Pages,Membership,以及Profile,SqlCacheDependency,但是代码量却减少了四分之一。同时,在事务、数据缓存、安全方面使用了.NET 2.0附带的特性,构建了一个灵活的最佳实践的应用程序。
他们利用了Project Conversion Wizard把项目从ASP.NET 1.1移植到了ASP.NET 2.0,然后做了以下改动:
1.用System.Transactions代替了原来的Serviced Components提供的事务功能
代码实现:PetShop.BLL.OrderSynchronous 的 public void Insert(PetShop.Model.OrderInfo order)。
2.用强类型的范型集合代替了原来的弱类型集合
public IList<ProductInfo> GetProductsByCategory(string category)
{
// Return new if the string is empty
if (string.IsNullOrEmpty(category))
return new List<ProductInfo>();
// Run a search against the data store
return dal.GetProductsByCategory(category);
}
3.采用ASP.NET 2.0 Membership来做认证和授权
4.创建了针对Oracle 10g的Custom ASP.NET 2.0 Membership Provider
5.利用ASP.NET 2.0的Custom Oracle 和 SQL Server Profile Providers 做用户状态管理,包括购物车等
6.采用了Master Pages,取代了原来的用户控件,来实现统一的界面效果
7.使用了ASP.NET 2.0 Wizard控件实现check-out
8.使用了SqlCacheDependency来实现数据库层次的缓存更新(cache invalidation)功能
9.使用了消息队列来实现异时订单处理。
2.整体架构:
数据库:(暂略)
项目列表:从整体可以看出,Pet Shop 4的项目体系已经很庞大,考虑的方面也较3.0更全面复杂。
序号 |
项目名称 |
描述 |
1 |
BLL |
业务逻辑层 |
2 |
CacheDependencyFactory |
缓存依赖类的工厂类 |
3 |
WEB |
表示层 |
4 |
DALFactory |
数据层的抽象工厂 |
5 |
DBUtility |
数据访问类组件 |
6 |
IBLLStrategy |
同步/异步策略接口 |
7 |
ICacheDependency |
缓存依赖类接口 |
8 |
IDAL |
数据访问层接口定义 |
9 |
IMessaging |
异时处理消息队列接口定义 |
10 |
IProfileDAL |
Profile的数据访问层接口定义 |
11 |
Membership |
Membership认证和授权管理 |
12 |
MessagingFactory |
异时处理消息队列的抽象工厂 |
13 |
Model |
业务实体 |
14 |
MSMQMessaging |
异时处理消息队列的实现 |
15 |
OracleDAL |
Oracle数据访问层 |
16 |
OracleProfileDAL |
Oracle的Profile Providers 做用户状态管理,包括购物车等 |
17 |
OrderProcessor |
后台处理进程,处理订单队列 |
18 |
Profile |
Profile的数据访问层 |
19 |
ProfileDALFactory |
ProfileDAL的工厂类(反射创建ProfileDAL) |
20 |
SQLProfileDAL |
SQL Server 的Profile Providers 做用户状态管理,包括购物车等 |
21 |
SQLServerDAL |
SQLServer数据访问层 |
22 |
TableCacheDependency |
缓存依赖实现类 |
项目分解:
由于整体已经有22个项目,所以,对于初学者一看就晕了,所以,我做了分解,可以大体上分几块去理解。
序号 |
项目名称 |
描述 |
1 |
WEB |
表示层 |
2 |
Model |
业务实体 |
3 |
BLL |
业务逻辑层 |
4 |
DALFactory |
数据层的抽象工厂 |
5 |
IDAL |
数据访问层接口定义 |
6 |
SQLServerDAL |
SQLServer数据访问层 |
7 |
OracleDAL |
Oracle数据访问层 |
8 |
DBUtility |
数据库访问组件基础类 |
9 |
CacheDependencyFactory |
缓存依赖类的工厂类 |
10 |
ICacheDependency |
缓存依赖类接口 |
11 |
TableCacheDependency |
缓存依赖实现类 |
12 |
IBLLStrategy |
同步/异步处理策略接口(实现在bll根据配置反射选择) |
13 |
MessagingFactory |
异时处理消息队列的抽象工厂 |
14 |
IMessaging |
异时处理消息队列接口定义 |
15 |
MSMQMessaging |
异时处理消息队列的实现 |
16 |
Profile |
Profile的数据访问层 |
17 |
ProfileDALFactory |
ProfileDAL的工厂类(反射创建ProfileDAL) |
18 |
IProfileDAL |
Profile的数据访问层接口定义 |
19 |
OracleProfileDAL |
Oracle的Profile Providers 做用户状态管理 |
20 |
SQLProfileDAL |
SQL Server 的Profile Providers 做用户状态管理 |
21 |
Membership |
Membership认证和授权管理 |
22 |
OrderProcessor |
后台处理进程,处理订单队列 |
3.Petshop 4中的设计模式:
工厂模式:
首当其冲的就是工厂模式,很容易就可以看出来,也是应用最多的。
DALFactory:数据访问层的抽象工厂(决定创建哪种数据库类型的数据访问层。可以选择:SQLServer,Oracle)
CacheDependencyFactory:缓存依赖类的工厂类。(创建具体表的缓存依赖)
MessagingFactory :异时处理消息队列的抽象工厂(反射创建具体的异时处理类)
ProfileDALFactory:ProfileDAL的工厂类(反射选择创建Oracle 和SQL Server的 ProfileDAL)
策略模式: IorderStrategy
中介模式
CategoryDataProxy ItemDataProxy ProductDataProxy
暂时只看了这么多,以后有时间继续分解,如果你有不同的见解或经验,也请写下来,好让大家来共同学习,共同探讨,共同进步。
(作者:李天平 转载请注明)
具体介绍可以参看MSDN:
.NET Pet Shop 4: Migrating an ASP.NET 1.1 Application to 2.0
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnbda/html/bdasamppet4.asp
下载:
Pet Shop 4
Microsoft .NET Pet Shop 4:将 ASP.NET 1.1 应用程序迁移到 2.0
适用于:
Microsoft .NET Framework 2.0
Microsoft Visual Studio 2005
Microsoft Windows Server 2003
Microsoft InterNET Information Services
Microsoft Message Queuing
Microsoft SQL Server 2005
Oracle 10G Database
摘要:.NET Pet Shop 应用程序的设计说明了构建企业 n 层 .NET 2.0 应用程序的最佳做法,这种应用程序可能需要支持各种数据库平台和部署方案。
单击此处下载 .NET Pet Shop 4.0.msi。
本页内容
概述 | |
工作效率 | |
从 ASP.NET 1.1 迁移到 2.0 | |
体系结构 | |
抽象工厂模式 | |
用户界面增强 | |
加密配置信息 | |
模型对象 | |
Order 和 Inventory 架构 | |
Profile 数据库架构 | |
小结 |
概述
.NET Pet Shop 应用程序的设计说明了构建企业 n 层 .NET 2.0 应用程序的最佳做法,这种应用程序可能需要支持各种数据库平台和部署方案。
.NET Pet Shop 4 项目的目标是:
• |
工作效率:减少了 .NET Pet Shop 3 的代码数量 - 我们减少了近 25% 的代码。 |
• |
从 ASP.NET 1.1 迁移到 2.0:利用 ASP.NET 2.0 的新功能 - 我们利用母版页、成员身份和配置文件,并设计出一个新的、吸引人的用户界面。 图 1. .NET Pet Shop 4.0 |
• |
企业体系结构:构建一个灵活的最佳做法应用程序 - 我们实现了设计模式,以及表示层、业务层和数据层的分离。 |
工作效率
与 .NET Pet Shop 3 相比,.NET Pet Shop 4 中的代码量约减少了近 25%。减少代码行数的主要好处体现在表示层和数据访问层。
在表示层,我们减少了大约 25% 的代码。登录和签出步骤比完整的 ASP.NET 页面更简洁,需要的代码和 html 更少。这是因为向导控件本身处理过程流代码。使用母版页意味着使用较少的 html 代码和用户控件管理布局。相比于 Pet Shop 3 用户管理代码,成员身份服务处理身份验证的方式更简洁。
我们看到数据层节省的代码量最多,高达 36%。ASP.NET 2.0 SQL 成员身份提供程序取代了帐户管理代码。
表 1 给出逐层分解的完整代码量。
表 1. .NET Pet Shop 版本 3 与版本 4 的代码量对比 | ||
v3 | v4 | |
表示层 |
1,822 |
1,365 |
模型 |
349 |
395 |
业务逻辑层 |
210 |
199 |
数据访问层 |
1,538 |
985 |
代码总行数 |
3,919 |
2,944 |
图 2 对此做了进一步的图解。
图 2. 代码量对比图
.NET Pet Shop 4 引入了几个新功能,包括自定义的 ASP.NET 2.0 配置文件提供程序,以及通过 MSMQ 进行的异步定单处理等。表 2 显示新功能的代码数量:
表 2. .NET Pet Shop 4 新功能的代码量 | |
自定义配置文件 |
853 |
Oracle 成员身份 |
586 |
缓存依赖项 |
90 |
消息队列 |
147 |
代码总行数 |
1,676 |
从 ASP.NET 1.1 迁移到 2.0
为了实现 .NET Pet Shop 4 的目标,我们制定了下列计划:
• |
使用项目转换向导将 .NET Pet Shop 3.2 代码基从 ASP.NET 1.1 移植到 ASP.NET 2.0。 |
• |
规划我们想要包括的 ASP.NET 2.0 功能。 |
• |
实现一个支持这些功能的 n 层体系结构。 |
项目转换向导
首先,Visual Studio.NET 2005 项目转换向导迅速升级 .NET Pet Shop 3.2 代码基。通过这一基本移植,我们能够初步了解经过编译并在 ASP.NET 2.0 上运行的 .NET Pet Shop 3.2。
版本 3 和版本 4 之间的变化
通过升级 .NET Pet Shop 3.2 代码基以便在.NET Framework 2.0 上运行以及对 ASP.NET 2.0 的研究,我们推出了要在 .NET Pet Shop 4.0 中实现的以下主要功能:
• |
用 System.Transactions 代替服务组件。 |
• |
用强类型集合的泛型代替松散类型的 ILists。 |
• |
ASP.NET 2.0 成员身份,用于用户身份验证和授权。 |
• |
用于 Oracle 10G 的自定义 ASP.NET 2.0 成员身份提供程序。 |
• |
ASP.NET 2.0 自定义 Oracle 和 SQL Server 配置文件提供程序,用于用户状态管理。 |
• |
用母版页取代 ASP.NET Web 用户控件,从而获得一致的外观。 |
• |
ASP.NET 2.0 向导控件。 |
• |
使用 SqlCacheDependency(而非基于超时)的数据库级缓存失效。 |
• |
启用基于消息队列构建的异步 Order 处理。 |
什么是 System.Transactions?
System.Transactions 是 .NET 2.0 框架中新增的事务控件命名空间。它是一种处理分布式事务的新方式,没有 COM+ 注册和 COM+ 目录的开销。请注意,Microsoft 分布式事务协调器用于初始化事务。
运行情况
同步定单处理中的 Order.Insert() 方法使用 System.Transactions 插入一个定单并更新库存。通过添加对 System.Transaction 命名空间的引用,并将定单插入方法和库存减少方法包装在 TransactionScope 内,我们实现了 Order.Insert() 方法,如代码清单 1 所示。
清单 1. 运行中的 System.Transactions
using System;using System.Transactions;using PetShop.IBLLStrategy;namespace PetShop.BLL {/// <summary>/// This is a synchronous implementation of IOrderStrategy/// By implementing IOrderStrategy interface, the developer can/// add a new order insert strategy without re-compiling the whole/// BLL./// </summary>public class OrderSynchronous : IOrderStrategy {.../// <summary>/// Inserts the order and updates the inventory stock within/// a transaction./// </summary>/// <param name="order">All information about the order</param>public void Insert(PetShop.Model.OrderInfo order) {using (TransactionScope ts = newTransactionScope(TransactionScopeOption.Required)) {dal.Insert(order);// Update the inventory to reflect the current inventory// after the order submission.Inventory inventory = new Inventory();inventory.TakeStock(order.LineItems);// Calling Complete commits the transaction.// Excluding this call by the end of TransactionScope's// scope will rollback the transaction.ts.Complete();}}}}
在 .NET Pet Shop 3 中,分布式事务由企业服务处理,需要 COM+ 注册。OrderInsert 类从服务组件派生,事务由 COM+ 处理。然后,服务组件使用 regsvr32 命令进行注册。
清单 2. Pet Shop 3 的定单插入
using System;using System.Collections;using System.EnterpriseServices;using System.Runtime.InteropServices;...namespace PetShop.BLL {/// <summary>/// A business component to manage the creation of orders/// Creation of an order requires a distributed transaction/// so the Order class derives from ServicedComponents/// </summary>[Transaction(System.EnterpriseServices.TransactionOption.Required)][ClassInterface(ClassInterfaceType.AutoDispatch)][ObjectPooling(MinPoolSize=4, MaxPoolSize=4)][Guid("14E3573D-78C8-4220-9649-BA490DB7B78D")]public class OrderInsert : ServicedComponent {.../// <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>/// <returns>/// The new orderId is returned in the order object/// </returns>[AutoComplete]public int Insert(OrderInfo order) {// Get an instance of the Order DAL using the DALFactoryIOrder dal = PetShop.DALFactory.Order.Create();// Call the insert method in the DAL to insert the headerint orderId = dal.Insert(order);// Get an instance of the Inventory business componentInventory inventory = new Inventory();inventory.TakeStock( order.LineItems);...// Set the orderId so that it can be returned to the callerreturn orderId;}}}
System.Transactions 的优点
从企业服务移动到 System.Transactions 可以简化部署,因为后者不需要使用 COM+ 目录。使用 COM+ 目录时,我们忽略了其他一些额外的功能,只保留了分布式事务支持。System.Transaction 使得在 ASP.NET 2.0 应用程序中编程和部署分布式应用程序变得十分简单。System.Transactions 在运行时的性能提高了 50%,因为它避免了对象实例化的 COM+ 目录查找所产生的开销。最后一个优点是,针对 SQL Server 2005 运行时,System.Transactions 能够检测到某个分布式事务何时针对宿主在一个 SQL Server 2005 实例上的两个不同数据库运行。在这种情况下,它能够将该分布式事务提升为一个本地事务,这样就可避免与分布式事务登录/两阶段提交相关的全部开销,从而极大地提高性能。
泛型
什么是泛型?
每次返回一个 Pet Shop 模型对象集合时,我们都针对该对象使用泛型类型的一个集合列表。这是 C# 2.0 的一个新增功能,称之为泛型。
运行情况
我们可以从清单 3 所示的 GetProductsByCategory 方法中看到泛型的运行情况。
清单 3. Product.cs (Pet Shop 4.0)
/// <summary>/// A method to retrieve products by category name/// </summary>/// <param name="order">The category name to search by</param>/// <returns>A Generic List of ProductInfo</returns>public IList<ProductInfo>GetProductsByCategory(string category) {// Return new if the string is emptyif (string.IsNullOrEmpty(category))return new List<ProductInfo>();// Run a search against the data storereturn dal.GetProductsByCategory(category);}
以下是 Pet Shop 3 中的等效代码,它返回一个 IList:
清单 4. Product.cs (Pet Shop 3)
/// <summary>/// A method to retrieve products by category name/// </summary>/// <param name="order">The category name to search by</param>/// <returns>/// An interface to an arraylist of the search results/// </returns>public IList GetProductsByCategory(string category) {// Return null if the string is emptyif (category.Trim() == string.Empty)return null;// Get an instance of the Product DAL using the DALFactoryIProduct dal = PetShop.DALFactory.Product.Create();// Run a search against the data storereturn dal.GetProductsByCategory(category);}
泛型的优点
泛型允许我们返回对象的强类型集合,而不是 .NET Pet Shop 3 中的 IList 集合。泛型强类型集合提供类型安全,其性能优于普通的集合。另外,泛型强类型集合将在 Visual Studio 2005 Intellisense 中出现,这可以提高开发人员工作效率。
ASP.NET 2.0 成员身份
成员身份提供一个通用的用户身份验证和管理框架。当用户信息存储在 SQL Server 中时,.NET Pet Shop 4 使用 SQL Server 成员身份提供程序;当用户信息存储在 Oracle 中时,.NET Pet Shop 4 使用自定义成员身份提供程序。
运行情况
要在 .NET Pet Shop 4 中实现成员身份,需要执行以下步骤:
• |
配置窗体身份验证。 <authentication mode="Forms"> <forms name="PetShopAuth" loginUrl="SignIn.aspx" protection="None" timeout="60"/> </authentication> |
• |
要使用 SQL 成员身份提供程序,我们必须安装成员身份数据库。成员身份数据库是在运行下列命令时由 ASP.NET 创建的。 %WinDir%\Microsoft.NET\Framework\<.NET version>\aspnet_regsql -S <server\instance> -E -A all -d MSPetShop4Services |
• |
配置 SQL 成员身份提供程序。 <membership defaultProvider="SQLMembershipProvider"> <providers> <add name="SQLMembershipProvider" type="System.Web.Security.SqlMembershipProvider" connectionStringName="SQLMembershipConnString" applicationName=".NET Pet Shop 4.0" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" requiresUniqueEmail="false" passwordFormat="Hashed"/> </providers> </membership> |
• |
ASP.NET Login 控件封装所有登录逻辑。CreateUserWizard 控件处理新的用户注册。 |
ASP.NET 2.0 成员身份的优点
通过成员身份服务,我们能够使用预建的用户身份验证和注册控件,而无需从头编写这些控件。最终结果是:为登录、登录状态、用户身份、用户注册和密码恢复编写的代码变少了。
而且,由于成员身份现在驻留在自己的数据库中,因此我们能够删除 .NET Pet Shop 3 中使用的 Accounts 表,然后使用 ASP.NET 2.0 创建的成员身份服务数据库。
用于 Oracle 10G的自定义成员身份提供程序
.NET 2.0 框架包括一个 SQL Server 成员身份提供程序。为了在应用程序使用 Oracle 成员身份数据库时保留用户帐户,我们为 Oracle 创建了一个自定义成员身份提供程序实现。我们仅实现了由 .NET Pet Shop 4 使用的方法,即 CreateUser 方法和 Login 方法。然而,任何希望将 Oracle10G与 ASP.NET 成员身份服务一起使用的用户都可以使用和/或扩展该代码。
运行情况
CreateUser 方法是 MembershipProvider 类的实现的方法之一。它深入探究 OracleMembershipProvider 的工作方式。
清单 5. OracleMembershipProvider.cs CreateUser(...)
using System;using System.Configuration.Provider;namespace PetShop.Membership {class OracleMembershipProvider : MembershipProvider {string password, string email, string passwordQuestion,string passwordAnswer, bool isApproved, object userId,out MembershipCreateStatus status) {// create connectionOracleConnection connection =new OracleConnection(OracleHelper.ConnectionStringMembership);connection.Open();OracleTransaction transaction =connection.BeginTransaction(IsolationLevel.ReadCommitted);try {DateTime dt = DateTime.Now;bool isUserNew = true;// Step 1: Check if the user exists in the Users// table: Create if notint uid = GetUserID(transaction, applicationId, username, true,false, dt, out isUserNew);if (uid == 0) { // User not created successfully!status = MembershipCreateStatus.ProviderError;return null;}// Step 2: Check if the user exists in the Membership table: Error// if yesif (IsUserInMembership(transaction, uid)) {status = MembershipCreateStatus.DuplicateUserName;return null;}// Step 3: Check if Email is duplicateif (IsEmailInMembership(transaction, email, applicationId)) {status = MembershipCreateStatus.DuplicateEmail;return null;}// Step 4: Create user in Membership tableint pFormat = (int)passwordFormat;if (!InsertUser(transaction, uid, email, pass, pFormat, salt, "","", isApproved, dt)) {status = MembershipCreateStatus.ProviderError;return null;}// Step 5: Update activity date if user is not newif(!isUserNew) {if(!UpdateLastActivityDate(transaction, uid, dt)) {status = MembershipCreateStatus.ProviderError;return null;}}status = MembershipCreateStatus.Success;return new MembershipUser(this.Name, username, uid, email,passwordQuestion, null, isApproved,false, dt, dt, dt, dt, DateTime.MinValue);}catch(Exception) {if(status == MembershipCreateStatus.Success)status = MembershipCreateStatus.ProviderError;throw;}finally {if(status == MembershipCreateStatus.Success)transaction.Commit();elsetransaction.Rollback();connection.Close();connection.Dispose();}}
未实现的方法成为空存根,如下所示:
public override string GetUserNameByEmail(string email) {throw new Exception("The method or operation is not implemented.");}
Oracle 10G成员身份提供程序的优点
由于我们希望 .NET Pet Shop 4 将成员身份数据存储在 Oracle 数据库以及 SQL Server 中,因此我们实现了一个自定义的成员身份提供程序。提供程序模型使我们能够将 Oracle 数据库与 ASP.NET 2.0 成员身份服务进行简单、快速地集成。
ASP.NET 2.0 配置文件
在 ASP.NET 2.0 中,可跨越多个 Web 应用程序,将用户信息存储到一个名为 Profile 的新服务中。.NET Pet Shop 4 的配置文件实现存储并检索用户的购物车、购物清单和帐户信息。这里的关键的一点是,很多用户会发现,假如为用户会话信息提供一个事务处理、集群安全的存储,这几乎可以完全替换他们对会话对象的使用。默认情况下,配置文件服务将数据序列化为一个 BLOB,存储在数据库中。但是,通过实现您自己的配置文件服务序列化服务可以获得更高的性能。对于 Pet Shop 4,创建了一个配置文件服务的自定义实现来降低序列化开销。
运行情况
• |
配置配置文件提供程序。 清单 6. 配置文件提供程序配置 <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> |
• |
迁移匿名配置文件。 清单 7. 迁移匿名配置文件 // Carry over profile property values from an anonymous to an // authenticated state 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(); } 清单 8. 购物车 using System; using System.Collections.Generic; using PetShop.Model; namespace PetShop.BLL { /// <summary> /// An object to represent a customer's shopping cart. /// This class is also used to keep track of customer's wish list. /// </summary> [Serializable] public class Cart { // Internal storage for a cart private Dictionary cartItems = new Dictionary(); /// <summary> /// Calculate the total for all the cartItems in the Cart /// </summary> public decimal Total { get { decimal total = 0; foreach (CartItemInfo item in cartItems.Values) total += item.Price * item.Quantity; return total; } } /// <summary> /// Update the quantity for item that exists in the cart /// </summary> /// Item Id /// Quantity public void SetQuantity(string itemId, int qty) { cartItems[itemId].Quantity = qty; } /// <summary> /// Return the number of unique items in cart /// </summary> public int Count { get { return cartItems.Count; } } /// <summary> /// Add an item to the cart. /// When ItemId to be added has already existed, this method /// will update the quantity instead. /// </summary> /// Item Id of item to add public void Add(string itemId) { CartItemInfo cartItem; 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++; } /// <summary> /// Add an item to the cart. /// When ItemId to be added has already existed, this method /// will update the quantity instead. /// </summary> /// Item to add public void Add(CartItemInfo item) { CartItemInfo cartItem; if (!cartItems.TryGetValue(item.ItemId, out cartItem)) cartItems.Add(item.ItemId, item); else cartItem.Quantity += item.Quantity; } /// <summary> /// Remove item from the cart based on itemId /// </summary> /// ItemId of item to remove public void Remove(string itemId) { cartItems.Remove(itemId); } /// <summary> /// Returns all items in the cart. Useful for looping through /// the cart. /// </summary> /// Collection of CartItemInfo public ICollection CartItems { get { return cartItems.Values; } } /// <summary> /// Method to convert all cart items to order line items /// </summary> /// A new array of order line items public LineItemInfo[] GetOrderLineItems() { LineItemInfo[] orderLineItems = new LineItemInfo[cartItems.Count]; int lineNum = 0; foreach (CartItemInfo item in cartItems.Values) orderLineItems[lineNum] = new LineItemInfo(item.ItemId, item.Name, ++lineNum, item.Quantity, item.Price); return orderLineItems; } /// <summary> /// Clear the cart /// </summary> public void Clear() { cartItems.Clear(); } } } |
ASP.NET 2.0 配置文件的优点
使用 ASP.NET 2.0,用户的购物车可以存储在数据库中并持久保留,这样,如果用户两三天后再回来,他们仍然拥自己的购物车。此外,配置文件服务是"按需"提供的,而会话状态对象对于任何引用它的页面,每页都要进行重新加载;配置文件服务的一个优势是只在实际需要时才加载。
而且,使用配置文件功能,我们能够从现有的 Pet Shop 3 数据库中删除 Account 表和 Profile 表,这样也会减少业务逻辑层和数据访问层中的代码量。
母版页
ASP.NET 2.0 提供一种通过使用母版页保持整个 Web 站点外观一致的新技术。.NET Pet Shop 4 母版页包含标头、LoginView 控件、导航菜单和呈现内容的 HTML。所有其他 Pet Shop Web 窗体都使用 Pet Shop 4 母版页。
运行情况
图 3 展示 .NET Pet Shop 4 母版页。
图 3. .NET Pet Shop 4 母版页
清单 9. 绑定母版页
<%@ Page AutoEventWireup="true" Language="C#"MasterPageFile="~/MasterPage.master" Title="Products"Inherits="PetShop.Web.Products" CodeFile="~/Products.aspx.cs" %>
ASP.NET 2.0 母版页的优点
使用母版页,我们能够只创建一种布局,然后即可对所有 .NET Pet Shop 页重用该布局。开发期间对该布局的任何更改都直接作用于母版页,其他页面更改的只涉及到内容。相反,通过将标头和导航栏封装在名为 NavBar.ascx 的 ASP.NET 用户控件中,可以实现 .NET Pet Shop 3 中的用户界面。.NET Pet Shop 3 中的每个 Web 窗体都包含用于控制布局的 NavBar 用户控件和 HTML。更改布局将涉及处理每个 Web 窗体上的 NavBar 用户控件,或修改每个 Web 窗体上的 HTML。
ASP.NET 2.0 向导控件
.NET Pet Shop 4 中的签出过程包含在 CheckOut 页面上的一个 Wizard 控件中。Wizard 是一个新控件,它提供一种实现逐步过程的新方法。Wizard 控件管理窗体间的导航、数据持久性以及每一步的状态管理。
运行情况
图 4. 签出向导控件
ASP.NET 2.0 向导控件的优点(单击图像查看大图像)
.NET Pet Shop 3 中的签出过程涉及一系列互相通信的 ASP.NET 页面。从购物车页面,用户可以转到签出页面;在签出页面,用户输入其账单信息,最后系统处理定单。该流程由一个名为 CartController 的自定义类控制,它使用会话状态管理步骤间的通信。
图 5. .NET Pet Shop 3 签出过程
使用 Wizard 控件,只需要较少的代码即可在 .NET Pet Shop 4 中实现签出,从而使该过程变得十分简单。
数据库级缓存失效
SQL Cache Dependency 是 ASP.NET 2.0 的一个新增对象,可用于在 SQL Server 中的数据更改时使缓存失效。Pet Shop 4 使用 SQL Cache Dependency 对象使目录、产品和项目缓存失效。
就现成可用的功能而言,Pet Shop 仅包含基于表的缓存依赖项实现。开发人员可以通过扩展 CacheDependency 对象来实现自己的缓存失效机制。机制实现后,就可以从 Web.config 配置 Pet Shop 4 的 CacheDependency。
请注意,Pet Shop 4 的 SQL CacheDependency 仅仅是为在 SQL Server 上运行而设计的。对于 Oracle,.NET Pet Shop 4 将回到基于时间的缓存过期。
运行情况
图 6 显示 SQL Server 的缓存依赖项:
图 6. Pet Shop 表缓存依赖项
数据库级缓存失效的优点
使用缓存失效,我们可以使显示的内容与 Pet Shop 数据库中的数据保持一致,但是仍能实现中间层对象缓存的优势,从而降低中间层上的运行时处理要求,以及减少数据库调用。这样可以提高应用程序的可伸缩性(它可以处理更多并发用户),同时还可降低数据库负载。
异步 Order 处理
我们作的另一处更改是添加了一个选项,以配置定单过程应该将事务直接(同步)提交给数据库,还是应该提交给指定的队列,稍后再对该队列中的定单进行处理(异步)。在异步定单处理过程中,如果用户提交了一个定单,该定单将进入一个队列。.NET Pet Shop 4 有一个要存储在 Microsoft 消息处理队列 (MSMQ) 中的实现。稍后,该定单队列可以由 Order 处理器控制台应用程序处理。该方法的一个优势是,定单数据库甚至不必进行客户运行,就能够处理定单。由于 MSMQ 使用持久队列,因此无需用户干涉仍然能够捕获所有定单,一旦处理应用程序和定单数据库再次上线,所有定单都将插入数据库中。
运行情况
为了处理同步和异步定单处理之间的算法改变,我们使用策略模式。在策略模式中,发出定单的方法是从 BLL.Order.Insert 方法中分离出来的。根据 OrderStrategy 的 Web.config 设置,使用了对应的 Insert 方法。默认情况下,.NET Pet Shop 配置为同步运行。
要配置 Order 策略,将 OrderStrategyClass 值从 OrderSynchronous 更改为 OrderAsynchronous。此外,对于异步定单处理,MSMQ 必须通过一个为 Pet Shop 而创建的私有队列来启用,如下所示。
<add key="OrderStrategyClass" value="PetShop.BLL.OrderSynchronous"/><add key="OrderQueuePath" value="private queue path"/>
同步发出定单
图 7 说明同步发出定单。当用户签出定单时,签出按钮单击事件处理程序调用 BLL 中的 Order Insert 方法。对于同步发出定单,BLL Order 对象使用 OrderSynchronousInsert 方法将新定单插入 Orders 数据库中,然后更新 Inventory 数据库以反映定单提交后的当前库存。
图 7. 同步发出定单
异步发出定单
图 8 说明异步发出定单。在 Web 站点上,如果用户单击 CheckOut 按钮,就会调用 BLL Order Insert 方法。然而,由于 OrderStrategy 是针对异步配置的,所以使用 OrderAsynchronous 策略。OrderAsynchronous 插入方法直接将定单信息发送到队列中。
图 8. 异步发出
定单处理器
定单处理器是一个控制台应用程序,我们创建它的目的是接收消息处理实现中的定单,并将这些定单转录到 Order 数据库和 Inventory 数据库中。定单处理器以多线程方式运行,以批处理方式处理定单。它重用同步定单策略将新定单插入到 Orders 数据库中,并减少 Inventory 数据库中的值。
异步定单处理的优点
其他许多企业应用程序也使用了异步定单处理。要想使 .NET Pet Shop 4 在定单以多线程方式处理时性能更佳,分离定单过程不失为一种方法。
体系结构
对于早期版本的 .NET Pet Shop,体系结构重点关注用户界面、应用程序逻辑和数据之间的完全分离。这一完全分离允许我们更改一个层的实现,而不会影响其他层。例如,我们可以更改数据库供应商,而不必更改业务逻辑代码。
图 9 中的图表说明 .NET Pet Shop 4 的高级逻辑体系结构。表示层 (WEB) 包含各种用户界面元素。业务逻辑层 (BLL) 包含应用程序逻辑和业务组件。数据访问层 (DAL) 负责与数据库交互,进行数据存储和检索。以下各部分中将对各层进行详细讨论。
图 9. .NET Pet Shop 4 的体系结构图(单击图像查看大图像)
抽象工厂模式
.NET Pet Shop 4 使用抽象工厂设计模式,该模式中的接口用于创建一系列相关或依赖的对象,而无需指定其具体类。数据访问层中有一个该模式的示例,其中包括针对 IDAL、DAL 工厂、Oracle DAL 和 SQL Server DAL 的项目。为缓存、库存和定单数据访问、消息处理,以及配置文件数据访问创建抽象工厂。
表示层
ASP.NET 2.0 包括许多可以提高开发人员工作效率的内置功能。构建 .NET Pet Shop 4 时,我们重新设计了用户界面,从而可以利用 ASP.NET 2.0 提供的新功能,如母版页、主题、皮肤、Wizard 控件和 Login 控件。为了保留用户帐户,我们利用成员身份提供程序(而不是使用 ASP.NET 会话状态)存储用户的购物车和喜欢的产品。新的配置文件提供程序可以存储可使编程和管理用户状态更加简单的强类型购物车。使用所有这些功能,我们能够快速实现 Pet Shop 表示层更改。
用户界面增强
.NET Pet Shop 4 彻底地呈现出一种新外观。新的用户界面支持更大的宠物目录,使用户可以更容易地查找和购买各种新宠物。更改 .NET Pet Shop 用户界面的外观后,我们对 .NET Pet Shop 提供的示例宠物很感兴趣。.NET Pet Shop 中现在有企鹅、小虫、熊猫,甚至骨骼、恐龙和透明的小猫!通过添加购物清单、浏览途径记录以及其他小功能,我们还可以改善购物体验。
加密配置信息
.NET Framework 2.0 引入了一个受保护的配置功能,我们可以使用该功能加密连接字符串。使用该功能,我们可以加密敏感的数据库用户名和密码信息。
当您选择"full source and database install"选项时,.NET Pet Shop 安装程序将自动运行一段脚本,以加密 Web.config 文件中存储的连接字符串。
要在"source only"安装上执行配置加密,运行安装目录中的 EncryptWebConfig.bat 文件。
C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\ASPNET_regiis.exe-pef "connectionStrings""C:\Program Files\Microsoft\.NET Pet Shop 4.0\Web"
业务逻辑层
.NET Pet Shop 4 的业务逻辑保留了 .NET Pet Shop 3 的大部分业务逻辑,如 Model 对象及其使用方式。为数不多的更改包括使用泛型,异步发出定单,以及 System.Transactions 命名空间。
模型对象
.NET Pet Shop 4 保留了 .NET Pet Shop 3 中的 Model 对象。这些对象是模仿数据库表结构的自定义轻量级类。它们在各应用程序层之间共享以相互通信。例如,如果返回一个目录中的多个产品,我们就返回一个 Product Model 对象集合。
数据访问层
BLL 与数据访问层通信以访问 Pet Shop 4 数据库中的数据。.NET Pet Shop 4 使用以下四个数据库:Inventory、Orders、Membership 和 Profile。对于 .NET Pet Shop 3,该版本支持 Oracle 和 SQL Server 数据库。
Order 和 Inventory 架构
.NET Pet Shop 4 中使用的 Orders 和 Inventory 的数据库架构是从 .NET Pet Shop 3 移植而来的。删除了几个未使用的字段。该数据库具有以下表的总体结构:
图 10. Pet Shop Orders 数据库
图 11. Pet Shop Inventory 数据库
Profile 数据库架构
Profile 数据库用于存储特定于用户的信息,如帐户信息和购物车内容。该数据库具有以下表的总体结构:
图 12. Pet Shop Profile 数据库