Performing Product Searches

The eCommerce site offers two ways to search for Books. The visitor can click a category link to view all books within that category; or a word or partial word can be entered into the search box to locate all books containing that text string in its database fields. The following illustration shows the Search.aspx page after locating all books in the "Database" category. A similar display appears for all books with field values containing the search text.

Figure 13-4. Layout of Search.aspx page.

The Search.aspx Content Page

The Search.aspx content page is loaded when either a category search or text search is performed. Matching records are retrieved and displayed on this page. First, take a look at the XHTML portion of this page in Listing 13-12.

<%@ Page MasterPageFile="eCommerce.master" Language="vb" Debug="True"%>

<SCRIPT Runat="Server">
  ...
</SCRIPT>

<asp:Content id="Search" ContentPlaceHolderID="CONTENT" Runat="Server">

<asp:Label id="Type" Font-Size="14pt" ForeColor="#990000" Runat="Server"/>

<asp:AccessDataSource id="BookSource" Runat="Server"
  DataFile="../Databases/BooksDB.mdb"/>

<asp:GridView id="Books" Runat="Server"
  DataSourceID="BookSource"
  AutoGenerateColumns="False"
  HeaderStyle-BackColor="#990000"
  HeaderStyle-ForeColor="#FFFFFF"
  CellPadding="5"
  Style="margin-top:10px">

  <Columns>

  <asp:BoundField
    HeaderText="ID"
    DataField="BookID"
    ItemStyle-Width="60"/>

  <asp:BoundField
    HeaderText="Title"
    DataField="BookTitle"
    ItemStyle-Width="200"/>

  <asp:TemplateField 
    HeaderText="Price"
    ItemStyle-Width="70"
    ItemStyle-HorizontalAlign="Right">
    <ItemTemplate>
      <asp:Label Runat="Server"
        Text='<%# String.Format("{0:C}", Eval("BookPrice")) %>'/>
    </ItemTemplate>
  </asp:TemplateField>

  <asp:TemplateField HeaderText="Special" 
    ItemStyle-Width="70"
    ItemStyle-HorizontalAlign="Right"
    ItemStyle-Font-Bold="True">
    <ItemTemplate>
      <asp:Label Runat="Server"
        Text='<%# String.Format("{0:C}", _
                  Get_Sale_Price(Eval("BookPrice"), Eval("BookSale"))) %>'/>
    </ItemTemplate>
  </asp:TemplateField>
		
  <asp:TemplateField
    HeaderText="View Details"
    ItemStyle-HorizontalAlign="Center">
    <ItemTemplate>
      <asp:LinkButton Text="Details" Runat="Server"
        OnCommand="View_Details"
        CommandName='<%# Eval("BookID") %>'/>
    </ItemTemplate>
  </asp:TemplateField>
		
  </Columns>

</asp:GridView>

</asp:Content>
Listing 13-12. Code for Search.aspx content page.

Two output controls are used. A Label control appears at the top of the page to display the book category or search text that is used in the search. A GridView control displays a table of books matching the search category or criterion. The GridView is populated through an AccessDataSource control linked to the database. Note, however, that no SelectCommand (SELECT statement) is precoded. The statement needed depends on the search category or criterion used to retrieve records. Therefore, a script is needed to compose this command and assign it to the AccessDataSource.

Displaying Matching Records

When the Search.aspx page loads, it has available to it a Request.QueryString collection containing a query string value passed from the master page. As a reminder, one of two query strings is passed,

?Category=value
?Criterion=value

depending on whether a BookType category is chosen from the search menu or a text string is typed in the search box. Since the Search.aspx page cannot know in advance which one is available, it captures both as variables, one of which will be null. (No error is caused if a non-existing query string is referenced. If there is no such query string, its reference produces a null value.) The valid search variable, then, is used to construct a SELECT statement to issue through the AccessDataSource to populate the GridView. This Page_Load portion of script for the search page, coded at the top of the page, is shown below.

<SCRIPT Runat="Server">

Sub Page_Load
  
  Dim Category As String = Request.QueryString("Category")
  Dim Criterion As String = Request.QueryString("Criterion")
  
  If Category <> "" Then
    
    Type.Text = Category & " Books"
    Dim SQLString As String 
    SQLString = "SELECT BookID, BookTitle, BookPrice, BookSale " & _
                "FROM Books " & _
                "WHERE BookType = '" & Category & "' ORDER BY BookID"
    BookSource.SelectCommand = SQLString
    
  ElseIf Criterion <> "" Then
    
    Type.Text = "'" & Criterion & "' Search Results"
    Dim SQLString As String 
    SQLString = "SELECT BookID, BookTitle, BookPrice, BookSale " & _
      "FROM Books WHERE " & _
      "BookID LIKE '%" & Criterion & "%' OR " & _
      "BookType LIKE '%" & Criterion & "%' OR " & _
      "BookTitle LIKE '%" & Criterion & "%' OR " & _
      "BookAuthor LIKE '%" & Criterion & "%' OR " & _
      "BookDescription LIKE '%" & Criterion & "%' " & _
      "ORDER BY BookID"
    BookSource.SelectCommand = SQLString
    
  End If

End Sub

...
</SCRIPT>
Listing 13-13. Code for Page_Load subprogram to capture a query string and issue a SelectCommand.

Two variables, Category and Criterion, are declared to capture the query string passed from the search choices. Request.QueryString("Category") is assigned to the former and Request.QueryString("Criterion") is assigned to the latter. Again, one of these variables will be null since only one of the query strings is passed to the page. Therefore, one of two routines composes a SELECT statement to retrieve matching records.

If a Category value is available, its name is assigned to the output Label for display. Then a SELECT statement is composed to retrieve database records in which the value of the BookType field matches that of the passed value stored in the variable. This statement is assigned to the SelectCommand of the AccessDataSource to populate the GridView with these books.

If, on the other hand, a Criterion value is available, this value is assigned to the output Label and a different SELECT statement is composed. In this case, a match is made if the entered text appears anywhere in the BookID, BookType, BookTitle, BookAuthor, or BookDescription fields. This statement is assigned to the AccessDataSource's SelectCommand to retrieve matching records for display.

The "Special" price column of the GridView is a calculated value. Its value is given by a call to function Get_Sale_Price(), passing the BookPrice field and BookSale field to the function. Code for this function is shown in Listing 13-14.

Function Get_Sale_Price (Price As Decimal, Sale As Boolean)

  If Sale = True Then
    Price = Price * Application("Discount")
    Return Price
  End If

End Function
Listing 13-14. Code for Get_Sale_Price function.

Recall that the BookSale field is a "Yes/No" field with a True or False value depending on whether the book has special pricing. If the passed argument Sale is True, then the book's price is calculated at a discount and returned to the calling statement; otherwise, nothing is returned to the calling statement and the display column remains blank. The reference to Application("Discount") as the discount percentage applied to the price is a reference to a global value and is explained below.

Displaying Product Details

The final column of the GridView contains a LinkButton labeled "Details." A click on this link transfers to the Details.aspx page where full information about the book is displayed. Since the Details.aspx page will need to be informed about which book's details to display, it needs to be passed a query string with this information attached. Therefore, LinkButtons are configured as command buttons to call the View_Details subprogram in order to compose this query string and redirect to the Details.aspx page. This subprogram, along with a reminder of its LinkButton caller, is shown below.

Sub View_Details (Src As Object, Args As CommandEventArgs)

  Response.Redirect("Details.aspx?BookID=" & Args.CommandName)

End Sub

<asp:LinkButton Runat="Server"
  Text="Details"
  OnCommand="View_Details"
  CommandName='<%# Eval("BookID") %>'/>
Listing 13-15. Code for View_Details subprogram and associated LinkButton control.

The CommandName assigned to a LinkButton is the BookID of the book appearing on this row of the GridView. When subprogram View_Details is called on a link click, this BookID is available through the subprogram's CommandName argument (Args.CommandName). This BookID value is associated with the name BookID in a query string appended to the URL for the Details.aspx page. Then, redirection takes place to this page.

The Specials.aspx Content Page

One of the site menu links opens the Specials.aspx page to show those books for which special discount pricing is available. This is a direct link to the page with no query strings attached. This page has the same visual appearance as the Search.aspx page. Complete coding for Specials.aspx is given in Listing 13-16.

<%@ Page MasterPageFile="eCommerce.master" Language="vb" Debug="True"%>

<SCRIPT Runat="Server">

Sub View_Details (Src As Object, Args As CommandEventArgs)

  Response.Redirect("Details.aspx?BookID=" & Args.CommandName)

End Sub

</SCRIPT>

<asp:Content id="Specials" ContentPlaceHolderID="CONTENT" Runat="Server">

<asp:Label Text="Specials" Font-Size="14pt" ForeColor="#990000" 
Runat="Server"/>

<asp:AccessDataSource id="SpecialsSource" Runat="Server"
  DataFile="../Databases/BooksDB.mdb"
  SelectCommand="SELECT * FROM Books WHERE BookSale = True ORDER BY BookID"/>
	
<asp:GridView id="BookGrid" Runat="Server"
  DataSourceID="SpecialsSource"
  AutoGenerateColumns="False"
  HeaderStyle-BackColor="#990000"
  HeaderStyle-ForeColor="#FFFFFF"
  Cellpadding="5"
  Style="margin-top:10px">
  
  <Columns>
  
  <asp:BoundField
    HeaderText="ID"
    DataField="BookID"
    ItemStyle-Width="60"/>
  
  <asp:BoundField
    HeaderText="Title"
    DataField="BookTitle"
    ItemStyle-Width="200"/>
  
  <asp:TemplateField
    HeaderText="Price"
    ItemStyle-Width="70"
    ItemStyle-HorizontalAlign="Right">
    <ItemTemplate>
      <asp:Label Runat="Server"
        Text='<%# String.Format("{0:C}", Eval("BookPrice")) %>'/>
    </ItemTemplate>
  </asp:TemplateField>
  
  <asp:TemplateField
    HeaderText="Special" 
    ItemStyle-Width="70"
    ItemStyle-HorizontalAlign="Right"
    ItemStyle-Font-Bold="True">
    <ItemTemplate>
      <asp:Label  Runat="Server"
        Text='<%# String.Format("{0:C}", _
                  Eval("BookPrice") * Application("Discount")) %>'/>
    </ItemTemplate>
  </asp:TemplateField>
  
  <asp:TemplateField
    HeaderText="View Details"
    ItemStyle-HorizontalAlign="Center">
    <ItemTemplate>
      <asp:LinkButton Text="Details" Runat="Server"
        OnCommand="View_Details"
        CommandName='<%# Eval("BookID") %>'/>
    </ItemTemplate>
  </asp:TemplateField>
  
  </Columns>

</asp:GridView>

</asp:Content>
Listing 13-16. Code for Specials.aspx page.

The AccessDataSource to populate the GridView contains a SelectCommand statement to choose all books from the Books table with a BookSale value of True. No script is required to compose this statement as on the Search.aspx page where different SELECT statements are required depending on a search category or criterion query string. Also, no function call is needed for the "Special" pricing column. In this case, all retrieved products have special discount pricing, so the formula can be built into the binding expression.

LinkButtons on this page work identically to those on the Search.aspx page. They call the View_Details subprogram which passes a BookID query string to the Details.aspx page. That page, therefore, receives a query string from two different pages, Search.aspx and Specials.aspx, both identifying a book about which details are displayed.

The Application Object

In two cases in previous scripts a reference to Application("Discount") is used to apply special discount pricing to products. In both the Get_Sale_Price function on the Details.aspx page and in the Label formula on the Specials.aspx page a fixed percentage of .90 needs to be applied to a book's regular price to determine its discount price. Normally, however, it is considered bad programming practice to hard code constant (fixed) values inside scripts. If these values change, then scripts need to be revised, possibly a time-consuming task, and worse, leading to oversights in locating and changing all instances of these constant values.

A better practice is to assign constant values to global variables—global to the entire site—where their declarations and valuations can take place one time only. These global variables, then, can be used in place of constant values throughout scripts. Whenever their values change, updating can take place one time and in one place.

ASP.NET provides a global object for maintaining global variables through its Application object. This object services in a similar manner to a Session object, except that an Application object is global to the entire site whereas a Session object is global to a single visitor. An Application object is created when a Web site is put into production, when a first visitor first arrives at any page of the site. The object remains in effect during all visitations by all visitors. It is reset only when the Application object itself is changed or if the server is restarted.

Configuration of an Application object takes place in the global.asax file. It is initialized in the Application_Start subprogram containing variable settings and scripts that take place one time only—when the Web application first starts. Listing 13-17 shows code added to the current global.asax file to declare a site-global Discount variable for calculating book discount prices and a Shipping variable for calculating shipping charges on orders.

<%@ Import Namespace="System.Data.OleDb" %>

Sub Application_Start
 
  '-- Declare site contants
  Application("Discount") = .90
  Application("Shipping") = .03

End Sub

Sub Session_Start

  '-- Increment visitor counter
  Dim DBConnection As OleDbConnection = New OleDbConnection( _
    "Provider=Microsoft.Jet.OLEDB.4.0;" & _
    "Data Source=" & Server.MapPath("Databases/BooksDB.mdb"))
  DBConnection.Open()
  Dim SQLString As String 
  SQLString = "UPDATE Counters SET VisitorCounter = VisitorCounter + 1"
  Dim DBCommand As OleDbCommand = New OleDbCommand(SQLString, DBConnection)
  DBCommand.ExecuteNonQuery()
  DBConnection.Close()

End Sub
Listing 13-17. Code for Application variables added to global.asax file.

Any scripts applying these constants do so through the references Application("Discount") and Application("Shipping"), which apply the values .90 and .03, respectively. Now, any changes to these percentages can be made in the global.asax file and automatically propogated throughout all scripts that reference these Application variables.