key features to implement in .NET Pet Shop 4.0:
What is System.Transactions?
System.Transactions is a new transaction control namespace in the .NET 2.0 Framework. It is a new way to handle distributed transactions without the overhead of COM+ registration and the COM+ catalog. Note that the Microsoft Distributed Transaction Coordinator is used to initiate the transactions.
See it in action
The Order.Insert() method in synchronous order processing uses System.Transactions to insert an order and update the inventory stock. We have implemented the Order.Insert() method by adding a reference to the System.Transaction namespace and wrapping the order insertion and inventory stock reduction methods inside of a TransactionScope, as shown in Code Listing 1.
Listing 1. System.Transactions in action
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 = new TransactionScope(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(); } } }}
In .NET Pet Shop 3, distributed transactions are handled by Enterprise Services and require COM+ registration. The OrderInsert class is derived from a Serviced Component and transactions are handled by COM+. The service component is then registered using the regsvr32 command.
Listing 2. Pet Shop 3 Order Insert
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 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); ... // Set the orderId so that it can be returned to the caller return orderId; } }}
Benefit of System.Transactions
Moving from Enterprise Services to System.Transactions simplifies deployment as it does not require the use of the COM+ Catalog. In using the COM+ Catalog we were carrying around a lot of extra weight for just distributed transaction support. System.Transaction makes it really simple to program and deploy distributed applications in ASP.NET 2.0 applications. System.Transactions is also up to 50 percent more performant at runtime, due to removing the overhead of COM+ catalog lookups for object instantiation. As a final benefit, System.Transactions is able to detect, when running against SQL Server 2005, when a distributed transaction is running against two different databases that are hosted on a single instance of SQL Server 2005. In this case, it is able to promote the distributed transaction to a local transaction, which avoids all overhead associated with distributed transaction logging/two phase commits, and significantly increases performance.
Generics
What are Generics?
Whenever a collection of Pet Shop model objects is returned, we use a collection list of the generic type for that object. This is a new feature of C# 2.0 known as Generics.
See it in Action
We can see Generics in action from the GetProductsByCategory method shown in Code Listing 3.
Listing 3. Product.cs (Pet Shop 4.0)
/// <summary> /// A method to retrieve products by category name /// </summary> /// <param name="category">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 empty if (string.IsNullOrEmpty(category)) return new List<ProductInfo>(); // Run a search against the data store return dal.GetProductsByCategory(category);}
Here is the equivalent code in Pet Shop 3 that returns an IList:
Listing 4. Product.cs (Pet Shop 3)
/// <summary> /// A method to retrieve products by category name /// </summary> /// <param name="category">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 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); }
Benefits of Generics
Generics allow us to return strongly typed collections of objects as opposed to the IList collections in .NET Pet Shop 3. Generic strongly typed collections offer type safety, and perform better than regular collections. Additionally, Generic strongly typed collections will show up on Visual Studio 2005 Intellisense, which can increase developer productivity.
ASP.NET 2.0 Membership
Membership provides a common user authentication and management framework. .NET Pet Shop 4 uses the SQL Server Membership Provider when user information is stored in SQL Server and a custom Membership Provider when user information is stored in Oracle.
See it in Action
To implement Membership in .NET Pet Shop 4, the following steps are necessary:
<authentication mode="Forms"> <forms name="PetShopAuth" loginUrl="SignIn.aspx" protection="None" timeout="60"/> </authentication>
%WinDir%\Microsoft.NET\Framework\<.NET version>\aspnet_regsql -S <server\instance> -E -A all -d MSPetShop4Services
<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>
Benefit of ASP.NET 2.0 Membership
With Membership services, we are able to use pre-built user authentication and registration controls instead of writing them from scratch. The end result is less code to write for login, login status, user identity, user registration, and password recovery.
Also, since Membership now resides on its own database, we are able to remove the Accounts table that is used in .NET Pet Shop 3 and use the Membership services database created by ASP.NET 2.0.
Custom Membership Provider for Oracle 10g
The .NET 2.0 Framework includes a SQL Server Membership provider. In order to maintain user accounts when the application uses an Oracle membership database, we created a custom membership provider implementation for Oracle. We only implemented the methods that are used by .NET Pet Shop 4, which are the CreateUser method and the Login method. This code, however, can be used and/or extended by any customer that wants to use Oracle10G with the ASP.NET membership services.
See it in Action
The CreateUser method is one of the implemented methods of the MembershipProvider class. It provides insight into how the OracleMembershipProvider works.
Listing 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 connection OracleConnection 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 not int 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 yes if (IsUserInMembership(transaction, uid)) { status = MembershipCreateStatus.DuplicateUserName; return null; } // Step 3: Check if Email is duplicate if (IsEmailInMembership(transaction, email, applicationId)) { status = MembershipCreateStatus.DuplicateEmail; return null; } // Step 4: Create user in Membership table int 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 new if(!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(); else transaction.Rollback(); connection.Close(); connection.Dispose(); }}
The unimplemented methods are left as empty stubs like so:
public override string GetUserNameByEmail(string email) { throw new Exception("The method or operation is not implemented.");}
Benefit of Membership Provider for Oracle 10g
We have implemented a custom Membership Provider since we want .NET Pet Shop 4 to store membership data in an Oracle database as well as in SQL Server. The provider model gives us the ability to integrate Oracle databases with ASP.NET 2.0 Membership Services simply and quickly.
ASP.NET 2.0 Profile
In ASP.NET 2.0, user information can be stored across multiple visits to a Web application in a new service called Profile. The Profile implementation for.NET Pet Shop 4 stores and retrieves users' shopping carts, wish lists, and account information. One key point here is that many customers will find that this can replace their use of the session object almost completely, providing a transacted, cluster-safe store for user session information. By default, the Profile service serializes the data as a BLOB that it stores into the database. However, higher performance can be achieved by implementing your own profile service serialization service. For PetShop 4, a custom implementation of the Profile service was created to reduce the serialization overhead.
See it in Action
Listing 6. Profile Provider Configuration
<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>
Listing 7. Migrating Anonymous Profiles
// 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();}
Listing 8. Shopping Cart
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<string, CartItemInfo> cartItems = new Dictionary<string, CartItemInfo>(); /// <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> /// <param name="itemId">Item Id</param> /// <param name="qty">Quantity</param> 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> /// <param name="itemId">Item Id of item to add</param> 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> /// <param name="item">Item to add</param> 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> /// <param name="itemId">ItemId of item to remove</param> public void Remove(string itemId) { cartItems.Remove(itemId); } /// <summary> /// Returns all items in the cart. Useful for looping through /// the cart. /// </summary> /// <returns>Collection of CartItemInfo</returns> public ICollection<CartItemInfo> CartItems { get { return cartItems.Values; } } /// <summary> /// Method to convert all cart items to order line items /// </summary> /// <returns>A new array of order line items</returns> 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(); } }}
Benefit of the ASP.NET 2.0 Profile
With ASP.NET 2.0, users' shopping carts are stored in a database and are persisted, so that if users come back 2-3 days later, they still have their cart. Additionally, the Profile service is "on demand," whereas session state objects get re-loaded per page on any page that references it; an advantage of Profile service is that it does not get loaded unless it's actually needed.
Furthermore, using the Profile feature we are able to remove Account and Profile tables from the existing Pet Shop 3 database—and this resulted in fewer lines of code in the Business Logic and Data Access Layers, as well.
Master Page
ASP.NET 2.0 provides a new technique for maintaining a consistent look and feel for an entire Web site by using a Master Page. The .NET Pet Shop 4 Master Page contains the header, LoginView control, navigation menu, and HTML for rendering content. All of the other Pet Shop Web forms are wired to use the Pet Shop 4 Master Page.
See it in Action
The .NET Pet Shop 4 Master Page is depicted in figure 3.
Figure 3. .NET Pet Shop 4 Master Page
Listing 9. Master Page wire-up
<%@ Page AutoEventWireup="true" Language="C#" MasterPageFile="~/MasterPage.master" Title="Products" Inherits="PetShop.Web.Products" CodeFile="~/Products.aspx.cs" %>
Benefit of ASP.NET 2.0 Master Pages
Using the Master Page, we are able to simply create a single layout that we can reuse for all of the .NET Pet Shop pages. Any changes to the layout during development are made directly to the Master Page, leaving the other pages simply for content. In contrast, the user interface in .NET Pet Shop 3 is implemented by encapsulating the header and navigation bar within an ASP.NET User Control called NavBar.ascx. Each of the Web forms in .NET Pet Shop 3 contain the NavBar user control as well as HTML to control the layout. Changing the layout would involve fumbling with the NavBar user control or modifying the HTML on each of the Web forms.
ASP.NET 2.0 Wizard Control
The check-out process in .NET Pet Shop 4 is contained within a single Wizard control that resides on the CheckOut page. The Wizard is a new control that provides a simple way to implement a step-by-step process. The Wizard control manages the navigation between forms, the data persistence, and the state management in each step.
See it in Action
Figure 4. Check-out Wizard control
Benefit of the ASP.NET 2.0 Wizard Control (Click on the image for a larger picture)
The process of checking out in .NET Pet Shop 3 involves a series of ASP.NET pages that communicate with each other. From the shopping cart, the user can go to the check-out page; from there, users enter their billing information and finally the order is processed. The flow is controlled by a custom class called CartController, which manages the communication between the steps using Session State.
Figure 5. .NET Pet Shop 3 Checkout Process
The Wizard Control makes implementing the checkout very simple in .NET Pet Shop 4 with less code.
Database Level Cache Invalidation
New to ASP.NET 2.0 is the SQL Cache Dependency object that can be used to invalidate the cache when the data from SQL Server has been changed. Pet Shop 4 uses the SQL Cache Dependency object for invalidating the Category, Products, and Item caches.
Out of the box, Pet Shop includes only the implementation for table-based cache dependency. Developers can implement their own caching invalidation mechanisms by extending the CacheDependency object. Once implemented, the CacheDependency for Pet Shop 4 can be configured from web.config.
Note that the SQL CacheDependency for Pet Shop 4 is only designed to run against SQL Server. For Oracle, .NET Pet Shop 4 will fall back to time-based cache expiration.
See it in Action
The cache dependency for SQL Server is shown in figure 6:
Figure 6. Pet Shop Table Cache Dependency
Benefit of Database Level Cache Invalidation
With cache invalidation we can keep presented content consistent with the data in the Pet Shop databases, yet still realize the benefits of object caching on the middle tier to reduce runtime processing requirements on the middle tier, and reduce database calls. This increases application scalability (it can handle more concurrent users), while also reducing load on the database.
Asynchronous Order Processing
One of the other changes that we have made is adding an option to configure whether the ordering process should commit the transactions directly (synchronously) to the databases or to a designated queue in which the orders will be processed at a later point (Asynchronous). In asynchronous order processing, when a user place an order, it goes to a queue. .NET Pet Shop 4 has an implementation for storing in Microsoft Messaging Queue (MSMQ). This queue of orders can be processed later by the Order Processor console app. An advantage of this approach is that the orders database does not even have to be running for customers to still be able to place orders. Since MSMQ is using a durable queue, all orders are still captured with no interruption for users, and will be inserted into the database once the processing application and the orders database come online again.
See it in Action
To handle the algorithm variations between synchronous and asynchronous order processing, we use the Strategy Pattern. In the Strategy Pattern, the order placement method is decoupled from the BLL.Order.Insert method. Based on the web.config setting for OrderStrategy, the corresponding Insert method is used instead. By default, the .NET Pet Shop is configured to work synchronously.
To configure the Order Strategy, change the OrderStrategyClass value from OrderSynchronous to OrderAsynchronous. In addition, for asynchronous order processing, MSMQ must be enabled with a private queue created for Pet Shop, as shown here.
<add key="OrderStrategyClass" value="PetShop.BLL.OrderSynchronous"/> <add key="OrderQueuePath" value="private queue path"/>
Figure 7 depicts synchronous order placement. When users check out their orders, the checkout button click event handler calls the Order Insert method within the BLL. For synchronous order placement, the BLL Order object uses the OrderSynchronous Insert method to insert a new order into the Orders database and then update the Inventory database to reflect the current inventory after the order has completed submission.
Figure 7. Synchronous order placement
Asynchronous Order Placement
Figure 8 depicts asynchronous order placement. On the Web site, when the user clicks the CheckOut button, the BLL Order Insert method is called. However, since the OrderStrategy is configured for Asynchronous, the OrderAsynchronous strategy is used. The OrderAsynchronous insert method plainly sends the order info to a queue.
Figure 8. Asynchronous Order Placement
Order Processor
The Order Processor is a console application that we created to receive the orders that are in the Messaging implementation and transcribe these orders into the Order and Inventory databases. The Order Processor works multi-threaded, and processes orders in batches. It re-uses the Synchronous order strategy to insert the new order into the Orders database and to decrement the Inventory database.
Benefit of Asynchronous Order Processing
Processing orders asynchronously can be found in many other enterprise applications. De-coupling the order process is one way we made .NET Pet Shop 4 perform better as the orders are processed in multi-threaded fashion.
As with the previous versions of the .NET Pet Shop, the architecture focuses on a clean separation between user interface, application logic, and data. This clean separation allows us to change an implementation in one layer without affecting the other layers. For example, we can change the database vendor without having to change the business logic code.
The diagram in figure 9 depicts the high-level logical architecture for .NET Pet Shop 4. The Presentation Layer (WEB) contains the various user interface elements. The Business Logic Layer (BLL) holds the application logic and business components. The Data Access Layer (DAL) is responsible for interacting with the databases for data storage and retrieval. Each of the tiers will be discussed in more detail in the following sections.
Figure 9. Architecture diagram of .NET Pet Shop 4 (Click on the image for a larger picture)
.NET Pet Shop 4 uses the Abstract Factory Design Pattern, in which interfaces are used to create families of related or dependent objects without specifying their concrete classes. One example of this pattern is within the Data Access Layer tier, which has projects for IDAL, DAL Factory, Oracle DAL, and SQL Server DAL. Abstract factories are created for caching, inventory and orders data access, messaging, and profile data access.
ASP.NET 2.0 includes many built-in features that increase developer productivity. In building .NET Pet Shop 4, we have redesigned the user interface to take advantage of the new features that ASP.NET 2.0 provides, such as Master Pages, Themes, Skins, Wizard control, and Login control. To maintain the user accounts, we utilize the Membership provider instead of using ASP.NET Session state to store users' shopping carts and favorite products. The new Profile provider can store a strongly typed shopping cart that makes programming and managing user state much easier. Using all of these features, we are able to quickly implement the Pet Shop presentation layer changes.
.NET Pet Shop 4 sports a clean new look and feel. The new user interface supports a larger pets catalog and makes it easier for users to find and purchase the various new pets. When we changed the look and feel of the .NET Pet Shop user interface, we had fun with the sample pets available from the .NET Pet Shop. The .NET Pet Shop now houses penguins, bugs, pandas, and even skeletons, dinosaurs, and transparent cats! We also improve the shopping experience by adding a wish list, breadcrumb trail, and other subtleties.
The .NET Framework 2.0 introduces a protected configuration feature that we used to encrypt connection strings. With this feature we can encrypt the sensitive database username and password information.
The .NET Pet Shop Installer will automatically run a script to encrypt the connection strings stored within the web.config file when you select the "full source and database install" option.
To perform the configuration encryption on the "source only" install, run the EncryptWebConfig.bat file found in the installed directory.
C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\aspnet_regiis.exe -pef "connectionStrings" "C:\Program Files\Microsoft\.NET Pet Shop 4.0\Web"
The business logic for.NET Pet Shop 4 retains much of the business logic from the .NET Pet Shop 3, such as Model objects and how they are used. The few changes are the use of Generics, asynchronous order placement, and the System.Transactions namespace.
.NET Pet Shop 4 carries over Model objects from .NET Pet Shop 3. These objects are custom lightweight classes that mimic the structure of database tables. These objects are shared across the application layers as a way to communicate with each other. For example, when returning multiple products in a category, we are returning a collection of Product Model objects.
The BLL communicates with the Data Access Layer to access data from the Pet Shop 4 databases. .NET Pet Shop 4 uses four databases: Inventory, Orders, Membership, and Profile. As with .NET Pet Shop 3, this version supports both Oracle and SQL Server databases.
The database schema for Orders and Inventory used in the .NET Pet Shop 4 is ported from the .NET Pet Shop 3. A few fields that are not used are removed. The databases have the following overall structure of tables:
Figure 10. Pet Shop Orders Database
Figure 11. Pet Shop Inventory Database
The Profile database is used to store user specific information, such as account info and shopping cart contents. The database has the following overall structure of tables:
Figure 12. Pet Shop Profile Database
The Microsoft .NET Pet Shop 4.0 application serves to highlight the key technologies and architecture that can be used to build scalable enterprise Web applications. Due to the enhancements in ASP.NET 2.0, we are able to build an n-tier enterprise application more quickly, allowing us to spend time building a richer and more fully featured Pet Shop.
The key changes and new features of .NET 2.0 we targeted were:
原文:http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnbda/html/bdasamppet4.asp