Introduction
e-Commerce web applications are everywhere these days, and many share a common set of functionality. In this article, I will show how to use the GridView
and ListView
controls to build a powerful product page with many of the features found on today's e-commerce sites. We'll build a bicycle store product grid using some free clip art bicycle images. The example files are user controls which can be easily added to a page. We're only using three images here to keep the size of the sample application small.
Background
This article assumes the reader has some familiarity with data binding the GridView
and ListView
controls using the ObjectDataSource
control.
The Database
A product page that does product comparisons requires at least three tables: one to hold the product data, and two to support the product comparison functionality. In this example, the product images are stored in the database, and an image handler is used to retrieve them. The images must be inserted in the database table from an application; you cannot add an image to a table from the SQL console. The example project includes a console app that will put three images in the database. Generally, there are at least two images necessary: a small one for the grid and comparison page, and a larger image for the product detail page. The rest of the table rows will consist of whatever information is desired for the product details page.
The product comparison functionality requires two tables: one to hold a unique key to distinguish sessions, and another to hold the user's selected items associated with the unique key. The domain-layer code uses ADO.NET to invoke Stored Procedures to display the products using pagination. The product comparison is handled through JavaScript, which counts up all the checkboxes in the checked state, creates a cookie to hold them, and invokes the comparison page with a query string of IDs. The ObjectDataSource
on the comparison page has a "SelectMethod
" attribute that points to a method which is called when the page is loaded. The ObjectDataSource.Select
method invokes a Stored Procedure to retrieve the information for the compared products, and handles the paging of the items if the item count exceeds the ListView
's GroupItemCount
. To run the example, first execute the database script, "BicycleDatabase.sql", found in the "Database Script" folder of the DatabaseImageInsert
project in SQL Server. Then set the DatabaseImageInsert
project as the StartUp project and execute it. This will put the product images in the database.
The Database Image Application
There are many examples which show how to select an image from disk and store it in a database. I found the code for that approach didn't work when the images are read directly from a directory; i.e., I couldn't read the image back with the image handler. Reading images from disk requires that a Bitmap
object be instantiated and read into a MemoryStream
. From the Stream
, you get a byte array which is given to the SqlCommand
object.
The Product Grid
The product grid uses a GridView
with an ObjectDataSource
implemented in a user control. We must implement two methods in our domain class for SelectCountMethod
and SelectMethod
. The grid will support paging and sorting, so those properties are set to true
. We set the DataKeyNames
to ProductID
for the primary key field of our data source, and DataSourceID
to the ID of our ObjectDataSource
. The img
element is filled by an image handler which is given the ProductID
of an item.
<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="ProductGrid.ascx.cs"
Inherits="eCommerceProductGrid.UserControls.ProductGrid" %>
<%@ Register assembly="System.Web.Extensions, Version=3.5.0.0,
Culture=neutral, PublicKeyToken=31bf3856ad364e35"
namespace="System.Web.UI.WebControls" tagprefix="asp" %>
<div style="float: left;">
<asp:Label ID="PageResultsLabel" CssClass="resultPage" runat="server"
Width="100%" EnableViewState="False"></asp:Label>
</div>
<br />
<div style="padding-top: 5px; padding-bottom: 5px;">
<asp:Button ID="btnCompare" runat="server" CssClass="buttonStyle"
Text="Compare Checked Items" Height="25" Width="190" EnableViewState="False" />
<asp:Label ID="ComparisonError" runat="server" OnLoad="ComparisonError_Load"
CssClass="errorMessage" EnableViewState="False" />
<div style="float: right; margin-right: 20%; border: 1px solid black;">
<asp:DropDownList ID="GridPageNumberList" runat="server" Enabled="true"
AutoPostBack="True"
OnSelectedIndexChanged="GridPageNumberList_SelectedIndexChanged">
<asp:ListItem Value="5">Display 5 per page</asp:ListItem>
<asp:ListItem Value="7">Display 7 per page</asp:ListItem>
</asp:DropDownList>
</div>
</div>
<asp:ObjectDataSource ID="SourceBicycles"
runat="server"
EnablePaging="True"
SelectCountMethod="CountBicycles"
SelectMethod="GetBicycleGridDetails"
SortParameterName="SortExpression"
TypeName="eCommereProductGrid.Domain.BicycleProductDB">
</asp:ObjectDataSource>
<asp:GridView ID="gridBicycles"
runat="server"
AllowPaging="True"
AllowSorting="True"
AutoGenerateColumns="False"
BackColor="White"
CellPadding="0"
DataKeyNames="ProductID"
DataSourceID="SourceBicycles"
EmptyDataText="No data returned from query"
EnableViewState="false"
Font-Names="Verdana"
Font-Size="Small"
GridLines="Horizontal"
OnDataBound="GridBicycles_DataBound"
OnPageIndexChanging="GridBicycles_PageIndexChanging"
OnRowDataBound="GridBicycles_RowDataBound"
PageSize="5"
Width="80%">
<FooterStyle BackColor="GhostWhite" BorderColor="Gainsboro" ForeColor="Blue" />
<HeaderStyle BackColor="GhostWhite" BorderColor="Gainsboro"
ForeColor="Blue" HorizontalAlign="Center" VerticalAlign="Middle" />
<PagerSettings Position="TopAndBottom" FirstPageText="First"
LastPageText="Last" Mode="NumericFirstLast" />
<PagerStyle BackColor="GhostWhite" BorderColor="Gainsboro"
ForeColor="Black" HorizontalAlign="Center" VerticalAlign="Middle"/>
<RowStyle BackColor="White" ForeColor="Black" />
<Columns>
<asp:BoundField DataField="ProductID" />
<asp:TemplateField ItemStyle-HorizontalAlign="Center" ItemStyle-VerticalAlign="Middle">
<ItemTemplate>
<input type="checkbox" id="checkBox" name="myCheckBox" runat="server" onclick=""
value='<%# DataBinder.Eval(Container.DataItem, "ProductID") %>' />
<br />
<b>Compare</b>
</ItemTemplate>
<ItemStyle HorizontalAlign="Center" VerticalAlign="Middle" Width="65px" />
</asp:TemplateField>
<asp:TemplateField>
<ItemTemplate>
<div style="padding-left: 15px; padding-right: 15px;">
<a href='<%# DataBinder.Eval(Container.DataItem,
"ProductGridLinkPage") %>?ProductID=
<%# DataBinder.Eval(Container.DataItem, "ProductID") %>'>
<img src='DatabaseImageHandler.ashx?ID=<%# Eval("ProductID") %>'
alt='<%# DataBinder.Eval(Container.DataItem, "BrandName") %>
<%# DataBinder.Eval(Container.DataItem, "ModelName") %>'
border="0" />
</a>
</div>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Sort" SortExpression="LinkTitle">
<ItemTemplate>
<table>
<tr>
<td>
<a href='<%# DataBinder.Eval(Container.DataItem,
"ProductGridLinkPage") %>;?productID=<%# DataBinder.Eval(
Container.DataItem, "ProductID") %>'>
<%# DataBinder.Eval(Container.DataItem, "LinkTitle")%>
</td>
</tr>
<tr>
<td>
<div id="summary" runat="server">
<div id="expandedDiv" runat="server" style="display: none">
<asp:Label ID="summaryExpanded" Runat="server"
Text='<%# Eval("Summary") %>'></asp:Label>
</div>
<div id="collapsedDiv" runat="server">
<asp:Label ID="summaryCollapsed" Runat="server"
Text='<%# Eval("Summary") %>'></asp:Label>
</div>
<span id="genericControl" runat="server"></span>
</div>
</td>
</tr>
<tr>
<td>
<asp:Label ID="standardCost" Runat="server"
CssClass="gridStandardPrice"
Text='<%# Eval("MSRP", "Price: {0:C}") %>'></asp:Label>
</td>
</tr>
<tr>
<td>
<asp:Label ID="listPrice" Runat="server"
Text='<%# Eval("ListPrice", "MSRP: {0:C}") %>'></asp:Label>
</td>
</tr>
</table>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
The Page_Load
event for the ProductGrid
calls the JavaScript function getCheckedBoxes()
which collects the IDs of the CheckBox
es that are checked and stores them in a cookie. The Compare button calls the JavaScript function compareButtonClicked()
which reads the checked items from the cookie and calls the ProductComparison.aspx page with a query string consisting of each of the checked item IDs. The GridView
's RowDataBound
event handler sets up the dynamic product summary text DIV
s to expand and contract. The GridView
DataBound
event handler sets up everything to do the product comparisons by assigning the JavaScript method to the "Compare" button and setting up the page number label.
using System;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using eCommereProductGrid.Domain;
namespace eCommerceProductGrid.UserControls
{
public partial class ProductGrid : UserControl
{
#region Fields
private const string CHECKBOX_ATTRIBUTE = "ID";
public const string CHECKBOX_STATE_SCRIPT =
"<script type='text/javascript'>getCheckedBoxes()</script>";
public const string COMPARE_ITEMS_FUNCTION = "compareCheckedItems(this,'{0}')";
public const string COMPARISON_RESULTS_TEXT = "Results {0} - {1} of {2}";
private const int PRODUCT_SUMMARY_TEXT_WIDTH = 75;
private int pagingIndex;
#endregion
#region Event Handlers
protected void Page_Load(object sender, EventArgs e)
{
ClientScriptManager csm = Page.ClientScript;
if (!csm.IsStartupScriptRegistered(GetType(), "CheckBoxState"))
{
csm.RegisterStartupScript(GetType(), "CheckBoxState",
CHECKBOX_STATE_SCRIPT);
}
}
protected void GridPageNumberList_SelectedIndexChanged(object sender, EventArgs e)
{
gridBicycles.PageSize = Convert.ToInt32(GridPageNumberList.SelectedValue);
gridBicycles.DataBind();
}
protected void ComparisonError_Load(object sender, EventArgs e)
{ ComparisonError = sender as Label;
}
protected void GridBicycles_DataBound(object sender, EventArgs e)
{
foreach (GridViewRow row in gridBicycles.Rows)
{
row.Cells[0].Visible = false;
}
btnCompare.Attributes.Add("onclick", "compareButtonClicked();return false;");
int maxRecords = GridRecordCount();
int pageRecordCount = (pagingIndex + 1) * gridBicycles.PageSize;
if (pageRecordCount > maxRecords)
{
pageRecordCount = maxRecords;
}
PageResultsLabel.Text = String.Format(COMPARISON_RESULTS_TEXT,
pagingIndex * gridBicycles.PageSize + 1, pageRecordCount, maxRecords);
}
protected void GridBicycles_PageIndexChanging(object sender,
GridViewPageEventArgs e)
{
pagingIndex = e.NewPageIndex;
}
protected void GridBicycles_RowDataBound(object sender, GridViewRowEventArgs e)
{
if (e.Row.RowType == DataControlRowType.DataRow)
{
TableCell cell = e.Row.Cells[2];
Label summaryExpanded = (Label)cell.FindControl("summaryExpanded");
HtmlGenericControl theCollapsedDiv =
(HtmlGenericControl)cell.FindControl("collapsedDiv");
HtmlGenericControl theExpandedDiv =
(HtmlGenericControl)cell.FindControl("expandedDiv");
string scriptCollapse =
"( <span class=\"caretback\">«</span><" +
"script language="\""javascript\" type=\"text/javascript\">" +
"if(document.getElementById){document.writeln('<a " +
"href=\"javascript:void(0)\" onclick=\"return" +
" toggleDisplay(' + \"'" + theCollapsedDiv.ClientID +
"'\" + ')\"> less</a>');}</script>)";
string summaryExpandedText = summaryExpanded.Text;
summaryExpanded.Text = summaryExpandedText + scriptCollapse;
Label summaryCollapsed = (Label)cell.FindControl("summaryCollapsed");
string originalText = summaryCollapsed.Text;
if (originalText.Length > PRODUCT_SUMMARY_TEXT_WIDTH)
{
String text = originalText.Substring(
PRODUCT_SUMMARY_TEXT_WIDTH - 1, 15);
int indexOfFirstBlank = text.IndexOf(' ');
if (indexOfFirstBlank != -1)
{
string truncatedText = originalText.Substring(0,
PRODUCT_SUMMARY_TEXT_WIDTH + indexOfFirstBlank - 1);
string scriptExpand = "( <span class=\"caret" +
"back\">»</span><script language="\""javascript\" " +
"type=\"text/javascript\">if(document" +
".getElementById){document.writeln('<a" +
" href=\"javascript:void(0)\" onclick=\"return " +
"toggleDisplay(' + \"'" +
theExpandedDiv.ClientID + "'\" +
')\"> more</a>');}</script>)";
summaryCollapsed.Text = truncatedText + "..." + scriptExpand;
}
}
HtmlGenericControl regGeneric =
(HtmlGenericControl)cell.FindControl("genericControl");
regGeneric.InnerHtml =
"<script language="\""javascript\" type" +
"=\"text/javascript\">registerToggle('" +
theCollapsedDiv.ClientID + "', '" +
theExpandedDiv.ClientID.ToString() + "');</script>";
HtmlInputCheckBox cb = (HtmlInputCheckBox)cell.FindControl("checkBox");
eCommereProductGrid.Domain.BicycleProduct gp =
(e.Row.DataItem as eCommereProductGrid.Domain.BicycleProduct);
cb.Attributes.Add(CHECKBOX_ATTRIBUTE, gp.ProductID.ToString());
cb.Attributes.Add("onclick",
String.Format(COMPARE_ITEMS_FUNCTION, gp.ProductID));
}
}
#endregion
#region Properties
#endregion
#region Private Methods
private int GridRecordCount()
{
return BicycleProductDB.CountBicycles();
}
#endregion
}
}
When the Compare Items button is clicked, the function compareButtonClicked()
is called which creates a cookie to hold the CheckBox
es in the checked state.
function createCookie(name, value, days)
{
if (days) {
var date = new Date();
date.setTime(date.getTime()+(days*24*60*60*1000));
var expires = "; expires="+date.toGMTString();
}
else {
var expires = "";
}
document.cookie = name+"="+value+expires+"; path=/";
}
function getCookie(name)
{
var nameFormatted = name+"=";
var cookies = document.cookie.split(';');
for(var i=0; i < cookies.length; i++) {
var cookie = cookies[i];
while (cookie.charAt(0) == ' ')
cookie = cookie.substring(1, cookie.length);
if (cookie.indexOf(nameFormatted) == 0)
return cookie.substring(nameFormatted.length, cookie.length);
}
return null;
}
function compareCheckedItems(checkBox, itemno)
{
var compareCookie = getCookie('compareCookie');
var comparedItems = new Array();
if (checkBox.checked == true) {
if (compareCookie) {
comparedItems = compareCookie.split(',');
comparedItems.unshift(itemno);
if (comparedItems.length > 12) {
comparedItems.pop();
}
compareCookie = comparedItems.join(',');
createCookie('compareCookie', compareCookie, 0);
}
else {
createCookie('compareCookie', itemno, 0);
}
}
else if (checkBox.checked == false) {
comparedItems = compareCookie.split(',');
for(var i = 0; i <= comparedItems.length-1; i++)
{
if (comparedItems[i] == itemno) {
comparedItems.splice(i,1);
compareCookie = comparedItems.join(',');
createCookie('compareCookie', compareCookie, 0);
}
}
}
}
function compareButtonClicked()
{
var compareCookie = getCookie('compareCookie');
var isValidSelection = false;
if (compareCookie) {
var comparedItems = new Array();
comparedItems = compareCookie.split(',');
if (comparedItems.length > 1) {
isValidSelection = true;
}
}
if (isValidSelection) {
var queryString = "";
for (var i = 0; i < comparedItems.length; i++) {
var item = "ID=" + comparedItems[i];
if (i != comparedItems.length - 1)
queryString = queryString + item + "&";
else
queryString = queryString + item;
}
if (document.domain == "localhost")
window.location.href = 'http://localhost:49625/ComparePage.aspx?' +
queryString;
else
window.location.href = 'http://' + document.domain +
'/ComparePage.aspx?' + queryString;
}
else
{
alert('Please select at least two items to compare.');
}
}
function makeNewCookie() {
var compareCookie = "";
createCookie('compareCookie', compareCookie, 0);
}
function getCheckedBoxes() {
var mc = getCookie('compareCookie');
if (mc) {
var checkedItems = new Array();
checkedItems = mc.split(',');
var checkBoxes = new Array();
checkBoxes = document.getElementsByTagName('input');
for (var i = 0; i <= checkedItems.length-1; i++) {
for (var x = 0; x <= checkBoxes.length-1; x++) {
if (checkedItems[i] == checkBoxes[x].value) {
checkBoxes[x].checked = true;
}
}
}
}
}
Here's the domain class for the grid's ObjectDataSource
. The class BicycleProduct
is merely a class with an overloaded constructor and automatic public
properties for each of the fields, and is omitted here for the sake of brevity.
using System.ComponentModel;
using System.Data;
using System.Data.SqlClient;
using System.Web.Configuration;
using System.Collections.Generic;
namespace eCommereProductGrid.Domain
{
public class BicycleProductDB
{
public static int CountBicycles()
{
int nRows;
using (SqlConnection connection =
new SqlConnection(WebConfigurationManager.
ConnectionStrings["BicycleDatabase"].ConnectionString))
{
SqlCommand command = connection.CreateCommand();
command.Connection = connection;
command.CommandType = CommandType.StoredProcedure;
command.CommandText = "CountBicycleProducts";
connection.Open();
nRows = (int)command.ExecuteScalar();
}
return nRows;
}
[DataObjectMethod(DataObjectMethodType.Select, false)]
public List<BicycleProduct> GetBicycleGridDetails(
string SortExpression, int startRowIndex, int maximumRows)
{
List<BicycleProduct> list = new List<BicycleProduct>();
DataSet dataSet = new DataSet();
using (SqlConnection connection =
new SqlConnection(WebConfigurationManager.
ConnectionStrings["BicycleDatabase"].ConnectionString))
{
using (SqlCommand command = connection.CreateCommand())
{
command.CommandType = CommandType.StoredProcedure;
command.CommandText = "GetAllProductsPaged";
command.Parameters.Add(new SqlParameter("@Start", SqlDbType.Int, 4));
command.Parameters["@Start"].Value = startRowIndex;
command.Parameters.Add(new SqlParameter("@Count", SqlDbType.Int, 4));
command.Parameters["@Count"].Value = maximumRows;
SqlDataAdapter adapter = new SqlDataAdapter(command);
connection.Open();
adapter.Fill(dataSet, "Bicycles");
}
}
DataView view = dataSet.Tables[0].DefaultView;
view.Sort = SortExpression;
foreach (DataRowView row in view)
{
BicycleProduct displayDetails = new BicycleProduct(
(string) row["BrandName"],
(string)row["Color"],
(string)row["GridPageImageName"],
(string)row["LinkTitle"],
(decimal)row["ListPrice"],
(string)row["ModelName"],
(decimal)row["MSRP"],
(int)row["PercentSaved"],
(int)row["ProductID"],
(string)row["ProductPageImageName"],
(string)row["ProductGridLinkPage"],
(decimal)row["Savings"],
(string)row["Summary"],
(decimal)row["Weight"]);
list.Add(displayDetails);
}
return list;
}
public static void GetProductDetailsFor(int ProductID, DataSet displaySet)
{
using (SqlConnection connection =
new SqlConnection(WebConfigurationManager.
ConnectionStrings["BicycleDatabase"].ConnectionString))
{
SqlCommand command = connection.CreateCommand();
command.CommandType = CommandType.StoredProcedure;
command.CommandText = "GetProductDetailsByProductID";
command.Parameters.Add(new SqlParameter("@ProductID", SqlDbType.Int, 4));
command.Parameters["@ProductID"].Value = ProductID;
SqlDataAdapter adapter = new SqlDataAdapter(command);
connection.Open();
adapter.Fill(displaySet, "ProductDetails");
}
}
}
}
Our image handler class receives the ID of the item to display, retrieves it from the database, and serves it up to the GridView
.
using System;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
using System.IO;
using System.Web;
namespace eCommerceProductGrid
{
public class DatabaseImageHandler : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
if (context.Request.QueryString["ID"] == null)
throw new ArgumentException("No parameter specified");
using (SqlConnection connection =
new SqlConnection(ConfigurationManager.
ConnectionStrings["BicycleDatabase"].ConnectionString))
{
string sql = "SELECT GridPageImage, " +
"ImageContentType FROM BicycleProduct WHERE ProductID=@ID";
SqlCommand sqlCommand = new SqlCommand(sql, connection);
sqlCommand.CommandType = CommandType.Text;
sqlCommand.Parameters.Add("@ID", SqlDbType.Int, 4);
sqlCommand.Parameters["@ID"].Value = context.Request.QueryString["ID"];
connection.Open();
SqlDataReader reader = sqlCommand.ExecuteReader(CommandBehavior.Default);
reader.Read();
try
{
MemoryStream stream = new MemoryStream((
byte[])reader["GridPageImage"]);
byte[] buffer = new byte[stream.Length];
int byteSeq = stream.Read(buffer, 0, (int)stream.Length);
context.Response.ContentType = reader["ImageContentType"].ToString();
while (byteSeq > 0)
{
context.Response.OutputStream.Write(buffer, 0, byteSeq);
byteSeq = stream.Read(buffer, 0, (int)stream.Length);
}
}
catch (SqlException ex)
{
context.Response.Write(ex.Message);
}
finally
{
reader.Close();
}
}
}
public bool IsReusable
{
get
{
return false;
}
}
}
}
The Product Comparison Page
The comparison page uses a ListView
and an ObjectDataSource
that's identical to the GridView
except without the SortParameterName
attribute. The ListView
is set to display four items per page with paging enabled.
<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="ProductComparison.ascx.cs"
Inherits="eCommerceProductGrid.UserControls.ProductComparison" %>
<asp:ObjectDataSource ID="sourceBicycles"
runat="server"
EnablePaging="True"
SelectCountMethod="CountBicycles"
SelectMethod="CompareBicycleDetails"
TypeName="eCommereProductGrid.Domain.CompareBicycleProductsDB">
<SelectParameters>
<asp:QueryStringParameter Name="QueryValues" QueryStringField="ID" />
</SelectParameters>
</asp:ObjectDataSource>
<asp:ListView ID="ListView"
Runat="server"
DataSourceID="sourceBicycles"
GroupItemCount="4">
<LayoutTemplate>
<table id="LayoutTable" runat="server" border="1">
<tr id="groupPlaceholder" runat="server">
</tr>
</table>
<asp:DataPager runat="server" ID="ContactsDataPager" PageSize="4">
<Fields>
<asp:TemplatePagerField>
<PagerTemplate>
<div class="productComparisonPager">
<b>
Page
<asp:Label runat="server" ID="CurrentPageLabel" Font-Size="Small"
Text="<%# Container.TotalRowCount>0 ?
(Container.StartRowIndex / Container.PageSize) + 1 : 0 %>" />
of
<asp:Label runat="server" ID="TotalPagesLabel" Font-Size="Small"
Text="<%# Math.Ceiling ((double)Container.TotalRowCount /
Container.PageSize) %>" />
<br />
</b>
</div>
</PagerTemplate>
</asp:TemplatePagerField>
<asp:NumericPagerField PreviousPageText="< Prev 4" NextPageText="Next 4 >" />
</Fields>
</asp:DataPager>
</LayoutTemplate>
<GroupTemplate>
<tr><td runat="server" id="itemPlaceholder" valign="top" /></tr>
</GroupTemplate>
<ItemTemplate>
<td>
<table class="comparisonTable" id="rptProduct" runat="server" border="0">
<tr>
<td style="height: 50px;font-size: small">
<a href='<%# DataBinder.Eval(Container.DataItem,
"ProductGridLinkPage") %>?productID=<%# DataBinder.Eval(
Container.DataItem, "ProductID") %>' />
<%# DataBinder.Eval(Container.DataItem, "LinkTitle")%> </td>
</tr>
<tr>
<td style="height: 50px;">
<a href='<%# DataBinder.Eval(Container.DataItem,
"ProductGridLinkPage") %>?ProductID=<%# DataBinder.Eval(
Container.DataItem, "ProductID") %>' />
<img src='DatabaseImageHandler.ashx?ID=<%# DataBinder.Eval(
Container.DataItem, "ProductID") %>'
alt="<%# DataBinder.Eval(Container.DataItem,
"BrandName") %> <%# DataBinder.Eval(Container.DataItem,
"ModelName") %>" border="0" />
</td>
</tr>
<tr>
<td><b>Brand:</b> <%# DataBinder.Eval(Container.DataItem, "BrandName")%></td>
</tr>
<tr>
<td><b>Model:</b> <%# DataBinder.Eval(Container.DataItem, "ModelName")%></td>
</tr>
<tr>
<td><b>Color:</b> <%# DataBinder.Eval(Container.DataItem, "Color")%></td>
</tr>
<tr>
<td><b>Weight:</b> <%# DataBinder.Eval(Container.DataItem, "Weight")%></td>
</tr>
<tr class="comparisonPrice">
<td><b>List Price:</b>
<%# DataBinder.Eval(Container.DataItem, "ListPrice", "{0:C}")%></td>
</tr>
<tr>
<td><b>MSRP:</b> <%# DataBinder.Eval(Container.DataItem, "MSRP", "{0:C}")%>
</td>
</tr>
<tr>
<td><b>Savings:</b> <%# DataBinder.Eval(Container.DataItem, "Savings", "{0:C}")%>
(<%# DataBinder.Eval(Container.DataItem, "PercentSaved")%>%)</td>
</tr>
<tr>
<td style="height: 35px" align="center">
<asp:Button ID="RemoveItemButton" runat="server" CommandName="REMOVE"
CommandArgument='<%# DataBinder.Eval(Container.DataItem, "ProductID") %>'
oncommand="ImageButton_Command" Text="Remove" />
</td>
</tr>
<tr>
<td style="height: 35px" align="center">
<asp:Button ID="DisplayItemButton1" runat="server" CommandName="DISPLAY"
CommandArgument='<%# DataBinder.Eval(Container.DataItem, "ProductID") %>'
Text="Display" oncommand="ImageButton_Command" />
</td>
</tr>
</tr>
</table>
</td>
</ItemTemplate>
</asp:ListView>
The Page_Load
event handler in the code-behind parses and stores the IDs in the query string so if there are more than two items in the comparison, the user can remove them. The button click handler simply executes the given command to either remove an item or display the product page for an item.
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Text;
using System.Web.UI.WebControls;
using System.Web.UI;
using eCommereProductGrid.Domain;
namespace eCommerceProductGrid.UserControls
{
public partial class ProductComparison : UserControl
{
#region Fields
private const string DISPLAY_ITEM = "DISPLAY";
private const string REMOVE_ITEM = "REMOVE";
private List<int> queryArgs = new List<int>();
#endregion
#region Event Handlers
protected void Page_Load(object sender, EventArgs e)
{
NameValueCollection coll = Request.QueryString;
String[] key = coll.AllKeys;
for (int loop1 = 0; loop1 < key.Length; loop1++)
{
String[] value = coll.GetValues(key[loop1]);
if (value != null && value.Length > 0)
{
for (int ix = 0; ix < value.Length; ix++)
{
int id = Convert.ToInt32(Server.HtmlEncode(value[ix]));
queryArgs.Add(id);
}
}
}
}
protected void ImageButton_Command(object sender, CommandEventArgs e)
{
if (e.CommandName == DISPLAY_ITEM)
{
int productID = Convert.ToInt32(e.CommandArgument);
string pageName = CompareBicycleProductsDB.GetProductPageName(productID);
Response.Redirect("~/" + pageName + "?ProductID=" + productID, true);
}
else if (e.CommandName == REMOVE_ITEM)
{
if (ListView.Items.Count > 2)
{
queryArgs.Remove(Convert.ToInt32((string)e.CommandArgument));
Response.Redirect("~/ComparePage.aspx?" + ParseQueryArgs());
}
}
}
#endregion
#region Private Methods
private string ParseQueryArgs()
{
StringBuilder query = new StringBuilder();
foreach (int parm in queryArgs)
{
if (query.Length != 0)
{
query.Append("&");
}
query.AppendFormat("ID={0}", parm);
}
return query.ToString();
}
#endregion
}
}
Running the Sample Code
The sample code is a Visual Studio 2008 solution and uses SQL Server 2008. When executing the solution under localhost, the port number that localhost uses must be the same port number in the JavaScript function compareButtonClicked()
that executes the comparison page. I added the port 49625 as a property of the solution, but your machine may require a different port. If so, change the properties of the web project to your port number and change line 92 of ProductComparison.js to include the new port number.
- Build the solution
- Set the
DatabaseImageInsert
project as the startup project
- Execute the SQL script in the Database Script folder in SQL Server
- Execute the
DatabaseImageInsert
project
- Set
eCommerceProductGrid
as the startup project and run it