Tải bản đầy đủ (.pdf) (77 trang)

Beginning asp net 2.0 with c phần 8 doc

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (1.44 MB, 77 trang )

4. In the dialog box that appears, you are going to add three extra steps. This is so you have five
steps: one for login, one for address, one for credit card details, one to confirm the order, and
one to finish the transaction. Start by clicking Add (see Figure 13-35) and entering Step 3 next to
Title. Then clicking Add again and enter Step 4. Click Add one more time and enter Step 5.
Figure 13-35
5. Go back and change the Title property in each so that it reads as shown in Figure 13-36.
Figure 13-36
6. Click OK.
7. From the Login section of the Toolbox, drag a Login box into the <asp:Wizard> control, as
shown in Figure 13-37.
507
E-Commerce
16_042583 ch13.qxd 4/4/06 2:50 PM Page 507
Figure 13-37
8. Click Source View. Add the following code to the Wizard step for Step 2 (Delivery Address):
<asp:checkbox id=”chkUseProfileAddress” runat=”server” autopostback=”True”
text=”Use membership address”
OnCheckedChanged=”chkUseProfileAddress_CheckedChanged”></asp:checkbox><br />
<table border=”0”>
<tr><td>Name</td><td><asp:textbox id=”txtName” runat=”server” /></td></tr>
<tr><td>Address</td><td><asp:textbox id=”txtAddress” runat=”server” /></td></tr>
<tr><td>City</td><td><asp:textbox id=”txtCity” runat=”server” /></td></tr>
<tr><td>County</td><td><asp:textbox id=”txtCounty” runat=”server” /></td></tr>
<tr><td>Postcode</td><td><asp:textbox id=”txtPostCode” runat=”server” />
</td></tr>
<tr><td>Country</td><td><asp:textbox id=”txtCountry” runat=”server” /></td></tr>
</table>
9. Add the following code to the Wizard step for Step 3 (Payment):
<asp:DropDownList id=”lstCardType” runat=”server”>
<asp:ListItem>MasterCard</asp:ListItem>
<asp:ListItem>Visa</asp:ListItem>


</asp:DropDownList>
<br />
Card Number: <asp:Textbox id=”txtNumber” runat=”server”
Text=”0123456789” ReadOnly=”True”/>
<br />
Expires:
<asp:textbox id=”txtExpiresMonth” runat=”server” columns=”2” />
/
<asp:textbox id=”txtExpiresYear” runat=”server” columns=”4” />
508
Chapter 13
16_042583 ch13.qxd 4/4/06 2:50 PM Page 508
10. Go back to Design View for Step 4 (Confirmation). Type the following:
Please confirm the amount you wish to have
deducted from your credit card.
11. Select ShoppingCart.ascx and drag it into the Wizard control above the text you have cre-
ated, as shown in Figure 13-38.
Figure 13-38
12. Click Complete and in Design View for Step 5 (Complete), type Thank you for your order.
13. Go to Source View and above the <asp:Wizard> control, add the following:
<asp:Label id=”NoCartlabel” runat=”server” visible=”false”>
There are no items in your cart. Visit the shop to buy items.
</asp:Label>
<div style=”float:right”>
<asp:LoginView ID=”LoginView1” Runat=”server”>
<AnonymousTemplate>
<asp:passwordrecovery id=”PasswordRecovery1” runat=”server” />
</AnonymousTemplate>
</asp:LoginView>
</div>

14. Above this code, add the following:
<%@ Import Namespace =”System.Data.SqlClient”%>
<%@ Import Namespace =”Wrox.Commerce”%>
509
E-Commerce
16_042583 ch13.qxd 4/4/06 2:50 PM Page 509
15. Save the design.
16. Go to Solution Explorer, and select checkout.aspx.cs.
17. Add the following code-behind in place of whatever is already there:
using System;
using System.Data;
using System.Data.SqlClient;
using System.Configuration;
using Wrox.Commerce;
using System.Web.UI.WebControls;
using System.Web.Security;
public partial class Checkout : System.Web.UI.Page
{
void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
if (Profile.Cart == null)
{
NoCartlabel.Visible = true;
Wizard1.Visible = false;
}
if (User.Identity.IsAuthenticated)
{
Wizard1.ActiveStepIndex = 1;

}
else
{
Wizard1.ActiveStepIndex = 0;
}
}
}
protected void chkUseProfileAddress_CheckedChanged(object sender ,
System.EventArgs e )
{
// fill the delivery address from the profile, but only if it’s empty
// we don’t want to overwrite the values
if (chkUseProfileAddress.Checked && txtName.Text.Trim() == “”)
{
txtName.Text = Profile.Name;
txtAddress.Text = Profile.Address;
txtCity.Text = Profile.City;
txtCounty.Text = Profile.County;
txtPostCode.Text = Profile.PostCode;
txtCountry.Text = Profile.Country;
}
}
protected void Wizard1_FinishButtonClick( object sender,
System.Web.UI.WebControls.WizardNavigationEventArgs e)
510
Chapter 13
16_042583 ch13.qxd 4/4/06 2:50 PM Page 510
{
// Insert the order and order lines into the database
SqlConnection conn = null;

SqlTransaction trans = null;
SqlCommand cmd;
try
{
conn = new
SqlConnection(ConfigurationManager.ConnectionStrings[“WroxUnited”].ConnectionString
);
conn.Open();
trans = conn.BeginTransaction();
cmd = new SqlCommand();
cmd.Connection = conn;
cmd.Transaction = trans;
// set the order details
cmd.CommandText = “INSERT INTO Orders(MemberName, OrderDate, Name, Address,
County, PostCode, Country, SubTotal, Discount, Total) “ +
“VALUES (@MemberName, @OrderDate, @Name, @Address, @County, @PostCode, @Country,
@SubTotal, @Discount, @Total)”;
cmd.Parameters.Add(“@MemberName”, SqlDbType.VarChar, 50);
cmd.Parameters.Add(“@OrderDate”, SqlDbType.DateTime);
cmd.Parameters.Add(“@Name”, SqlDbType.VarChar, 50);
cmd.Parameters.Add(“@Address”, SqlDbType.VarChar, 255);
cmd.Parameters.Add(“@County”, SqlDbType.VarChar, 50);
cmd.Parameters.Add(“@PostCode”, SqlDbType.VarChar, 15);
cmd.Parameters.Add(“@Country”, SqlDbType.VarChar, 50);
cmd.Parameters.Add(“@SubTotal”, SqlDbType.Money);
cmd.Parameters.Add(“@Discount”, SqlDbType.Money);
cmd.Parameters.Add(“@Total”, SqlDbType.Money);
cmd.Parameters[“@MemberName”].Value = User.Identity.Name;
cmd.Parameters[“@OrderDate”].Value = DateTime.Now;
cmd.Parameters[“@Name”].Value =

((TextBox)Wizard1.FindControl(“txtName”)).Text;
cmd.Parameters[“@Address”].Value =
((TextBox)Wizard1.FindControl(“txtAddress”)).Text;
cmd.Parameters[“@County”].Value =
((TextBox)Wizard1.FindControl(“txtCounty”)).Text;
cmd.Parameters[“@PostCode”].Value =
((TextBox)Wizard1.FindControl(“txtPostCode”)).Text;
cmd.Parameters[“@Country”].Value =
((TextBox)Wizard1.FindControl(“txtCountry”)).Text;
cmd.Parameters[“@SubTotal”].Value = Profile.Cart.SubTotal;
cmd.Parameters[“@Discount”].Value = Profile.Cart.MemberDiscount;
cmd.Parameters[“@Total”].Value = Profile.Cart.Total;
int OrderID = Convert.ToInt32(cmd.ExecuteScalar());
// change the query and parameters for the order lines
cmd.CommandText = “INSERT INTO OrderLines(OrderID, ProductID, Quantity,
Price) “ +
511
E-Commerce
16_042583 ch13.qxd 4/4/06 2:50 PM Page 511
“VALUES (@OrderID, @ProductID, @Quantity, @Price)”;
cmd.Parameters.Clear();
cmd.Parameters.Add(“@OrderID”, SqlDbType.Int);
cmd.Parameters.Add(“@ProductID”, SqlDbType.Int);
cmd.Parameters.Add(“@Quantity”, SqlDbType.Int);
cmd.Parameters.Add(“@Price”, SqlDbType.Money);
cmd.Parameters[“@OrderID”].Value = OrderID;
foreach (CartItem item in Profile.Cart.Items)
{
cmd.Parameters[“@ProductID”].Value = item.ProductID;
cmd.Parameters[“@Quantity”].Value = item.Quantity;

cmd.Parameters[“@Price”].Value = item.Price;
cmd.ExecuteNonQuery();
}
// commit the transaction
trans.Commit();
}
catch (SqlException SqlEx)
{
// some form of error - rollback the transaction
// and rethrow the exception
if (trans != null)
trans.Rollback();
CreateOrderErrorLabel.Visible = true;
// Log the exception
// Tools.log(“An error occurred while creating the order”, SqlEx)
throw new Exception(“An error occurred while creating the order”, SqlEx);
}
finally
{
if (conn != null)
conn.Close();
}
// we will only reach here if the order has been created sucessfully
// so clear the cart
Profile.Cart.Items.Clear();
}
protected void Wizard1_NextButtonClick( object sender,
System.Web.UI.WebControls.WizardNavigationEventArgs e)
{
if (e.CurrentStepIndex == 0)

{
System.Web.UI.WebControls.Login l = (Login)Wizard1.FindControl(“Login1”);
if (Membership.ValidateUser(l.UserName, l.Password))
{
512
Chapter 13
16_042583 ch13.qxd 4/4/06 2:50 PM Page 512
FormsAuthentication.SetAuthCookie(l.UserName, l.RememberMeSet);
e.Cancel = false;
}
else
{
l.InstructionText = “Your login attempt was not successful. Please try
again.”;
l.InstructionTextStyle.ForeColor = System.Drawing.Color.Red;
e.Cancel = true;
}
}
else
{
if (!User.Identity.IsAuthenticated)
{
e.Cancel = true;
Wizard1.ActiveStepIndex = 0;
}
}
}
protected void Wizard1_ActiveStepChanged( object sender, System.EventArgs e)
{
if (!User.Identity.IsAuthenticated)

Wizard1.ActiveStepIndex = 0;
}
}
18. Open ShoppingCartPage.aspx and in Design View, add a hyperlink to the page. Right-click
the link and change the properties as shown in the following table.
Property Value
ID Checkout
Text Checkout
NavigateURL ~/Checkout.aspx
19. Run Wroxshop.aspx, add two scarves to your shopping cart, and click Checkout. Supply login
details in the fields shown in Figure 13-39.
20. Click Next after you’re logged in, and then either click your membership address or supply
your address details (see Figure 13-40).
21. Click Next, and you’ll arrive at the screen shown in Figure 13-41. This is your credit card
handler — it doesn’t require any user details.
513
E-Commerce
16_042583 ch13.qxd 4/4/06 2:50 PM Page 513
Figure 13-39
Figure 13-40
Figure 13-41
514
Chapter 13
16_042583 ch13.qxd 4/4/06 2:50 PM Page 514
22. Click Next. On the last page (see Figure 13-42), you see a summary of the details.
Figure 13-42
23. Click Finish to end the checkout.
How It Works
This completes your e-commerce pipeline. You started by creating the five stages of the checkout process
using the

<asp:wizard> control. The login stage used a Login control, and the delivery address used a
check box and a series of text boxes to record the details. The payment stage took the credit card details
via a drop-down list, which contained the type of credit card, and you had text boxes for the card num-
ber and expiration date. You didn’t validate these details in any way. In the confirmation stage, you just
inserted a copy of the shopping cart control, and the last step simply displayed a short thank you
message.
You added a control
LoginView, which contained your anonymous template:
<asp:LoginView ID=”LoginView1” Runat=”server”>
<AnonymousTemplate>
<asp:passwordrecovery id=”PasswordRecovery1” runat=”server” />
</AnonymousTemplate>
</asp:LoginView>
This displayed the password recovery control, which is displayed to aid any user who might have for-
gotten their password.
It was left to the code-behind to provide the meat of the example. When the page first loads, you check
to see if there is anything in the cart. If there isn’t, then you make the Wizard invisible and show the
nocartlabel, which informs the user that there is nothing in the cart. The second check is to see if the
user identity has been authenticated. This is a test of whether or not they have logged in. If they have
logged in already, you jump them past the login stage, or else you have to get them logged in first:
void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
515
E-Commerce
16_042583 ch13.qxd 4/4/06 2:50 PM Page 515
{
if (Profile.Cart == null)
{
NoCartlabel.Visible = true;

Wizard1.Visible = false;
}
if (User.Identity.IsAuthenticated)
{
Wizard1.ActiveStepIndex = 1;
}
else
{
Wizard1.ActiveStepIndex = 0;
}
}
}
The next procedure in the code is the code that responds to the check box being altered in Step 2, the
delivery address. If this box is checked, you fill the text boxes with the details stored in the user’s profile.
Otherwise you leave them empty:
protected void chkUseProfileAddress_CheckedChanged( object sender,
System.EventArgs e)
{
// fill the delivery address from the profile, but only if it’s empty
// we don’t want to overwrite the values
if (chkUseProfileAddress.Checked && (txtName.Text.Trim() == “”))
{
txtName.Text = Profile.Name;
txtAddress.Text = Profile.Address;
txtCity.Text = Profile.City;
txtCounty.Text = Profile.County;
txtPostCode.Text = Profile.PostCode;
txtCountry.Text = Profile.Country;
}
}

NextButtonClick is used to check whether the user has logged in successfully and can therefore
progress to the next step of the Wizard. This step only comes into play if you are actually on the login
stage at the time. You check to see if the user has been validated and, if not, you display an appropriate
error message informing the user that they aren’t able to log in this time. Otherwise you validate the user:
protected void Wizard1_NextButtonClick( object sender,
System.Web.UI.WebControls.WizardNavigationEventArgs e)
{
if (e.CurrentStepIndex == 0)
{
System.Web.UI.WebControls.Login l = (Login)Wizard1.FindControl(“Login1”);
if (Membership.ValidateUser(l.UserName, l.Password))
{
FormsAuthentication.SetAuthCookie(l.UserName, l.RememberMeSet);
516
Chapter 13
16_042583 ch13.qxd 4/4/06 2:50 PM Page 516
e.Cancel = false;
}
else
{
l.InstructionText = “Your login attempt was not successful. Please try
again.”;
l.InstructionTextStyle.ForeColor = System.Drawing.Color.Red;
e.Cancel = true;
}
}
else
{
if (!User.Identity.IsAuthenticated)
{

e.Cancel = true;
Wizard1.ActiveStepIndex = 0;
}
}
}
FinishButtonClick contains perhaps the longest set of code, but it isn’t as daunting as it looks. This is
where you write the user’s order to the database. You have to be able to roll this back if a mistake has
occurred. You start by creating a connection string, and you create a transaction. Then you read in all of
the details supplied in the checkout process into parameters. There are a lot of them! You have the mem-
ber name, the delivery address, the credit card details, and the whole shopping cart total:
protected void Wizard1_FinishButtonClick( object sender,
System.Web.UI.WebControls.WizardNavigationEventArgs e)
{
// Insert the order and order lines into the database
SqlConnection conn = null;
SqlTransaction trans = null;
SqlCommand cmd;
try
{
conn = new
SqlConnection(ConfigurationManager.ConnectionStrings[“WroxUnited”].ConnectionString
);
conn.Open();
trans = conn.BeginTransaction();
cmd = new SqlCommand();
cmd.Connection = conn;
cmd.Transaction = trans;
// set the order details
cmd.CommandText = “INSERT INTO Orders(MemberName, OrderDate, Name, Address,
County, PostCode, Country, SubTotal, Discount, Total) “ +

“VALUES (@MemberName, @OrderDate, @Name, @Address, @County, @PostCode, @Country,
@SubTotal, @Discount, @Total)”;
cmd.Parameters.Add(“@MemberName”, SqlDbType.VarChar, 50);
cmd.Parameters.Add(“@OrderDate”, SqlDbType.DateTime);
517
E-Commerce
16_042583 ch13.qxd 4/4/06 2:50 PM Page 517
cmd.Parameters.Add(“@Name”, SqlDbType.VarChar, 50);
cmd.Parameters.Add(“@Address”, SqlDbType.VarChar, 255);
cmd.Parameters.Add(“@County”, SqlDbType.VarChar, 50);
cmd.Parameters.Add(“@PostCode”, SqlDbType.VarChar, 15);
cmd.Parameters.Add(“@Country”, SqlDbType.VarChar, 50);
cmd.Parameters.Add(“@SubTotal”, SqlDbType.Money);
cmd.Parameters.Add(“@Discount”, SqlDbType.Money);
cmd.Parameters.Add(“@Total”, SqlDbType.Money);
cmd.Parameters[“@MemberName”].Value = User.Identity.Name;
cmd.Parameters[“@OrderDate”].Value = DateTime.Now;
cmd.Parameters[“@Name”].Value =
((TextBox)Wizard1.FindControl(“txtName”)).Text;
cmd.Parameters[“@Address”].Value =
((TextBox)Wizard1.FindControl(“txtAddress”)).Text;
cmd.Parameters[“@County”].Value =
((TextBox)Wizard1.FindControl(“txtCounty”)).Text;
cmd.Parameters[“@PostCode”].Value =
((TextBox)Wizard1.FindControl(“txtPostCode”)).Text;
cmd.Parameters[“@Country”].Value =
((TextBox)Wizard1.FindControl(“txtCountry”)).Text;
cmd.Parameters[“@SubTotal”].Value = Profile.Cart.SubTotal;
cmd.Parameters[“@Discount”].Value = Profile.Cart.MemberDiscount;
cmd.Parameters[“@Total”].Value = Profile.Cart.Total;

int OrderID = Convert.ToInt32(cmd.ExecuteScalar());
After you’ve written the data into the Orders table, you need to create an order in which you write into
the OrderLines table. This contains an order ID, the product ID, the quantity, and the price. After this,
you commit the transaction:
// change the query and parameters for the order lines
cmd.CommandText = “INSERT INTO OrderLines(OrderID, ProductID, Quantity,
Price) “ +
“VALUES (@OrderID, @ProductID, @Quantity, @Price)”;
cmd.Parameters.Clear();
cmd.Parameters.Add(“@OrderID”, SqlDbType.Int);
cmd.Parameters.Add(“@ProductID”, SqlDbType.Int);
cmd.Parameters.Add(“@Quantity”, SqlDbType.Int);
cmd.Parameters.Add(“@Price”, SqlDbType.Money);
cmd.Parameters[“@OrderID”].Value = OrderID;
foreach (CartItem item in Profile.Cart.Items)
{
cmd.Parameters[“@ProductID”].Value = item.ProductID;
cmd.Parameters[“@Quantity”].Value = item.Quantity;
cmd.Parameters[“@Price”].Value = item.Price;
cmd.ExecuteNonQuery();
}
// commit the transaction
518
Chapter 13
16_042583 ch13.qxd 4/4/06 2:50 PM Page 518
trans.Commit();
}
The next part is the exception handler. If there is any kind of database error, you have to roll back the
exception and write it to the error log. Exception handling is explained in more detail in Chapter 15. This
code is specifically tailored to handle SQL errors and will cause an error in the application:

catch (SqlException SqlEx)
{
// some form of error - rollback the transaction
// and rethrow the exception
if (trans != null)
trans.Rollback();
CreateOrderErrorLabel.Visible = true;
// Log the exception
// Tools.log(“An error occurred while creating the order”, SqlEx)
throw new Exception(“An error occurred while creating the order”, SqlEx);
}
Last, you close the connection and you clear the cart profile of the items if the transaction has been
successful:
finally
{
if (conn != null)
conn.Close();
}
// we will only reach here if the order has been created sucessfully
// so clear the cart
Profile.Cart.Items.Clear();
There is also a failsafe step that checks to see if you have jumped in the Wizard. Normally this will be
under the direction of the program — in other words, you check to see if the user has logged in and jump
them forward one step. However, it’s possible that an unscrupulous user might have jumped into this
procedure halfway through, or that the procedure has accidentally “forgotten” the login details (nor-
mally caused by the session variable being lost — this might happen if ASP.NET restarts halfway
through the Wizard). In this case, you check to see if the user has logged in, and if you have no record of
them (in other words, they aren’t authenticated), then you jump them back to the login dialog:
protected void Wizard1_ActiveStepChanged( object sender, System.EventArgs e)
{

if (!User.Identity.IsAuthenticated)
Wizard1.ActiveStepIndex = 0;
}
}
The checkout process is a lengthy one, but it is the most essential part. If you get this wrong, you will
never get any orders!
519
E-Commerce
16_042583 ch13.qxd 4/4/06 2:50 PM Page 519
Secure Transactions
You might be forgiven for thinking that you’re missing one vital part of the process. How do you ensure
that your transaction isn’t compromised and that credit card details aren’t left wide open to the ether? Of
course the nature of the HTTP protocol is exactly that, you send text across to the web server and you
receive text back again. There’s nothing to stop anybody out there from listening and recording your
details.
Fortunately, there is a two-pronged attack with which you can ensure transactions are secure and that
the credit card details and other confidential information are not compromised:
❑ Encryption: You must encode, or scramble, the information that is sent to the web server and
received back from the web server. The web server has a public key, and users will have a pri-
vate key that enables them to decode the information. Only having the public key and the pri-
vate key together will allow you to encrypt the message. The web server will have a public key
and its own private key at the other end. To encrypt messages, you use a secure communica-
tions protocol. Either Secure Sockets Layer (SSL) or Secure HTTP (S-HTTP) would provide this
functionality. You can specify encryption methods and whether to use SSL on a connection in
the
Web.config file.
❑ Certificates: To guarantee that the site you are dealing with at the other end is reputable, it can
be certified by a Certificate Authority. Verisign (
www.verisign.com) is perhaps the most com-
mon Certificate Authority. The authority is paid a yearly fee by the e-commerce vendor and in

return, the authority performs checks on the business to prove that it is legitimate. These checks
are then recorded in the form of a certificate. You can browse particular sites’ certificates during
the checkout process. To make your site trustworthy, you should go about obtaining a certificate
from a Certificate Authority.
You’re not going to implement any of these features on the Wrox United site for reasons of practicality,
but if you want to implement an e-commerce solution, you must make use of encryption and certificates.
What Else Can You Do?
Having gone this far in the chapter, you probably deserve a cup of tea and a sit down. However, while
you’re enjoying your well-earned brew, this would be a good time to get your thinking cap on and have
a think about what else you could add to the shop. An e-commerce site is like a community and can con-
tinually evolve as your site evolves — you shouldn’t think that you’ve done everything possible with it.
The following list outlines some things to consider as your e-commerce site evolves:
❑ Improving the product catalog: You can show how many products are currently in stock, how
long they will take to deliver, and the release date of a product. You can add customer reviews
or testimonies to how wonderful or appalling the product is, and add a cross-linking reference
that mentions which other items a customer bought when they purchased a particular item.
❑ Improving membership tracking and personalization: Add a member discount, record credit
card details, and mail out special offers related to past purchases (so if a customer bought a
replica kit in 2004, when the 2005 version comes out, it might be good to e-mail them).
520
Chapter 13
16_042583 ch13.qxd 4/4/06 2:50 PM Page 520
❑ Improving the shopping cart: Make the shopping cart visible at all times.
❑ Improving the checkout process: Make the checkout process simpler so that it can be achieved
in as few clicks as possible.
Summary
Hopefully this rather intense chapter hasn’t scared you away. E-commerce is a lengthy and complex
process—however, the new features of ASP.NET 2.0 make it approachable and possible to program for
the first time without weeks of heartache and stress. Although e-commerce isn’t something to be taken
lightly, it is something that can be added to an application with a little bit of thought and careful work.

This chapter started by describing the e-commerce pipeline, which is outlined as follows:
❑ Select an item from the catalog
❑ Put the item in the shopping cart
❑ Check out with the item or items
❑ Supply address details
❑ Pay for the item
❑ Confirm the transaction
You started by creating a design for your product catalog and then you built a Catalog page. From the
catalog you allowed the user to hone in on particular items, and you did this via a Product Item page.
Neither of these items specifically required the use of the shopping cart, so you held off creating one.
They just queried the database and displayed the relevant details. However, without these pages, you
would not be able to shop effectively.
With a catalog working, you could add the cart. The cart consisted of two objects: the
CartItem object
(one for each item selected by the user from the catalog and the
ShoppingCart object, (which contained
a bundle of
CartItem objects). To enable the shopping cart, you added Insert, Update, and Delete
methods, which allowed you to put things into, change the amount of, and remove items from your
shopping cart. Last, you connected the shopping cart to your catalog by creating an Add an Item button
to your Product Item page.
Next you created a checkout process that handled the login, the confirmation of the delivery address, and
the credit card details, and finished the procedure. Although you couldn’t handle the card details with the
application, you learned about the various options offered. Finally you learned how to make the transac-
tions secure and some ways to extend and improve the e-commerce procedure in Wrox United.
Exercises
An e-commerce site could potentially offer many extra features. In these exercises, you’re going to focus
on just one. Some fan sites offer the capability for their members to purchase items at a reduced price, a
membership discount. How would you go about implementing it? Each question is about a stage of the
implementation and together they will give you this functionality.

521
E-Commerce
16_042583 ch13.qxd 4/4/06 2:50 PM Page 521
1. The member discount is something that is applied to the shopping cart as you add items to the
cart. What do you need to add to the
ShoppingCart object to make sure it can store a discount
of 10% for fan club members? You can assume that you can detect a fan club member with the
property
HttpContext.Current.User.IsInRole(“FanClubMember”).
Hint: You will need to create a subtotal as well.
2. How can you display the member discount details on the Shopping Cart page so that only a fan
club member will see them?
522
Chapter 13
16_042583 ch13.qxd 4/4/06 2:50 PM Page 522
14
Performance
Throughout the book you’ve learned a range of techniques to help you build web sites, and really
concentrated on the possibilities regarding what controls and code you can use to produce great
functionality for your site. One thing you haven’t looked at, though, is how to make your site per-
form as well as it possibly can. After all, it doesn’t matter how great your site looks—if it per-
forms badly, it fails. Internet users are an impatient lot and expect sites to be fast.
Although performance should be addressed throughout the design and building of a site, this isn’t
always practical, especially for the beginner. So this chapter revisits some of the earlier pages to
see how they can be improved, and discusses the techniques that can be used to create the best
performing sites.
In particular, this chapter looks at the following:
❑ How to design and test for performance
❑ The techniques to use in ASP.NET pages and data handling to ensure the fastest
possible pages

❑ What caching is and how it can be used
Let’s start with the things you can do to either existing code or new code that you write.
Simple Techniques
Several simple things are easy to do and provide good performance, as well as being good design
techniques and aiding future development work and maintenance. After all, writing applications
isn’t just about getting the best from them now, but also getting the best from them in the future.
17_042583 ch14.qxd 4/4/06 2:51 PM Page 523
Being able to fix and update web applications easily is just as much a part of development as producing
the application in the first place. This section on simple techniques looks at the following:
❑ How to dispose of resources after they are no longer required
❑ How to ensure connecting to a database is done in the best possible way
❑ How using stored procedures can improve data access performance
❑ How to use generics to improve performance of collections
❑ How session state can be minimized to allow less processing to be done by ASP.NET
❑ How view state can be tuned to reduce the amount of data sent to and from the web server
This section starts with object disposal.
Object Disposal
In performance terms, certain things are expensive; that is, they can lead to performance problems. The
reason for this is that objects need resources to manage them, resources such as CPU and memory. The
fewer of these resources used, the less work the server is doing, which in turn leads to more pages for
more users. If the use of these resources can be minimized, the site will perform better, and part of that
minimization is to make sure you only use the resource for as little time as possible.
In general, objects that use expensive resources like the file system, graphics, or databases should be dis-
posed of as soon as they are no longer needed. The only exception is database connections in ASP.NET
applications, as discussed in the “Database Connections” section later in the chapter. Disposal of objects
frees up resources, such as files and memory, allowing the web server to perform more efficiently. By
default, resources are disposed of automatically by the Garbage Collector, but it is possible to improve
performance by taking control of object disposal yourself, and you can do this in two ways. You can
either use a standard pattern for creating the resource, using it, and then disposing of it, or you can use
the

using statement. This section looks at both methods, because you’ll see both in documentation.
In the Wrox United site, one area where this is used is for images. Certain users have permission to
upload images. Administrators can upload new images for the shop, the owner and coach can upload
player pictures, and reporters and fan club members can upload match pictures. Part of this upload pro-
cess involves creating a thumbnail image, which uses the
Image object, something that should be dis-
posed of as soon as it’s no longer required. Disposal is necessary for two reasons. The first is because the
image is a file-based resource, and the file may be required by other pages, so making sure you don’t
have any connection to it means it’s available for others — the sooner you remove it, the sooner someone
else can access it. The second reason for disposal is because images take memory, so disposing of the
image means the memory is freed and available for other processes.
The routine for creating thumbnails is in the
ImageHandling.cs file, in the App_Code directory, and is a
simple class with a single shared method (actually, there are two methods, but one is only required as
part of the image handling and isn’t actually used).
The general structure of this code is to create a new
Image object from an existing image stored on disc.
Then a new
Image object is created using the GetThumbnailImage method, which specifies the new
width and height. It’s pretty simple, but it involves two
Image objects, so it requires two lots of disposal.
The next section looks at how this routine works using the two ways of resource disposal.
524
Chapter 14
17_042583 ch14.qxd 4/4/06 2:51 PM Page 524
Disposal with try/catch
To dispose using the try/catch methods you follow this simple pattern:
try
{
// create resource

}
catch()
{
// handle exception
}
finally
{
// dispose of resource
}
Your image code using this pattern is as follows:
public static void GenerateThumbnail(string SourceImagePath,
string TargetImagePath)
{
short newHeight;
short newWidth;
Image sourceImage = null;
Image targetImage = null;
try
{
sourceImage = Image.FromFile(SourceImagePath);
newHeight = short(sourceImage.Height * 0.25);
newWidth = short(sourceImage.Width * 0.25);
Image.GetThumbnailImageAbort abort1 = new
Image.GetThumbnailImageAbort(ImageHandling.ThumbnailCallback);
try
{
targetImage = sourceImage.GetThumbnailImage(newWidth, newHeight,
cb, IntPtr.Zero);
targetImage.Save(TargetImagePath, Imaging.ImageFormat.Gif);
}

catch (Exception ex)
{
// log exception
}
finally
{
if (targetImage != null)
targetImage.Dispose();
}
}
catch (Exception ex)
{
525
Performance
17_042583 ch14.qxd 4/4/06 2:51 PM Page 525
// log exception
}
finally
{
if (sourceImage != null)
sourceImage.Dispose();
}
}
You can immediately see this is a little hard to read. There are two try/catch blocks, one within the
other. The outer one is for
sourceImage — the original image. This is loaded from a file using Image.
FromFile
, and then the new width and height are calculated using the Height and Width properties of
the source image—the new height and width are 25% of the original. After the new size is defined, a
callback variable (

cb) is created in case there is an error during the creation of the thumbnail — the
GetThumbnailImage method will call the callback if an error occurs. We’re not actually handling any
errors because we decided that it isn’t critical if thumbnails aren’t generated. If you have an application
where it is critical to know about these errors, you could log the error in the callback routine.
The inner
try/catch block then surrounds targetImage, which is generated from sourceImage using
GetThumbnailImage with the new width and height. This creates a new image based on the new size.
After it is generated,
targetImage is then saved to a file as a GIF image.
The
finally blocks of each try/catch check that the Image object exists before disposing of the object,
by calling the
Dispose method.
Disposal with Using
The using statement makes the preceding code much simpler, as shown here:
public static void GenerateThumbnail(string SourceImagePath,
string TargetImagePath)
{
using (Image sourceImage = Image.FromFile(SourceImagePath))
{
short newHeight = (short) Math.Round((double)(sourceImage.Height * 0.25));
short newWidth = (short) Math.Round((double)(sourceImage.Width * 0.25));
Image.GetThumbnailImageAbort abort1 = new
Image.GetThumbnailImageAbort(ImageHandling.ThumbnailCallback);
using (Image targetImage = sourceiMage.GetThumbnailImage(newWidth, newHeight,
abort1, IntPtr.Zero))
{
targetImage.Save(TargetImagePath, ImageFormat.Gif);
}
}

}
You can immediately see how much easier this is to read, as well as how much more sense it makes. The
using statement created the resource, which is then automatically disposed of when the trailing brace
(
}) of the code block is reached. The syntax for the using statement is as follows:
using (resource)
{
// code that uses the resource
}
526
Chapter 14
17_042583 ch14.qxd 4/4/06 2:51 PM Page 526
What happens is that when the trailing brace (}) is reached, the resource is disposed of immediately—
there’s no waiting for the Garbage Collector to dispose of it. In the code you have the following:
using (Image sourceImage = Image.FromFile(SourceImagePath))
This is similar to declaring variables, in that it declares a variable, sourceImage, and assigns it a value.
Unlike variable declaration though, the variable is disposed of as soon as the trailing brace is reached. So
as soon as the trailing brace is done, the
sourceImage variable is gone. In fact, because its scope is
defined as part of the
using statement, sourceImage isn’t accessible outside of the using code block.
You only need to explicitly dispose of resources if they are deemed expensive resources, such as files
(images, text files, and so forth) or graphics resources that use lots of memory. Normal variables and
objects, even those you might think take a lot of memory such as a
DataSet, should not be disposed of
explicitly.
Database Connections
Fetching data from a database can be done automatically by way of the data source controls, or manu-
ally though the objects in the
System.Data namespaces. One of these objects applies to connecting to

a database — for SQL Server or SQL Server Express, that object is the
SqlConnection. In general,
databases are limited to the number of connections they can have. Each connection takes resources and
may stop another application from connecting, so these should be used as sparingly as possible, and
only kept open for as short a time as possible. The general rule is to open the connection as late as possi-
ble, fetch the data, and close the connection as soon as possible, disposing of the connection once done.
The
Using statement is excellent for this:
using (SqlConnection conn = new SqlConnection(“. . .”))
{
// code that uses the connection
}
Here the using statement keeps track of the connection object, conn, which is closed and disposed of
when the trailing brace is reached.
In general, if you are manually creating connections, you should dispose of them as soon as you are fin-
ished. If you are using the data source controls, object disposal is handled automatically for you.
Stored Procedures
In the examples you’ve seen so far in this book, the data has been fetched either using a data source con-
trol such as the
SqlDataSource or using code, where a SqlCommand or SqlDataAdapter were used. In
all cases, the command that fetched the data, the SQL, was entered directly, such as:
SELECT [ProductID], [Name], [Description], [Price], [PictureURL] FROM [Products]
Now there’s nothing intrinsically wrong with this — it’s a standard SQL statement that works fine.
However, it’s not the fastest way to fetch data, because the database has to work out exactly how it is
going to fetch the data when the command is executed. This involves creating what’s known as an execu-
tion plan — a plan of how the SQL is going to be executed, how tables are to be joined if there are multiple
527
Performance
17_042583 ch14.qxd 4/4/06 2:51 PM Page 527
tables, which indexes to use, and so on, and it does this every time. After the plan has been created, the

stored procedure is also compiled and the compiled copy is the one that is executed.
A much better solution would be to work out the execution plan and store it for subsequent use. So
when a query is executed, the plan is ready and doesn’t need recalculating. You can do this by way of
stored procedures, which are a way of wrapping up SQL into an easily manageable form. It’s a bit like a
procedure or a function in that the SQL to be executed can be wrapped in a stored procedure and the
stored procedure name used to execute it. Consider this
SqlDataSource:
<asp:SqlDataSource id=”SqlDataSource1” runat=”server”
ConnectionString=”<%$ConnectionStrings:WroxUnited%>”
SelectedCommand=”SELECT [ProductID], [Name], [Description], [Price], [PictureURL]
FROM [Products]”
</asp:SqlDataSource>
Using a stored procedure, the code would look like this:
<asp:SqlDataSource id=”SqlDataSource1” runat=”server”
ConnectionString=”<%$ConnectionStrings:WroxUnited%>”
SelectedCommand=”usp_Products”
SelectCommandType=”StoredProcedure”
</asp:SqlDataSource>
From the code perspective, this is already better for two reasons: it makes the code neater and easier to
read; and it abstracts the SQL into a central place, the database. Having the SQL in the database is good
because it’s the natural place for it — it’s code that deals directly with the tables and columns. It also
means your code is easier to manage because you know that all of the SQL is in one place. Asensible nam-
ing scheme means stored procedures are easy to locate —we’ve used the naming scheme of
usp_ followed
by the name of the table. The prefix
usp_ is a common one and denotes User Stored Procedure—many of
system stored procedures that SQL Server provide are prefixed by
sp_, so adding the u makes it obvious
which procedures are ours and which are the server’s. The other addition when using stored procedures
is the

SelectCommandType attribute, which tells the SqlDataSource that the command being issued is a
stored procedure, rather than a textual SQL statement.
The syntax for creating a stored procedure is as follows:
CREATE PROCEDURE ProcedureName
AS
SqlStatement
ProcedureName is the name of the procedure (usp_Products) and SqlStatement is the SQL statement
that will be run when the stored procedure is called. So, how do you actually go about creating stored
procedures, and what exactly do they contain? You give this a go in the following Try It Out.
Try It Out Creating and Using Stored Procedures
1.
In the WroxUnited VWD application for this chapter (Chapters/Begin/Chapter14/
WroxUnited
), load the Shop.aspx file and change the SelectCommand to usp_Products.
528
Chapter 14
17_042583 ch14.qxd 4/4/06 2:51 PM Page 528
2. Add the following new attribute to the SqlDataSource:
SelectCommandType=”StoredProcedure”
3. Save the file.
4. Select the Database Explorer tab and expand WroxUnited.mdf, which will appear under Data
Connections (see Figure 14-1).
Figure 14-1
5. Right-click Stored Procedures and select Add New Stored Procedure from the menu.
6. Modify the procedure so that it looks like this:
CREATE PROCEDURE dbo.usp_Products
AS
SELECT ProductID, [Name], Description, Price, PictureURL
FROM Products
ORDER BY [Name]

7. Save and close the procedure.
8. Right-click WroxUnited.mdf and select Close Connection from the menu. This ensures that
when you run the application, you don’t receive an error telling you that the data file couldn’t
be opened because it is in use by another process.
9. On the Wrox United web site, navigate to the Shop page to confirm that the page displays as
you expect it to. You’ll see no change from the user perspective, because stored procedures are
a programming concept, and apart from speed, have no impact on what the user sees.
How It Works
The first thing to look at is the stored procedure itself:
CREATE PROCEDURE dbo.usp_Products
AS
529
Performance
17_042583 ch14.qxd 4/4/06 2:51 PM Page 529
SELECT ProductID, [Name], Description, Price, PictureURL
FROM Products
ORDER BY [Name]
So you can see that the name of your stored procedure is usp_Products. The dbo part indicates the
owner of the procedure, and in this case the owner is
dbo — a synonym for the database owner.
Anything after the
AS statement is the actual procedure itself, and yours simply consists of a SELECT
statement. In fact, this is the same SELECT statement that was generated by VWD when the
SqlDataSource was created, with the addition of an ORDER BY clause to order the results by the product
name. This is a good way to combine the ease of data source controls with stored procedures—let the
designer create the control, and then copy the SQL statement into a stored procedure and use the proce-
dure name in the data source control. The column
Name is wrapped within square brackets because Name
is a keyword within SQL. The stored procedure works without the brackets, but using them tells SQL
Server that this use of

Name is a column, and saves SQL Server having to work that out on its own.
The procedure name starts with
usp_ for usability and historic reasons. You don’t want your stored pro-
cedure to have the same name as the table, but keeping the table name is useful, because it helps to iden-
tify which stored procedures are related to which tables. In SQL Server, the system-stored procedures
start with
sp_ (an acronym for stored procedure), so using that as a prefix would confuse your stored
procedures with the system ones. So
usp_ (an acronym for user stored procedure) is generally used
instead. This isn’t a requirement, but you’ll find that it is a common practice.
SQL is both case and context insensitive, meaning no line continuation characters are required.
In the ASP.NET page, the SQL statement has been replaced by the name of the stored procedure. By
default, the
SqlDataSource control expects a SQL statement, so when using a stored procedure, you
have to add the
SelectCommandType attribute, setting its value to StoredProcedure. This tells
ASP.NET that the command isn’t a SQL statement to be executed, but that the named stored procedure
should be used instead.
Modifying Stored Procedures
To modify a stored procedure, you open it (double-click or right-click and select Open from the menu)
from within the Database Explorer, where you will be presented with the following:
ALTER PROCEDURE dbo.usp_Products
AS
SELECT ProductID, Name, Description, Price, PictureURL
FROM Products
ORDER BY Name
Notice that CREATE PROCEDURE has been replaced with ALTER PROCEDURE. The CREATE statement is
only used when creating stored procedures, and subsequently
ALTER is used. After you’ve completed
your changes, perhaps changing the columns being selected, you can just save the file, and the proce-

dure in the database will be updated. In fact, just the matter of saving a new query changes
CREATE to
ALTER.
Stored Procedure Names
In addition to using the usp_ prefix, you may want to use a consistent naming scheme for your stored
procedures. For example, a procedure that returns all rows from a table could simply have the prefix
530
Chapter 14
17_042583 ch14.qxd 4/4/06 2:51 PM Page 530
plus the table name, or perhaps be GetTableName, usp_Products or usp_GetProducts. For a
procedure that returns on a single row, the single form would work well:
usp_GetProduct, or
usp_GetProductByID. Stored procedures that modify data should include the modification type:
usp_InsertProduct or usp_UpdateProduct. Alternatively, place the table name before the action:
usp_ProductInsert, usp_ProductUpdate, or usp_ProductGet. This latter scheme means that all
procedures related to the same table will show together in the procedure list, while the former scheme
means that procedures are grouped by their action. There’s no single way of doing this, and it’s really
just a matter of preference, but pick one scheme and stick to it, so that you can find procedures easily.
Using the Query Builder
If you don’t want to use the copy-and-paste method for creating SQL statements, you can use the Query
Builder. You can access this when creating or modifying a stored procedure by selecting the Insert SQL
item from the right mouse menu—just right-click anywhere in the stored procedure editor window to
get the menu. You first see a window where you select the table (see Figure 14-2).
Figure 14-2
You can double-click a table to add it, or select the table and use the Add button. When you have
selected your table and closed the window, you see the Query Builder, shown in Figure 14-3.
The Query Builder is an excellent way of learning SQL, because it presents you with an easy-to-under-
stand graphical way of designing queries, and shows the actual SQL statement. When the Query Builder
is closed, the SQL is inserted into the stored procedure. It doesn’t update the existing SQL but inserts the
new SQL instead.

If you want to use the Query Builder to edit the existing SQL in a stored procedure, you need to place
the cursor within the SQL block when editing the procedure—this block is outlined in blue—and select
the Design SQL Block from the right mouse menu. When you select this option, the Query Builder comes
up right away with the existing SQL in place.
531
Performance
17_042583 ch14.qxd 4/4/06 2:51 PM Page 531

×