The asp:Repeater control is also 'lookless' it only generates the HTML element that we specify using templates, which fits in well with the design our shopping cart page - we don't need
Trang 2public void AddShoppingCartItem(string customerName, string productCode) {
DataSet previousItem = GetShoppingCartItem(customerName, productCode);
products = new IBuyAdventure.ProductsDB(m_ConnectionString);
DataSet productDetails = products.GetProduct(productCode);
String description =
(String) productDetails.Tables[0].Rows[0]["ProductDescription"];
String productName =
Trang 3(String) productDetails.Tables[0].Rows[0]["ProductName"];
double unitPrice =
(double) productDetails.Tables[0].Rows[0]["UnitPrice"];
String insertStatement = "INSERT INTO ShoppingCarts (ProductCode, "
+ "ProductName, Description, UnitPrice, CustomerName, "
+ "Quantity) values ('" + productCode + "', @productName, "
+ "@description, " + unitPrice + ", '" + customerName + "' , 1)";
SqlConnection myConnection = new SqlConnection(m_ConnectionString);
SqlCommand myCommand = new SqlCommand(insertStatement, myConnection);
Trang 4}
}
The first interesting point about the code is that it checks if the item is already in the shopping cart by calling
GetShoppingCartItem, and if it does already exist, it simply increases the quantity for that item and updates it in the database using the UpdateShoppingCartItem function
The second interesting point comes about because the ADO.NET code that adds a new cart item uses the SqlCommand class Since the IBuyAdventure product descriptions can contain single quotes, we need to ensure that any quotes within the description do not conflict with the quotes used to delimit the field To do this we use the SqlCommand object to execute our query, making use of parameters in our SQL, like @description, to avoid any conflict The values for the parameters are then specified using the Parameters collections of the SqlCommand object:
myCommand.Parameters.Add(
new SqlParameter("@description", SqlDbType.VarChar, 255));
Once the SQL statement is built, the command object can be connected, the statement executed, and then disconnected:
myCommand.Connection.Open();
myCommand.ExecuteNonQuery();
myCommand.Connection.Close();
Displaying the Shopping Cart and Changing an Order
The shopping cart allows customers to specify a quantity for each product in the cart, and displays the price per item, and total price for the quantity ordered At any time, a customer can change the order quantity or remove one or more items from the cart by checking the Remove box and clicking Recalculate An item will also be removed if the customer enters
Trang 5built-in selection and editing functionality provided by the other list controls such as the asp:DataList and
asp:DataGrid All of the items are always checked and processed during a postback, and the cart contents (the dataset bound to the asp:Repeater control) is always generated during each postback
The asp:Repeater control is also 'lookless' (it only generates the HTML element that we specify using templates), which fits in well with the design our shopping cart page - we don't need a complete table to be generated by the control (the table's start and header rows are part of the static HTML)
Data Source/HTML/ASPX for the Shopping Cart
The shopping cart data source is provided by the CartDB component, which is bound to the myList asp:repeater control:
The HTML used to render the shopping cart, including the ItemTemplate rendered for each item in the
MyList.DataSource is shown next, although some parts of the HTML page formatting (for example the font settings) have been removed to keep it short and easily readable:
<table colspan="8" cellpadding="5" border="0" valign="top">
<tr valign="top">
<td align="center" bgcolor="#800000">Remove</td>
<td align="center" bgcolor="#800000">Product Code</td>
Trang 6<td align="center" bgcolor="#800000">Product Name</td>
<td align="center" bgcolor="#800000" width="250">Description</td>
<td align="center" bgcolor="#800000">Quantity</td>
<td align="center" bgcolor="#800000">Unit Price</td>
<td align="center" bgcolor="#800000">Unit Total</td>
Trang 8<%# String.Format("{0:C}", (((int)DataBinder.Eval(Container.DataItem, "Quantity"))
* ((double) DataBinder.Eval(Container.DataItem, "UnitPrice")) )) %>
<asp:button text="Recalculate" OnClick="Recalculate_Click" runat="server" />
<asp:button text="Go To Checkout" OnClick="Checkout_Click" runat="server" />
</td>
</tr>
Trang 9</table>
The code shown above is similar to that which we have seen earlier, so it should be easy to follow The important point to
note is that all the fields that need to be available when a postback occurs are marked with the id and runat="server"
attributes When the customer causes a postback by pressing the Recalculate button, the ASP.NET page can access the
Remove checkbox control, the database cart ID hidden field control, and the Quantity field control for each list item, and
update the database accordingly
For each row in the ShoppingCarts table for this customer, the asp:Repeater control will contain a list item
containing these three controls, which can be programmatically accessed:
To associate each list item within the asp:Repeater control with a specific database cart item, a hidden field is used to
store the unique ID for the entry:
<input id="ShoppingCartID" type="hidden"
value='<%#DataBinder.Eval(
Container.DataItem, "ShoppingCartID", " {0:g}") %>'
Trang 10runat="server">
As discussed earlier, the contents of the shopping cart are always stored in the SQL Server table named ShoppingCarts, and manipulated using the business object named CartDB To populate the shopping cart with items, the ASP.NET page invokes the PopulateShoppingCartList function This occurs when the page is loaded for the first time (that is, when Page.PostBack is false), and after each postback that leads to the database being modified - items added, deleted,
or changed To retrieve the cart items and data bind the asp:Repeater control, this function uses the
GetShoppingCartItems method of the CartDB object:
Trang 11UnitPrice = (double) dt.Rows[lIndex]["UnitPrice"];
Quantity = (int) dt.Rows[lIndex]["Quantity"];
The total stored in the fTotal parameter is defined as a Double earlier in the page definition:
// Total for shopping basket
<td colspan="6"></td><td colspan="2" align="right">
Total is <%=String.Format("{0:C}", fTotal ) %>
</td>
</tr>
Trang 12
When customers change the order quantity for products in their cart, or marks items to be removed, they click the
Recalculate button This button was created using the asp:button control with its OnClick event wired up to the
Recalculate_Click function:
<asp:button text="Recalculate" OnClick="Recalculate_Click" runat="server" />
The Recalculate_Click function updates the database based on the changes users made to the quantities, and the items they have added or deleted It then retrieves the updated cart items from the database, rebinds the repeater control
to the updated data set, and finally creates a status message informing the user how many items (if any) are currently in the cart These functions are, in turn, delegated within the event handler to three different functions:
void Recalculate_Click(Object sender, EventArgs e) {
// Update Shopping Cart
PopulateShoppingCartList causes the shopping cart to be re-read from the database and bound to the
asp:Repeater control This will cause the page to render an updated view of the cart to the user
To perform the required database updates, the UpdateShoppingCartDatabase function iterates through each of the
Trang 13list items (the rows) within the asp:Repeater control and checks each item to see if it should be deleted or modified:
void UpdateShoppingCartDatabase() {
IBuyAdventure.ProductsDB inventory =
new IBuyAdventure.ProductsDB(getConnStr());
IBuyAdventure.CartDB cart = new IBuyAdventure.CartDB(getConnStr());
for (int i=0; i<MyList.Items.Count; i++) {
int Quantity = Int32.Parse(quantityTxt.Text);
if (remove.Checked == true || Quantity == 0)
cart.DeleteShoppingCartItem(
Int32.Parse(shoppingCartIDTxt.Value));
else {
cart.UpdateShoppingCartItem(
Trang 14Checkout Processing and Security
When customers are ready to commit to purchasing the goods that are currently in their shopping cart, they can click the
Go to Checkout button in the shopping cart page, or click the shopping basket image located on the navigation bar The
security system used in IBuyAdventure takes advantage of forms-based authentication (also called cookie-based security), as introduced in Chapter 14 When a customer hits any of the pages that require authentication, if they haven't already signed in, the page login.aspx is displayed:
Trang 15The ASP.NET runtime knows to display this page if a user is not logged in because all pages that require authentication are located in a directory called SECURE It contains a web.config file, which specifies anonymous access is not allowed:
Trang 16</system.web>
</configuration>
Remember that '?' means 'anonymous users'
Using a specific directory to contain secure items is a simple yet flexible way of implementing security in ASP.NET applications When the ASP.NET runtime determines that an anonymous user is trying to access a page in a secure directory of our application, it knows which page to display because the web.config file located in the root directory has
a cookie element with a loginurl attribute that specifies it:
Trang 17This configuration basically says, if the ibuyadventurecookie cookie is present and it has not been tampered with, the user has been authenticated and so can access secure directions, if authorized; if not present, redirect to the URL specified by loginurl
Forms-Based Authentication in Web Farms
For forms-based authentication to work within a web farm environment, the decryptionkey attribute of the cookie element must be set, and not left blank or specified as the default value of autogenerate The decryptionkey attribute should be set the same on all machines within the farm The length of the string is 16 characters for DES encryption (56/64 bit), or 48 characters for Triple-DES encryption (128 bit) If you do use the default value it will cause
a different encryption string to be generated by each machine in the farm, and cause the session authentication to fail between different machines as a user moves between servers If this happens a CryptographicException will be thrown and the user will be presented with a screen saying the data is bad, or could not be decoded
The Login.aspx Page Event Handlers
The Login button on the login form is created using an asp:button control, which has the OnClick event wired up to the LoginBtn_Click event handler:
<td colspan="2" align="right">
<asp:button Text=" Login " OnClick="LoginBtn_Click" runat="server" />
</td>
When the button is clicked, the LoginBtn_Click event handler is invoked It validates users, and then redirects them
to the original page The validation and redirection code is shown here:
void LoginBtn_Click(Object sender, EventArgs e) {
IBuyAdventure.UsersDB users = new IBuyAdventure.UsersDB(getConnStr());
IBuyAdventure.CartDB cart = new IBuyAdventure.CartDB(getConnStr());
if (users.ValidateLogin(UserName.Text, Password.Text)) {
Trang 18• The client is marked as authenticated by calling the RedirectFromLoginPage method of the
FormsAuthentication object, which we discussed in Chapter 14.
• This causes the cookie named ibuyadventurecookie to be sent back to the client, so we know from here on
in that the client has been authenticated
• The user is redirected back to the page that initially caused the login form to be displayed
If customers have previously registered, they can login via the Login page This will then redirect them back to the original page that caused the Login page to be displayed This redirection code is actually implemented by the Login page we have created, and does require some extra work on our part We will see this next
Handling Page Return Navigation During Authentication
When the ASP.NET runtime determines that a secure item has been accessed, it will redirect the user to our Login page,
Trang 19and include a query string parameter named ReturnURL As the name suggests, this is the page that we will redirect users to once we are happy that they should be allowed access to it When displaying our page, we need to save this value,
as it will be lost during the postbacks where we validate the user The approach used in our page is to store the value in
a hidden field during the Page_Load event:
void Page_Load(Object sender, EventArgs e)
Trang 20is stored in the ReturnUrl hidden control
First Time Customer - Registration
If customers have not registered with our application before, they can click the Registration hyperlink, and will be presented with a user registration form to fill in:
We have kept the form simple for this case study, and only ask for an email address and password In a commercial application, this form would probably include additional information such as the name and address of the customer
As the registration page (Register.aspx) is opened from a hyperlink in the login page (login.aspx), we have to ensure that we pass on the ReturnUrl parameter, so that the registration page knows where to redirect users once they have completed the form To do this, we dynamically create the hyperlink in the registration form during the Page_Load
Trang 21event of the login page:
Those of you with a keen eye will have spotted that customers can actually log in at any time by clicking the Sign In /
Register hyperlink located in the page header Once a user is successfully authenticated, this hyperlink changes to say Sign Out:
The sign in or out code is implemented in the header user control (UserControl/header.ascx) where the Page_Load event handler dynamically changes the text of the signInOutMsg control, depending on the authentication state of the
Trang 22current user:
<%@ Import Namespace="System.Web.Security" %>
<script language="C#" runat="server">
private void Page_Load( Object Sender, EventArgs e ) {
Trang 23The SignInOut code is wired up as part of the control declaration:
<td>
<asp:linkbutton style="font:8pt verdana" id="signInOutMsg"
runat="server" OnClick="SignInOut" />