PetShop 4.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从 ASP.NET 1.1 迁移到 2.0
体系结构体系结构
抽象工厂模式抽象工厂模式
用户界面增强用户界面增强
加密配置信息加密配置信息
模型对象模型对象
Order 和 Inventory 架构Order 和 Inventory 架构
Profile 数据库架构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 的代码量对比
 v3v4

表示层

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 数据库

 

小结

Microsoft .NET Pet Shop 4.0 应用程序用来重点说明构建可伸缩企业 Web 应用程序所使用的关键技术和体系结构。由于 ASP.NET 2.0 中的增强功能,我们能够更快速地构建 n 层企业应用程序,这使我们可以花时间构建更丰富、功能更全面的 Pet Shop。

.NET 2.0 的主要更改和新增功能包括:

System.Transactions允许更快速的事务处理和更简单的部署,而无需使用 COM+ 目录。

泛型:允许我们返回对象的强类型集合而不是 .NET Pet Shop 3 中的 IList 集合。支持更简单的编码,因为智能感知会识别集合中的类型对象。

ASP.NET 2.0 成员身份服务:提供了通用的用户身份验证和管理框架,可以极大地减少与创建和维护用户帐户信息相关的代码量。允许我们使用预建的用户身份验证和注册控件,而无需从头编写这些控件。最终结果是可以为登录、登录状态、用户身份、用户注册和密码恢复编写更少的代码。

ASP.NET 2.0 配置文件服务:为特定于用户的信息(如购物车)替换会话对象的使用。为用户会话信息提供了一个集群安全的事务处理存储,用户会话信息可以跨多个用户对站点的访问进行维护。

ASP.NET 2.0 母版页:提供了一种新技术以保持整个 Web 站点的外观一致,仅通过更新母版页,就可以轻松地将全局更改应用于站点多个页面的外观。

ASP.NET 2.0 向导控件:一个新的服务器端控件,它提供了一种实现逐步过程的简单方法。我们使用它来减少 Pet Shop 4.0 签出过程的编码量。

ASP.NET 2.0 SQL 缓存依赖项:允 许中间层对象缓存在后端数据库信息更改时自动失效。对于 SQL 2000,它在表级别有效(如同在 Pet Shop 4 中一样),而对于 SQL Server 2005,它也可以在单独的行级别有效。利用该功能,缓存的数据库信息可以始终保持是最新的,同时仍利用缓存来降低中间层和数据库服务器上的负载。

通过消息处理的异步定处理选项:虽然不是 .NET 2.0 的功能(.NET 1.1 也提供此功能),我们还是对 Pet Shop 4 进行了扩展,使其能够选择是通过 MSMQ 和 System.Messaging 使用消息处理,还是直接将标准的同步事务用于数据库。倘若有更强的可靠性和潜在的可伸缩性,这会将定单处理与定单数据库分离。

转到原英文页面

一、Model主要功能: 1、 将每一个“业务实体”抽象成“(瘦数据)类”,可以很好地“划分”各个“对象”,操作更加清晰 2、 用于在应用程序各层之间传递数据,被用做传输数据的“容器” 3、 这就是所谓的“建模”过程! 4、 Model各个类(Model文件夹中的各个文件)划分或者说编制的原则,更趋向于模拟整个系统中的业务实体 二、实现细节: 1、 PetShop中Model的规划与数据库表的关系: (1) AccountInfo类——Account表 (2) AddressInfo类——无直接对应关系(对应Account表中一部分字段) (3) CartItemInfo类——无直接对应关系 (4) CreditCardInfo类——无直接对应关系 (5) ItemInfo类——Item表 (6) LineItemInfo类——无直接对应关系 (7) OrderInfo类——Orders表 (8) ProductInfo类——Product表 2、 为每一个Model中的类都标记了[Serializable],说明这些类可以被传行化,但是不能被继承! 3、 AccountInfo.cs文件:用户在网站注册的信息,及喜好选择情况 4、 使用构造函数可以初始化私有字段;使用属性可以读取私有字段(但使用属性不能设置私有字段的值) 5、 其中包含一个AddressInfo类的私有变量,和一个AddressInfo类的属性 6、 命名空间为PetShop.Model 7、 AddressInfo.cs文件:用户真实的个人姓名、住址和电话号码等信息 8、 与AccountInfo不同的是,AddressInfo类允许使用属性设置私有变量的值 9、 CartItemInfo.cs文件:描述购物车中每一种所选商品的信息的类 10、 该“类”对象的某些信息(如这里的Subtotal属性)可能并不是此类的“自然信息”,而需要经过简单计算而得到!这些简单但必要的信息也要在类的设计中体现出来! 11、 CreditCardInfo.cs文件:表示特定一张信用卡的信息 12、 ItemInfo.cs文件:一个Item指的是category"product"item,如猫"波斯猫"成年男波斯猫(或成年女波斯猫)。这个文件表示一个Item的所有信息 13、 productDesc字段的作用? 14、 LineItemInfo.cs文件:注意与CartItemInfo类的区别!LineItemInfo是用来描述用户最终确认的订单当中的某一种类的产品的信息的类 15、 同样包括了Subtotal属性 16、 OrderInfo.cs文件:用于显示用户某一个订单具体信息的类,在此一个订单当中,可能包括多个商品种类,即包括多个LineItemInfo对象(实际上在OrderInfo类中也确实存在LineItemInfo类型对象的一个数组!) 17、 ProductInfo.cs文件:包括一个特定Product的信息,如波斯猫 三、启发: 1、 来自Directory项目结束后的启发,以后做设计的时候,要将每个实体抽象为一个类,在整个系统中进行操作。 2、 在任意一个类当中,可能不只包括此实体类的自然信息,也可以包括一些对其他地方数据调用有用的属性信息,如根据数量和单价计算出来的总价属性,或者标志此实体的直属上级实体的属性 四、问题: 1、 忽然发现在MSDN上有文章,关于数据实体的:浏览 2、 抽象这些业务实体模型为瘦数据类的原则是什么?什么样的业务实体可以被抽象,或者说进行抽象后更有意义? 3、 ItemInfo类中,productDesc字段的作用? 一、IDAL主要功能: 1、 这完全是“工厂模式”的一部分实现而已 2、 这是一组接口类,其中包括了每个要公开的数据访问方法。为每个数据库产品单独编写的DAL(数据访问层)都要实现这组接口所定义的方法 3、 也就是规定了在DAL中编写的“对用户账号进行操作的类”所必须执行的方法! 4、 IDAL要达到的目的是:实现业务逻辑(BLL)与数据库访问(DAL)的完全分离!!! 5、 IDAL各个类(IDAL文件夹中的各个文件)划分或者说编制的原则,更趋向于“将对数据库的不同操作进行归类”,考虑的主要方面是数据库操作!!!例如,有对用户账号进行的一系列数据库操作,则将这一系列操作统一放置于IAccount接口(将来实现后的Account类)文件当中 二、实现细节: 1、 IAccount.cs文件:为针对不同数据库产品编写的“操作用户账号的类”所必须实现的一系列方法定义契约 2、 IInventory.cs文件:定义“操作库存量的类”所必须实现的一系列方法;或者说将操作库存量的一系列(所有)方法做一个汇总 3、 IItem.cs文件:定义“操作某一Item的类”所必须实现的一系列方法。(Item在Model中定义了,是指具体某一类别的Product,如男猫或女猫) 4、 对于其中的GetItemsByProduct()方法,返回的是一个ArrayList的接口类型(IList)的对象(见问题部分!) 5、 IOrder.cs文件:定义了一组DAL层中“操作用户订单的类”必须执行的方法。其中包括“添加一张新订单”的方法Insert()和根据一个已有的订单号取得此订单详细信息的方法GetOrder(),此方法返回的是一个OrderInfo对象。(Model中的OrderInfo类模型定义了用户的某一张Order中相关的信息,如发货地点,总价,信用卡号码等等) 6、 IProduct.cs文件:定义类一组在DAL层中编写的“对Product进行操作的类” 7、 IProfile.cs文件:定义一组在DAL层编写的“对用户Profile进行操作的类” 三、启发: 1、 这样就可以让在BLL层只针对IDAL层定义的接口进行编程(使用IDAL接口定义的这些方法)就可以了!!!无论在底层使用了什么厂家的数据库产品,有区别的只是针对此数据库产品编写的DAL层(相同的方法,如SignIn()方法,对于不同的数据库产品,可能有不同的实现方式!),而不会影响到上层的BLL层已经编写好的内容! 2、 从这里可以看到软件架构师和程序员工作上的区别!架构师要负责的是搭建系统的层次结构,定义接口;而程序员要负责的是针对接口的具体代码实现过程! 3、 这个IDAL接口的使用,主要是为了保证在底层数据库实现,甚至数据库产品发生变化的时候,不需要对上层BLL层的业务逻辑进行大量的修改!BLL层针对IDAL接口编程即可!!! 4、 IDAL文件夹中定义的所有的接口中的所有的方法,包括了整个程序要对数据库进行操作的所有方法 5、 由于PetShop只是一个演示程序,所以若对数据库操作的某一类别(如对Account进行操作)中定义的“操作类”不够用(如除了接口中定义的几个方法外还需要其他Account操作),还可以在接口中追加其他的方法,用以约束DAL层的实现类(如Account类)必须执行这些新增加的方法! 四、问题: 1、 定义这组接口后,如何保证为每个单独的数据库产品编写的DAL都执行这组接口所定义的方法?(答案:编写DAL的时候必须刻意保证,否则就失去了定义IDAL层分割BLL和DAL层的意义!!!) 2、 既然在实现GetItemsByProduct()方法的时候,也是要把返回的所有Item对象添加到一个ArrayList当中去,但为什么GetItemsByProduct()方法返回的不是一个ArrayList对象,而是一个IList接口的对象呢?仅仅是基类引用的方法吗?这里的基类引用有什么用呢?是否是由于为了不限制使用的数组形式?执行IList则可以使用ArrayList,也可以使用其他形式的数组??? 3、 IProduct.cs文件中为何要将查询条件参数定义为一个string类型的数组?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值