From the August 2002 issue of MSDN Magazine

MSDN Magazine

Commerce with ASP.NET

Leverage the Authentication and Form Validation Features of ASP.NET to Bolster Your Commerce App

Jason Lefebvre and Robert Lair

This article assumes you're familiar with ASP.NET and Visual Basic .NET

Level of Difficulty    1   2   3 

Download the code for this article: ASPNETCommerceApp.exe (45 KB)

SUMMARY If you're planning to build an e-commerce site, you'll be pleased to see that ASP.NET makes it easier than ever. Existing controls can be used and extended to add a great deal more functionality than you might expect. In this article, forms-based authentication is used to verify the identity of users and make certain areas of the site, such as the check-out page, inaccessible to unauthorized users. The power and flexibility of validation controls are demonstrated using the CustomValidator control to connect to a Web Service that verifies addresses. A shopping cart is then implemented in ASP.NET using the DataGrid, and finally, credit card authorization and billing are performed.

Before you can bring your business to the Web in an e-commerce site, you have to define the catalog of goods and services you plan to offer. While ASP.NET can't help you build the actual catalog content for your site, it can certainly help you build a consistent and impressive catalog navigation and management system with user controls.
      A user control enables you to encapsulate a logical piece of your Web site's functionality in a single location. It is roughly analagous to an include file in ASP 3.0. Rather than adding the code for your menu system to each page in your application, you can place the code in one file and then add your menu user control wherever it is needed. This gives you a single place to make menu modifications, so you don't have to replicate the same change across all the pages in your application.
      There are several very important differences between user controls in ASP.NET and include files in ASP 3.0. First, user controls are treated as real controls. You can pass values to and from a user control and you can call any public methods exposed by the control. Additionally, you can increase the performance of your application by caching your user control's output. User controls give you significantly more flexibility in application design than include files allow you.
      The first step in designing your own user control is deciding what site functionality you want to encapsulate. In the case of a catalog menu, the decision is easy. Figure 1 shows the menu user control we've created. As you can see, it's a simple horizontal menu enabling a user to navigate through the various product categories in a Web site. Figure 2 contains the code for a user control to display the horizontal menu shown in Figure 1.

Figure 1 Horizontal Menu
Figure 1 Horizontal Menu

      Much like a Web form, the menu user control has three logical sections. At the top of the file, there is a set of directives to specify the language and import namespaces. Next is a server-side script that has two purposes: to process the currently selected link from the Request collection to highlight the selected item on the menu, and to retrieve the list of categories from the database and bind to the DataList Web control.
      The last section of code is the user interface for the control, which uses the DataList Web control. The DataList outputs HTML based on a defined template. Templates are defined within the DataList using specialized tags.
      Seven templates can be defined in a DataList, each affecting different items.

  • AlternatingItemTemplate
  • EditItemTemplate
  • FooterTemplate
  • HeaderTemplate
  • ItemStyleTemplate
  • SelectedItemTemplate
  • SeparatorTemplate

      We are interested in two of these templates, ItemTemplate and SelectedItemTemplate. The ItemTemplate contains the HTML layout for all items in the DataList. The SelectedItemTemplate contains the layout for the item in the DataList that is selected, overriding the ItemTemplate. Here's a basic DataList that has an ItemTemplate and SelectedItemTemplate defined:

  <asp:DataList id="MyDataList" runat="server">
  
<ItemTemplate>
<h1>Item</h1>
</ItemTemplate>
<SelectedItemTemplate>
<h1>Selected Item</h1>
</SelectedItemTemplate>
</asp:DataList >

      When a page containing a DataList is requested, the DataList is bound to a DataSource containing a number of records. For each record, the DataList generates a new item using the HTML defined within the <ItemTemplate> tag. For the currently selected item, the DataList generates a new item using the HTML defined within the <SelectedItemTemplate> tag.
      After placing the code from Figure 2 into a file named menu.ascx in our application root, there are only two steps required for using the menu control in any Web form in our application. First, we register the control using a page directive at the top of our form:

  <%@ Register TagPrefix="IBuySpy" TagName="Menu"  Src="_MenuNew.ascx" %>
  

      Once we have registered the control, we can place it anywhere on a Web form using standard XML notation:

  <Widgets:Menu id="Menu1" runat="server" />
  

      Figure 3 shows you how to use the menu user control to place a menu on a Web form.

User Authentication

      Handling user authentication is a common task in e-commerce. While the user is browsing your site, you can store any information in a cookie or Session object, but once they want to register or purchase something you'll need to authenticate them and store some detailed information. There are three points at which it makes sense to authenticate the user—immediately when they navigate to your site, just before adding their first item to the shopping cart, or when they want to check out. No matter when you do it, implementing forms-based authentication using ASP.NET is very easy.
      Let's assume that on your site users can browse and add items to their shopping cart, but they must authenticate before they can check out. To enable this process, you first need to turn on forms-based authentication by placing the following code in the system.Web section of your application's Web.config file:

  <authentication mode="Forms">
  
<forms name="ApplicationAuth"
loginUrl="login.aspx"
protection="All"
path="/" />
</authentication>

If your checkout page is CheckOut.aspx, then you'd add this code into the Web.Config file within the configuration section:

  <configuration>
  
<!--The rest of your config file-->
<location path="CheckOut.aspx">
<system.web>
<authorization>
<deny users="?" />
</authorization>
</system.web>
</location>
</configuration>

      Now whenever an unauthenticated user attempts to access CheckOut.aspx, they will be forwarded to the login.aspx page, which can use any one of a number of methods to authenticate the user. As an example, you could connect to a Remote Authentication Dial-in User Service (RADIUS) server, authenticate through the Lightweight Directory Access Protocol (LDAP), or just perform a simple username/password check against a SQL database.
      Whatever method you choose for authentication, once a user has been authenticated, the following static method is called to send the user to the page they were originally trying to access:

  FormsAuthentication.RedirectFromLoginPage(
  
username,
createPersistantCookie)


The first argument is a string identifying the user. The second is a boolean value that indicates whether the cookie created is permanent. You can handle unsuccessful user authentication any way you want. Displaying a message or forwarding a user to another page are common solutions.
      Later, if you need the authenticated user's name, you can use the User.Identity.Name to retrieve the value you placed in the username argument of the RedirectFromLoginPage method. This enables you to create targeted advertising for specific users.

Validating User Input

      Improperly validated user input can quickly bring about an untimely end to a customer's order. Providing client-side input validation is a necessity for nearly any e-commerce site. Input validation can be easily achieved using the Microsoft® .NET Framework built-in validation controls.
      Using validation controls, you can make sure your customers don't leave required fields empty, verify they have entered either a five-digit or nine-digit ZIP code, validate a credit card number, or just about any other input validation that an e-commerce site requires. Validation controls have been thoroughly covered elsewhere, but the code in Figure 4 provides a new twist: a custom validator is used in conjunction with a Web Service to validate a customer's address against a USPS database.
      The ZIP Code Resolver Web Service is provided by EraServer.NET at https://webservices.eraserver.net. If you navigate to this URL and select the ZipCodeResolver Web Service, you see that there are four methods in the Web Service, each providing slightly different functionality for validating ZIP codes and addresses. We used the ShortZipCode method to perform the validation. You can test this method directly by navigating to https://webservices.eraserver.net/zipcoderesolver/zipcoderesolver.asmx?op=ShortZipCode, entering an access code (use "9999" for testing), address, city, and state, and then clicking Invoke. The Web Service returns a string in an XML package containing the ZIP code for the address entered.
      Before you attempt to call a Web Service, you need a way to inform the compiler about the Web Service's methods and arguments. This can be done by generating a proxy class using the WSDL.exe utility that ships with the Microsoft .NET Framework. The generated proxy class is human-readable (go ahead and examine it) and simply provides the code necessary to reroute the local method calls to the Web Service. The proxy class also strips the return values from the XML returned by the Web Service so that you don't have to parse XML in your code.
      WSDL.exe is very easy to use. The following command line will generate a Visual Basic® .NET proxy class in the current directory:

  wsdl https://webservices.eraserver.net/zipcoderesolver/   zipcoderesolver.asmx /language:vb
  

Once executed with these arguments, the WSDL command-line utility will connect to the ZipCodeResolver Web Service, analyze its XML WSDL contract, and generate a Visual Basic .NET file named ZipCodeResolver.vb in your current directory. To use this file, you must compile it to a library and place it in the /bin directory located in the root of your Web application.
      To compile the ZipCodeResolver.vb file into a library DLL, you can use the following command line:

  C:\Temp>vbc /target:library
  
/out:ZipCodeResolver.dll zipcoderesolver.vb
/r:System.Web.Services.dll /r:System.XML.dll
/r:System.Web.dll /r:System.dll /r:System.Data.dll


This command creates a file in your current directory named ZipCodeResolver.dll. Place this in the /bin directory in the root of your Web application. Now you will be able to access the methods of the Web Service the same way you would access any local class. The code in Figure 4 shows you the complete Web Service with a CustomValidator control that uses the ZipCodeResolver Web Service to perform validation.
      Once this form is loaded into a browser, it will look like the form in Figure 5. Notice the CustomValidator control near the end of the listing. The control validates the ZIP code field of the Web form. When the form is submitted to the server, the ServerValidation method is then called.

Figure 5 Running the Form
Figure 5 Running the Form

      This method creates an instance of the ZipCodeResolver object as provided by the proxy class just created. In order to access the object, you must import the ZipCodeResolver namespace using the Import directive as shown at the top of Figure 4. The ServerValidation method then calls the ShortZipCode method of the ZipCodeResolver Web Service and verifies that the ZIP code entered by the user matches the one returned by the Web Service. If it matches, validation succeeds and the CustomValidator control's IsValid property is set to true. If the ZIP codes do not match, then the IsValid property is set to false, the validation fails for the page, and the user is presented with an error as in Figure 6. When the proper ZIP code is entered, validation succeeds and the user sees a screen like the one in Figure 7.

Figure 6 Error Message
Figure 6 Error Message

      As it stands now, this example is not suited to a production e-commerce site. The ZipCodeResolver service provided by EraServer.NET is for personal use only, since it uses the USPS Web site to retrieve the address information. But that's not the only reason you can't use it in production. If there's an error in the USPS database, or if your customer has a newly created address, your user might receive an error they can't resolve and you lose business. Many e-commerce shops perform customer address validation as part of the business logic for a site after a customer has submitted their order but before the order is actually fulfilled. By performing this sort of validation on the page, you can warn customers of potential problems and hopefully reduce the number of incorrect addresses submitted.

Figure 7 Validation Success
Figure 7 Validation Success

Implementing a Shopping Cart

      No e-commerce site is complete without a shopping cart. It serves as a temporary storage location for items your customers want to buy. From the customer's perspective, when they want to check out, the items in their cart are accounted for, paid for, and shipped.
      Of course, as any e-commerce developer knows, there is much more to the process than this. When checking out, the items in the customer's cart must be moved from the temporary shopping cart to a more permanent storage location, such as a set of tables containing order information. Additionally, the customer's credit card must be authorized and billed, before the items they have ordered can actually be shipped to them.
      Implementing a shopping cart in ASP.NET is easy with the help of the powerful DataGrid Web control. In ASP 3.0, if you wanted to take a recordset and display it on the page as a table, you were forced to loop through the recordset row by row and manually build the HTML constructs required to create a table. The DataGrid does all this for you. Simply pass it an ADO.NET DataTable in the DataSource property, bind it, and it will build the table for you, saving lots of work. The appearance of the DataGrid is easily changed by using cascading style sheets (CSS). For more information, please see https://samples.gotdotnet.com/quickstart/aspplus/.
      The following code contains a simple DataGrid. Since AutoGenerateColumns is set to true, the DataGrid will simply display whatever data is in the DataSet that you pass to it:

  <asp:DataGrid runat="server"         id="MyList"         AutoGenerateColumns="true"></asp:DataGrid>
  

This may be good enough for simply displaying data to the user, but since this is a shopping cart, we need more granular control. For instance, the item quantity on the shopping cart should be editable.
      Luckily, the DataGrid offers very precise control of the various columns and their appearance. The first step is to set AutoGenerateColumns to false. Then we can access the DataGrid's column collection by placing data columns inside the <column> tag within the DataGrid. This sounds more difficult than it really is. The following code displays only two columns, the Product Name and the Model. Since these fields are not editable and require no special formatting, we can just use the BoundColumn Web control to define the column:

  <asp:DataGrid runat="server" id="MyList" AutoGenerateColumns="False">
  
<Columns>
<asp:BoundColumn HeaderText="Model Name" DataField="ProductName"/>
<asp:BoundColumn HeaderText="Model Number" DataField="ProductID"/>
</Columns>
</asp:DataGrid>

      The DataGrid in this code creates output like that shown in Figure 8. Once you realize that you're using tags to manually add items to the column collection of the DataGrid control, the task becomes much easier.

Figure 8 DataGrid Output
Figure 8 DataGrid Output

      Now, back to the reason we're working with the columns manually in the first place—we need a way to let a user modify the quantity of an item within the DataGrid control. BoundColumns do not offer this functionality. Luckily, there is another type of column, called a TemplateColumn, that gives you much more detailed control of the column content and appearance. Building on our previous example, if you want to add another column containing an editable text field for quantity, you just add a TemplateColumn to the collection, as in Figure 9.
      The new column created by the code in Figure 9 contains a TextBox Web control. The DataBinder.Eval method that is shown there outputs the correct Quantity from the database into the TextBox. You can use this trick to work with the CheckBox and Label Web controls, as well as with other Web controls.
      You've just seen how to add an editable text field to a column in the DataGrid Web control. Interesting, but useless if we can't apply the changes that were made to the text field back to the database. You should note that the ID of the TextBox control in Figure 9 is set to Quantity. So, to access the value once the user submits the shopping cart Web form, you should just be able to access Quantity.Text, right?
      Unfortunately, the answer is no. There's no easy way to access the value. The simplest way to save the updated value back to the database is to iterate through the rows of the DataGrid by using the Items collection, locate the TextBox using the FindControl method, get the value of the TextBox, and then use ADO.NET to save the value back to your data source:

     Dim i As Integer
  
For i = 0 To MyList.Items.Count - 1
' Obtain references to row's controls
Dim quantityTxt As TextBox = _
CType(MyList.Items(i).FindControl
("Quantity"), TextBox)

' *** Now you can access the
' quantityText.Text value ***
' *** and use it to save back to
' the data source ***
Next

      The actual ADO.NET code required to save data to the database is beyond the scope of this article. For a complete example, download the IBuySpy store demo available at https://www.ibuyspy.com. For more information about DataGrids in general, see Dino Esposito's Cutting Edge columns in MSDN® Magazine.

Incorporating Credit Card Processing Centers

      As your customer checks out, you must verify that they can pay for what they've purchased. Since it is illegal to bill a credit card without shipping the product, and since it would be bad business to ship your product without first verifying that your customer can pay, credit card processing companies provide a method for quickly authorizing cards. By authorizing the credit card, you are guaranteeing that the funds will be there after shipment (except for a few limited cases, for example the credit card being cancelled in that short period of time between authorization and billing).
      Most online credit card processing companies provide ActiveX® DLLs that handle your connection to their servers and perform the credit card authorization. Sometime in the near future these companies will offer assemblies that will plug directly into your Visual Studio® .NET or ASP.NET projects. For now, you have COM interoperability.
      COM interoperability enables you to use COM objects in your ASP.NET applications. The easiest way to access a COM object in ASP.NET is to use late binding. Just as in ASP 3.0, the Server object in ASP.NET offers a CreateObject method that will return an instance of any COM object registered on the system. So, if the ProgID for your component is MyComponent.Authorize, then to use that object in ASP.NET, you can simply create an object like so:

  Object oAuthor = Server.CreateObject("MyComponent.Authorize")
  

You can then access the methods and properties of the object as you would in ASP 3.0 or Visual Basic 6.0.
      Rather than relying on late binding, which carries a noteworthy performance hit, you can build a proxy class for the COM object. The proxy class contains any plumbing necessary to connect to the COM object and also tells the compiler about the object's methods and properties. For Visual Studio .NET users, this means you'll get IntelliSense® for the object, as well. The Microsoft .NET Framework ships with a utility named tlbimp.exe (Type Library Importer) that will automatically build the proxy class for you. The utility is easy to use from a command prompt:

  tlbimp OriginalDLL.dll /out:MyNewDLL.dll
  

      Place the new DLL in the /bin directory in your application root, and you can work with the COM object just like any other object in ASP.NET.

Performance and Optimization

      ASP.NET offers several new tools for tuning the performance of your e-commerce site. One of those tools is data caching. With ASP.NET, it is now possible to store expensive objects in memory. For instance, if you want to increase your site's performance, you could cache repetitive calls to the database for items that won't change often, such as detailed product information.
      The cache can be used exactly as you would use any key-value-based dictionary object. If you have an expensive DataSet, rather than build the DataSet each time it is needed, place it in the cache the first time that you access it by using the following code:

  Cache("Categories") = dsCategories
  

      It can then be read out of the cache like this:

  myDataSet = Ctype(Cache("Categories"), DataSet)
  

Notice that you use the Ctype method to cast the object returned from the cache to type DataSet before assignment. This is necessary since the cache returns an object. Objects placed in the cache in this manner will be available until you restart the application or until you manually flush the cache. Figure 10 shows a complete example of how to cache a call to a database.
      If you are implementing caching in a component, you will need to find the current instance of the Cache object through the HttpContext object:

  HttpContext context = HttpContext.Current;
  

Then the cache can be accessed using the command context.Cache["CachedItem"].
      So far, you've only seen how to use the Cache object in its simplest form. By adding new items to the cache using the Insert method of the Cache object, you can add dependencies and expiration times for the cached items. For instance, suppose you are storing a menu in an XML file in the root of your Web site and caching it the first time it is accessed. (SQL Server™ might not be available, or your application's menu might require the ease of configuration that XML provides.) Naturally, when the file changes, the menu should automatically reflect those changes. By using the Insert method to add the menu data to the cache, you can create a dependency of the cached object on the file itself. If the file changes, the menu is removed from the cache and added back in the next time it is accessed. To put the menu in the cache, use the Insert method of the Cache object like this:

  Cache.Insert("mainMenu", sXMLMenu, new CacheDependency
  
("C:\Inetpub\wwwroot\myCommerceSite\mainMenu.xml"))

      The first argument defines the name of the cached item. The second argument defines the content that is to be cached. The last argument is of type CacheDependency and defines the location of the file on which the cached item is dependent.
      In addition to providing the ability to cache individual chunks of data, ASP.NET also offers output caching. When output caching is enabled for a page, the page is served directly from memory; there's no execution of any code or disk access. This is an ideal choice for pages in your application that rarely change. If the page has any dynamic content at all, such as a customized welcome message, you shouldn't use output caching. In this case, each version of the page will be cached separately and will be a detriment to your e-commerce site's performance.
      There are a few different ways to implement output caching. The easiest way is to use the following page directive:

  <%@ OutputCache Duration="60" %>
  

By placing this code at the top of your page, the entire response is cached. The duration attribute is measured in seconds.

Conclusion

      This article showed how ASP.NET enables you to build better, faster e-commerce sites than you could before. You saw how to take advantage of some of the new features in ASP.NET to build an e-commerce site, including how to use User-Controls and the DataList Web control to build a navigational menu for your online catalog, how to use forms-based authentication, how to validate user input, how to build a shopping cart, and how to perform some of the many other e-commerce tasks now made easy with ASP.NET.

For related articles see:
ASP Column archive
For background information see:
https://samples.gotdotnet.com/quickstart/aspplus/doc/wsbehavior.aspx
https://www.gotdotnet.com/team/tools/web_svc/

Jason Lefebvre is cofounder and Vice President of Intensity Software Inc. (https://www.intensitysoftware.com). He has worked on the NetCOBOL conversion of the IBuySpy sample app. Jason is the coauthor of Teach Yourself ADO.NET in 24 Hours (Sams, 2002).
Robert Lair is President/CEO of Intensity Software Inc. He and Jason coauthored Pure ASP.NET (Sams, 2001). He has been working with Microsoft .NET since early in 2001 when he worked on the IBuySpy demo application. You can reach him at robertlair@asppages.com or https://www.robertlair.com.