CARVIEW |
Product Overview
Hooray! A new and improved edition of this book is now available!
The 1st edition of Build Your Own ASP.NET Website Using C# & VB.NET was a very fine book but we replaced it with a new and updated edition in October 2006.
However, if you do happen to own a copy of 'Build Your Own ASP.NET Website Using C# & VB.NET' you may still be interested in:
- Downloading the 1st edition code archive
- Viewing the list of known corrections and typos
Build Your Own ASP.NET Website Using C# & VB.NET
Corrections & Typos
Find a mistake not listed here? Contact us to let us know!
Got the new 2nd edition? See corrections to the 2nd edition here.
Confirmed typos in the March 2006 reprint:
Some of the errors listed here may also apply to the previous edition (see below), although page numbers may differ slightly.
- p.41 code listing 1
text="Click Me"
should bevalue="Click Me"
- p.192 Figure 6.13
The following image is missing: - p. 235 Table 7.28
Table heading should read "Returning Whole Numbers Using the CEILING Function"
Confirmed typos in the May 2005 reprint:
Some of the errors listed here may also apply to the previous edition (see below), although page numbers may differ slightly.
- p.222 paragraph 4
The string concatenation operator for MSDE/SQL Server is +, not &. Consequently, the SELECT query shown here must be adjusted if you are using one of those servers.
The MSDE/SQL Server version of the query is:SELECT EmployeeID, FirstName + LastName AS Name
FROM Employees - p.223 paragraph 2
The string concatenation operator for MSDE/SQL Server is +, not &. Consequently, the SELECT query shown here must be adjusted if you are using one of those servers.
The MSDE/SQL Server version of the query is:SELECT EmployeeID, FirstName + ' ' + LastName AS Name FROM Employees
- p.223 last paragraph
The & operator should be listed as &/+, since the MSDE/SQL Server equivalent of the Access & operator is +. - p.226 code listing
Only MS Access requires you to indicate date values with hashes (#). For MSDE/SQL Server, this syntax will actually cause an error. On those servers, simply give date values as quoted strings.
The MSDE/SQL Server version of the query is:SELECT *
FROM CompanyEvents
WHERE Date='7/4/2003' - p.227 paragraph 1-2
The MSDE/SQL Server equivalent of the Access DATE() function is GETDATE().
The MSDE/SQL Server version of the query is:SELECT * FROM CompanyEvents WHERE DATE = GETDATE()
- p.227 code listing 2
This query uses Access-specific syntax. The equivalent query for MSDE/SQL Server must use the GETDATE() function instead of DATE(), and must not have quotes around the first argument of DATEADD():SELECT * FROM CompanyEvents WHERE Date BETWEEN GETDATE() AND DATEADD(m, 1, GETDATE())
- p.227 bullet 1
The first argument for DATEADD() must not have quotes on MSDE/SQL Server. The values for those servers are therefore m for month, w for week, and d for day. - p.227 bullet 3
The MSDE/SQL Server equivalent of the Access DATE() function is GETDATE(). - p.230 code listing 1
For this query to work on MSDE/SQL Server, the first argument of DATEPART() must not be quoted, and the Access string concatenation operator (&) must be replaced with its MSDE/SQL Server equivalent (+):SELECT DATEPART(m, Date) + '/' + DATEPART(yyyy, Date) AS Month, COUNT(*) AS NumberOfEvents FROM CompanyEvents GROUP BY DATEPART(m, Date) + '/' + DATEPART(yyyy, Date)
- p.230 code listing 2
The HAVING clause incorrectly refers to NumberOfEvents, which is a column name alias and cannot be used in this clause. The corrected query for MS Acces is:SELECT DATEPART('m', Date) & '/' & DATEPART('yyyy', Date) AS Month, COUNT(*) AS NumberOfEvents FROM CompanyEvents GROUP BY DATEPART('m', Date) & '/' & DATEPART('yyyy', Date) HAVING COUNT(*) > 2
Additionally, for this query to work on MSDE/SQL Server, the first argument of DATEPART() must not be quoted, and the Access string concatenation operator (&) must be replaced with its MSDE/SQL Server equivalent (+):SELECT DATEPART(m, Date) + '/' + DATEPART(yyyy, Date) AS Month, COUNT(*) AS NumberOfEvents FROM CompanyEvents GROUP BY DATEPART(m, Date) + '/' + DATEPART(yyyy, Date) HAVING COUNT(*) > 2
- p.278 code listing 2
On all three lines, Parametes should be Parameters. - p.274-275 code listings
TheSubmitHelpDesk
event handler should check if the page has successfully validated before writing the submitted values to the database. This is done by checkingPage.IsValid
. This is the VB.NET version:Sub SubmitHelpDesk(s As Object, e As EventArgs) If Page.IsValid Then objCmd = New OleDbCommand( _ ... objConn.Close() Response.Redirect("helpdesk.aspx") End If End Sub
This is the C# version:void SubmitHelpDesk(Object s, EventArgs e) { if (Page.IsValid) { objCmd = new OleDbCommand( ... objConn.Close(); Response.Redirect("helpdesk.aspx"); } }

First Edition: February 2005
Confirmed typos in the February 2005 reprint:
Some of the errors listed here may also apply to the previous edition (see below), although page numbers may differ slightly.
The credits page of this edition identifies the printing date as February 2005.
- p.22 paragraph 2
Web Data Administrator is now available from this URL: https://msdn.microsoft.com/asp.net/downloads/tools/ - p.24 footnote
'to need to' should be 'you need to'. - p.55 4th code listing
The syntax for the VB.NET multiple declaration is wrong. The code should be:Dim strCarType As String, strCarColor = "blue", strCarModel As String
- p.66 1st paragraph
'Switch Case (VB.NET)' should be 'Select Case (VB.NET)' - p.96-97 code listings
The code for processing the simple form contains a couple of syntax errors. The changes are indicated in bold here:<script runat="server" language="VB"> Sub Click(s As Object, e As EventArgs) Dim i As Integer Response.Write("Your name is: " & txtName.Value & "<br />") Response.Write("Your email is: " & txtEmail.Value & "<br />") Response.Write("You like to work with:<br />") For i = 0 To servermodel.Items.Count - 1 If servermodel.Items(i).Selected Then Response.Write(" - " & servermodel.Items(i).Text & "<br />") End If Next i Response.Write("You like .NET: " & likedotnet.Value) End Sub </script>
<script runat="server" language="C#"> void Click(Object s, EventArgs e) { Response.Write("Your name is: " + txtName.Value + "<br />"); Response.Write("Your email is: " + txtEmail.Value + "<br />"); Response.Write("You like to work with:<br />"); for (int i = 0; i <= servermodel.Items.Count - 1; i++) { if (servermodel.Items[i].Selected) { Response.Write(" - " + servermodel.Items[i].Text + "<br />"); } } Response.Write("You like .NET: " + likedotnet.Value); } </script>
- p.294 last code listing
This listing should be labelled VB.NET. - p.296 code listing
When declaring the SQL queries to be stored in strChecking and strSavings, the VB.NET string concatenation syntax (&) is used instead of the C# syntax (+):strChecking = "UPDATE Checking SET Balance = Balance - 500 " + "WHERE Customer='zak'"; strSavings = "UPDATE Savings SET Balance = Balance + 500 " + "WHERE Customer='zak'";
- p.740 index
'Switch and Switch Case statements' should be 'Switch and Select Case statements'.
Confirmed typos in the August 2004 reprint:
Some of the errors listed here may also apply to the previous edition (see below), although page numbers may differ slightly.
The credits page of this edition identifies the printing date as August 2004.
- p.67 last paragraph
"instead of 10" should be "instead of 0". - p.87
The tag for HtmlInputCheckBox is <input type="checkbox" runat="server">. - p.96-97 code listings
The code for processing the simple form does not correctly handle multiple selections in the servermodel menu. Here is a corrected version of the script for both languages:<script runat="server" language="VB"> Sub Click(s As Object, e As EventArgs) Response.Write("Your name is: " & txtName.Value & "<br />") Response.Write("Your email is: " & txtEmail.value & "<br />") Response.Write("You like to work with:<br />") For i = 0 To servermodel.Items.Count - 1 If servermodel.Items(i).Selected Then Response.Write(" - " & servermodel.Items(i).Text & "<br />") End If Next i Response.Write("You like .NET: " & likedotnet.Value) End Sub </script>
<script runat="server" language="C#"> void Click(Object s, EventArgs e) { Response.Write("Your name is: " + txtName.Value + "<br />"); Response.Write("Your email is: " + txtEmail.Value + "<br />"); Response.Write("You like to work with:<br />"); for (int i = 0; i <= servermodel.Items.Count - 1; i++) { if (servermodel.Items(i).Selected) { Response.Write(" - " + servermodel.Items(i).Text + "<br />"); } } Response.Write("You like .NET: " + likedotnet.Value); } </script>
- p.104 last paragraph
"he main event" should be "the main event". - p.147 code listing 1
Type="Integer" attribute missing from the <asp:RangeValidator> tag. Note that it appears in the following code listing. - p.157 code listing 4
The regular expression for a Web URL should contain non-whitespace characters (\S), not whitespace characters (\s). The correct code is:^https?://\S+\.\S+$
- p.176 table 6.5
The HelpDesk table is missing a column:Column Name Access Data Type SQL Data Type Identity Allow Null StationNumber Text Nvarchar No Yes - p.177 table 6.7
The CompanyEventID column should be named EventID. - p.185 table 6.21
The EmployeeStoreOrders row should be removed from this table. - p.210 table 7.4
The value $5.99 appears twice in the table, when it should only appear once. - p.232 table 7.25
"Quantities" in the table caption should be "Costs". - p.272-273
The Page_Load event handler in helpdesk.aspx should not fill the drop-down lists from the database on postback (see p.112). Later on, when these list are used in form submissions, refilling them on postback would cause the selected values to be lost before they could be used by the form submission event handler. In VB.NET, the Page_Load event handler should take this form:<script runat="server" language="VB"> Dim objConn As New OleDbConnection( _ "Provider=Microsoft.Jet.OleDb.4.0;" & _ "Data Source=C:\InetPub\wwwroot\Dorknozzle\Database\" & _ "Dorknozzle.mdb") Dim objCmd As OleDbCommand Dim objRdr As OleDbDataReader Sub Page_Load() If Not IsPostBack Then objConn.Open() objCmd = new OleDbCommand("SELECT * FROM HelpDeskCategories", objConn) ... objConn.Close() End If End Sub </script>
In C#, the updated code should take this form:<script runat="server" language="C#"> OleDbConnection objConn = new OleDbConnection( "Provider=Microsoft.Jet.OleDb.4.0;" + "Data Source=C:\InetPub\wwwroot\Dorknozzle\Database\" + "Dorknozzle.mdb"); OleDbCommand objCmd; OleDbDataReader objRdr; void Page_Load() { if (!IsPostBack) { objConn.Open(); objCmd = new OleDbCommand("SELECT * FROM HelpDeskCategories", objConn); ... objConn.Close(); } } </script>
- p.277 both code listings
The code presented for SubmitHelpDesk has one weakness: the resulting page gives no indication that the form submission was successful, or even that it occurred at all. A simple way to improve this is to reload the blank form page following the submission, rather than letting ASP.NET preserve the contents of all the form fields. To do this, simply redirect the browser back to the form page after processing the form submission. In VB.NET, it looks like this:Sub SubmitHelpDesk(s As Object, e As EventArgs) ... objConn.Close() Response.Redirect("helpdesk.aspx") End Sub
In C#:void SubmitHelpDesk(Object s, EventArgs e) { ... objConn.Close(); Response.Redirect("helpdesk.aspx"); }
- p.297 code listing 1
On the final line of this code listing, strChecking should be strSavings.
p.298 code listing 1 - p.425 code listing 3
C# requires that the Application["PageCounter"] variable have a value before it can be incremented. As a result, the code must check if the variable is unset, and give it an initial value when needed:void Page_Load() { if (Application["PageCounter"] == null) { Application["PageCounter"] = 0; } Application["PageCounter"] = (int)Application["PageCounter"] + 1; lblCounter.Text = Convert.ToString(Application["PageCounter"]); }
- p.428 code listing 2
C# requires that the Application["PageCounter"] variable have a value before it can be incremented. As a result, the code must check if the variable is unset, and give it an initial value when needed:void Page_Load() { Application.Lock(); if (Application["PageCounter"] == null) { Application["PageCounter"] = 0; } Application["PageCounter"] = (int)Application["PageCounter"] + 1; lblCounter.Text = Convert.ToString(Application["PageCounter"]); Application.UnLock(); }
- p.447 code listing 2
C# requires that the Session["PageCounter"] variable have a value before it can be incremented. As a result, the code must check if the variable is unset, and give it an initial value when needed:void Page_Load() { if (Session["PageCounter"] == null) { Session["PageCounter"] = 0; } Session["PageCounter"] = (int)Session["PageCounter"] + 1; lblCounter.Text = Convert.ToString(Session["PageCounter"]); }
- p.457 paragraph 1
ItemURL should be ImageURL. - p.458 FindItem() description
addToCart() should be AddToCart(). - p.462 second last paragraph
addToCart() should be AddToCart(). - p.479 code listing 1
This code listing should be labelled C#, as it contains the C# version of this page's code. - p.480-482
The DataKeyField of the dgCart DataGrid is CartID, not ItemID. As a result, we must look for the row of the objCartDT DataTable with a matching CartID field, not the ItemID field as the code in this listing suggests. Here is the corrected VB.NET code, with changes indicated in bold:Sub dgCart_Update(s As Object, e As DataGridCommmandEventArgs) Dim txtQuantity As TextBox Dim intCartID As Integer
And in C#:
intCartID = dgCart.DataKeys(e.Item.ItemIndex) txtQuantity = e.Item.FindControl("txtQuantity") For Each objDR In objCartDT.Rows
If objDR("CartID") = intCartID Then objDR("Quantity") = Int32.Parse(txtQuantity.Text) Exit For End If Next lblTotal.Text = "$" & GetItemTotal() dgCart.EditItemIndex = -1 dgCart.DataSource = objCartDT dgCart.DataBind() End Subvoid dgCart_Update(Object s, DataGridCommandEventArgs e) { TextBox txtQuantity; int intCartID; intCartID = Convert.ToInt32(dgCart.DataKeys[e.Item.ItemIndex]); txtQuantity = (TextBNox)e.Item.FindControl("txtQuantity"); foreach (DataRow objDR in objCartDT.Rows) { if (intCartID == (int)objDR["CartID"]) { objDR["Quantity"] = Convert.ToInt32(txtQuantity.Text); break; } } lblTotal.Text = "$" + GetItemTotal(); dgCart.EditItemIndex = -1; dgCart.DataBind(); }
- p.482 paragraph 2
As per the code changes above, the variable name should be intCartID, not intItemID, and the matching field should be CartID, not ItemID. - p.620-624
Whenever dtmDate.Month and dtmDate.Day are used as array indexes in the appointment scheduler example, you need to subtract one from their value, as array indexes start from zero, wheras the Month and Day properties start from one. Here's the updated version of the example's VB.NET code with changes in bold:<script runat="server" language="VB">
And the C# version:
Dim arrCalendar(12, 31) As String
Dim fmtrBinaryFormatter As New BinaryFormatter()
Dim strmFileStream As FileStream
Dim dtmDate As DateTime Sub Page_Load() If Cache("arrCalendar") Is Nothing Then If File.Exists("C:\schedule.bin") Then strmFileStream = New FileStream("C:\schedule.bin", FileMode.Open) arrCalendar = CType(fmtrBinaryFormatter.Deserialize(strmFileStream), Array) strmFileStream.Close() Cache("arrCalendar") = arrCalendar End If Else arrCalendar = Cache("arrCalendar") End If End Sub Sub Save_Click(s As Object, e As EventArgs) dtmDate = myCalendar.SelectedDate arrCalendar(dtmDate.Month - 1, dtmDate.Day - 1) = myNotes.Text strmFileStream = New FileStream("C:\schedule.bin", FileMode.Create) fmtrBinaryFormatter.Serialize(strmFileStream, arrCalendar) strmFileStream.Close() Cache("arrCalendar") = arrCalendar End Sub Sub Calendar_RenderDay(s As Object, e As DayRenderEventArgs) Dim dtmDate As DateTime = e.Day.Date Dim ctlCell As TableCell = e.Cell If arrCalendar(dtmDate.Month - 1, dtmDate.Day - 1) <> "" Then ctlCell.BackColor = Color.FromName("Red") End If End Sub Sub Get_Appt(s As Object, e As EventArgs) dtmDate = myCalendar.SelectedDate myNotes.Text = arrCalendar(dtmDate.Month - 1, dtmDate.Day - 1) End Sub Sub Delete_Click(s As Object, e As EventArgs)
dtmDate = myCalendar.SelectedDate
arrCalendar(dtmDate.Month - 1, dtmDate.Day - 1) = ""
strmFileStream = New FileStream("C:\schedule.bin", FileMode.Create)
fmtrBinaryFormatter.Serialize(strmFileStream, arrCalendar)
strmFileStream.Close()
Cache("arrCalendar") = arrCalendar
End Sub
</script><script runat="server" language="C#">
String[,] arrCalendar = new String[12, 31];
BinaryFormatter fmtrBinaryFormatter = new BinaryFormatter();
FileStream strmFileStream;
DateTime dtmDate;
void Page_Load() {
if (Cache["arrCalendar"] == null) {
if (File.Exists("C:\\schedule.bin")) {
strmFileStream = new FileStream("C:\\schedule.bin", FileMode.Open);
arrCalendar = (String[,])fmtrBinaryFormatter.Deserialize(strmFileStream);
strmFileStream.Close();
Cache["arrCalendar"] = arrCalendar;
}
} else {
arrCalendar = (String[,])Cache["arrCalendar"];
}
}
void Save_Click(Object s, EventArgs e) {
dtmDate = myCalendar.SelectedDate;
arrCalendar[dtmDate.Month - 1, dtmDate.Day - 1] = myNotes.Text;
strmFileStream = new FileStream("C:\\schedule.bin", FileMode.Create);
fmtrBinaryFormatter.Serialize(strmFileStream, arrCalendar);
strmFileStream.Close();
Cache["arrCalendar"] = arrCalendar;
}
void Calendar_RenderDay(Object s, DayRenderEventArgs e) {
DateTime dtmDate = e.Day.Date;
TableCell ctlCell = e.Cell;
if (arrCalendar[dtmDate.Month - 1, dtmDate.Day - 1] != null) {
ctlCell.BackColor = Color.FromName("Red");
}
}
void Get_Appt (Object s, EventArgs e) {
dtmDate = myCalendar.SelectedDate;
myNotes.Text = arrCalendar[dtmDate.Month - 1, dtmDate.Day - 1];
}
void Delete_Click(Object s, EventArgs e) {
dtmDate = myCalendar.SelectedDate;
arrCalendar[dtmDate.Month - 1, dtmDate.Day - 1] = null;
strmFileStream = new FileStream("C:\\schedule.bin", FileMode.Create);
fmtrBinaryFormatter.Serialize(strmFileStream, arrCalendar);
strmFileStream.Close();
Cache["arrCalendar"] = arrCalendar;
}
</script> - p.636 paragraph 2
OleDebDataAdapter should be OleDbDataAdapter. - p.675 code listing 1
The totalCount integer variable must be converted to a String in C# before it can be displayed:lblTotalResults.Text = Convert.ToString(totalCount);
When the cmdSavings OleDbCommand object is instantiated, strChecking should be strSavings.

First Edition: April 2004
Confirmed typos in the April 2004 First Edition:
The credits page of this edition identifies the printing date as April 2004.
- p.22 footnote
The URL should be https://msdn.microsoft.com/downloads/ - p.44 paragraph 1
"Asyou" should be "As you". - p.45 paragraph 1
"inhouse" should be "in-house". - p.72
Duplicate definitions for OleDbConnection and OleDbCommand should not appear. - p.83
The last four code listings on this page are excerpts from sample.vb, not sample.aspx. - p.137 2nd last paragraph
Two commas after ErrorMessage, where there should only be one. - p.177 tables 6.8, 6.9, and 6.10
The Category, Subject, and Status columns should be of type Text/Nvarchar, not Number/Int. - p.178
If you are using MSDE, rather than the full-fledged MS SQL Server, you may encounter this error message when you attempt to import tje Dorknozzle.sql file supplied in the code archive:
Device activation error. The physical file name 'C:\Program Files\Microsoft SQL Server\MSSQL\data\Dorknozzle.mdf' may be incorrect.
The reason for this error is that MSDE typically uses a different data directory name than SQL Server.
In order to successfully import the Dorknozzle.sql file, you will first need to open the file in a text editor and modify the two paths that appear near the top in the CREATE DATABASE query. The data directory for MSDE is usually C:\Program Files\Microsoft SQL Server\MSSQL$NETSDK\data. - p.210 table 7.4
The query output incorrectly contains the same value more than once. $1.99 appears twice and $5.99 appears three times. Because the query is a SELECT DISTINCT, these values will only actually appear once each. - p.266
</footerTemplate> should be </FooterTemplate> - p.286 code listing 3
@EmployeeID should be the last parameter assigned a value:Sub UpdateEmployee(s As Object, e As EventArgs) objCmd = New OleDbCommand( _ "UPDATE Employees SET Name=@Name, Username=@Username, " & _ "Address=@Address, City=@City, State=@State, Zip=@Zip, " & _ "HomePhone=@HomePhone, Extension=@Extension, " & _ "MobilePhone=@MobilePhone " & _ "WHERE EmployeeID=@EmployeeID", objConn) objCmd.Parameters.Add("@Name", txtName.Text) objCmd.Parameters.Add("@Username", txtUsername.Text) objCmd.Parameters.Add("@Address", txtAddress.Text) objCmd.Parameters.Add("@City", txtCity.Text) objCmd.Parameters.Add("@State", txtState.Text) objCmd.Parameters.Add("@Zip", txtZip.Text) objCmd.Parameters.Add("@HomePhone", txtHomePhone.Text) objCmd.Parameters.Add("@Extension", txtExtension.Text) objCmd.Parameters.Add("@MobilePhone", txtMobilePhone.Text) objCmd.Parameters.Add("@EmployeeID", _ ddlEmployees.SelectedItem.Value) objConn.Open() objCmd.ExecuteNonQuery() objConn.Close() End Sub
- p.287 code listing 1
@EmployeeID should be the last parameter assigned a value:void UpdateEmployee(Object s, EventArgs e) { objCmd = new OleDbCommand( "UPDATE Employees SET Name=@Name, Username=@Username, " + "Address=@Address, City=@City, State=@State, Zip=@Zip, " + "HomePhone=@HomePhone, Extension=@Extension, " + "MobilePhone=@MobilePhone " + "WHERE EmployeeID=@EmployeeID", objConn); objCmd.Parameters.Add("@Name", txtName.Text); objCmd.Parameters.Add("@Username", txtUsername.Text); objCmd.Parameters.Add("@Address", txtAddress.Text); objCmd.Parameters.Add("@City", txtCity.Text); objCmd.Parameters.Add("@State", txtState.Text); objCmd.Parameters.Add("@Zip", txtZip.Text); objCmd.Parameters.Add("@HomePhone", txtHomePhone.Text); objCmd.Parameters.Add("@Extension", txtExtension.Text); objCmd.Parameters.Add("@MobilePhone", txtMobilePhone.Text); objCmd.Parameters.Add("@EmployeeID", ddlEmployees.SelectedItem.Value); objConn.Open(); objCmd.ExecuteNonQuery(); objConn.Close(); }
- p.334 paragraph 2
The TextBox control has ID txtName, not "Name". - p.335 code listing 2
The code e.Item.FindControl("txtName") should be in bold. - p.337 paragraph 1
Possible values for ButtonType are LinkButton and PushButton, not Button. - p.393 paragraph 1
"Justas" should be "Just as". - p.417 last line
"theDataTable" should be "the DataTable". - p.437 paragraph 1
<customErrrors> should be <customErrors>. - p.643 figure 16.12
The "Admin Tools" link should appear in the navigation menu here. - p.671 paragraph 2
The paragraph should end: "Finally, it shows headerPanel, which contains all of the controls that display the results." - p.672 paragraph 1
The first sentence should be: "As you can see, we've created an integer variable (totalCount), a string variable (displayTitle), and a StringBuilder variable (sb)." - p.674 paragraph 1
The paragraph should end: "...when the query asks for the first page of results (i.e. the record argument is zero), the button is hidden; otherwise, we keep it visible."
Product Information
Features
NEW RELEASE!
Latest Update: June 2005
- Installation instructions for ASP.NET and MSDE
- OpenBAK™ Eurobind Binding - ‘perfect binding’ look - stays open on your desk!
- All examples given in VB.NET and C#
- Uses FREE tools - no expensive software required
- All code available for download
- Completely indexed
Product Details
Edition: |
1st |
Price: | NO LONGER AVAILABLE |
Pages: | 746 |
ISBN: | 0-9579218-6-1 |
Last Updated: | June 2005 |
Questions?
- Why Order from SitePoint?
- Frequently Asked Questions
- Track My Order
- FREE Shipping Offer
- Shipping Times and Rates
- Payment Options
- 100% Money-Back Guarantee
- Contact Sales Support
Claim Your PDF
Bought a SitePoint PDF? Download the PDF here.
Code Archive
Bought this SitePoint book? Download it's code archive for free.
SitePoint Books
Editorial Reviews
- DEV REVIEWS
- Anand Narayanaswamy - ASP101.com
- John Peterson - Asp.netPRO Magazine
- Mike Riley - ASPAlliance.com
- Jose Fuentes - Dan Morgan
- Dan Morgan