Export (0) Print
Expand All

Migrating a Commerce Server Site from ASP to ASP.NET

 

Microsoft Corporation

April 2003

Applies to:
      Microsoft® Commerce Server 2002

Summary: Learn how to migrate a Microsoft Commerce Server 2000 site to Commerce Server 2002. This article describes the Commerce Server Application Framework and object model, compares sample sites, and outlines key issues that you need to understand when migrating. (96 printed pages)

Contents

Introduction
Commerce Server .NET Application Framework
Object Mapping from ASP to ASP.NET
ASP to ASP.Net Migration Programming Issues
Comparing the ASP-Based Solution Sites to the ASP.NET-Based Commerce Server 2002 Retail Site
Summary
Appendices
   Appendix 1: login.aspx Code Listing
   Appendix 2: edit.aspx Code Listing
   Appendix 3: addtobasket.aspx Code Listing
   Appendix 4: product.aspx Code Listing
   Appendix 5: ResourceFactory.cs Code Listing
   Appendix 6: redir.aspx Code Listing
   Appendix 7: Product.asp Code Listing
   Appendix 8: ProductDetails.aspx Code Listing

Introduction

ASP.NET is a set of technologies in the Microsoft® .NET Framework for building Web applications and XML Web Services. ASP.NET pages execute on the server and generate markup such as Hypertext Markup Language (HTML), Wireless Markup Language (WML), or Extensible Markup Language (XML) that is sent to a desktop or mobile browser. ASP.NET pages use a compiled, event-driven programming model that improves performance and enables the separation of application logic and user interface.

Although both Active Server Pages (ASP) and ASP.NET have the same goal of delivering efficient, flexible architectures for Web applications, the essential difference between these technologies is that ASP.NET provides a truly object-oriented, event-driven developer experience. Therefore, by using ASP.NET instead of ASP, you write fewer lines of code to create the same result. It also means that with ASP.NET, it is much easier for you to write more organized, cleanly structured code. Clearer, simpler code is easier to debug, test, deploy, and maintain; companies and organizations that have migrated Web sites from ASP to ASP.NET report dramatic improvements in code efficiency and code volume.

ASP and ASP.NET have many important internal differences. Because of these differences, to take full advantage of ASP.NET, it is recommended that you do not port your ASP-based Commerce Server site code to an ASP.NET site. Essentially, you need to rewrite your ASP-based code to migrate to ASP.NET. Note that this document does not provide specific prescriptive guidance for rewriting your site. The complexity and variety of site architectures and the major feature and functionality differences between ASP and ASP.NET preclude this approach.

This document provides guidance for Commerce Server 2000 Web site architects and developers who are considering migrating to Microsoft Commerce Server 2002, which uses ASP.NET. This document answers the following important questions:

  • How do I migrate to ASP.NET?

    This document discusses the Commerce Server Application Framework to give you a conceptual foundation of the new Commerce Server architecture. Then, the new object model is explained, showing how Commerce Server objects are mapped from ASP to ASP.NET. Use of the objects within these object models is then illustrated in code examples from the ASP.NET-based Commerce Server sitelets. The changes required on the page programming level are then outlined in substantial detail.

  • What are the differences between an ASP- and an ASP.NET-based site?

    This document provides a comparison between the ASP-based Commerce Server 2000 Solution Sites and the ASP.NET-based Commerce Server 2002 Retail Site. These sites have similar purposes and overall structures. A delineation of the application and request cycles, the file structure, and the page structure of each of these sites provides a solid basis for analysis of the key differences and similarities between Commerce Server ASP and ASP.NET-based sites.

  • What are the key programming issues that I need to understand when migrating?

    This document enumerates the core programming tasks that you need to complete to migrate or rewrite code successfully in your Commerce Server ASP.NET-based application.

The document concludes with a summary of the issues surrounding migration from ASP to ASP.NET.

Several appendices containing cited code listings are included for reference.

Commerce Server .NET Application Framework

To effect a smooth transition from Commerce Server using ASP/ Component Object Model (COM) to Commerce Server using ASP.NET, you must have a good understanding of the Commerce Server .NET Application Framework components and their interrelations. For more information about the framework architecture and components, see "Commerce Server .NET Application Framework" in Commerce Server 2002 Help.

Most Commerce Server functionality derives from COM components. Since ASP.NET-based applications cannot communicate with COM components, the .NET Application Framework provides an Interop layer to enable .NET-based applications to access Commerce Server functionality.

This section describes the programmatic components of the Commerce Server Application Framework and then gives an overview of the Commerce Modules, including guidance on when to build them and how to enable them in the Web.config file.

Programmatic Components

The following figure shows the internal workflow of the components in the platform interacting to fulfill a request from a user.

Ee783776.mscs_csnetmig01(en-US,CS.10).gif

Figure 1. Internal workflow of the components in the platform when interacting to fulfill a request from a user

Commerce Server .NET Application Framework Components

The Commerce Server .NET Application Framework introduces the Base Class Library (BCL) and the Common Language Runtime Interop Layer to the Commerce Server platform. The BCL and the Common Language Runtime Interop Layer enable Commerce Server objects and services to be used within ASP.NET. In addition, the Commerce Server .NET Application Framework uses a new application runtime that simplifies the process of building new Commerce Server applications.

The BCL enables you to access most pre-existing Commerce Server COM objects through a programming model consistent with the Common Language Specification (CLS) and the .NET Framework design guidelines. Internally, many of the classes in the BCL use existing Commerce Server COM objects.

Between the BCL and the Commerce Server COM platform is the Common Language Runtime Interop Layer, providing an interoperability layer between COM components and .NET-based applications. It conceals the differences between managed code and unmanaged code, so that COM components are accessible from the common language runtime and vice versa.

The Common Language Runtime Interop Layer is primarily composed of Primary Interop Assemblies (PIAs). Assemblies are the primary building blocks of a .NET Framework application. Interop assemblies act as a bridge between managed and unmanaged code, mapping COM object members to equivalent .NET-managed members. PIA pertains to assemblies made specifically for COM objects. The Common Language Runtime Interop Layer is responsible for running and managing the PIAs created for Commerce Server.

The BCL uses the PIAs to interoperate with Commerce Server 2002 core services. You should use the BCL instead of using the PIAs directly. The Common Language Runtime Interop Layer does not manage the lifetime of a COM object for you; the BCL does, resulting in increased performance and stability.

Commerce Modules

Commerce Modules are an extension of the ASP.NET runtime and are based on the Hypertext Transfer Protocol (HTTP) module architecture that the runtime provides. Commerce Modules encapsulate the automatic configuration of all Commerce Server Application Runtime components. The CommerceModule class is the base class from which all Commerce Modules derive. After an application is loaded into the Commerce Server Application Runtime, you can use the CommerceContext class to access all page and application scope components provided by Commerce Server from within the global.asax file, a Web form, or any custom component.

Using Commerce Modules decreases the amount of code that you need to write in the global.asa file and on individual ASP pages to initialize components. The initialization code is encapsulated in Commerce Modules that read configuration data from the Web.config file.

Some Commerce Modules are configurable and some are not. If the Commerce Module is configurable, the module references the associated IConfigurationSectionHandler interface. IConfigurationSectionHandler interface instances are defined in the <sectionGroup name="Commerce Server"> section of the Web.config file. Commerce Modules are defined and initialized in the system.web httpModules section of the Web.config file.

The following table contains a description of each of the Commerce Modules.

Table 1. Description of each of the Commerce Modules

Module NameDescription
CommerceApplicationModuleBy definition, an application must use CommerceApplicationModule to be considered a Commerce Server .NET Application Framework application. All other CommerceModules depend on the CommerceApplicationModule.
CommerceAuthenticationModuleProvides a framework for managing authentication of site users. Provides the means for client-side cookie detection and creates an instance of AuthManagerInfo at page scope.
CommerceCacheModuleCreates the collection of CacheManager caches used on the site.
CommerceCatalogModuleCreates a default CatalogContext object for the site.
CommerceOrderModuleCreates a default OrderContext object for the site.
CommerceProfileModuleCreates a default ProfileContext object for the site.
CommerceContentSelectionModuleCreates the collection of ContentSelectionContext objects available for site usage.
CommerceExpressionModuleCreates an ExpressionEvaluator component for use on the site.

Enabling CommerceModules in the Web.config File

The <httpModules> section of the Web.config file contains the configuration settings for the Commerce Modules available for use in the site. The order in which the modules appear in the Web.config file is significant. Any module dependent upon another module must be listed after the module on which it is dependent.

The CommerceApplicationModule class is a requirement for an application to be considered part of a .NET-based Commerce Server application, and it must be listed before all other CommerceModules classes in the <httpModules> section in the Web.config file.

Building Commerce Modules

While Commerce Modules are an extension to the HTTP module architecture in ASP.NET, it is recommended that you implement or build Commerce Modules instead of implementing the IHttpModule interface. This approach is recommended because inheriting from the CommerceModule class enables Commerce Server to manage the dependencies between modules in the application.

A third party providing extensions or plug-ins for the Commerce Server platform should build a Commerce Module if any of the following are true:

  • The extension depends on a resource or resources in the Commerce Server Administration database.
  • The extension depends on other Commerce Server modules.
  • The extension requires global configuration before its functionality is used on site pages.
  • The extension needs to subscribe to request processing events raised by the HttpApplication class.
  • Other Commerce Modules might depend on this module being configured.

Object Mapping from ASP to ASP.NET

With the new Commerce Server BCL, you have to learn a different object model to determine which classes to use for the same task for in an ASP.NET-based site. The mapping between a COM object and a BCL object is not always a one to one mapping. In some cases, the functionality of a COM object can be found in a single method of a BCL object. This section describes the coding differences between ASP and ASP.NET-based sites for each of the major systems available through the BCL. This section also lists the most common COM objects for the system and their BCL counterpart. An example of the usage of the objects in the appropriate sitelet is also included.

This section outlines ASP to ASP.NET object mapping in each of the Commerce Server systems (Authentication, Order, Catalog, Profiling, and Targeting). For each of these systems, this section provides a discussion of object mapping issues, an ASP.NET object diagram, a table of the mappings of objects from ASP to ASP.NET, and an appropriate sitelet sample of system objects in use.

Authentication

The AuthManager object and the AuthFilter Internet Server Application Programming Interface (ISAPI) filter contains most of the Authentication system functionality in Commerce Server ASP-based sites. (The login.asp page provides the remainder of the functionality.) Both the AuthManager object and the AuthFilter ISAPI filter are available in the Commerce Server 2002 .NET Application Framework; however, the functionality of the AuthManager object has been split into four types: AuthManager, AuthenticationInfo, AuthTicket, and ProfileTicket.

Outside of the Commerce Server .NET Application Framework, the .NET Application Framework also has built-in support for forms-based and passport-based authentication. You can use these methods instead of the Commerce Server objects, but they do have a limitation: the Data Warehouse reporting features are available only if you use the AuthManager object. However, you can use ASP.NET authentication as well as Commerce Server authentication by using the AuthManager object to create an authTicket for the user once that user has been authenticated. You can then leverage the Profiling System to create a profile for this user, and the Data Warehouse tracking system can then provide tracking features to follow the user activities. If your ASP.NET authentication scheme allows anonymous users, you can create ProfileTickets instead, which allows the Data Warehouse to track the user as an anonymous user.

The following figure shows the object model for the Microsoft.CommerceServer.Runtime namespace.

Ee783776.mscs_csnetmig02(en-US,CS.10).gif

Figure 2. Object model for the Microsoft.CommerceServer.Runtime namespace

Microsoft.CommerceServer.Runtime Namespace Object Model

The following table shows the mappings of objects between ASP and ASP.NET. The .NET-based types are in the Microsoft.CommerceServer.Runtime namespace.

Table 2. Mappings of objects between ASP and ASP.NET.

ASPASP.NET
AuthManagerAuthManager

AuthenticationInfo

AuthTicket

ProfileTicket

AuthFilter ISAPIAuthFilter ISAPI

Using the Authentication Objects: The Profiles Sitelet

The primary goal of the Profile Sitelet is to demonstrate through code how to use the Profiling System in Commerce Server 2002 to create user profiles, set user profile attributes, and to read user profile data by using the Profiling System objects. This section examines the login.aspx and the edit.aspx files in the Profile Sitelet to illustrate the use of authentication objects in ASP.NET-based sites. Reviewing all of the Profile Sitelet files is a recommended way to become familiar with the subsystem functionality and good coding practices when using these objects. You can find the full file listings in Appendix 1: login.aspx Code Listing and Appendix 2: edit.aspx Code Listing.

At the beginning of the login.aspx file, a series of import statements set up the availability of several classes for use, and the ResourceManager is retrieved from the Application object.

A CommerceContext object is then instantiated, as shown in the following code:

CommerceContext CommerceCurrent = CommerceContext.Current;

Using this, the user profile is retrieved, based on the logon name, as shown in the following code:

Profile UserData =   
   CommerceCurrent.ProfileSystem.GetProfile(Utility.UserLogonName, 
   LoginTextBox.Text, Utility.ProfileTypeUser)

The password that the user enters is checked against the password in the user profile. If it matches, the user is authenticated, and the user ID is set. Based on the user ID, a cookie is set on the client computer, as shown in the following code:

If (PasswordTextBox.Text == (string)UserData[Utility.UserPassword].Value)
{
   string UserId = (string)UserData[Utility.UserId].Value;
   CommerceCurrent.AuthenticationInfo.SetAuthTicket(UserId, 
      Utility.AuthCookieSupport, Utility.AuthTimeOut);

When the user attempts to change items in the user profile, the code in the edit.aspx file checks whether that user is authenticated and whether the cookie has been set more than 90 minutes in the past, as shown in the following code:

if (!CommerceCurrent.AuthenticationInfo.IsAuthenticated(90))

If both of these checks pass, the user is able to change the values in the user profile, and the changes are saved to the data store. For more information about the Profile Sitelet, see "ASP.NET-Based Profile Sitelet" in Commerce Server 2002 Help.

Order System

The OrderForm and OrderGroup objects are the core of the Order System functionality in both the COM and .NET-based models. Most of the objects follow a one-to-one mapping path between ASP and ASP.NET.

The following figure shows the object model for the Microsoft.CommerceServer.Runtime.Orders namespace.

Ee783776.mscs_csnetmig03(en-US,CS.10).gif

Figure 3. Object model for the Microsoft.CommerceServer.Runtime.Orders namespace

Microsoft.CommerceServer.Runtime.Orders Namespace Object Model

The following table shows the mappings of objects between ASP and ASP.NET. The .NET-based types are in the Microsoft.CommerceServer.Runtime.Orders namespace unless otherwise noted.

Table 3. Mappings of objects between ASP and ASP.NET for Microsoft.CommerceServer.Runtime.Orders Namespace Object Model

ASPASP.NET
OrderGroupBasket, OrderTemplate, and PurchaseOrder classes are all derived from the OrderGroup abstract base class.

OrderContext is a factory object for these types.

OrderForm

OrderGroup.Value("OrderForms").Value("OrderFormName")

OrderForm
OrderForms dictionary OrderGroup.Value("OrderForms")OrderFormCollection
Item dictionary

OrderForm.Items(n)

LineItem
Items SimpleList

OrderForm.Items

LineItemCollection
Address dictionaryOrderAddress
Addresses dictionaryOrderAddressCollection
OrderGroupManagerOrderGroupSearch

OrderGroupSearchOptions

MtsPipeline

PooledPipeline

OrderPipeline

(Microsoft.CommerceServer.Runtime.Pipelines)

Pipeline Context dictionaryPipelineInfo

Using the Orders System Objects: The Order Sitelet

The primary goal of the Order Sitelet is to demonstrate how users create an order and then confirm and view the status of that order. This section examines the addtobasket.aspx file in the Order Sitelet to illustrate the use of the Order System objects in ASP.NET sites. Reviewing all of the Order Sitelet files is a recommended way to become familiar with the subsystem functionality and demonstrates good coding practices when using these objects Appendix 3: addtobasket.aspx Code Listing contains the full addtobasket.aspx file.

The functionality of the addtobasket.aspx page includes adding an item to a shopping basket, running a pipeline to update the basket, and saving the basket to the database. Note that the pipeline is configured in the Web.config file.

At the beginning of the addtobasket.aspx file, a series of import statements sets up the availability of several classes for use. For example, the Basket and LineItem objects are made available by the following line:

<%@ Import Namespace="Microsoft.CommerceServer.Runtime.Orders" %>

A reference to the ResourceManager object is then created, and all of the ResourceManager utilities are now made available:

ResourceManager rm       = ResourceFactory.SiteletResourceManager;

The following example assumes that the user is adding an item to the basket, and that the _isAdding property has been set to true in the following Page_Load function code:

if(Utility.addRequest == Request.QueryString[Utility.requestType]) {
   _isAdding = true;
}

The LoadBasket function is then called; within this function, the basket is loaded, based on the user ID, as shown in the following code:

Basket basket = _ordersys.GetBasket(new 
   Guid(CommerceContext.Current.UserID));

Since _isAdding is true, the AddToBasket function is called, and the item is added to the basket, as shown in the following code:

CreateOrderForm(orderFormName);
string product = Request.QueryString[Utility.productLabel];
LineItem item = new LineItem();
item.Quantity = 1;
item.ProductID = product;
item.ProductCatalog = Utility.catalogName;
_basket.OrderForms[orderFormName].LineItems.Add(item);

Following this, the UpdateBasket function runs the pipeline against the basket and then saves the basket to the database, as shown in the following code:

PipelineInfo info = new PipelineInfo(Utility.basketLabel);
info["catalog_language"] = _language;
info["CacheName"] = Utility.dicountsLabel;
info.Language = _language;
info.Profiles.Add("User", 
   CommerceContext.Current.UserProfile);
_basket.RunPipeline(info);
_basket.Save();

At this point, the new item has been added to the basket, the basket has been updated to reflect this, and the basket has been serialized to the data store. For more information about the Order Sitelet, see "ASP.NET-Based Order Sitelet" in Commerce Server 2002 Help.

Catalogs

The COM object model for catalogs includes methods and properties for both design-time and run-time catalog activities. The Microsoft.CommerceServer.Runtime.Catalog namespace exposes only the run-time functionality and wraps the catalog set functionality.

Accessing the Objects in the Catalog Hierarchy

At the root of the Catalog namespace is the CatalogContext object. A CatalogContext object is loosely similar to a CatalogManager object in COM, with the management functionality removed and the searching functionality factored out into a separate object (the CatalogSearch object). At application start, the CommerceCatalogModule creates an instance of the CatalogContext object and makes it available at the application level through a static property, CatalogContext, and to the page through an instance property, CommerceContext.Current.CatalogSystem.

From a CatalogContext object, you can gain access to the rest of the catalog hierarchy, including the familiar ProductCatalog, Product, and Category objects. You can get a ProductCatalog object from the CatalogContext.GetCatalog method, a Product object from the ProductCatalog.GetProduct method, and a Category object from the ProductCatalog.GetCategory method.

Primary issues surrounding the differences between the ASP and ASP.NET catalog approaches include the following:

  • In the COM-based model, search results are returned through Microsoft ActiveX® Data Objects (ADO) Recordsets. In the .NET-based model, the search results are returned through ADO.NET datasets or other appropriate structures such as collections.
  • In the COM-based model, searching is made available through different objects. These objects include the CatalogManager.Search object and the Category.Search object. The scope of the search is determined by which object you search with. In the .NET-based model, searching is done through a separate object, CatalogSearch, which uses properties that you set to scope the search.
  • In the COM-based model, many of the methods use parameters so you can specify how data is to be returned, including items such as sort order and paging parameters. Unfortunately, each of the methods is inconsistent in the set of parameters that they accept. In the .NET-based model this is specified in a more standard and consistent way by providing an instance of the CatalogSearchOptions object. The DebugContext object contains warnings about any settings that are ignored.
  • In the COM-based model, a separate object gets the catalog collection, based on the profile of the user. In the .NET-based model, the CommerceContext object exposes this functionality.
  • The .NET-based model does not expose the attributes for all the catalog properties in one dataset. You need to get the PropertyNames collection and iterate it to get the property attributes for each property.
  • All the methods for the Product and Category objects that return a recordset containing the parent or child category information also return the display name for each category in the Recordset. The corresponding methods in the .NET-based model return ReadOnlyStringCollections collections that do not contain the category display name. You can call the Category.GetCategoryProperties method to get the display names for each category. The one exception to this is the Category.GetChildCategories method, which returns a dataset.

The following figure shows the object model for the Microsoft.CommerceServer.Runtime.Catalog namespace.

Ee783776.mscs_csnetmig04(en-US,CS.10).gif

Figure 4. Object model for the Microsoft.CommerceServer.Runtime.Catalog namespace

Microsoft.CommerceServer.Runtime.Catalog Namespace Object Model

The following table shows the mappings of objects between ASP and ASP.NET. The .NET-based types tables are in the Microsoft.Commerceserver.Runtime.Catalog namespace unless otherwise noted.

Table 4. Mappings of objects between ASP and ASP.NET for Microsoft.CommerceServer.Runtime.Catalog Namespace Object Model

ASPASP.NET
CatalogManagerCatalogContext
ProductCatalogProductCatalog
CategoryCategory
ProductProduct
CatalogSetsCommerceContext.GetCatalogsForUser
CatalogToVendorAssociationNone, use COM Interop (no Primary Interop Assembly provided)

Using the Catalog System Objects: The ASP.NET Catalog Sitelet

The primary goal of the Catalog Sitelet is to demonstrate how to use the Catalog object model to browse and search a product catalog, providing you with a functional example of the catalog application programming interface (API) in action. This section examines the product.aspx file in the Catalog Sitelet to illustrate the use of the Catalog System objects in ASP.NET-based sites. To become familiar with the subsystem functionality and good coding practices when using these objects, it is recommend that you review all of the Catalog Sitelet files . The full product.aspx file is found Appendix 4: product.aspx Code Listing.

The functionality of the product.aspx page includes population of the page controls with catalog data.

At the beginning of the product.aspx file, a series of import statements sets up the availability of several classes for use. For example, the Category and Product objects are made available by the following line:

<%@ Import Namespace="Microsoft.CommerceServer.Runtime.Catalog

A reference to the resource manager is created in the Page_Init function, and all of the resource manager utilities are now made available. The Page_Init function also creates a Catalog object (catalog):

   catalog = CommerceContext.Current.CatalogSystem.GetCatalog(Utility.catalogName);

The Page_Load function instantiates SelectedCulture, a CultureInfo object, which is used to localize the page content. Also within the Page_Load function, the catalog object is used to create a product object (myProduct), and then a DataSet (data) stores the attributes of the product:

Product myProduct = catalog.GetProduct(productid);
DataSet data = myProduct.GetProductProperties();

This DataSet is then used to populate the page controls. For more information on the Catalog Sitelet, see the "ASP.NET-Based Catalog Sitelet" topic in Commerce Server 2002 Help.

Profiling System

The scope and function of the COM Profile objects are similar to the Profile objects available in the Base Class Library. The ProfileContext type is similar to a ProfileService object in COM, and the Profile type is similar to the COM ProfileObject type.

In COM, there are three main ASP application-scoped Profile System related entities:

  • Profile service instance
  • OLE DB Provider for Commerce Server instance
  • BizDataAdmin object instance

In the .NET-based model, the key profile service functionality, the OLE DB Provider for Commerce Server handle, and selected site term related functionality is housed in a single ProfileContext object.

The following figure shows the object model for the Microsoft.CommerceServer.Runtime.Profiles namespace.

Ee783776.mscs_csnetmig05(en-US,CS.10).gif

Figure 5. Object model for the Microsoft.CommerceServer.Runtime.Profiles namespace

Microsoft.CommerceServer.Runtime.Profiles Namespace Object Model

The following table shows the mappings of objects between ASP and ASP.NET. The .NET-based types tables are in the Microsoft.Commerceserver.Runtime.Profiles namespace unless otherwise specified.

Table 5. Mappings of objects between ASP and ASP.NET for Microsoft.CommerceServer.Runtime.Profiles Namespace Object Model

ASPASP.NET
ProfileServiceProfileContext
ProfileObjectProfilePropertyCollection
ProfileObject.FieldsProfilePropertyGroupCollection

Using the Profile System Objects: the Profile Sitelet

The primary goal of the Profile Sitelet is to demonstrate through code how to use the Profiling System in Commerce Server 2002 to create user profiles, set user profile attributes, and to read user profile data using the Profiling System objects. This section examines the edit.aspx file in the Profile Sitelet to illustrate the use of the Profile System objects in ASP.NET sites. Reviewing all of the Profile Sitelet files is a recommended way to become familiar with the subsystem functionality and good coding practices when using these objects. The full edit.aspx file is found in Appendix 2: edit.aspx Code Listing.

The functionality of the Profile Sitelet edit.aspx page demonstrates how user profiles can be changed by the user.

At the beginning of the edit.aspx file, a series of import statements sets up the availability of several classes for use. For example, the ProfileService and Profile objects are made available by the following line:

<%@ Import Namespace="Microsoft.CommerceServer.Runtime.Profiles

A reference to the resource manager is created in the Page_Init function, and all of the resource manager utilities are now made available.

The Page_Load function instantiates SelectedCulture, a CultureInfo object, which is used to localize the page content.

The PopulateProfileData function first gets a reference (UserData) to the user profile of the current user and then populates the controls, based on this data. An example is shown in the following code, which illustrates the population of the UserIDTextBox control:

Profile UserData = CommerceCurrent.UserProfile;
UserIDTextBox.Text = (string)UserData[Utility.UserLogonName].Value;

When the OK button on the page is clicked (firing the OK_Click event), the user profile fields are updated by the contents of the controls by the following code:

try {
      Profile UserData = CommerceCurrent.UserProfile;

      UserData[Utility.UserPassword].Value = PasswordTextBox.Text;
      UserData[Utility.UserFirstName].Value = FirstNameTextBox.Text;
      UserData[Utility.UserLastName].Value = LastNameTextBox.Text;
      UserData[Utility.UserEMail].Value = EMailTextBox.Text;
      UserData[Utility.UserLanguage].Value =  
         UserLanguageDropDown.SelectedItem.Value;

      UserData.Update();

      CommerceCurrent.UserProfile = UserData;
    }

At this point, the user profile fields are updated, and the profile is saved to the data store. For more information on deploying the Catalog Sitelet, see the "ASP.NET-Based Profile Sitelet" topic in Commerce Server 2002 Help.

Targeting System

The Targeting System includes the campaign management system (advertisement and discount promotions, including content selection pipelines), the expression evaluator, and the List Manager/Direct Mailer services.

The following figure shows the object model for the Microsoft.CommerceServer.Runtime. Targeting namespace.

Ee783776.mscs_csnetmig06(en-US,CS.10).gif

Figure 6. Object model for the Microsoft.CommerceServer.Runtime. Targeting namespace

Microsoft.CommerceServer.Runtime.Targeting Namespace Object Model

The following table shows the mappings of objects between ASP and ASP.NET. The .NET-based types tables are in the Microsoft.CommerceServer.Runtime.Targeting namespace unless otherwise noted.

Table 6. Mappings of objects between ASP and ASP.NET for Microsoft.CommerceServer.Runtime.Targeting Namespace Object Model

ASPASP.NET
ContentSelectorContentSelector
CacheManagerCommerceCache, CommerceCacheCollection
OrderPipelineContentSelectionPipeline

(Microsoft.CommerceServer.Runtime.Pipelines)

ContentListContentItemCollection

Note that the COM ContentList object continues to be used internally in the pipeline.

CSF global context dictionaryContentSelectionContext
ExpressionEvalExpressionEvaluator
ExpressionStoreExpressionStore RCW (LISTMANAGERLib.dll, Microsoft.CommerceServer.Interop.Targeting)
ListManagerListManager RCW (Microsoft.CommerceServer.Interop.Targeting)
PredictorClientPredictorClient RCW

(PREDICTORCLIENTLib.dll, Microsoft.CommerceServer.Interop.Targeting)

Using the Targeting System Objects: The Advertising Sitelet

The primary goal of the Advertising Sitelet is to demonstrate the use and behavior of the components that work together to display advertisements in a Commerce Server site. In this sitelet, you view a demonstration of targeted advertisements for hard-coded profile data to learn and understand how the ContentSelector object works. The profile data for this sitelet is stored in the site Microsoft® SQL Server™ database; this database is populated by Commerce Server Site Packager during the sitelet installation process. This section examines the ResourceFactory.cs file and the redir.aspx file in the Advertising Sitelet to illustrate the use of the Targeting System objects in ASP.NET sites. Reviewing all of the Advertising Sitelet files is a recommended way to become familiar with the subsystem functionality and good coding practices when using these objects. The full file listings are found in Appendix 5: ResourceFactory.cs Code Listing and Appendix 6: redir.aspx Code Listing.

Important core functionality of the Advertising Sitelet is found in the ResourceFactory.cs file. The ResourceFactory.cs file uses the .NET Targeting, Profile, and Pipeline classes to build a list of advertisements.

The first part of the ResourceFactory.cs file involves the ContentSelector object building a list of profile-appropriate advertisements in the GetContent function. The GetContent function has the following input parameters: a profile of the current user, the PageGroup parameter (a list of possible advertisements to show the user), a number indicating how many advertisements to display on the page that the user views, and a parameter indicating the desired language.

In the GetContent function, a CommerceContext object (ctx) and a ContentSelector object (cso) are instantiated as shown in the following code:

CommerceContext ctx = CommerceContext.Current;
// Get a ContentSelector
ContentSelector cso = ctx.TargetingSystem.SelectionContexts["advertising"].GetSelector();

The GetContent parameters are then validated, and the ContentSelector object properties are set by using these parameters. The ContentSelector object then calls GetContent to return the list of advertisements as shown in the following code:

StringCollection Ads = cso.GetContent();

The RecordEvent function of the ResourceFactory.cs file captures the results when the advertisement is clicked. The redir.aspx page uses this function to run the RecordEvents pipeline before the user is sent to the destination page, so the site can keep track of users redirected to other sites. The RecordEvent function call is shown in the following code:

new Utility().RecordEvent(format.ToInt32(coll["ciid"]), coll["cachename"], 
   format.ToInt32(coll["PageGroupId"]));

At this point, the advertisement has been selected, and the user action (clicking on the advertisement) has been recorded into the log files. For more information on the Advertising Sitelet, see the "ASP.NET-Based Advertising Sitelet" topic in Commerce Server 2002 Help.

ASP to ASP.Net Migration Programming Issues

In addition to understanding the new Commerce Server programming model, you must know the core programming tasks to be completed for you to migrate or rewrite code successfully in an ASP.NET application.

Although the designers of ASP.NET have preserved a substantial amount of backward-compatibility with ASP applications, you need to be aware of a few key items before moving a Web application from ASP to ASP.NET. A solid understanding of the technologies that have changed or been introduced with the .NET platform and ASP.NET makes this process easier.

This section explores a number of areas of change to provide an understanding of how to migrate an ASP application to an ASP.NET environment. At the same time, it points out some of the new features of ASP.NET that can be leveraged to improve an existing application. This section includes discussion of structural changes, Microsoft® Visual Basic® language changes, COM-related changes, application configuration changes, and security-related changes. Note that this is not meant to be a comprehensive overview of all of the new features in ASP.NET; instead, the focus is on information that you need for a successful migration.

Core API Changes

The core APIs of ASP consist of a few intrinsic objects (such as the Request, Response, and Server objects) and their associated methods. With the exception of a few changes, these APIs continue to function under ASP.NET. All of the core API changes are related to the Request object and are shown in the following table:

Table 7. Intrinsic objects

MethodChange
Request(item)In ASP, this property is a Collection. In ASP.NET, Request is a NameValueCollection property that returns a string based on the passed-in-item key.
Request.QueryString(item)In ASP, this property is a Collection. In ASP.NET, QueryString is a NameValueCollection property that returns a string based on the passed-in-item key.
Request.Form(item)In ASP, this property is a Collection. In ASP.NET, Form is a NameValueCollection property that returns a string based on the passed-in-item key.

If the item you are accessing contains exactly one value for the specified key, you do not need to modify your code. However, if a given key has multiple values, you need to use a different method to return the collection of values. Also, note that collections in Microsoft Visual Basic® .NET are zero-based, whereas the collections in Visual Basic Scripting Edition (VBScript) (used in ASP applications) are one-based.

Structural Changes

Structural changes are those that affect the layout and coding style of ASP pages. You need to be aware of several of these to ensure your code works in ASP.NET.

Code Blocks: Declaring Functions and Variables

In ASP, you can declare subroutines and global variables between code delimiters .In ASP.NET, these declarations are no longer functional. You must instead declare all of your functions and variables inside a script block.

Mixing Programming Languages

In ASP, you have two programming language choices: JScript® or VBScript. You are free to mix and match blocks of script in the same page.

In ASP.NET, you can use any common language runtime-compliant language. Microsoft® Visual C#®, Visual Basic .NET, and JScript are the current languages provided by Microsoft. Note that VBScript does not exist in the .NET platform. It has been fully subsumed by Visual Basic .NET. Although you can program in any of these languages, it is important to note that you cannot mix languages on the same page as you could do in ASP. It is possible to have Page1.aspx of your application contain C# code while Page2.aspx of the same application contains Visual Basic .NET code. However, you cannot mix them together in a single page.

New Page Directives

In ASP, you must place all directives on the first line of a page within the same delimiting block as is shown in the following code:

<%LANGUAGE="VBSCRIPT" CODEPAGE="123"%>

In ASP.NET, you are now required to place the Language directive with a Page directive, as shown in the following code:

<%@Page Language="VB" CodePage="123"%>
<%@QutputCache Duration="120" VaryByParam="none" %>

You can have as many lines of directives as you need. Directives may be located anywhere in your .apsx file, but standard practice is to place them at the beginning of the file. Note that several new directives have been added with ASP.NET.

Render Functions Are No Longer Valid

In ASP, you can render sections of HTML by using what is termed a "Render Function." A Render Function is basically a subroutine that contains areas of HTML embedded throughout its body as shown in the following example:

<%Sub RenderSub()
%>
<H3> This is HTML text being rendered. </H3>
<%End Sub
RenderSub
%>

This type of coding is no longer allowed in ASP.NET. It is easy for Render functions to become unreadable and unmanageable when you start to mix and match code and HTML. The simplest way to make this coding work in ASP.NET is to replace the HTML outputs with calls to the Response.Write method as shown in the following code:

<script language="vb" runat="server">
  Sub RenderSub()
     Response.Write("<H3> This is HTML text being rendered. </H3>")
   End Sub
</script>

<%
   Call RenderSub()
%>

Depending on the complexity and amount of rendering code you use, you may want to investigate using custom Web controls, which enable you to programmatically set your HTML attributes and to separate your code from your content. Doing so makes your code more readable.

Localization

Resource management, a feature of the Microsoft .NET Framework class library, can extract localizable elements from source code and store them with a string key as resources. At runtime, an instance of the ResourceManager class can resolve the key to the original resource or a localized version. Resources can be stored as independent files or as a part of an assembly. For more information about resources and localization, see the .NET Framework SDK documentation.

Commerce Server Authentication and ASP.NET Authentication

Use of the Commerce Server authentication model allows for fallback from cookie mode to URL mode, in the case of the user not allowing cookies on the browser. In the ASP.NET authentication model, you must choose whether authentication runs in cookie mode or URL mode. It is important that you understand this limitation of ASP.NET when considering making changes to your Web site authentication.

Visual Basic Language Changes

VBScript has been replaced by the more complete and powerful Visual Basic .NET. This section highlights some noteworthy issues that you are likely to encounter that are related to this language change.

Variant Data Type Issues

The VARIANT types in VBScript are not a part of .NET, and thus are not supported in Visual Basic .NET. Therefore, all of the ASP variables silently move from VARIANT types to Object types in a ported code page. Most variables used in your application can and should be changed to a corresponding primitive type, depending on your needs. If your variable is really an Object type in Visual Basic terms, explicitly declare it as an Object type in ASP.NET.

Visual Basic Date Type

One VARIANT type that warrants some special attention is the VT_DATE type, which manifests itself in Visual Basic as a Date type. In Visual Basic, a Date type is stored in a double format that uses four bytes. In Visual Basic .NET, Date uses the common language runtime DateTime type, which has an eight-byte integer representation.

Since everything is a VARIANT in ASP, your intended Date variables will compile and may continue to work, depending on how they are used. It is possible, however, to encounter unexpected problems when performing certain operations with the variable because the underlying type has been changed. Care must be taken in areas where you pass the date value into COM objects as long integer values or perform certain casting operations on date types using CLng.

Option Explicit Is Now the Default

In ASP, the Option Explicit keywords are available but are not enforced as the default. In Visual Basic .NET, Option Explicit is now the default, so all variables need to be declared. It is good practice to be even more rigid by changing your setting to Option Strict. Doing so forces you to declare all of your variables as a specific data type. If you choose not to, your code is less than optimal, as all undeclared variables become Object types. Most implicit conversions still work, but performance and safety improves if you explicitly declare all of your variables to the appropriate types.

LET and SET Are No Longer Supported

Objects can be assigned to one another directly as shown in the following code:

MyObj1 = MyObj2.

You no longer need to use the SET or LET statements. If you use these statements in your ASP code, they must be removed during the code migration.

Using Parentheses with Method Calls

In ASP, you can freely call methods on objects without using parentheses. In ASP.NET, you must use parentheses with all of your calls, even for methods that do not take any parameters. Writing your code as shown in the example below causes it to function correctly in ASP.NET.

Sub WriteData()
   Response.Write("This is data")
End Sub
Call WriteData()

ByVal Is Now the Default

In Visual Basic, all parameter arguments are, by default, passed by reference (ByRef). In Visual Basic .NET, all arguments are now passed by value (ByVal) by default. If you still need to have passing by reference behavior, you must explicitly use the ByRef keyword in front of your parameters as shown in the following code:

Sub MyByRefSub (ByRef Value)
   Value = 108;
End Sub

When you are moving your code to ASP.NET, it is a good practice to double-check each parameter used in your method calls to ensure that the change is made correctly.

No More Default Properties

The concept of default properties no longer exists in Visual Basic .NET. Therefore, if you have ASP code that relies on a default property that one of your objects provided, you need to change this code to reference to the property that you want, as shown in the following code:

'ASP Syntax (Implicit retrieval of Column Value property)
Set Conn = Server.CreateObject("ADODB.Connection")
Conn.Open("TestDB")
Set RS = Conn.Execute("Select * from Products")
Response.Write RS("Name")

'ASP.NET Syntax (Explicit retrieval of Column Value property)
Conn = Server.CreateObject("ADODB.Connection")
Conn.Open("TestDB")
RS = Conn.Execute("Select * from Products")
Response.Write (RS("Name").Value)

Changes in Data Types

In Visual Basic .NET, Integer type values are 32 bits and Long types are 64 bits.

Problems may arise when invoking methods on COM objects from ASP.NET or calling Win32® API calls inside your custom Visual Basic components. Pay special attention to the actual data types that are required to ensure that you are passing in or casting your values correctly.

Structured Exception Handling

Although On Error Resume Next and On Error Goto error handling techniques are still allowed in Visual Basic .NET, they do not constitute error handling best practice. Visual Basic .NET provides a complete system of structured exception handling that uses the Try, Catch, and Finally keywords. This new model is preferred for error handling as it gives a more powerful and consistent mechanism in dealing with application errors.

COM-Related Changes

With the introduction of the .NET Framework and ASP.NET, COM has not been changed, but you should be aware of a few fundamental COM issues. The following sections outline these issues.

ASPCOMPAT Attribute

You can still use these Single Threaded Apartment (STA) components without having to change any code, but you need to include the compatibility attribute aspcompat=true in a Page tag on the ASP.NET page as is shown in the following example:

<%@Page aspcompat=true Language=VB%>

Using this compatibility attribute forces the page to execute in STA mode, ensuring that the component continues to function correctly. If you attempt to use an STA component without specifying this tag, an exception is thrown.

Setting this attribute to true also allows your page to call COM+ 1.0 components that require access to the unmanaged ASP built-in objects. These components are accessible by using the ObjectContext object. If you set this tag to true, page performance is slightly reduced, so set this tag only if necessary.

Early Binding vs. Late Binding

In ASP, all calls to COM objects occur through the IDispatch interface. This feature is known as "late binding" because IDispatch handles calls to the objects indirectly at run time. In ASP.NET, you can continue to invoke components as shown in the following code:

Dim Obj As Object
Obj = Server.CreateObject("ProgID")
Obj.MyMethodCall

This manner of accessing your components is not preferred , however. With ASP.NET, you can take advantage of early binding and create your objects directly, as shown in the following example:

Dim Obj As New MyObject
MyObject.MyMethodCall()

Early binding allows components to interact in a type-safe manner. To take advantage of early binding with COM components, you need to add a reference in your project in much the same way that you add a COM reference to a Visual Basic 6.0 project. Assuming that you are using Visual Studio NET, a managed proxy object is created behind the scenes as a wrapper around the COM component, giving the semblance of dealing directly with your COM component as a .NET component.

Some performance overhead is involved when using COM interoperability because the proxy object introduces an extra layer. In most cases, however, this overhead is not a factor because the amount of processing that needs to occur is still substantially less than that required by your indirect IDispatch calls. Of course, it is best to use newly created, managed objects, but this is not always immediately possible because of the investments in legacy COM components.

OnStartPage and OnEndPage Methods

If you rely on legacy OnStartPage and OnEndPage methods to access ASP-intrinsic objects, you need to use the ASPCOMPAT directive and use Server.CreateObject to create your component in an early-bound fashion, as shown below:

Dim Obj As MyObj
Obj = Server.CreateObject(MyObj)
Obj.MyMethodCall()

Instead of using the "ProgID," this example uses the actual type in an early-bound manner. For this to work, you must add a reference to your COM component in the Visual Studio project so that the early-bound wrapper class is created. This reference should be the only case where you must continue to use Server.CreateObject.

Application Configuration Changes

In ASP, all Web application configuration information is stored in the system registry and the Internet Information Services (IIS) Metabase. This storage makes it difficult to view or modify settings because often the correct administration tools are not installed on the server. ASP.NET introduces a new configuration model based on human-readable XML files. Each ASP.NET application has its own Web.config file in its main application directory. This Web.config file functions to control the custom configuration, behavior, and security of the Web application.

You can use the Internet Services Manager snap-in to inspect and change the settings for your ASP.NET application, but with the exception of some security settings, most settings made by using the IIS administration tool are ignored by ASP.NET applications, so configuration settings must be made in the Web.config file.

The following table shows some of the more important configuration sections you can set in your file.

Table 8. Important configuration sections you can set in your file.

Web.config File SettingDescription
appSettingsConfigures custom application settings.
authenticationConfigures ASP.NET authentication support.
pagesIdentifies page-specific configuration settings.
processModelConfigures the ASP.NET process model settings on IIS systems.
sessionStateSpecifies session state options.

Classes are available in the .NET Base Class Libraries that simplify programming access to these settings.

Security-Related Changes

ASP.NET security is primarily driven from settings in the security sections of the Web.config file. ASP.NET works in concert with IIS to provide a complete security model for the application. IIS security settings are a few of the application settings that can carry over and be applied to an ASP.NET application in a manner similar to that used in ASP.

Authentication

For authentication, ASP.NET supports the options shown in the following table.

Table 9. Authentication options supported in ASP.NET

TypeDescription
WindowsASP.NET uses Windows authentication.
FormsCookie-based, custom login forms.
PassportExternal Microsoft-provided Passport Service.
NoneNo authentication is performed.

These options are the same that you have in ASP, with the exception of the new Passport authentication option. As an example, the following configuration section enables Windows-based authentication for an application:

<configuration>
   <system.web>
      <authentication mode="Windows"/>
   </system.web>
</configuration>

Authorization

Once users have been authenticated, your site must authorize what resources those users are allowed to access. The following sample shows access being granted to "dsimpson" while everyone else is denied access:

<authorization>
   <allow users="NORTHAMERICA\dsimpson"/>
   <deny users="*"/>
</authorization>

Impersonation

"Impersonation" refers to the process in which an object executes code under the identity of the entity on whose behalf it is performing. In ASP, impersonation enables code to run on the behalf of an authenticated user. Alternately, users can run anonymously under a special identity. By default, ASP.NET does not do per-request impersonation; this is different from ASP. If you rely on this capability, you must enable impersonation in your Web.config file as shown in the following code:

<identity>
   <impersonation enable = "true"/>
</identity>

Data Access

Another key focus for your migration is that of data access. ADO.NET is a powerful new way to access data. The topic of data access goes beyond the scope of this document. For the most part, you can continue to use ADO as you have in the past, but you should consider ADO.NET as a means to improve your data access methods within your ASP.NET application.

Comparing the ASP-Based Solution Sites to the ASP.NET-Based Commerce Server 2002 Retail Site

Comparing the ASP-based Commerce Server 2000 Solution Sites to the ASP.NET-based Commerce Server 2002 Retail Site shows the application structure, workflow, and process similarities as well as the differences between ASP and ASP.NET. This section highlights elements of each site and then provides a comparative analysis of each.

ASP: Workflow of the Application

A Web application is active when one or more users are in the process of browsing the Web site. The Global.asa file (and various include files referenced by the Global.asa file) define a large set of information, such as the different connection strings, to various Commerce Server databases. Much of this information (which is stored in the Application object) does not change during the life of the application.

The Global.asa file defines global variables for the site, as well as constants used in sizing HTML form controls. Application-level environment variables are set in the Application_OnStart subroutine; this subroutine executes when the application is started for the first time. The Global.asa file includes many files that provide utility routines used for working with various site objects, forms, resources, and so on.

The application remains loaded in memory until you manually unload it by using the Internet Services Manager console. Unloading an application resets session-level data and application-level data.

ASP: The Request/Response Round Trip

When a client Web browser transmits an HTTP request, the Request cycle begins. IIS creates a unique user ID at the user's first visit; this ID is assigned to the Web browser of the user. This user ID also becomes the session ID, stored by the browser in a session cookie. If the user browses to another site and then returns for a subsequent visit, the session cookie is sent along with an HTTP request; this provides a mechanism for the IIS identification of the user. Note that the session cookie is destroyed when the browser is closed. Once IIS handles the session cookie, the requested Web page is loaded onto the server. IIS then executes the code on that Web page. If the ASP code includes calls to the Response.Write method, outputting HTML, that HTML is sent to the browser. The request lifecycle ends once the page is completely processed by the server.

If the Session objects are not used, then IIS does not store any information once the page has completed execution. As a result, data must be persisted in a data store in order for subsequent page requests to include the required data. (Solution Sites use Session objects sparingly because they cannot be shared within a Web farm.)

ASP: Application Structure

The following table describes the functionality of important Commerce Server 2000 Solution Sites pages.

Table 10. Functionality of important Commerce Server 2000 Solution Sites pages

PageDescription
Basket.aspAllows users to view current shopping basket, update and delete items.
Category.aspAllows users to browse specific product-category contents. Can display subcategories and all products assigned to the current category.
Default.aspHome page of the Web site.
Product.aspDisplays information for a given product. Allows users to add the product to the shopping basket.
Search.aspAllows users to execute a catalog search for a particular product.

ASP: Product.asp Page Structure

The Product.asp page (found in Appendix 7: Product.asp Code Listing of this document) has many elements that are typical of production site ASP pages and contains the following features:

  • Several files (over 10). The include files encapsulate specific functionality, making it possible to reuse code within the Solution Site.
  • The Sub Main subroutine, which sets the page variables and contains the primary page code.
  • Product information, which is retrieved from the catalog; the page functionality includes the display of friendly error messages on errors.
  • The htmProduct variable, which aggregates HTML code based on the results of functions that render catalog data into HTML.
  • Several helper functions, such as htmRenderBuiltInProperties, htmRenderUserDefinedProductProperties, and htmRenderVariantsList, which render product properties and variants into HTML. All HTML is generated dynamically; this dynamic HTML may create performance issues on production sites.

ASP.NET: Workflow of the Application

The ASP.NET application workflow begins when a request arrives and the ASP.NET runtime creates an AppDomain for the application. It then creates the first instance of the HttpApplication class within the AppDomain. The AppDomain can create subsequent instances of the HttpApplication class because ASP.NET can create a pool of instances. Each request to the Web server is serviced by a single instance of the HttpApplication class.

Next, ASP.NET fires the OnStart event. This is normally synchronized by the Application_Start event handler in the global.asax file.

The HttpApplication class creates and initializes the HTTP modules for servicing the application request. Modules are initialized in the order in which they appear in the Web.config file. Some of the HTTP modules created are Commerce Modules. The Init method for each module is called, in which the module can subscribe to other request-processing events raised by the HttpApplication class.

Next, the Init method of the HttpApplication class is called. The Init method is a virtual method that can be overridden in the global.asax file; however, unlike the Application_Start event handler, the Init method is called once per HttpApplication class instance.

Before ASP.NET terminates the AppDomain, the HttpApplication class raises the OnEnd event.

ASP.NET: The Request/Response Round Trip

When a new request is received, the ASP.NET runtime raises the BeginRequest event. All subscribers to that event are then called in a specific order. Next, the CommerceApplicationModule class creates an instance of the CommerceContext class. The current CommerceContext instance can then be accessed from any object by accessing the static property CommerceContext.Current.

Next, the other Commerce Modules that subscribe to the BeginRequest event are called. Each module has the option of setting some state in the current CommerceContext instance. For example, the CommerceCatalogModule class, if configured in the Web.config file, copies a reference from its instance of the CatalogContext class into the current instance of the CommerceContext class.

Next, the Web form is run.

Finally, the EndRequest event is raised, and its subscribers are called. The CommerceApplicationModule class disposes of the CommerceContext object. When the CommerceContext object is disposed of, the CommerceApplicationModule class also disposes of all resources that were registered with it during processing of the page.

ASP.NET: Application Structure

The following table describes the functionality of important ASP.NET Retail Site pages.

Table 11. Functionality of important ASP.NET Retail Site pages

PageDescription
Basket.aspxAllows users to view current shopping basket, update and delete items.
Category.aspxAllows users to browse specific product-category contents. Can display subcategories and all products assigned to the current category.
Default.aspxHome page of the Web site.
ProductDetails.aspxDisplays information for a given product. Allows users to add the product to the shopping basket.
Search.aspxAllows users to execute a catalog search for a particular product.

ASP.NET sites reuse user controls in many areas of the site. These controls can be customized for the specific requirements of your site.

The following control functionality is defined by ASP.NET:

  • Address entry
  • Authentication
  • Catalog navigation

The Web.config file defines application properties; it replaces the Global.asa file for configuration purposes. The Global.asax file contains global program code that defines pipelines and HttpModules and serves as an implementation point for global events, objects, and variables.

ASP.NET: ProductDetails.aspx Page Structure

The Product.asp page (see Appendix 7: Product.asp Code Listing for the Product.asp code listing) has many elements that are typical of production site ASP pages, and corresponds in functionality to the Product.asp page. The ProductDetails.aspx page (Appendix 8: ProductDetails.aspx Code Listing of this document) contains the following features:

  • Form controls that are implemented by using ASP.NET server-side controls.
  • Many elements on the page controlled by server-side code. These elements include the runat="server" property in the element tag.
  • Specialized controls used by Commerce Server to extend ASP.NET . In the ProductDetails.aspx file, an example of these controls is the code that creates the product information. The following code is responsible for inserting an Add to Cart section in the site:
    <CommerceSite:AddToCart id="addToCartCtl" Qty="1" runat="server"></CommerceSite>

    There is no server-side script code; all page functionality is encapsulated in the controls, making for easier management of code.

ASP and ASP.NET: Comparison Points

An important similarity of the ASP and ASP.NET sites is that their application file structures are virtually identical: the same pages constitute the application in both sites.

The rendering logic of the ASP-based Solution Site is replaced by server controls in the ASP.NET site. Much of the functionality of the include files is also replaced by server controls in the ASP.NET site. Various pages of the ASP.NET Retail Site reuse these controls; this reuse results in a substantial reduction in the number of lines of code necessary to build the site. The ASP.NET code is also more readable, with the control logic encapsulating and hiding functionality.

Both sites use global configuration files, but the ASP.NET file used for this purpose is an XML-formatted file named Web.config. The ASP.NET global.asax file contains global program code. This file is composed of sections that define the pages in the Web application, the HttpModules, the pipelines, and other structures, as necessary, according to the functionality of a particular site.

The ASP.NET page contains no traditional server-side script, making the page look more like HTML. The code is more manageable as a result.

Both sites are dynamic in that very little of the site code is written as static HTML. However, the performance of the ASP.NET-based site is better because the ASP.NET pages are compiled prior to site users accessing them.

Summary

ASP.NET provides advantages over ASP. These advantages are summarized as follows:

  • Cleaner, more readable and maintainable code
  • Improved deployment, scalability, security, reliability, and performance
  • Better browser and device support

To understand and make use of the advantages that the ASP.NET-based architecture provides, you must be familiar with the Commerce Server Application Framework, its components, and the revised object model. The framework consists of a set of Commerce Server Modules that are created as an extension of ASP.NET. The object hierarchy provides a simple, intuitive structure for accessing the Commerce Server components.

There are many similarities and differences between the Commerce Server 2002 Retail Site and the Commerce Server 2000 Solution Site. Rendering logic, configuration logic, and the use of server controls are key differences that allow for more robust, rapidly deployed, and maintainable Web sites using the ASP.NET model.

Appendices

Appendix 1: login.aspx Code Listing

<%@ Page Description="Create Page" CodePage="65001" Language="c#" %>
<%@Import Namespace="ProfileSitelet"%>
<%@Import Namespace="System.Resources"%>
<%@Import Namespace="System.Threading"%>
<%@Import Namespace="System.Globalization"%>
<%@Import Namespace="System.Data"%>
<%@Import Namespace="Microsoft.CommerceServer.Runtime"%>
<%@Import Namespace="Microsoft.CommerceServer.Runtime.Profiles"%>
<%@Import Namespace="Microsoft.CommerceServer.Runtime.Configuration"%>
<%@Import Namespace="Microsoft.CommerceServer.Runtime.Diagnostics"%>
<%@Register TagPrefix="Localized" TagName="Footer" src="footer.ascx" %>
<!---------------------------------------------------------------------
--  File:      login.aspx--
--  Summary:   Login page.--
--  Sample:    Profile Sitelet--
-----------------------------------------------------------------------
--  This file is part of the Microsoft Commerce Server 2002 SDK--
--  Copyright (C) 2002 Microsoft Corporation.  All rights reserved.--
-- This source code is intended only as a supplement to Microsoft
-- Commerce Server 2002 and/or on-line documentation. See these other
-- materials for detailed information regarding Microsoft code samples.—

-- THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
-- KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
-- IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
-- PARTICULAR PURPOSE.
---------------------------------------------------------------------->
<HTML>
   <HEAD>
      <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
      <meta content="Microsoft Visual Studio 7.0" name="GENERATOR">
      <meta content="C#" name="CODE_LANGUAGE">
      <meta content="JavaScript (ECMAScript)" name=
                          "vs_defaultClientScript">
      <meta content="http://schemas.microsoft.com/intellisense/ie5" 
         name="vs_targetSchema">
      <LINK href="sitelet.css" type="text/css" rel="stylesheet">
      <script language="C#" runat="Server">
         ResourceManager rm;
         CommerceContext CommerceCurrent = CommerceContext.Current;
         string strLanguage = null;
         
         void Page_Init(Object sender, EventArgs args)
         {
            //Get the ResourceManager from the Application object.
            rm = ResourceFactory.RManager;
            if(rm == null)
            {
               //Resources are not available.
               Server.Transfer(Utility.NoResourcePage);
            }
         }
         
         void Page_Load(Object sender, EventArgs args)
         {
            CultureInfo SelectedCulture;
            NameValueCollection coll = Request.QueryString;
            
            strLanguage = coll[Utility.Language];
            
            try {
               SelectedCulture = 
                  CultureInfo.CreateSpecificCulture(strLanguage);
            }
            catch {
               SelectedCulture = 
                  CultureInfo.CreateSpecificCulture(Utility.English);
               strLanguage = Utility.English;
            }
            
            Footer.Language = strLanguage;
            
            if(null != SelectedCulture)
            {
               Thread.CurrentThread.CurrentCulture = SelectedCulture;
               Thread.CurrentThread.CurrentUICulture = 
                  Thread.CurrentThread.CurrentCulture;
            }
            
            if (!IsPostBack)
            {
               OK.Text = rm.GetString(Utility.OkLabel);
               Cancel.Text = rm.GetString(Utility.CanacelLabel);
               
               LoginLabel.Text = rm.GetString(Utility.LoginPageLabel);
               LoginValidator.ErrorMessage="* 
                  "+rm.GetString(Utility.InvalidUserId);
               
               PasswordLabel.Text = rm.GetString(Utility.PasswordLabel);
               PasswordValidator.ErrorMessage="* 
                  "+rm.GetString(Utility.InvalidPassword);
            }
         }
         
         void Cancel_Click(Object sender, EventArgs args)
         {
            LoginTextBox.Text = "";
            PasswordTextBox.Text = "";
         }
         
         void OK_Click(Object sender, EventArgs args)
         {
            if (IsValid)
            {
               try {
                  Profile UserData = 
                     CommerceCurrent.ProfileSystem.GetProfile
                        (Utility.UserLogonName, 
                        LoginTextBox.Text,Utility.ProfileTypeUser);
                  
                  if (null == UserData) {
                     Utility.SetStatus(Response, 
                     rm.GetString(Utility.LoginFailed)+" -- 
                     "+rm.GetString(Utility.BadCredentials),
                        strLanguage, true);
                  }
                  
                  if (PasswordTextBox.Text == 
                     (string)UserData[Utility.UserPassword].Value)
                  {
                     string UserId = 
                       (string)UserData[Utility.UserId].Value;
                     CommerceCurrent.AuthenticationInfo.SetAuthTicket
                        (UserId, Utility.AuthCookieSupport,
                        Utility.AuthTimeOut);
                  }
                  else
                  {
                     Utility.SetStatus(Response, 
                        rm.GetString(Utility.LoginFailed)+" -- 
                        "+rm.GetString(Utility.BadCredentials),
                        strLanguage, true);
                  }
               }
               catch(ThreadAbortException) {
                  throw;
               }
               catch(Exception e) {
                  Utility.SetStatus(Response, 
                     rm.GetString(Utility.LoginFailed)+" 
                     -- "+e.Message, strLanguage, true);
               }
               
               Utility.SetStatus(Response, 
                  rm.GetString(Utility.LoginSuccess), 
                  strLanguage, false);
            }
         }
      </script>
      <title>
         <%=rm.GetString(Utility.LoginTitle)%>
      </title>
   </HEAD>
   <BODY class="Normal">
      <DIV class="Heading">
         <%=rm.GetString(Utility.LoginSubtitle)%>
      </DIV>
      <BR>
      <FORM name="frmCreate" method="post" runat="server">
         <TABLE>
            <TR>
               <TD>
                  <asp:Label id="LoginLabel" runat="server"></asp:Label>
               </TD>
               <TD>
                  <asp:textbox id="LoginTextBox" 
                     runat="server"></asp:textbox>
                  <asp:RequiredFieldValidator 
                     id="LoginValidator" runat="server"
                     ControlToValidate="LoginTextBox"
                     Display="dynamic">
                  </asp:RequiredFieldValidator>
               </TD>
            </TR>
            <TR>
               <TD>
                  <asp:Label id="PasswordLabel" 
                     runat="server"></asp:Label>
               </TD>
               <TD>
                  <asp:textbox id="PasswordTextBox" 
                     runat="server" MaxLength="32" 
                     TextMode="Password">************</asp:textbox>
                  <asp:RequiredFieldValidator id="PasswordValidator" 
                     runat="server"
                     ControlToValidate="PasswordTextBox"
                     Display="dynamic">
                  </asp:RequiredFieldValidator>
               </TD>
            </TR>
            <TR>
               <TD>
                  <asp:Button id="OK" runat="server" 
                     OnClick="OK_Click"></asp:Button>
               </TD>
               <TD>
                  <asp:Button id="Cancel" 
                     runat="server" OnClick="Cancel_Click" 
                     CausesValidation="false"></asp:Button>
               </TD>
            </TR>
         </TABLE>
         <LOCALIZED:FOOTER id="Footer" 
            runat="Server"></LOCALIZED:FOOTER>
      </FORM>
   </BODY>
</HTML>

Appendix 2: edit.aspx Code Listing

<%@ Page Description="Create Page" CodePage="65001" Language="c#" %>
<%@Import Namespace="ProfileSitelet"%>
<%@Import Namespace="System.Resources"%>
<%@Import Namespace="System.Threading"%>
<%@Import Namespace="System.Globalization"%>
<%@Import Namespace="System.Data"%>
<%@Import Namespace="Microsoft.CommerceServer.Runtime"%>
<%@Import Namespace="Microsoft.CommerceServer.Runtime.Profiles"%>
<%@Import Namespace="Microsoft.CommerceServer.Runtime.Configuration"%>
<%@Import Namespace="Microsoft.CommerceServer.Runtime.Diagnostics"%>
<%@Register TagPrefix="Localized" TagName="Footer" src="footer.ascx" %>
<!---------------------------------------------------------------------
--  File:      edit.aspx
--
--  Summary:   Modify a profile.
--
--  Sample:    Profile Sitelet
--
-----------------------------------------------------------------------
--  This file is part of the Microsoft Commerce Server 2002 SDK
--
--  Copyright (C) 2002 Microsoft Corporation.  All rights reserved.
--
-- This source code is intended only as a supplement to Microsoft
-- Commerce Server 2002 and/or on-line documentation. See these other
-- materials for detailed information regarding Microsoft code samples.
--
-- THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
-- KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
-- IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
-- PARTICULAR PURPOSE.
---------------------------------------------------------------------->
<HTML>
   <HEAD>
      <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
      <meta content="Microsoft Visual Studio 7.0" name="GENERATOR">
      <meta content="C#" name="CODE_LANGUAGE">
      <meta content="JavaScript (ECMAScript)" 
         name="vs_defaultClientScript">
      <meta content="http://schemas.microsoft.com/intellisense/ie5" 
         name="vs_targetSchema">
      <LINK href="sitelet.css" type="text/css" rel="stylesheet">
      <script language="C#" runat="Server">
         ResourceManager rm;
         CommerceContext CommerceCurrent = CommerceContext.Current;
         string strLanguage = null;
         
         void Page_Init(Object sender, EventArgs args)
         {
            //Get the ResourceManager from the Application object.
            rm = ResourceFactory.RManager;
            if(rm == null)
            {
               //Resources are not available.
               Server.Transfer(Utility.NoResourcePage);
            }
         }
         
         void Page_Load(Object sender, EventArgs args)
         {
            CultureInfo SelectedCulture;
            NameValueCollection coll = Request.QueryString;
            
            strLanguage = coll[Utility.Language];
            
            try {
               SelectedCulture = 
                  CultureInfo.CreateSpecificCulture(strLanguage);
            }
            catch {
               SelectedCulture = 
                  CultureInfo.CreateSpecificCulture(Utility.English);
               strLanguage = Utility.English;
            }
            
            Footer.Language = strLanguage;
            
            if(null != SelectedCulture)
            {
               Thread.CurrentThread.CurrentCulture = SelectedCulture;
               Thread.CurrentThread.CurrentUICulture = 
                  Thread.CurrentThread.CurrentCulture;
            }
            
            if (!IsPostBack)
            {
              if (!CommerceCurrent.AuthenticationInfo.IsAuthenticated(90))
               {
                  Utility.SetStatus(Response, 
                     rm.GetString(Utility.NotLoggedIn), strLanguage, 
                        true);
               }
               
               OK.Text = rm.GetString(Utility.OkLabel);
               Cancel.Text = rm.GetString(Utility.CanacelLabel);
               
               UserIdLabel.Text = rm.GetString(Utility.UserIdLabel);
               UserIdValidator.ErrorMessage="* 
                  "+rm.GetString(Utility.InvalidUserId);
               
               PasswordLabel.Text = rm.GetString(Utility.PasswordLabel);
               PasswordValidator.ErrorMessage="* 
                  "+rm.GetString(Utility.InvalidPassword);
               
               FirstNameLabel.Text = rm.GetString(Utility.FirstNameLabel);
               FirstNameValidator.ErrorMessage="* 
                  "+rm.GetString(Utility.InvalidFirstName);
               
               LastNameLabel.Text = rm.GetString(Utility.LastNameLabel);
               LastNameValidator.ErrorMessage="* 
                  "+rm.GetString(Utility.InvalidLastName);
               
               EMailLabel.Text = rm.GetString(Utility.EMailLabel);
               EMailValidator.ErrorMessage="* 
                  "+rm.GetString(Utility.InvalidEMail);
               
               UserLanguageLabel.Text = 
                  rm.GetString(Utility.UserLanguageLabel);
               
               UserLanguageDropDown.DataSource = 
                  CommerceCurrent.ProfileSystem.GetSiteTerm
                     (Utility.LangaugesSiteTerm).Elements;
               UserLanguageDropDown.DataTextField = "Name";
               UserLanguageDropDown.DataValueField = "Value";
               UserLanguageDropDown.DataBind();
               
               PopulateProfileData();
            }
         }
         
         void Cancel_Click(Object sender, EventArgs args)
         {
            PopulateProfileData();
         }
         
         void OK_Click(Object sender, EventArgs args)
         {
            if (IsValid)
            {
               try {
                  Profile UserData = CommerceCurrent.UserProfile;
                  
                  UserData[Utility.UserPassword].Value = 
                     PasswordTextBox.Text;
                  UserData[Utility.UserFirstName].Value = 
                     FirstNameTextBox.Text;
                  UserData[Utility.UserLastName].Value = 
                     LastNameTextBox.Text;
                  UserData[Utility.UserEMail].Value = EMailTextBox.Text;
                  UserData[Utility.UserLanguage].Value = 
                     UserLanguageDropDown.SelectedItem.Value;
                  
                  UserData.Update();
                  
                  CommerceCurrent.UserProfile = UserData;
               }
               catch(Exception e) {
                  Utility.SetStatus(Response, 
                     rm.GetString(Utility.UnknownError)+
                     " -- "+e.Message, strLanguage, true);
               }
               
               Utility.SetStatus(Response, 
                  rm.GetString(Utility.ModifySuccess),
                  strLanguage, false);
            }
         }
         
         void PopulateProfileData()
         {
            Profile UserData = CommerceCurrent.UserProfile;
            
            UserIdTextBox.Text = 
               (string)UserData[Utility.UserLogonName].Value;
            PasswordTextBox.Text = 
              (string)UserData[Utility.UserPassword].Value;
            FirstNameTextBox.Text = 
               (string)UserData[Utility.UserFirstName].Value;
            LastNameTextBox.Text = 
               (string)UserData[Utility.UserLastName].Value;
            EMailTextBox.Text = (string)UserData[Utility.UserEMail].Value;
            
            UserLanguageDropDown.SelectedIndex = -1;
            
            foreach(ListItem Language in UserLanguageDropDown.Items)
            {
               if (Language.Value == 
                  (string)UserData[Utility.UserLanguage].Value)
               {
                  Language.Selected = true;
               }
            }
         }
      </script>
      <title>
         <%=rm.GetString(Utility.ModifyTitle)%>
      </title>
   </HEAD>
   <BODY class="Normal">
      <DIV class="Heading">
         <%=rm.GetString(Utility.ModifyTitle)%>
      </DIV>
      <BR>
      <FORM name="frmCreate" method="post" runat="server">
         <TABLE>
            <TR>
               <TD>
                  <asp:Label id="UserIdLabel" runat="server"></asp:Label>
               </TD>
               <TD>
                  <asp:textbox id="UserIdTextBox" runat="server" 
                     Enabled="False"></asp:textbox>
                  <asp:RequiredFieldValidator id="UserIdValidator" 
                     runat="server"
                     ControlToValidate="UserIdTextBox"
                     Display="dynamic">
                  </asp:RequiredFieldValidator>
               </TD>
            </TR>
            <TR>
               <TD>
                  <asp:Label id="PasswordLabel" 
                     runat="server"></asp:Label>
               </TD>
               <TD>
                  <asp:textbox id="PasswordTextBox" runat="server" 
                     MaxLength="32" TextMode="Password"></asp:textbox>
                  <asp:RequiredFieldValidator id="PasswordValidator" 
                     runat="server"
                     ControlToValidate="PasswordTextBox"
                     Display="dynamic">
                  </asp:RequiredFieldValidator>
               </TD>
            </TR>
            <TR>
               <TD>
                  <asp:Label id="FirstNameLabel" 
                     runat="server"></asp:Label>
               </TD>
               <TD>
                  <asp:textbox id="FirstNameTextBox" 
                     runat="server"></asp:textbox>
                  <asp:RequiredFieldValidator id="FirstNameValidator" 
                     runat="server"
                     ControlToValidate="FirstNameTextBox"
                     Display="dynamic">
                  </asp:RequiredFieldValidator>
               </TD>
            </TR>
            <TR>
               <TD>
                  <asp:Label id="LastNameLabel" 
                     runat="server"></asp:Label>
               </TD>
               <TD>
                  <asp:textbox id="LastNameTextBox" 
                     runat="server"></asp:textbox>
                  <asp:RequiredFieldValidator id="LastNameValidator" 
                     runat="server"
                     ControlToValidate="LastNameTextBox"
                     Display="dynamic">
                  </asp:RequiredFieldValidator>
               </TD>
            </TR>
            <TR>
               <TD>
                  <asp:Label id="EMailLabel" runat="server"></asp:Label>
               </TD>
               <TD>
                  <asp:textbox id="EMailTextBox" 
                     runat="server"></asp:textbox>
                  <asp:RegularExpressionValidator id="EMailValidator" 
                     runat="server"
                     ControlToValidate="EMailTextBox"
                     ValidationExpression=".*@.*\..*"
                     Display="dynamic">
                  </asp:RegularExpressionValidator>
               </TD>
            </TR>
            <TR>
               <TD>
                  <asp:Label id="UserLanguageLabel" 
                     runat="server"></asp:Label>
               </TD>
               <TD>
                  <asp:dropdownlist id="UserLanguageDropDown" 
                     runat="server"></asp:dropdownlist>
               </TD>
            </TR>
            <TR>
               <TD>
                  <asp:Button id="OK" runat="server" 
                     OnClick="OK_Click"></asp:Button>
               </TD>
               <TD>
                  <asp:Button id="Cancel" runat="server" 
                     OnClick="Cancel_Click" 
                     CausesValidation="false"></asp:Button>
               </TD>
            </TR>
         </TABLE>
         <LOCALIZED:FOOTER id="Footer" runat="Server"></LOCALIZED:FOOTER>
      </FORM>
   </BODY>
</HTML>

Appendix 3: addtobasket.aspx Code Listing

<%@ Page Language="C#" CodePage="65001" %>
<%@ Import Namespace="System.Resources" %>
<%@ Import Namespace="System.Threading" %>
<%@ Import Namespace="System.Globalization" %>
<%@ Import Namespace="System.Reflection" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System" %>
<%@ Import Namespace="Microsoft.CommerceServer.Runtime" %>
<%@ Import Namespace="Microsoft.CommerceServer.Runtime.Orders" %>
<%@ Import Namespace="OrderSitelet" %>
<%@Register TagPrefix="OrderSitelet" TagName="Footer" src="footer.ascx" %>
<!---------------------------------------------------------------------
--  File:      addtobasket.aspx
--
--  Summary:   Page used to display basket.
--
--  Sample:    Order Sitelet
--
-----------------------------------------------------------------------
--  This file is part of the Microsoft Commerce Server 2002 SDK
--
--  Copyright (C) 2002 Microsoft Corporation.  All rights reserved.
--
-- This source code is intended only as a supplement to Microsoft
-- Commerce Server 2002 and/or on-line documentation. See these other
-- materials for detailed information regarding Microsoft code samples.
--
-- THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
-- KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
-- IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
-- PARTICULAR PURPOSE.
---------------------------------------------------------------------->
<script language="C#" runat="server">
    ResourceManager rm       = ResourceFactory.SiteletResourceManager;
    OrderContext    _ordersys = CommerceContext.Current.OrderSystem;
    bool            _isAdding = false;    
    Basket          _basket   = null;
    string          _language;
    string          orderFormName;
   
    void Page_Init(object source, EventArgs args) {
        _language = Request.QueryString[Utility.languageLabel];

        try {

            Thread.CurrentThread.CurrentCulture =  
               CultureInfo.CreateSpecificCulture(_language);
            Thread.CurrentThread.CurrentUICulture = 
                 Thread.CurrentThread.CurrentCulture;

        } catch (ArgumentException e) {

            // just in case the user modifies the GET request 
            //themselves, handle the case
            // of an error and default to using US English for the sitelet
            _language = Utility.englishLanguage;
            Thread.CurrentThread.CurrentCulture = 
               CultureInfo.CreateSpecificCulture(_language);
            Thread.CurrentThread.CurrentUICulture = 
               Thread.CurrentThread.CurrentCulture;
            
        }
        Footer.Language = _language;
    }

    void Page_Load(object source, EventArgs e) {
        if(Utility.addRequest == Request.QueryString[Utility.requestType]) {
         _isAdding = true;
        }
        
        _basket   = LoadBasket();
        
      orderFormName = rm.GetString(Utility.orderName);       
      
        if(!IsPostBack) {
            PageSubTitle.Text = rm.GetString(Utility.BasketPageSubTitle);
            Quantity.InnerHtml = rm.GetString(Utility.Quantity);
            Name.InnerHtml = rm.GetString(Utility.Name);
            Price.InnerHtml = rm.GetString(Utility.Price);
            DiscountedPrice.InnerHtml = 
               rm.GetString(Utility.DiscountedPrice);
            
            if (_isAdding) {
                AddToBasket();
            }
        }           
        UpdateBasket(true);
    }

    void BasketListing_ItemCommand(object source, 
       RepeaterCommandEventArgs args) {
        Repeater repeats = source as Repeater;
        LineItemCollection coll = repeats.DataSource as LineItemCollection ;
        int index = Convert.ToInt32(args.CommandArgument);
        coll.Remove(index);
        UpdateBasket(true);
    }

    void CreateOrderForm(string orderFormName) {
        if(null == _basket.OrderForms[orderFormName])
            _basket.OrderForms.Add(new OrderForm(orderFormName));      
    }

    Basket LoadBasket() {
        Basket basket = _ordersys.GetBasket(new 
           Guid(CommerceContext.Current.UserID));
        return basket;
    }

    void AddToBasket() {
      CreateOrderForm(orderFormName);
        string product = Request.QueryString[Utility.productLabel];
        LineItem item = new LineItem();
        item.Quantity = 1;
        item.ProductID = product;
        item.ProductCatalog = Utility.catalogName;
        _basket.OrderForms[orderFormName].LineItems.Add(item);
    }

    void UpdateBasket(bool refresh) {
        if (refresh) {
            PipelineInfo info = new PipelineInfo(Utility.basketLabel);
            info["catalog_language"] = _language;
            info["CacheName"] = Utility.dicountsLabel;
            info.Language = _language;
            info.Profiles.Add("User", 
               CommerceContext.Current.UserProfile);
            _basket.RunPipeline(info);
            _basket.Save();
        }

      RemoveEmptyOrderForms();
      
      if(null != _basket.OrderForms[orderFormName] && 0 < 
         _basket.OrderForms[orderFormName].LineItems.Count) {
            OrderListing.DataSource = _basket.OrderForms;
            OrderListing.DataBind();            
        }else {
            Response.Redirect(rm.GetString(Utility.catalogPageUrl)
               + "?" + Utility.languageLabel + "=" + 
               Request.QueryString[Utility.languageLabel]);
        }
    }

    void RemoveEmptyOrderForms() {
        ArrayList orderformNames = GetNames();
        ' Set orderforms variable
        Microsoft.CommerceServer.Runtime.Orders.OrderFormCollection 
           orderforms = _basket.OrderForms;
        for(int index= orderforms.Count-1; index >= 0 ; index--) {
            string name = orderformNames[index] as string;
            OrderForm orderform = orderforms[name];
            if(null != orderform) {
                if(0 == orderform.LineItems.Count) {
                    orderforms.Remove(orderform.Name);
                }
            }
        }
    }

    ArrayList GetNames() {
        OrderFormCollection orderforms = _basket.OrderForms;
        if(0 < orderforms.Count) {
            ArrayList list = new ArrayList();
            foreach(OrderForm orderform in orderforms) {
                list.Add(orderform.Name);
            }
            return list;
        }
        return null;
    }

    string RenderPrice(decimal price) {

        switch (_language) {
            case Utility.englishLanguage:
                return String.Format("{0:C}", price);

            case Utility.frenchLanguage:
            case Utility.germanLanguage:
                return String.Format("{0:C}", (price * 
                   SiteletCurrencyTable.Euro));

            case Utility.japaneseLanguage:
                return String.Format("{0:C}", 
               (price * SiteletCurrencyTable.Yen));
        }

        return String.Format("{0:C}", price);

    }

    string RenderCheckOutUrl() {
        StringBuilder url = new 
           StringBuilder(rm.GetString(Utility.billingPageUrl));
        url.Append("?");
        url.Append(Utility.languageLabel);
        url.Append("=");
        url.Append(_language);
        return url.ToString();
    }

</script>
<html>
   <head>
      <link rel="stylesheet" type="text/css" href="sitelet.css">
      <title><%=rm.GetString(Utility.BasketPageTitle)%></title>
   </head>
   <body Class="Normal">
         <form id="basketform" method="post" runat="server">
            <asp:Label id="PageSubTitle" runat="server"/>
            <table width="80%" cellpadding="1" cellspacing="0" border="0">
               <tr valign="top" bgcolor="#000000">
                  <td align="center" id="Quantity" width="10%" 
                     runat="server" 
                     style="COLOR: #ffffff"></td>
                  <td align="center" id="Name" width="60%" runat="server" 
                     style="COLOR: #ffffff"></td>
                  <td align="center" id="Price" width="10%" runat="server" 
                     style="COLOR: #ffffff"></td>
                  <td align="center" id="DiscountedPrice" width="10%" 
                     runat="server" style="COLOR: #ffffff"></td> 
                  <td >&nbsp;<br>
                  </td>
               </tr>
            <asp:Repeater id="OrderListing" runat="server">
               <ItemTemplate>
                  <tr>
                     <td colspan=5>
                        <%# DataBinder.Eval(Container.DataItem, "Name") %>
                     </td>
                  </tr>                  
                  <asp:Repeater id="BasketListing" 
                    DataSource=<%#DataBinder.Eval(Container.DataItem, 
                    "LineItems")%> 
                    OnItemCommand="BasketListing_ItemCommand" 
                    runat="server">
                     <ItemTemplate>
                        <tr>
                           <td align="left" width="10%" runat="server">
                              <asp:Label ID="QuantityContent" 
                                 Runat="server">
                                 <%# ((LineItem)Container.DataItem)
                                    ["Quantity"] %>
                              </asp:Label>
                           </td>
                           <td align="left" width="60%" runat="server">
                              <asp:Label ID="ProductName" Runat="server">
                                 <%#
                                 ((LineItem)Container.DataItem)
                                              ["_product_name"]
                                 %>
                              </asp:Label>
                              <br />
                              <asp:Label ID="Description" Runat="server">
                                 <%# ((LineItem)Container.DataItem)
                                    ["_product_description"] %>
                              </asp:Label>
                           </td>
                           <td valign="right" width="10%" runat="server">
                              <asp:Label ID="PriceContent" 
                                 Font-Name="verdana" 
                                 Runat="server">
                                 <%#  
                                 RenderPrice
                             (Decimal.Parse(((LineItem)Container.DataItem)
                                ["_product_cy_list_price"].ToString())) %>
                              </asp:Label>
                           </td>
                           <td valign="right" width="10%" 
                                           runat="server">
                              <asp:Label ID="DiscountedPriceContent" 
                                 Font-Name="verdana" Runat="server">
                                 <%# 
                                 RenderPrice(
                             Decimal.Parse(((LineItem)Container.DataItem)
                            ["_cy_oadjust_adjustedprice"].ToString())) %>
                              </asp:Label>
                           </td>
                           <td valign="center">
                              <asp:LinkButton id="RemoveBtn" 
                                                    CommandName="remove" 
             CommandArgument='<%# ((LineItem)Container.DataItem).Index %>
                                                        ' runat="server">
                                          <%# rm.GetString("L_Remove") %>
                              </asp:LinkButton>
                           </td>   
                        </tr>
                     </ItemTemplate>
                     <SeparatorTemplate>
                        <tr>
                           <td colspan="5">
                              <hr noshade runat="server"/>
                           </td>
                        </tr>
                     </SeparatorTemplate>
                  </asp:Repeater>
               </ItemTemplate>
               <FooterTemplate>
                  <tr>
                     <td colspan="5" align="right">
                        <a href='<%# RenderCheckOutUrl() %>'>
                           <%# rm.GetString(Utility.PlaceOrder) %>
                        </a>
                     </td>
                  </tr>                        
               </FooterTemplate>
            </asp:Repeater>
            </table>
         <OrderSitelet:Footer runat="server" id="Footer" />
      </form>
   </body>
</html>

Appendix 4: product.aspx Code Listing

<%@ Page Description="Localized Page" CodePage="65001" %>
<%@Import Namespace="System.Globalization"%>
<%@Import Namespace="System.Resources"%>
<%@Import Namespace="System.Threading"%>
<%@Import Namespace="System"%>
<%@Import Namespace="catalogsitelet"%>
<%@Import Namespace="Microsoft.CommerceServer.Runtime"%>
<%@Import Namespace="Microsoft.CommerceServer.Runtime.Catalog"%>
<%@Import Namespace="Microsoft.CommerceServer.Runtime.Configuration"%>
<%@Import Namespace="Microsoft.CommerceServer.Runtime.Diagnostics"%>
<%@Import Namespace="System.Data"%>
<%@Register TagPrefix="Localized" TagName="Footer" src="footer.ascx" %>
<!---------------------------------------------------------------------
--  File:      product.aspx
--
--  Summary:   Page is used to display product details.
--
--  Sample:    Catalog Sitelet
--
-----------------------------------------------------------------------
--  This file is part of the Microsoft Commerce Server 2002 SDK
--
--  Copyright (C) 2002 Microsoft Corporation.  All rights reserved.
--
-- This source code is intended only as a supplement to Microsoft
-- Commerce Server 2002 and/or on-line documentation. See these other
-- materials for detailed information regarding Microsoft code samples.
--
-- THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
-- KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
-- IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
-- PARTICULAR PURPOSE.
---------------------------------------------------------------------->
<HTML>
   <HEAD>
      <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
      <meta name="GENERATOR" Content="Microsoft Visual Studio 7.0">
      <meta name="CODE_LANGUAGE" Content="C#">
      <meta name="vs_defaultClientScript" 
         content="JavaScript (ECMAScript)">
      <meta name="vs_targetSchema" 
         content="http://schemas.microsoft.com/intellisense/ie5">
      <script runat="Server" Language="C#">
         ResourceManager rm;
         string strLanguage = null;
         ProductCatalog catalog = null;
         DataRow product = null;
         string productDisplayName = null;
         
         void Page_Init(Object sender, EventArgs args) {
            //ResourceManager from the Application object.
            rm = ResourceFactory.RManager;
            catalog = CommerceContext.Current.CatalogSystem.GetCatalog
               (Utility.catalogName);
            if(rm == null) {
               //Resources are not available.
               Server.Transfer(Utility.errorPage);
            }
         }
         
         void Page_Load(Object sender, EventArgs args) {
            CultureInfo SelectedCulture;

            NameValueCollection coll = Request.QueryString;
            strLanguage = coll[Utility.languageLabel];

            try {
               SelectedCulture = 
                  CultureInfo.CreateSpecificCulture(strLanguage);
            }
            catch(Exception e) {
               SelectedCulture = 
               CultureInfo.CreateSpecificCulture
                  (Utility.englishLanguage);
               strLanguage = Utility.englishLanguage;
            }

            Footer.Language = strLanguage;                  
            catalog.ActiveLanguage = strLanguage;

            if(null != SelectedCulture)   {
               Thread.CurrentThread.CurrentCulture = SelectedCulture;
               Thread.CurrentThread.CurrentUICulture = 
                  Thread.CurrentThread.CurrentCulture;
            }
            string productid = coll[Utility.productID];
            if (!IsPostBack && productid != null) {
               try   {
                  Product myProduct = catalog.GetProduct(productid);
                  DataSet data = myProduct.GetProductProperties();
                  product = data.Tables[0].Rows[0];
                  
                  try {
                     productDisplayName = (string)product["name"];
                  }   
                  catch(Exception e) {
                     productDisplayName  = "";
                  }
                  try {
                     ProductImage.ImageUrl = 
                       (string)product["Image_filename"];
                  }
                  catch(Exception e) {
                     ProductImage.ImageUrl = null;
                  }
                  
                  try {
                     ProductID.Text = (string)product["name"];
                  }
                  catch(Exception e) {
                     ProductID.Text = null;
                  }

                  try {
                     IntroDateLabel.Text = 
                        rm.GetString(Utility.productPageIntro);
                     DateTime dateTime = 
                        (DateTime)product["IntroductionDate"];
                     IntroDate.Text = dateTime.ToShortDateString();
                  }
                  catch(Exception e) {
                     IntroDateLabel.Text = null;
                  }

                  try {
                     PriceLabel.Text = 
                        rm.GetString(Utility.productPagePrice);
                     Price.Text = 
                        "$" + product["cy_list_price"].ToString();
                  }
                  catch(Exception e) {
                     PriceLabel.Text = null;
                  }

                  try {
                     ProductCodeLabel.Text = 
                        rm.GetString(Utility.productPageCode);
                     ProductCode.Text = product["ProductCode"].ToString();
                  }
                  catch(Exception e) {
                     ProductCodeLabel.Text = null;
                  }
                  
                  try {
                     DataSet variantData = myProduct.GetVariants();
                     VariantListing.DataSource = variantData;
                     VariantListing.DataBind();
                     if(0 != variantData.Tables[0].Rows.Count) {
                        Variants.Text = 
                           rm.GetString(Utility.productPageColor);
                     }else {
                        Variants.Text = null;
                     }
                  }
                  catch(Exception e) {
                     Variants.Text = null;
                  }
                  
                  try {
                     DescribeLabel.Text = 
                        rm.GetString(Utility.productPageDescription);
                     Description.Text = (string)product["Description"];
                  }
                  catch(Exception e) {
                     DescribeLabel.Text = null;
                  }
                  
                  try   {
                     Size.Text = (string)product
                        ["ProductSize"].ToString();
                     if("" != Size.Text.Trim()) {
                        SizeLabel.Text = 
                           rm.GetString(Utility.productPageSize);
                     }
                  }
                  catch(Exception e) {
                     SizeLabel.Text = null;
                  }
                  try {
                     int available = (int) product["OnSale"];
                     if(1 == available) {
                        AvailableLabel.Text = 
                           rm.GetString(Utility.productPageAvailable);
                     }else {
                        AvailableLabel.Text = 
                           rm.GetString(Utility.productPageUnavailable);
                     }
                  }
                  catch(Exception e) {
                     AvailableLabel.Text = null;
                  }      
               }
               catch(Exception e) {
                  //Eat exception
               }
            }
         }
      </script>
      <title>
         <%=rm.GetString(Utility.productPageTitle)+"
            "+productDisplayName%>
      </title>      
      <LINK REL="stylesheet" TYPE="text/css" HREF="sitelet.css">
   </HEAD>
   <body>
      <DIV CLASS="Heading">
         <%= rm.GetString(Utility.productPageTitle)+" 
               "+productDisplayName%>
      </DIV>
      <BR>
      <BR>
      <form id="Productform" method="post" runat="server">
         <asp:Table id="Product" runat="server" Width="50%"
            cellpadding="2" cellspacing="0" Height="175px">
            <asp:TableRow>
               <asp:TableCell ColumnSpan="100">
                  <B>
                     <asp:Label ID="ProductID" Runat="server" />
                  </B>
                  <br>
                  <asp:Label ID="IntroDateLabel" Runat="server" />
                  <asp:Label ID="IntroDate" Runat="server" />
                  <br>
               </asp:TableCell>
            </asp:TableRow>
            <asp:TableRow>
               <asp:TableCell ColumnSpan="70">
                  <asp:Image id="ProductImage" runat="server"></asp:Image>
               </asp:TableCell>
               <asp:TableCell ColumnSpan="30">
                  <b>
                     <asp:Label ID="PriceLabel" Runat="server" />
                  </b>
                  <asp:Label ID="Price" Runat="server" />
                  <br>
                  <b>
                     <asp:Label ID="ProductCodeLabel" Runat="server" />
                  </b>
                  <asp:Label ID="ProductCode" Runat="server" />
                  <br>
               </asp:TableCell>
            </asp:TableRow>
            <asp:TableRow>
               <asp:TableCell ColumnSpan="100">
                  <B>
                     <asp:Label ID="Variants" Runat="server" />
                  </B>
                  <br>
                  <asp:Repeater id="VariantListing" runat="server">
                     <ItemTemplate>
                        <asp:Label id="Color" runat="server">
                           <%# DataBinder.Eval(Container.DataItem,
                              "ProductColor") %>
                        </asp:Label>
                     </ItemTemplate>
                     <SeparatorTemplate>
                        <BR>
                     </SeparatorTemplate>
                  </asp:Repeater>
                  <BR>
               </asp:TableCell>
            </asp:TableRow>
            <asp:TableRow>
               <asp:TableCell ColumnSpan="100">
                  <B>
                     <asp:Label ID="DescribeLabel" Runat="server" />
                  </B>
                  <BR>
                  <asp:Label ID="Description" Runat="server" />
                  <BR>
               </asp:TableCell>
            </asp:TableRow>
            <asp:TableRow>
               <asp:TableCell ColumnSpan="100">
                  <B>
                     <asp:Label ID="SizeLabel" Runat="server" />
                  </B>
                  <asp:Label ID="Size" Runat="server" />
                  <BR>
               </asp:TableCell>
            </asp:TableRow>
         </asp:Table>
         <asp:Label ID="AvailableLabel" Runat="server" />
         <Localized:Footer runat="server" id="Footer" />
      </form>
   </body>
</HTML>

Appendix 5: ResourceFactory.cs Code Listing

-----------------------------------------------------------------------
--  This file is part of the Microsoft Commerce Server 2002 SDK
--
--  Copyright (C) 2002 Microsoft Corporation.  All rights reserved.
--
-- This source code is intended only as a supplement to Microsoft
-- Commerce Server 2002 and/or on-line documentation. See these other
-- materials for detailed information regarding Microsoft code samples.
--
-- THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
-- KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
-- IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
-- PARTICULAR PURPOSE.
---------------------------------------------------------------------->
using System;
using System.Reflection;
using System.Resources;
using System.Threading;
using System.Collections.Specialized;
using Microsoft.CommerceServer.Runtime;
using Microsoft.CommerceServer.Runtime.Caching;
using Microsoft.CommerceServer.Runtime.Profiles;
using Microsoft.CommerceServer.Runtime.Targeting;
using Microsoft.CommerceServer.Runtime.Pipelines;
using Microsoft.CommerceServer.Runtime.Diagnostics;
using System.Runtime.InteropServices;
using Microsoft.CommerceServer.Interop.Caching;


namespace AdSitelet
{
   /// <summary>
   /// Summary description for RManager.
   /// </summary>
   public class ResourceFactory 
   {
      static ResourceManager _rm;

      public static ResourceManager RManager 
      {
         get 
         {
            if(_rm == null) 
            {
               lock(typeof(ResourceFactory))
               {
                  if(_rm == null) 
                  {
                     _rm = new ResourceManager( "adsitelet", 
                        Assembly.GetExecutingAssembly(), null);
                  }
               }
            }
            return _rm;
         }
      }
   }
   
   public class Utility 
   {
      public const string errorPage = @"noresource.aspx";
      public const string English = @"L_Language_DisplayNameEN";
      public const string German = @"L_Language_DisplayNameDE";
      public const string French = @"L_Language_DisplayNameFR";
      public const string Japanese = @"L_Language_DisplayNameJP";

      public const string languageLabel = @"Language";

      public const string siteletTitle =  @"L_Sitelet_Title";
      public const string siteletSubTitle =  @"L_Sitelet_SubTitle";
      public const string femalePageTitle =  @"L_Page_Title_Female";
      public const string guestPageTitle =  @"L_Page_Title_Guest";
      public const string malePageTitle =  @"L_Page_Title_Male";
      public const string mainPageTitle =  @"L_Page_Title";
      public const string pageSubTitle =  @"L_Page_SubTitle";

      public const string femaleUserID =  @"L_FemaleUser_ID";
      public const string maleUserID =  @"L_MaleUser_ID";

      public const string englishLanguage = @"en-US";
      public const string germanLanguage = @"de-DE";
      public const string frenchLanguage = @"fr-FR";
      public const string japaneseLanguage = @"ja-JP";

      public const string logonName = @"GeneralInfo.logon_name";
      public const string userSchema = @"UserObject";
      public const string errorNoAds = @"L_Error_NoAd";

      public const string mainPageUrl = @"L_MainPage";
      public const string malePageUrl = @"L_HTMLURL_Male";
      public const string femalePageUrl = @"L_HTMLURL_Female";
      public const string guestPageUrl = @"L_HTMLURL_Guest";
      public const string defaultPageUrl = @"L_HTMLURL_Home";

      public const string homeUrlText = @"L_HTMLURLText_Home";
      public const string maleUrlText = @"L_HTMLURLText_Male";
      public const string femaleUrlText = @"L_HTMLURLText_Female";
      public const string guestUrlText = @"L_HTMLURLText_Guest";

      public Utility(){}

      public string GetContent(Profile userProfile, 
         string pageGroup, int numRequested, string language) 
      {
         // Create a targeting profile
         CommerceContext ctx = CommerceContext.Current;
            
         // Get a ContentSelector
         ContentSelector cso = 
            ctx.TargetingSystem.SelectionContexts
               ["advertising"].GetSelector();
            
         if(null != pageGroup && "" != pageGroup.Trim()) 
         {
            ctx.TargetingSystem.TargetingContextProfile
               ["PageGroup"].Value = 
               pageGroup;
         }
         else 
         {
            ctx.TargetingSystem.TargetingContextProfile
               ["PageGroup"].Value = 
               "Home";
         }
            
         string userLanguage = null;
         if(null != userProfile) 
         {
            cso.Profiles.Add("User", userProfile);
            userLanguage  = (string)userProfile
               ["GeneralInfo.Language"].Value;
         }
            
         if(null == userLanguage) 
         {
            ctx.TargetingSystem.TargetingContextProfile
               ["Language"].Value = 
               language;
         }
         else 
         {
            ctx.TargetingSystem.TargetingContextProfile
               ["Language"].Value = 
               userLanguage;
         }
            
         if (numRequested > 0) 
         {
            cso.ItemsRequested = numRequested;
         }

         cso.Profiles.Add("targetingContext", 
            ctx.TargetingSystem.TargetingContextProfile);
            
         cso.TraceMode = true;

         StringCollection Ads = cso.GetContent();
            
         if(null != Ads && Ads.Count > 0) 
         {
            return Ads[0];
         }
         else 
         {
            return null;
         }
      }

      public void RecordEvent(int ciid, string cacheName, int pageGroupId)
      {
         //Get Commerce Context.
         CommerceContext ctx = CommerceContext.Current;
         if(null == ctx) 
         {
            return;
         }

         //Get advertisement cache from cachemanager.
         CommerceCache cache = ctx.Caches[cacheName];
         if(null == cache) 
         {
            return;
         }
            
         Microsoft.CommerceServer.Runtime.IDictionary cacheData = 
           (Dictionary) 
            cache.GetCache();
         if(null == cacheData) 
         {
            return;
         }
                        
         //Get record event pipeline from framework.
         PipelineBase recordEventPipeline = ctx.Pipelines["recordevent"];
         if(null != recordEventPipeline) 
         {
            //Create order and context dictionaries, assuming it will be 
            //successful.
            Microsoft.CommerceServer.Runtime.IDictionary orderDictionary =
               new Microsoft.CommerceServer.Runtime.Dictionary();
            Microsoft.CommerceServer.Runtime.IDictionary contextDictionary =
               new Microsoft.CommerceServer.Runtime.Dictionary();
            if(null == orderDictionary && null == contextDictionary) 
            {
               return;
            }

            //Initialize order dictionary.
            ContentListFactory contentFactory = null;
            orderDictionary["_winners"] = ciid;
            orderDictionary["_event"] = "CLICK";

            orderDictionary["_performance"] = cacheData["_performance"];
            contentFactory = (ContentListFactory)cacheData["Factory"];
            ContentList contentList = 
               contentFactory.CreateNewContentList();
            orderDictionary["_content"] = contentList;
               
            //Initialize contxt dictionary.
            contextDictionary["SiteName"] = ctx.SiteName;
            contextDictionary["PageGroupId"] = pageGroupId;
               
            //Execute Record event pipeline and log event to weblog.
            recordEventPipeline.Execute
               (orderDictionary, contextDictionary);
               
            //Release refernces to COM objects.
            if(null != cacheData) 
            {
               Marshal.ReleaseComObject(cacheData);
            }            
            if(null != orderDictionary) 
            {
               Marshal.ReleaseComObject(orderDictionary);
            }            
            if(null != contextDictionary) 
            {
               Marshal.ReleaseComObject(contextDictionary);
            }
            if(null != contentFactory) 
            {
               Marshal.ReleaseComObject(contentFactory);
            }
            if(null != contentList) 
            {
               Marshal.ReleaseComObject(contentList);
            }         
         }
         else 
         {
            return;
         }
      }
   }
}

Appendix 6: redir.aspx Code Listing

<%@ Page Description="Default Page" CodePage="65001" %>
<%@Import Namespace="AdSitelet"%>
<%@Import Namespace="System.Threading"%>
<%@Import Namespace="System.Resources"%>
<%@Import Namespace="System.Globalization"%>
<%@Import Namespace="Microsoft.CommerceServer.Runtime"%>
<%@Import Namespace="Microsoft.CommerceServer.Runtime.Caching"%>
<%@Import Namespace="Microsoft.CommerceServer.Runtime.Profiles"%>
<%@Import Namespace="Microsoft.CommerceServer.Runtime.Targeting"%>
<%@Import Namespace="Microsoft.CommerceServer.Runtime.Pipelines"%>
<%@Import Namespace="Microsoft.CommerceServer.Runtime.Diagnostics"%>
<%@Import Namespace="Microsoft.CommerceServer.Interop.Caching"%>
<%@Import Namespace="System.Runtime.Serialization"%>
<%@Import Namespace="System.Runtime.InteropServices"%>
<!---------------------------------------------------------------------
--  File:      redir.aspx
--
--  Summary:   Redirects to Url.
--
--  Sample:    Ad Sitelet
--
-----------------------------------------------------------------------
--  This file is part of the Microsoft Commerce Server 2002 SDK
--
--  Copyright (C) 2002 Microsoft Corporation.  All rights reserved.
--
-- This source code is intended only as a supplement to Microsoft
-- Commerce Server 2002 and/or on-line documentation. See these other
-- materials for detailed information regarding Microsoft code samples.
--
-- THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
-- KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
-- IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
-- PARTICULAR PURPOSE.
---------------------------------------------------------------------->
<HTML>
   <HEAD>
      <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
      <meta name="GENERATOR" Content="Microsoft Visual Studio 7.0">
      <meta name="CODE_LANGUAGE" Content="C#">
      <meta name="vs_defaultClientScript" content="JavaScript
         (ECMAScript)">
      <meta name="vs_targetSchema" 
         content="http://schemas.microsoft.com/intellisense/ie5">
      <script runat="Server" Language="C#">
         void Page_Init(Object sender, EventArgs args) {
            //Initialization code
         }

         void Page_Load(Object sender, EventArgs args) {
            //Get query string parameters
            NameValueCollection coll = Request.QueryString;
            //Records event
            FormatterConverter format = new FormatterConverter();
            new Utility().RecordEvent(format.ToInt32(coll["ciid"]), 
               coll["cachename"], format.ToInt32(coll["PageGroupId"]));
            //Redirect to target URL
            Response.Redirect(coll["url"]);
         }
         
      </script>
      <LINK REL="stylesheet" TYPE="text/css" HREF="sitelet.css">
   </HEAD>
   <body>
   </body>
</HTML>

Appendix 7: Product.asp Code Listing

<!-- #INCLUDE FILE="include/header.asp" -->
<!-- #INCLUDE FILE="include/const.asp" -->
<!-- #INCLUDE FILE="include/html_lib.asp" -->
<!-- #INCLUDE FILE="include/catalog.asp" -->
<!-- #INCLUDE FILE="include/std_access_lib.asp" -->
<!-- #INCLUDE FILE="include/std_cache_lib.asp" -->
<!-- #INCLUDE FILE="include/std_cookie_lib.asp" -->
<!-- #INCLUDE FILE="include/std_ordergrp_lib.asp" -->
<!-- #INCLUDE FILE="include/std_pipeline_lib.asp" -->
<!-- #INCLUDE FILE="include/std_profile_lib.asp" -->
<!-- #INCLUDE FILE="include/std_url_lib.asp" -->
<!-- #INCLUDE FILE="include/std_util_lib.asp" -->
<!-- #INCLUDE FILE="template/discount.asp" -->
<!-- #INCLUDE FILE="include/setupenv.asp" -->
<!-- #INCLUDE FILE="template/layout1.asp" -->
<%
' ======================================================================
' Product.asp
' Display page for Product Details
'
' Commerce Server 2000 Solution Sites 1.0
' ----------------------------------------------------------------------
'  This file is part of Microsoft Commerce Server 2000
'
'  Copyright (C) 2000 Microsoft Corporation.  All rights reserved.
'
' This source code is intended only as a supplement to Microsoft
' Commerce Server 2000 and/or on-line documentation.  See these other
' materials for detailed information regarding Microsoft code samples.
'
' THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
' KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
' IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
' PARTICULAR PURPOSE.
' =============================================================================

Sub Main()
   Dim sCatalogName, sCategoryName, sProductID, sVariantID, sCacheKey, 
      sProductName, sIdentifyingVariantProperty, sBtnText
   Dim BasketAffinity, ProductAffinity, nDiscountsToShow
   Dim mscsCatalog, mscsProduct, rsProperties, rsVariants,
   Dim rsPropertyAttributes
   Dim bHasVariants
   Dim htmTitle, htmContent, htmFormContent, htmProduct, urlAction
   Dim sProductAction
   
   Call EnsureAccess()
   
   sPageTitle = mscsMessageManager.GetMessage
      ("L_Product_HTMLTitle", sLanguage)
   
   htmPageContent = Null
   bHasVariants = False   
   
   ' Get the catalog name
   sCatalogName = GetRequestString(CATALOG_NAME_URL_KEY, Null)
   Call EnsureUserHasRightsToCatalog(sCatalogName, m_UserID)
   
   ' Get the product ID
   sProductID = GetRequestString(PRODUCT_ID_URL_KEY, Null)

   ' Get the Product Action key
   sProductAction = GetRequestString(PRODUCT_ACTION_KEY, Null)
   
   If IsNull(sCatalogName) Or IsNull(sProductID) Then
      Response.Redirect
        (GenerateURL(MSCSSitePages.BadURL, Array(), Array()))
   End If

   ' Get the category name
   sCategoryName = GetRequestString(CATEGORY_NAME_URL_KEY, Null)

   'Get the variant ID
   sVariantID = GetRequestString(VARIANT_ID_URL_KEY, Null)

   ' Get the catalog fragment from the cache, or render it
   sCacheKey = sCatalogName & sCategoryName & sProductID & sVariantID
   htmPageContent = LookupCachedFragment("ProductPageCache", sCacheKey)
   
   If IsNull(htmPageContent) Then
      Set mscsCatalog = MSCSCatalogManager.GetCatalog(sCatalogName)
      If mscsCatalog Is Nothing Then
         Response.Redirect
            (GenerateURL(MSCSSitePages.BadCatalogItem, Array(), 
            Array()))
      End If
      
      Set mscsProduct = mscsCatalog.GetProduct(sProductID)
      If mscsProduct Is Nothing Then
         Response.Redirect
           (GenerateURL(MSCSSitePages.BadCatalogItem, Array(), 
                                                     Array())) 
      End If
      
      Set rsProperties = mscsProduct.GetProductProperties
      
      ' "name" is a required product property and cannot have null value.
      sProductName = rsProperties.Fields(PRODUCT_NAME_PROPERTY_NAME).Value
      
      ' Determine if the product has variants
      Set rsVariants = mscsProduct.Variants
      If Not rsVariants.EOF Then 
         bHasVariants = True
      End If

      ' Render the product's built in properties (ie price)
      ' (we don't do this for variants here,
      ' as they may differ per variant
      If Not bHasVariants Then
         htmProduct = htmRenderBuiltInProperties( _
                                       rsProperties, _
                                       PRODUCT_PRICE_PROPERTY_NAME, _
                                       MSCSSiteStyle.Body _
                                    ) & CRLF
      End If
    
      ' Render user-defined properties
      htmProduct = htmProduct & 
         htmRenderUserDefinedProductProperties
            (rsProperties, MSCSSiteStyle.Body)
            & CRLF
      
       ' If the product is a variant, render each of the variants 
       ' (along with built-in properties for each variant)

      If bHasVariants Then
         sIdentifyingVariantProperty =
            mscsCatalog.IdentifyingVariantProperty
         htmFormContent = RenderText(mscsMessageManager.GetMessage
          ("L_Product_Has_Variants_HTMLText", sLanguage),
             MSCSSiteStyle.Body) & CR
          htmFormContent = htmFormContent & htmRenderVariantsList
            (sIdentifyingVariantProperty, rsVariants, sVariantID) & CRLF
      End If

      ' Render the AddToBasket form   
      htmFormContent = htmFormContent & RenderHiddenField
         (CATALOG_NAME_URL_KEY, sCatalogName)
      htmFormContent = htmFormContent & RenderHiddenField
        (CATEGORY_NAME_URL_KEY, sCategoryName)
      htmFormContent = htmFormContent & RenderHiddenField
         (PRODUCT_ID_URL_KEY, sProductID)
      
      If Not bHasVariants And (rsProperties.Fields
         (PROD_CLASSTYPE_FLDNAME) = cscProductFamilyClass) Then
         htmFormContent = htmFormContent & RenderText
       (mscsMessageManager.GetMessage
          ("L_ProdFamNoVariant_ErrorMessage",sLanguage),
             MSCSSiteStyle.Warning)
          & BR
      Else
         htmFormContent = htmFormContent &
         RenderText(mscsMessageManager.GetMessage
           ("L_Specify_Product_Quantity_HTMLText", sLanguage),
              MSCSSiteStyle.Body)
         htmFormContent = htmFormContent & 
            RenderTextBox(PRODUCT_QTY_URL_KEY, 1, 
            3, 3, MSCSSiteStyle.TextBox)
         
         sBtnText = mscsMessageManager.GetMessage
            ("L_Add_To_Basket_Button", 
            sLanguage)
         htmFormContent = htmFormContent & 
            RenderSubmitButton(SUBMIT_BUTTON, 
            sBtnText, MSCSSiteStyle.Button)
      End If
      
      urlAction = GenerateURL(MSCSSitePages.AddItem, Array(), Array())
      htmContent = htmProduct & RenderForm(urlAction, htmFormContent, 
                                                           HTTP_POST) 
         
      htmTitle = RenderText(sProductName, MSCSSiteStyle.Title)
      htmPageContent = htmTitle & CRLF & htmContent

      Call CacheFragment("ProductPageCache", sCacheKey, htmPageContent)
   End If

   ' Show user message, if needed   
   Select Case (sProductAction)
      Case Null
      Case ""
      Case ADD_ACTION
         htmPageContent = htmPageContent & "<br>" & 
            mscsMessageManager.GetMessage("pur_AddItem", sLanguage) &
                                                               "<br>"
   End Select
   
   ' Populate the discount banner slot
   Set ProductAffinity = GetShownProductsDetails()
   BasketAffinity = Null
   nDiscountsToShow = 1
   htmDiscountBannerSlot = RenderDiscounts(ProductAffinity, 
       BasketAffinity, nDiscountsToShow)
End Sub


' -----------------------------------------------------------------------------
' htmRenderBuiltInProperties
'
' Description:
'   Renders the product's built in properties, such as price
'
' Parameters:
'
' Returns:
'
' Notes :
'   none
' ------------------------------------------------------------------------
Function htmRenderBuiltInProperties(ByVal rsProperties,
                     ByVal sPropertyName, ByVal style) 
    Dim sText
    
    sText = mscsMessageManager.GetMessage
       ("L_Product_Price_DisplayName_HTMLText", 
       sLanguage) 
    sText = sText & ": " &   
       htmRenderCurrency(rsProperties.Fields(sPropertyName).Value)
    htmRenderBuiltInProperties = RenderText(sText, style)
End Function


' ----------------------------------------------------------------------
' htmRenderUserDefinedProductProperties
'
' Description:
'   Renders the product's user-defined properties
'   Certain properties are treated specially, such as
'      IMAGE_FILENAME_PROPERTY_NAME
'
' Parameters:
'
' Returns:
'
' Notes :
'   none
' ----------------------------------------------------------------------
Function htmRenderUserDefinedProductProperties
   (ByVal rsProperties, ByVal style)
    Dim fldProperty
    Dim sPropertyName
    Dim htmProperty
    Dim nWidth, nHeight
    Dim bShowProperty
   
   ' Iterate each property
   For Each fldProperty In rsProperties.Fields
      ' Find out if the property can be shown.
      ' Do not display empty properties.
      bShowProperty = False
      If Not IsNull(fldProperty.Value) Then
         ' Filter out built-in properties. 
         ' Built-in properties do not have 
         ' attributes.
         If Not IsNull(MSCSCatalogAttribs.Value(fldProperty.Name)) Then
            If MSCSCatalogAttribs.Value(fldProperty.Name).Value
              (DISPLAY_ON_SITE_ATTRIBUTE) = True Then
               bShowProperty = True
            End If
         End If
      End If
      
      If bShowProperty Then
         Select Case UCase(fldProperty.Name)
              Case UCase(IMAGE_FILENAME_PROPERTY_NAME)
               nWidth = PeekField(rsProperties, IMAGE_WIDTH_PROPERTY_NAME)
               nHeight = PeekField(rsProperties, IMAGE_HEIGHT_PROPERTY_NAME)
                htmProperty = BR & RenderImage(rsProperties.Fields
                 (IMAGE_FILENAME_PROPERTY_NAME).Value, nWidth, nHeight, 
                  mscsMessageManager.GetMessage
                    ("L_Standard_Image_Description_Text", sLanguage), "") & CRLF
                                                      
            Case UCase(PRODUCT_NAME_PROPERTY_NAME), _
                 UCase(IMAGE_WIDTH_PROPERTY_NAME), _
                 UCase(IMAGE_HEIGHT_PROPERTY_NAME)
                htmProperty = ""  
                                                          
            Case Else
                ' Use DisplayName attribute if it is set, otherwise 
                ' use PropertyName
               sPropertyName = sGetPropertyDisplayName(fldProperty.Name)
                htmProperty = RenderText(FormatOutput(LABEL_TEMPLATE, 
                   Array(sPropertyName)) & 
                      fldProperty.Value, MSCSSiteStyle.Body)  
                   & BR
         End Select
         htmRenderUserDefinedProductProperties = 
         htmRenderUserDefinedProductProperties & htmProperty
      End If
   Next
End Function

' ----------------------------------------------------------------------
' PeekField
'
' Description:
'   This function attempts to read a field off a recordset.
'   If the field doesn't exist, it returns null.
'
' Parameters:
'
' Returns:
'
' Notes :
'   none
' ----------------------------------------------------------------------
Function PeekField(ByVal rs, ByVal sFieldName)
   PeekField = Null
   On Error Resume Next
      PeekField = rs.Fields(sFieldName).Value
   On Error Goto 0
End Function


' ----------------------------------------------------------------------
' htmRenderVariantsList
'
' Description:
'   Render all variants of the given product, with a radio button 
'in front of them so the shopper can choose which variant to buy
'
' Parameters:
'
' Returns:
'
' Notes :
'   none
' ----------------------------------------------------------------------
Function htmRenderVariantsList(ByVal sIdentifyingVariantProperty,
   ByVal rsVariants, ByVal sSelectedVariantID)
    Dim listProps, arrHeaderCols, arrDataCols, arrAttLists, i, htmRows,
    Dim bSelected
    
   Set listProps = listGetVariantPropertiesToShow(rsVariants.Fields)
   
   ' Size arrays to accomodate the table columns. 
   ReDim arrHeaderCols(listProps.Count)
   ReDim arrDataCols(listProps.Count)
   ReDim arrAttLists(listProps.Count)
   
   ' Column containg radio buttons does not require a header.
   arrHeaderCols(0) = ""
   
   ' Put the display name of each user-defined property 
   ' in table header row.
   For i = 0 to listProps.Count - 2
      arrHeaderCols(i + 1) = sGetPropertyDisplayName(listProps(i))
   Next
   
   ' The last column is the price column.
   arrHeaderCols(listProps.Count) = 
      mscsMessageManager.GetMessage
         ("L_Product_Price_DisplayName_HTMLText", sLanguage)
   
   ' Render the table header row, all columns within the 
   ' row are center-justified.
   htmRows = RenderTableHeaderRow(arrHeaderCols, Array(),
      MSCSSiteStyle.TRCenter)
   
   ' Set the alignment attribute for first data column (radio buttons).
   arrAttLists(0) = MSCSSiteStyle.TDCenter
   
   ' Set the alignment attribute for columns containing 
   ' user-defined properties.
   ' Note that we assume a user-defined variant property 
   ' has a data type of string.
   '   If you have assigned a different data type to such 
   ' a property, you may need to align it 
   ' differently (e.g. currency data type requires right alignment.) 
   For i = 0 to listProps.Count - 2
      arrAttLists(i + 1) = MSCSSiteStyle.TDLeft
   Next
   
   ' Set the alignment attribute for the last column (price).
   arrAttLists(listProps.Count) = MSCSSiteStyle.TDRight
   
   ' If default selection is not specified, select the first
   ' variant in the list.
   If IsNull(sSelectedVariantID) Then
      sSelectedVariantID = rsVariants.Fields
      (sIdentifyingVariantProperty).Value
   End If
   
   ' Iterate thru all variants and render each as a table row.
    While Not rsVariants.EOF 
      bSelected = False
      
      If sSelectedVariantID = rsVariants.Fields
      (sIdentifyingVariantProperty).Value Then
         bSelected = True
      End If
      
      arrDataCols(0) = RenderRadioButton("variant_id",   
         rsVariants.Fields(sIdentifyingVariantProperty).Value,
         bSelected, MSCSSiteStyle.RadioButton)
      
' Note that we assume a user-defined variant property has a 
' data type of string. If you have assigned a different data type to
' a such a property, you may need to perform additional 
' transformation on the property value (such as calling 
' htmRenderCurrency() for currency values) prior to displaying it.
      For i = 0 to listProps.Count - 2
         arrDataCols(i + 1) = rsVariants.Fields(listProps(i)).Value
      Next
      
      arrDataCols(listProps.Count) = 
         htmRenderCurrency(rsVariants.Fields("cy_list_price").Value)
      
      htmRows = htmRows & RenderTableDataRow(arrDataCols, arrAttLists, 
         MSCSSiteStyle.TRMiddle)
      rsVariants.MoveNext
   Wend
   
   htmRenderVariantsList = 
      RenderTable(htmRows, MSCSSiteStyle.BorderedTable)
End Function


Function listGetVariantPropertiesToShow(ByVal fldsProps)
   Dim listProps, fldProp
   
   Set listProps = GetSimpleList()
   
   ' Iterate thru all properties to built a list of properties to display.
   For Each fldProp In fldsProps
      ' Exclude built-in properties.
      If (fldProp.Name <> "TimeStamp") And 
        (fldProp.Name <> "cy_list_price") And 
        (fldProp.Name <> "VariantID") Then
         ' Include properties that are marked for display.
         If MSCSCatalogAttribs.Value(fldProp.Name).Value
            ("DisplayOnSite") = True Then
            listProps.Add(fldProp.Name)
         End If
      End If
   Next
   
   ' Though price is a built-in property, it must be displayed.
   listProps.Add("cy_list_price")
   
   Set listGetVariantPropertiesToShow = listProps
End Function
   

' ----------------------------------------------------------------------
' sGetPropertyDisplayName
'
' Description:
'   Gets the display name for a catalog property where available, 
' otherwise returns the property column name
'
' Parameters:
'   sPropName         - The name of the property
'
' Returns:
'
' Notes :
'   none
' ----------------------------------------------------------------------
Function sGetPropertyDisplayName(ByVal sPropName)
' Use property's display name attribute if it is set
If Not 
IsNull(MSCSCatalogAttribs.Value(sPropName).Value(DISPLAY_NAME_ATTRIBUTE)) Then
   sGetPropertyDisplayName =    
   MSCSCatalogAttribs.Value(sPropName).Value(DISPLAY_NAME_ATTRIBUTE)
   Else
      ' Otherwise, use property name
       sGetPropertyDisplayName =    
       MSCSCatalogAttribs.Value(sPropName).Value(PROPERTY_NAME_ATTRIBUTE)
   End If
End Function
%>

Appendix 8: ProductDetails.aspx Code Listing

<!---------------------------------------------------------------------
  FileName :    ProductDetails.aspx
  Description :    Displays the catalog browser control, products list 
   control, search control and the navigation tabs control       
  
  This file is part of the Microsoft Commerce Server 2002 SDK

  Copyright (C) 2002 Microsoft Corporation.  All rights reserved.

  Commerce Server 2002 and/or on-line documentation. See these other
  materials for detailed information regarding Microsoft code samples.

  THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
  PURPOSE.
----------------------------------------------------------------------->
<%@ Register TagPrefix="Auth" 
Namespace="Microsoft.CommerceServer.Site.Authentication" 
Assembly="Microsoft.CommerceServer.Retail2002.Authentication" %>
<%@ Register TagPrefix="CommerceSite" TagName="VariantDisplay" 
Src="controls\catalogs\cs\VariantDisplay.ascx" %>
<%@ Register TagPrefix="CommerceSite" TagName="CurrencyConverter" 
Src="controls\Transactions\CurrencyConverter.ascx" %>
<%@ Register TagPrefix="CommerceSite" TagName="AddToCart" 
Src="controls\Transactions\AddToCart.ascx" %>
<%@ Register TagPrefix="CommerceSite" TagName="CompositeHeader1" 
Src="controls\Miscellaneous\CompositeHeader1.ascx" %>
<%@ Register TagPrefix="CommerceSite" TagName="PageBottom" 
Src="controls\Miscellaneous\PageBottom.ascx" %>
<%@ Register TagPrefix="CommerceSite" TagName="CatalogSearch" 
Src="controls\Catalogs\vb\Search.ascx"%>
<%@ Register TagPrefix="CommerceSite" TagName="CatalogBrowser" 
Src="controls\Catalogs\vb\Browser.ascx"%>
<%@ Register TagPrefix="CommerceSite" TagName="CatalogTabs" 
Src="controls\Catalogs\vb\NavigationTabs.ascx"%>
<%@ Register TagPrefix="campaign" 
Namespace="Microsoft.CommerceServer.Site.Campaign" 
Assembly="Microsoft.CommerceServer.Retail2002.Campaign" %>
<%@ Page CodeBehind="ProductDetails.aspx.vb" 
   Language="vb" AutoEventWireup="false" 
Inherits="Microsoft.CommerceServer.Site.ProductDetailsPage" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML>
    <HEAD>
        <title>
            <% Response.Write
(Microsoft.CommerceServer.Site.Components.CommerceApplication.ResourceString
   (Microsoft.CommerceServer.Site.Constants.ResID.PageTitle)) %>
        </title>
        <!--  P R O D U C T   D E T A I L S   P A G E  -->
        <meta content="JavaScript" name="vs_defaultClientScript">
        <meta content="http://schemas.microsoft.com/intellisense/ie5" 
          name="vs_targetSchema">
        <meta content="Microsoft Visual Studio.NET 7.0" name="GENERATOR">
        <meta content="VisualStudio.HTML" name="ProgId">
        <meta content="Microsoft Visual Studio.NET 7.0" name="Originator">
        <meta http-equiv="Content-Type" content="text/html; 
           charset=iso-8859-1">
        <LINK href=".\Images\styles.css" type="text/css" rel="stylesheet">
    </HEAD>
    <body text="#000000" bgColor="#ffffff" leftMargin="0" topMargin="0">
        <form id="formProduct" method="post" runat="server">
            <CommerceSite:CompositeHeader1 id="compositeHeader" 
              runat="server"></CommerceSite:CompositeHeader1>
            <table height="100%" cellSpacing="0" cellPadding="0" 
               width="100%" 
              border="0">
                <tr>
                    <td vAlign="top" align="left" width="170" 
                      background=".\Images\LeftGradient.jpg">
                        <CommerceSite:CatalogSearch id="catalogSearchCtl" 
                          runat="server"></CommerceSite:CatalogSearch><br>
                        <CommerceSite:CatalogBrowser id="catalogBrowser" 
                          runat="server"></CommerceSite:CatalogBrowser>
                    </td>
                    <td vAlign="top" align="left">
                        <table cellSpacing="0" cellPadding="0" 
                           width="100%" border="0">
                            <tr>
                                <td style="PADDING-LEFT: 20px;
                                   PADDING-TOP: 8px" 
                                  background=".\Images\TabBKG.jpg">
                                    <table cellSpacing="0" cellPadding="0" 
                                      width="100%" border="0">
                                        <tr>
                                            <td width="80%">
                                                <CommerceSite:CatalogTabs
                                         id="catalogTabs" runat="server">
                                              </CommerceSite:CatalogTabs>
                                            </td>
                                            <td vAlign="center"
                                               width="7%">
                                                <Auth:SignInLink 
                           class="signInLink" id="signInLinkCtl" 
                                runat="server"></Auth:SignInLink>
                                            </td>
                                        </tr>
                                    </table>
                                </td>
                            </tr>
                            <tr>
                                <td height="30"><IMG height="30"
               src=".\Images\RoundedCorner.jpg" width="13"></td>
                            </tr>
                        </table>
                        <br>
                        <table style="PADDING-LEFT: 10px" height="100%"
               cellSpacing="0" cellPadding="0" width="100%" border="0">
                            <tr>
                                <td vAlign="top" align="left">
                                    <!-- C a t e g o r y   P a t h -->
                                    <asp:Repeater id="repeaterPath" 
                                       Runat="server" 
                                  OnItemDataBound="CategoryPath_OnItemDataBound" > 
                                        <ItemTemplate>
                                            <a href="" runat="server" 
                 onserverclick="Category_Clicked" class="aCategoryId" 
                                     id="lnkCategoryDisplayName"></a> 
                                         <asp:Literal ID=
                                        "ltlCategoryName" 
                                        Runat="server" 
                                        Text="<%# Container.DataItem %>"
                                        Visible=False>
                                        </asp:Literal> 
                                        </ItemTemplate>
                                      <SeparatorTemplate>
                                            <span style=
                                            "font-color:#0000FF;
                                            font-weight:bold;
                                            font-size:12px">&nbsp;>
                                            &nbsp;</span>
                                      </SeparatorTemplate>
                                      <FooterTemplate>
                                            <br>
                                            <br>
                                      </FooterTemplate>
                                    </asp:Repeater>
                                    <br>
                                    <!-- P r o d u c t   I n f o -->
                                    <table cellSpacing="0" cellPadding="0" 
                                                   width="100%"
                                                    border="0">
                                        <tr>
                                            <td id="productName" 
                        class="aProductId" style="FONT-SIZE:14px;
                         PADDING-BOTTOM:3px" runat="server"></td>
                                            <td width="50%" align="right">
                                           <CommerceSite:CurrencyConverter
                                    id="currencyConverter" runat="server">
                                         </CommerceSite:CurrencyConverter>
                                            </td>
                                        </tr>
                                    </table>
                                    <br>
                                    <br>
                                    <table cellSpacing="0" cellPadding="0"
                                                  width="100%" border="0">
                                        <tr>
                                           <td rowspan="3" style="PADDING-
                                                               RIGHT:3px">
                                                <img id="productImage" 
                               height="120" width="120" runat="server">
                                            </td>
                                            <td align="left" 
                                      valign="top">&nbsp;</td>
                                           <td        id="productCurrency"
                                                            align="right" 
                                valign="bottom" runat="server">&nbsp;</td>
                                        </tr>
                                        <tr>
                                                           <td rowspan="2" 
                                                       id="productDetails" 
                                                 valign="top" align="left" 
                                        style="FONT-SIZE:11px" width="43%" 
                                                runat="server">&nbsp;</td> 
                                            <td width="30%" valign="top" 
                                                  style="FONT-SIZE:12px">
                                                     <table width="100%" 
                              cellSpacing="0" cellPadding="0" border="0">
                                                    <tr>
                                                      <td id="retailPrice"
                                          align="left" width="100%" style=
                                    "PADDING-RIGHT:10px; FONT-WEIGHT:bold;
                                FONT-SIZE:11px" runat="server">&nbsp;</td>
                                                        <td 
             id="productInitialPrice" align="right" style="FONT-SIZE:11px"
                                                runat="server">&nbsp;</td>
                                                    </tr>
                                                    <tr>
                                                        <td id="discount" 
                                     align="left" style="FONT-WEIGHT:bold;
                                FONT-SIZE:11px" runat="server">&nbsp;</td>
                                                         <td align="right"
                                        style="FONT-SIZE:11px">&nbsp;</td>
                                                    </tr>
                                                    <tr>
                                                        <td id="ourPrice" 
                                     align="left" style="FONT-WEIGHT:bold;
                                FONT-SIZE:11px" runat="server">&nbsp;</td>
                                               <td id="productFinalPrice" 
                                     align="right" style="FONT-SIZE:11px" 
                                                runat="server">&nbsp;</td>
                                                    </tr>
                                                </table>
                                            </td>
                                        </tr>
                                        <tr>
                                      <td align="right" valign="bottom">
                                                <CommerceSite:AddToCart 
                                                      id="addToCartCtl" 
                        Qty="1" runat="server"></CommerceSite:AddToCart>
                                            </td>
                                        </tr>
                                        <tr>
                                            <td id="productDescription"
              colspan="3" style="PADDING-TOP:10px" runat="server"></td>
                                        </tr>
                                        <tr>
                                            <td colspan="3" 
                                     height="50">&nbsp;</td>
                                        </tr>
                                        <tr>
                                            <td colspan="3" width="100%" 
                                                           height="100"> 
                                            <CommerceSite:VariantDisplay 
                                   id="variantDisplayCtl" runat="server">
                                           </CommerceSite:VariantDisplay>
                                            </td>
                                        </tr>
                                    </table>
                                    <br>
                                    <br>
                                    <br>
                                    <br>
                                    <br>
                                </td>
                              <td vAlign="top" align="middle" width="126">
                                    <campaign:ContentItemControl 
                              id="contentItemCtl" runat="server" 
                   Size="Vertical"></campaign:ContentItemControl>
                                </td>
                            </tr>
                            <tr style="PADDING-BOTTOM:5%">
                                <td vAlign="top" 
                  height="100"><CommerceSite:PageBottom id="pageBottom" 
                  runat="server"></CommerceSite:PageBottom></td>
                                <td width="126"></td>
                            </tr>
                        </table>
                    </td>
                </tr>
            </table>
        </form>
    </body>
</HTML>
Was this page helpful?
(1500 characters remaining)
Thank you for your feedback
Show:
© 2014 Microsoft