+ All Categories
Home > Documents > Assignment Instructions

Assignment Instructions

Date post: 23-Oct-2014
Category:
Upload: jhandwaal
View: 80 times
Download: 6 times
Share this document with a friend
Popular Tags:
470
Displaying Sorted Records A common need when displaying a set of database records is the ability to order them by various of their data fields. Although the GridView and DataGrid controls include declarative statements to sort on data fields, the Repeater and DataList do not include this feature. Sorting for these controls must be scripted. Sorting with a Repeater In the following example, records from the BooksDB.mdb database are sorted for display in a Repeater. Sorting is triggered by clicking a button that serves as the column heading. DB111 Databa se Oracle Database K. Loney $69.9 9 10 DB222 Databa se Databases in Depth C. J. Date $29.9 5 6 DB333 Databa se Database Processing D. Kroenke $136. 65 12 DB444 Databa se Access Database Design S. Roman $34.9 5 25 DB555 Databa se SQL Server 2005 P. Debetta $29.9 9 0 GR111 Graphi cs Adobe Photoshop CS2 Adobe $29.9 9 4 GR222 Graphi cs Learning Web Design J. Niederst $39.9 5 8 GR333 Graphi cs Macromedia Flash Professional T. Green $44.9 9 17 GR444 Graphi cs Digital Photographer Handbook M. Freeman $24.9 5 22 GR555 Graphi cs Creating Motion Graphics T. Meyer $59.9 5 13 HW111 Hardwa re How Computers Work R. White $29.9 9 8
Transcript
Page 1: Assignment Instructions

Displaying Sorted Records

A common need when displaying a set of database records is the ability to order them by various of their data fields. Although the GridView and DataGrid controls include declarative statements to sort on data fields, the Repeater and DataList do not include this feature. Sorting for these controls must be scripted.

Sorting with a Repeater

In the following example, records from the BooksDB.mdb database are sorted for display in a Repeater. Sorting is triggered by clicking a button that serves as the column heading.

DB111 Database Oracle Database K. Loney $69.99 10

DB222 Database Databases in Depth C. J. Date $29.95 6

DB333 Database Database Processing D. Kroenke $136.65 12

DB444 Database Access Database Design S. Roman $34.95 25

DB555 Database SQL Server 2005 P. Debetta $29.99 0

GR111 Graphics Adobe Photoshop CS2 Adobe $29.99 4

GR222 Graphics Learning Web Design J. Niederst $39.95 8

GR333 Graphics Macromedia Flash Professional T. Green $44.99 17

GR444 Graphics Digital Photographer Handbook M. Freeman $24.95 22

GR555 Graphics Creating Motion Graphics T. Meyer $59.95 13

HW111 Hardware How Computers Work R. White $29.99 8

HW222 Hardware Upgrading and Repairing PCs S. Mueller $59.99 5

HW333 Hardware USB System Architecture D. Anderson $49.99 1

HW444 Hardware Designing Embedded Hardware J. Catsoulis $44.95 3

HW555 Hardware Contemporary Logic Design R. Katz $102.95 2

SW111 Software Java How to Program Deitel $98.59 9

SW222 Software C Programming Language B. Kernighan $44.25 12

SW333 Software Programming C# J. Liberty $44.95 0

SW444 Software Programming PHP R. J. Lerdorf $39.95 17

SW555 Software Visual Basic.NET Programming P. Vick $49.99 13

SY111 Systems Operating System Concepts A. Silberschatz $95.75 1

SY222 Systems The UNIX Operating System J. D. Peek $19.95 12

SY333 Systems Windows Server 2003 W. R. Stanek $29.99 25

SY444 Systems Linux in a Nutshell S. Figgins $44.95 14

SY555 Systems Mastering Active Directory R. R. King $49.99 8

WB111 Web Ajax in Action D. Crane $22.67 14

Page 2: Assignment Instructions

WB222 Web Professional ASP.NET 2.0 B. Evjen $32.99 21

WB333 Web Cascading Style Sheets E. A. Meyer $39.95 6

WB444 Web DOM Scripting J. Keith $23.09 8

WB555 Web Microsoft ASP.NET 2.0 D. Esposito $29.99 12

Figure 8-16. Sorting a Repeater control.

Coding for the Repeater includes a row of buttons replacing the column titles in the <HeaderTemplate>. These are <asp:Button> controls using OnCommand and CommandNameproperties to pass field names to a subprogram that retrieves a recordset in ascending order by that field value.

<SCRIPT Runat="Server">

Sub SortRepeater (Src As Object, Args As CommandEventArgs)

Dim SQLString As String SQLString = "SELECT * FROM Books ORDER BY " & Args.CommandName BookSource.SelectCommand = SQLString

End Sub

</SCRIPT>

<form Runat="Server">

<asp:AccessDataSource id="BookSource" Runat="Server" DataFile="../Databases/BooksDB.mdb" SelectCommand="SELECT * FROM Books ORDER BY BookID"/>

<asp:Repeater id="BookTable" DataSourceID="BookSource" Runat="Server"> <HeaderTemplate> <table id="RepeaterTable" border="1" style="border-collapse:collapse"> <tr style="background-color:#707070; color:#FFFFFF"> <th> <asp:Button Text="ID" Runat="Server" OnCommand="SortRepeater" CommandName="BookID" Font-Size="8pt"/> </th> <th> <asp:Button Text="Type" Runat="Server" OnCommand="SortRepeater" CommandName="BookType" Font-Size="8pt"/> </th> <th> <asp:Button Text="Title" Runat="Server" OnCommand="SortRepeater" CommandName="BookTitle" Font-Size="8pt"/> </th> <th> <asp:Button Text="Author" Runat="Server" OnCommand="SortRepeater" CommandName="BookAuthor" Font-Size="8pt"/> </th>

Page 3: Assignment Instructions

<th> <asp:Button Text="Price" Runat="Server" OnCommand="SortRepeater" CommandName="BookPrice" Font-Size="8pt"/> </th> <th> <asp:Button Text="Qty" Runat="Server" OnCommand="SortRepeater" CommandName="BookQty" Font-Size="8pt"/> </th> </tr> </HeaderTemplate> <ItemTemplate> <tr style="vertical-align:top"> <td style="text-align:center"><%# Eval("BookID") %></td> <td><%# Eval("BookType") %></td> <td><%# Eval("BookTitle") %></td> <td><%# Eval("BookAuthor") %></td> <td style="text-align:right"><%# Eval("BookPrice") %></td> <td style="text-align:right"><%# Eval("BookQty") %></td> </tr> </ItemTemplate> <FooterTemplate> </table> </FooterTemplate></asp:Repeater>

</form>Listing 8-16. Repeater coding and scripting for record sorting.

You should note that the OnCommand event handlers in the buttons are not command events associated with the Repeater itself, as is the case when AllowSorting="True" is specified for a GridView or DataGrid and command events are issued through these controls. These buttons are independent sources of command events that individually call theSort_Repeater subprogram. Sorting requires no more than composing an SQL statement to retrieve records in a particular field order. That order is given by the CommandNameargument passed to the subprogram by a button click. The ordered recordset is re-bound to the Repeater by assigning the SELECT statement to the SelectCommand property of the AccessDataSource associated with the Repeater.

Sorting with a DataList

A DataList control does not have a built-in sorting feature. In the following example, a set of radio buttons selects a sort field and indicates ascending or descending sequence. A button is added to call the Sort_DataList subprogram.

 Order By: 

ID  Type  Title  Author  Price  Quantity

Direction: 

ASC DESC

Page 4: Assignment Instructions

ID: DB111Type: DatabaseTitle: Oracle DatabaseAuthor: K. LoneyPrice: $69.99Quantity: 10

ID: DB222Type: DatabaseTitle: Databases in DepthAuthor: C. J. DatePrice: $29.95Quantity: 6

ID: DB333Type: DatabaseTitle: Database ProcessingAuthor: D. KroenkePrice: $136.65Quantity: 12

ID: DB444Type: DatabaseTitle: Access Database DesignAuthor: S. RomanPrice: $34.95Quantity: 25

ID: DB555Type: DatabaseTitle: SQL Server 2005Author: P. DebettaPrice: $29.99Quantity: 0

ID: GR111Type: GraphicsTitle: Adobe Photoshop CS2Author: AdobePrice: $29.99Quantity: 4

ID: GR222Type: GraphicsTitle: Learning Web DesignAuthor: J. NiederstPrice: $39.95Quantity: 8

ID: GR333Type: GraphicsTitle: Macromedia Flash ProfessionalAuthor: T. GreenPrice: $44.99Quantity: 17

Page 5: Assignment Instructions

ID: GR444Type: GraphicsTitle: Digital Photographer HandbookAuthor: M. FreemanPrice: $24.95Quantity: 22

ID: GR555Type: GraphicsTitle: Creating Motion GraphicsAuthor: T. MeyerPrice: $59.95Quantity: 13

ID: HW111Type: HardwareTitle: How Computers WorkAuthor: R. WhitePrice: $29.99Quantity: 8

ID: HW222Type: HardwareTitle: Upgrading and Repairing PCsAuthor: S. MuellerPrice: $59.99Quantity: 5

ID: HW333Type: HardwareTitle: USB System ArchitectureAuthor: D. AndersonPrice: $49.99Quantity: 1

ID: HW444Type: HardwareTitle: Designing Embedded HardwareAuthor: J. CatsoulisPrice: $44.95Quantity: 3

ID: HW555Type: HardwareTitle: Contemporary Logic DesignAuthor: R. KatzPrice: $102.95Quantity: 2

ID: SW111Type: SoftwareTitle: Java How to ProgramAuthor: DeitelPrice: $98.59Quantity: 9

ID: SW222Type: SoftwareTitle: C Programming LanguageAuthor: B. KernighanPrice: $44.25Quantity: 12

ID: SW333Type: SoftwareTitle: Programming C#Author: J. LibertyPrice: $44.95Quantity: 0

ID: SW444Type: SoftwareTitle: Programming PHPAuthor: R. J. LerdorfPrice: $39.95Quantity: 17

ID: SW555Type: SoftwareTitle: Visual Basic.NET ProgrammingAuthor: P. VickPrice: $49.99Quantity: 13

ID: SY111 ID: SY222

Page 6: Assignment Instructions

Type: SystemsTitle: Operating System ConceptsAuthor: A. SilberschatzPrice: $95.75Quantity: 1

Type: SystemsTitle: The UNIX Operating SystemAuthor: J. D. PeekPrice: $19.95Quantity: 12

ID: SY333Type: SystemsTitle: Windows Server 2003Author: W. R. StanekPrice: $29.99Quantity: 25

ID: SY444Type: SystemsTitle: Linux in a NutshellAuthor: S. FigginsPrice: $44.95Quantity: 14

ID: SY555Type: SystemsTitle: Mastering Active DirectoryAuthor: R. R. KingPrice: $49.99Quantity: 8

ID: WB111Type: WebTitle: Ajax in ActionAuthor: D. CranePrice: $22.67Quantity: 14

ID: WB222Type: WebTitle: Professional ASP.NET 2.0Author: B. EvjenPrice: $32.99Quantity: 21

ID: WB333Type: WebTitle: Cascading Style SheetsAuthor: E. A. MeyerPrice: $39.95Quantity: 6

ID: WB444Type: WebTitle: DOM ScriptingAuthor: J. KeithPrice: $23.09Quantity: 8

ID: WB555Type: WebTitle: Microsoft ASP.NET 2.0Author: D. EspositoPrice: $29.99Quantity: 12

Page 7: Assignment Instructions

Figure 8-17. Sorting a DataList control.

A table to format the radio buttons is added immediately before the DataList. The first set of buttons permits choice of a sort field; the second set is for choosing the direction of sort. A standard button calls the Sort_DataList subprogram.

<SCRIPT Runat="Server">

Sub Sort_DataList (Src As Object, Args As EventArgs)

Dim SQLString As String = "SELECT * FROM Books ORDER BY " & _ SortButtons.SelectedValue & " " & DirectionButtons.SelectedValue BookSource.SelectCommand = SQLString

End Sub

</SCRIPT>

<form Runat="Server">

<asp:AccessDataSource id="BookSource" Runat="Server" DataFile="../Databases/BooksDB.mdb" SelectCommand="SELECT * FROM Books ORDER BY BookID"/>

<table border="1" width="550" style="border-collapse:collapse"> <tr style="vertical-align:bottom; background-color:#707070; color:#FFFFFF"> <td style="font-size:10pt"> <b> Order By: </b><br/> <asp:RadioButtonList id="SortButtons" Runat="Server" RepeatDirection="Horizontal" RepeatLayout="Flow"> <asp:ListItem Text="ID " Value="BookID" Selected="True"/> <asp:ListItem Text="Type " Value="BookType"/> <asp:ListItem Text="Title " Value="BookTitle"/> <asp:ListItem Text="Author " Value="BookAuthor"/> <asp:ListItem Text="Price " Value="BookPrice"/> <asp:ListItem Text="Quantity " Value="BookQty"/> </asp:RadioButtonList> </td> <td style="font-size:10pt"> <b>Direction: </b><br/> <asp:RadioButtonList id="DirectionButtons" Runat="Server" RepeatDirection="Horizontal" RepeatLayout="Flow"> <asp:ListItem Text="ASC" Value="ASC" Selected="True"/> <asp:ListItem Text="DESC" Value="DESC"/> </asp:RadioButtonList> </td> <td> <asp:Button Text="Sort" OnClick="Sort_DataList" Runat="Server" /> </td> </tr> </table>

<asp:DataList id="BookList" DataSourceID="BookSource" Runat="Server" Width="550" CellSpacing="3" CellPadding="5" RepeatColumns="2" RepeatDirection="Horizontal" GridLines="Both" ItemStyle-BackColor="#F9F9F9"

Page 8: Assignment Instructions

ItemStyle-Font-Names="Times New Roman" ItemStyle-Font-Size="9pt"> <ItemTemplate> <asp:Image Width="100" Style="float:left" Runat="Server" ImageUrl='<%# "../eCommerce/BookPictures/" & Eval("BookID") & ".jpg" %>'/> <b>ID: </b> <asp:Label Text='<%# Eval("BookID") %>' Runat="Server"/><br/> <b>Type: </b> <asp:Label Text='<%# Eval("BookType") %>' Runat="Server"/><br/> <b>Title: </b> <asp:Label Text='<%# Eval("BookTitle") %>' Runat="Server"/><br/> <b>Author: </b> <asp:Label Text='<%# Eval("BookAuthor") %>' Runat="Server"/><br/> <b>Price: </b> <asp:Label Text='<%# Eval("BookPrice") %>' Runat="Server"/><br/> <b>Quantity: </b> <asp:Label Text='<%# Eval("BookQty") %>' Runat="Server"/><br/> </ItemTemplate>

</asp:DataList>

</form>Listing 8-17. DataList coding and scripting for record sorting.

As in the previous example, the Sort_DataList subprogram creates an SQL statement that is assigned to the AccessDataSource's SelectCommand property. In this case, two values are appended to the statement. The field name is given by the value of the checked radio button in the first set; the value ASC or DESC is given by the second set. The resulting SQL statement is in the format SELECT * FROM Books ORDER BY field ASC (or DESC). This same sorting technique, of course, can be applied to a Repeater control.

Displaying Selected Records

When displaying records from a database table it is often convenient to be able to select particular records for display rather than displaying the entire recordset. This selection involves specifying some criterion value for one of the fields and then extracting only those records which meet that criterion.

Selecting Records with the Repeater

The following Repeater permits record sorting by clicking buttons for those sort fields. In addition, selections are provided to choose subsets of records from the Books table of theBooksDB.mdb database which meet specified search criteria. For instance, the following subset of records shows all books for which the BookType field equals "Database", sorted in ascending sequence by BookID. Different selection fields and comparison values can be chosen from drop-down lists and displayed in different sort orders.

 Select Field:                             

 Sort:  ASC DESC

Description

Page 9: Assignment Instructions

DB111 Database

Oracle Database

K. Loney Get thorough coverage of Oracle Database 10g from the most comprehensive reference available, published by Oracle Press. With in-depth details on all the new features, this powerhouse resource provides an overview of database architecture and Oracle Grid Computing technology, and covers SQL, SQL*Plus, PL/SQL, dynamic PL/SQL, object-oriented features, and Java programming in the Oracle environment. You'll also find valuable database administration and application development techniques, plus an alphabetical reference covering major Oracle commands, keywords, features, and functions, with cross-referencing of topics.

$69.99 10

DB222 Database

Databases in Depth

C. J. Date

In Database in Depth, author and well-known database authority Chris Date lays out the fundamentals of the relational model. Don't let a lack to formal education in database theory hold you back. Instead, let Chris's clear explanation of relational concepts, set theory, the difference between model and implementation, relational algebra, normalization, and much more set you apart and well above the competition when it comes to getting work done with a relational database.

$29.95 6

DB333 Database

Database Processing

D. Kroenke

Revised to reflect the needs of today's users, this 10th edition of Database Processing assures that you will learn marketable skills. By presenting SQL SELECT statements near the beginning of the book readers will know early on how to query data and obtain results-seeing firsthand some of the ways that database technology is useful in the marketplace. By utilizing free software downloads, you will be able to actively use a DBMS product by the end of the 2nd chapter. Each topic appears in the context of accomplishing practical tasks. Its spiral approach to database design provides users with enhanced information not available in other database books on the market.

$136.65 12

DB444 Database

Access Database Design

S. Roman

When using software products with graphical interfaces, we frequently focus so much on the details of how to use the interface that we forget about the general concepts that allow us to understand and use the software effectively. This is particularly true of a powerful database product like Microsoft Access. Novice, and sometimes even experienced, programmers are so concerned with how something is done in Access that they often lose sight of the general principles that underlie their database applications. Access Database Design and Programming takes you behind the details of the Access interface, focusing on the general knowledge necessary for Access power users or developers to create effective database applications.

$34.95 25

DB555 Databas SQL Server P. Get a developer-focused introduction to the new programmability features in the next version of

$29.99 0

Page 10: Assignment Instructions

e 2005 Debetta Microsoft SQL Server-including integration with the Microsoft .NET Framework-and learn powerful new ways to manipulate your servers. Whether you're a developer currently working with T-SQL or Microsoft Visual Studio.NET, or you're responsible for database administration, you'll see how to draw from your existing skills and knowledge to exploit new SQL Server technology. With introductory-level code samples written in both T-SQL and C#, you'll understand how to take advantage of the cross-platform interoperability, native support for XML and Web services, shared language base, and other programming innovations to build better solutions from business intelligence to enterprise data management.

Figure 8-18. Selecting and sorting with a Repeater control.

Coding the Repeater

In order to make a selection and specify a sort order, an SQL statement must be composed to read like the following statement used in the AccessDataSource control to retrieve the initial set of "Database" BookTypes in ascending BookID order.

SELECT * FROM Books WHERE BookType='Database' ORDER BY BookID ASCListing 8-18. Format for SELECT statement to retrieve records in sorted order.

Controls are appended to the Repeater to collect information to create this statement. A DropDownList permits selection of a field name, a second DropDownList selects a conditional operator, and a TextBox provides the value for comparison. These three controls permit creation of an SQL WHERE clause giving the criterion for selecting records from theBooks table. A RadioButtonList is also added to specify the sort order of selected records and to compose the SQL statement's ORDER BY clause.

<asp:AccessDataSource id="BookSource" Runat="Server" DataFile="../Databases/BooksDB.mdb" SelectCommand="SELECT * FROM Books WHERE BookType='DataBase' ORDER BY BookID ASC"/>

<asp:Panel id="FieldPanel" BackColor="#707070" ForeColor="#FFFFFF" Width="530" Runat="Server"> <b> Select Field: </b> <asp:DropDownList id="FieldName" Runat="Server"> <asp:ListItem Value="BookID" Text="ID"/> <asp:ListItem Value="BookType" Text="Type" Selected="True"/> <asp:ListItem Value="BookTitle" Text="Title"/> <asp:ListItem Value="BookAuthor" Text="Author"/> <asp:ListItem Value="BookDescription" Text="Description"/> <asp:ListItem Value="BookPrice" Text="Price" /> <asp:ListItem Value="BookQty" Text="Quantity"/> </asp:DropDownList> <asp:DropDownList id="TheOperator" Runat="Server"> <asp:ListItem Value=" LIKE " Text="Contains"/> <asp:ListItem Value=" Not LIKE " Text="Does Not Contain"/> <asp:ListItem Value=" < " Text="Less Than"/> <asp:ListItem Value=" = " Text="Equal To" Selected="True"/>

Page 11: Assignment Instructions

<asp:ListItem Value=" > " Text="Greater Than"/> <asp:ListItem Value=" <> " Text="Not Equal To"/> </asp:DropDownList> <asp:TextBox id="FieldValue" Text="Database" Runat="Server"/><br/> <b> Sort: </b> <asp:RadioButtonList id="DirectionButtons" Runat="Server" RepeatDirection="Horizontal" RepeatLayout="Flow"> <asp:ListItem Text="ASC" Value="ASC" Selected="True"/> <asp:ListItem Text="DESC" Value="DESC"/> </asp:RadioButtonList></asp:Panel>

<asp:Repeater id="RepeaterDisplay" DataSourceID="BookSource" Runat="Server"> <HeaderTemplate> <table id="RepeaterTable" border="1" style="width:530px; border-collapse:collapse"> <tr style="background-color:#707070; color:#FFFFFF"> <th> <asp:Button Text="ID" Font-SIze="8pt" Runat="Server" OnCommand="Sort_Repeater" CommandName="BookID"/> </th> <th> <asp:Button Text="Type" Font-Size="8pt" Runat="Server" OnCommand="Sort_Repeater" CommandName="BookType"/> </th> <th> <asp:Button Text="Title" Font-Size="8pt" Runat="Server" OnCommand="Sort_Repeater" CommandName="BookTitle"/> </th> <th> <asp:Button Text="Author" Font-Size="8pt" Runat="Server" OnCommand="Sort_Repeater" CommandName="BookAuthor"/> </th> <th>Description</th> <th> <asp:Button Text="Price" Font-Size="8pt" Runat="Server" OnCommand="Sort_Repeater" CommandName="BookPrice"/> </th> <th> <asp:Button Text="Qty" Font-Size="8pt" Runat="Server" OnCommand="Sort_Repeater" CommandName="BookQty"/> </th> </tr> </HeaderTemplate> <ItemTemplate> <tr style="vertical-align:top"> <td style="text-align:center"> <asp:Label Text='<%# Eval("BookID") %>' Runat="Server"/></td> <td><asp:Label Text='<%# Eval("BookType") %>' Runat="Server"/></td> <td><asp:Label Text='<%# Eval("BookTitle") %>' Runat="Server"/></td> <td><asp:Label Text='<%# Eval("BookAuthor") %>' Runat="Server"/></td> <td><asp:Panel ScrollBars="Auto" Width="180" Height="60" Runat="Server"> <asp:Label Font-Size="9" Style="line-height:10pt" Runat="Server" Text='<%# Eval("BookDescription") %>'/><br/> </asp:Panel></td> <td style="text-align:right">

Page 12: Assignment Instructions

<asp:Label Text='<%# Eval("BookPrice") %>' Runat="Server"/></td> <td style="text-align:right"> <asp:Label Text='<%# Eval("BookQty") %>' Runat="Server"/></td> </tr> </ItemTemplate> <FooterTemplate> </table> </FooterTemplate></asp:Repeater>Listing 8-19. Code for Repeater to choose fields and values to retrieve records in sorted order.

Field name selection is made through an <asp:DropDownList> supplying field names from the Books table as item Value properties. Field names are hard-coded as ListItem values; however, an AccessDataSource control could have been associated with this DropDownList to extract names from the database.

<b> Select Field: </b><asp:DropDownList id="FieldName" Runat="Server"> <asp:ListItem Value="BookID" Text="ID"/> <asp:ListItem Value="BookType" Text="Type" Selected="True"/> <asp:ListItem Value="BookTitle" Text="Title"/> <asp:ListItem Value="BookAuthor" Text="Author"/> <asp:ListItem Value="BookDescription" Text="Description"/> <asp:ListItem Value="BookPrice" Text="Price" /> <asp:ListItem Value="BookQty" Text="Quantity"/></asp:DropDownList>Listing 8-20. Selecting a comparison field for the Repeater.

Conditional operators are selected from a second DropDownList whose Values are actual conditional operators and whose Text properties are verbal equivalents. (If you are not familiar with conditional operators used in SQL statements for Access databases, refer to the SQL appendix.)

<asp:DropDownList id="TheOperator" Runat="Server"> <asp:ListItem Value=" LIKE " Text="Contains"/> <asp:ListItem Value=" Not LIKE " Text="Does Not Contain"/> <asp:ListItem Value=" < " Text="Less Than"/> <asp:ListItem Value=" = " Text="Equal To" Selected="True"/> <asp:ListItem Value=" > " Text="Greater Than"/> <asp:ListItem Value=" <> " Text="Not Equal To"/></asp:DropDownList>Listing 8-21. Selecting a conditional operator for the Repeater.

Finally, the search criterion is given in a TextBox control. Values appearing in SQL statements are not case sensitive, so either lower-case, upper-case, or mixed-case characters can be entered.

<asp:TextBox id="FieldValue" Text="Database" Runat="Server"/>Listing 8-22. Specifying a comparison value for the Repeater.

Composing an SQL Statement

The values from the three controls can be used to construct an SQL WHERE clause for selecting records that meet the criterion. For instance, when "BookType" is selected from

Page 13: Assignment Instructions

theFieldName DropDownList, " = " is selected from the TheOperator list, and "Database" is entered in the FieldValue textbox, then concatenating the strings

" WHERE " & FieldName.SelectedValue& TheOperator.SelectedValue& "'"& FieldValue.Text& "'"

produces the string

WHERE BookType = 'Database'

which can be appended to a SELECT statement to extract records based on this condition test. Of course, slightly different concatenations are needed for different operators and different field types, but the idea works the same.

Composition of the SELECT statement takes place in the Sort_Repeater subprogram that was used previously and is now expanded to include additional steps to create an SQL statement with a WHERE clause composed from selected drop-down values. The column header buttons call this subprogram which assigns the composed statement to theSelectCommand property of the AccessDataSource.

Sub Sort_Repeater (Src As Object, Args As CommandEventArgs)

Dim SQLString As String = "SELECT * FROM Books" If FieldValue.Text <> "" Then If TheOperator.SelectedValue = " LIKE " _ OR TheOperator.SelectedValue = " Not LIKE " Then '-- "Contains" comparison, e.g., '-- WHERE field LIKE 'value' SQLString &= " WHERE " & FieldName.SelectedValue & _ TheOperator.SelectedValue & "'%" & FieldValue.Text & "%'" Else If FieldName.SelectedValue <> "BookPrice" AND _ FieldName.SelectedValue <> "BookQty" Then '-- Alphanumeric comparising, e.g., '-- WHERE field = 'value' SQLString &= " WHERE " & FieldName.SelectedValue & _ TheOperator.SelectedValue & "'" & FieldValue.Text & "'" Else '-- Numeric comparison, e.g., '-- WHERE field = value SQLString &= " WHERE " & FieldName.SelectedValue & _ TheOperator.SelectedValue & FieldValue.Text End If End If End If

Page 14: Assignment Instructions

SQLString &= " ORDER BY " & Args.CommandName SQLString &= " " & DirectionButtons.SelectedValue BookSource.SelectCommand = SQLString End SubListing 8-23. Composing a SELECT statement for the Repeater.

Composing the WHERE clause depends on the user having entered a search criterion value in the textbox. If the textbox is empty, this portion of the subprogram is not run and noWHERE clause is appended to the SQLString.

The format of the WHERE clause differs slightly when specifying string versus numeric comparisons. When the comparison value is a string, it must be enclosed in single quotes (apostrophes); when the comparison value is a number, no quotes are used. For example,

WHERE BookType = 'Database'WHERE BookPrice > 50

So, the script supplies different SQL coding for the BookID, BookType, BookTitle, BookAuthor, and BookDescription fields (which are strings whose comparison values are enclosed in apostrophes) versus the BookPrice and BookQty fields (which are numbers whose comparison values are not enclosed in single quotes).

When using LIKE or  Not LIKE comparisons, all values are treated as strings and no differentiation in coding is made. The script uses the general search format  LIKE '%value%' to locate the entered value anywhere in the field. This type of search is used in the BookDescription field where any word or partial word can be found anywhere in the field.

After the WHERE clause is composed and appended to the SQLString variable, the ORDER BY clause is added.

SQLString &= " ORDER BY " & Args.CommandNameListing 8-24. Adding an ORDER BY field name to the SELECT statement.

As before, the sort field is given by the CommandName associated with the button clicked in the column header. Next, either ASC or DESC is appended to the SQLString depending on which of the radio buttons is checked.

SQLString &= " " & DirectionButtons.SelectedValueListing 8-25. Adding a sort direction to the SELECT statement.

Finally, the completed SQLString is issued by assigning it to the SelectCommand property of the AccessDataSource associated with the Repeater.

BookSource.SelectCommand = SQLStringListing 8-26. Assigning the SELECT statement to the SelectCommand property of the AccessDataSource.

The data source is bound to the Repeater with records matching the search criterion and sorted according to the button click.

Page 15: Assignment Instructions

Selecting Records with the DataList and DataGrid

It is not necessary to show coding for either the DataList or DataGrid control. A selection panel can be added to these controls to work identically to the Repeater. TheSort_DataList and Sort_DataGrid subprograms used in previous examples are modified in the same way as is done for the Repeater.

Displaying Records with SQL

If the database administrator is familiar with the SQL language, searching and sorting can be done simply by entering an SQL statement into a textbox which is then used to populate a display control.

Selecting Records with the Repeater

In the following example the statement

SELECT * FROM Books WHERE BookType='Database' ORDER BY BookID ASC

is assigned to the AccessDataSource control associated with the Repeater to display an initial set of records. Then, the statement

SELECT * FROM Books WHERE BookType='Graphics' ORDER BY BookPrice DESC

has been entered into a textbox. When the "Select" button clicked, this statement is used to repopulate the Repeater with the chosen records. Any valid SQL statement entered into the textbox retrieves and displays the associated records. The only restriction is that all fields (*) must be selected in order to retrieve sufficient data to bind to all of the Repeater's display fields.

SQL Statement:

 

ID Type Title Author Description Price Qty

DB111 Database

Oracle Database

K. Loney

Get thorough coverage of Oracle Database 10g from the most comprehensive reference available, published by Oracle Press. With in-depth details on all the new features, this powerhouse resource provides an overview of database architecture and Oracle Grid Computing technology, and covers SQL, SQL*Plus, PL/SQL, dynamic PL/SQL, object-oriented features, and Java programming in the Oracle environment. You'll also find valuable database administration and application development techniques, plus an alphabetical reference covering major Oracle commands, keywords, features, and functions, with cross-referencing of topics.

$69.99 10

DB222 Databas Databases C. J. In Database in Depth, author and well-known $29.95 6

Page 16: Assignment Instructions

e in Depth Date database authority Chris Date lays out the fundamentals of the relational model. Don't let a lack to formal education in database theory hold you back. Instead, let Chris's clear explanation of relational concepts, set theory, the difference between model and implementation, relational algebra, normalization, and much more set you apart and well above the competition when it comes to getting work done with a relational database.

DB333 Database

Database Processing

D. Kroenke

Revised to reflect the needs of today's users, this 10th edition of Database Processing assures that you will learn marketable skills. By presenting SQL SELECT statements near the beginning of the book readers will know early on how to query data and obtain results-seeing firsthand some of the ways that database technology is useful in the marketplace. By utilizing free software downloads, you will be able to actively use a DBMS product by the end of the 2nd chapter. Each topic appears in the context of accomplishing practical tasks. Its spiral approach to database design provides users with enhanced information not available in other database books on the market.

$136.65 12

DB444 Database

Access Database Design

S. Roman

When using software products with graphical interfaces, we frequently focus so much on the details of how to use the interface that we forget about the general concepts that allow us to understand and use the software effectively. This is particularly true of a powerful database product like Microsoft Access. Novice, and sometimes even experienced, programmers are so concerned with how something is done in Access that they often lose sight of the general principles that underlie their database applications. Access Database Design and Programming takes you behind the details of the Access interface, focusing on the general knowledge necessary for Access power users or developers to create effective database applications.

$34.95 25

DB555 Database

SQL Server 2005

P. Debetta

Get a developer-focused introduction to the new programmability features in the next version of Microsoft SQL Server-including integration with the Microsoft .NET Framework-and learn powerful new ways to manipulate your servers. Whether you're a developer currently working with T-SQL or Microsoft Visual Studio.NET, or you're responsible for database administration, you'll see how to draw from your existing skills and knowledge to exploit new SQL Server technology. With introductory-level code samples written in both T-SQL and C#, you'll understand how to take advantage of the cross-platform interoperability, native support for XML and Web services, shared language base, and other programming innovations to build better solutions from business intelligence to enterprise data management.

$29.99 0

Figure 8-19. Selecting records with SQL statements for a Repeater control.

Page 17: Assignment Instructions

If an error is made in typing the SQL statement, an error message is displayed below the textbox. You can check this out by corrupting the above displayed SQL statement.

Coding the Repeater

Coding for the Repeater is identical to that used in previous examples. <asp:TextBox>, <asp:Button>, and <asp:Label> controls are added to solicit the SQL statement, call theDisplay_Repeater subprogram, and display an error message if the SQL statement is invalid.

<asp:AccessDataSource id="BookSource" Runat="Server" DataFile="../Databases/BooksDB.mdb" SelectCommand="SELECT * FROM Books WHERE BookType='Database' ORDER BY BookID ASC"/>

<b>SQL Statement:</b><br/><asp:TextBox id="SQLStatement" TextMode="MultiLine" Columns="60" Font-Size="9pt" Runat="Server"Text="SELECT * FROM Books WHERE BookType='Graphics' " & _ "ORDER BY BookPrice DESC"/><asp:Button Text="Select" Runat="Server" OnClick="Display_Repeater"/><br/><asp:Label id="ERRMsg" EnableViewState="False" ForeColor="#FF0000" Runat="Server"/>

<asp:Repeater id="RepeaterDisplay" DataSourceID="BookSource" Runat="Server"> <HeaderTemplate> <table id="RepeaterTable" border="1" style="width:530px; border-collapse:collapse"> <tr style="background-color:#707070; color:#FFFFFF"> <th>ID</th> <th>Type</th> <th>Title</th> <th>Author</th> <th>Description</th> <th>Price</th> <th>Qty</th> </tr> </HeaderTemplate> <ItemTemplate> <tr style="vertical-align:top"> <td style="text-align:center"> <asp:Label Text='<%# Eval("BookID") %>' Runat="Server"/></td> <td><asp:Label Text='<%# Eval("BookType") %>' Runat="Server"/></td> <td><asp:Label Text='<%# Eval("BookTitle") %>' Runat="Server"/></td> <td><asp:Label Text='<%# Eval("BookAuthor") %>' Runat="Server"/></td> <td><asp:Panel ScrollBars="Auto" Width="180" Height="60" Runat="Server"> <asp:Label Font-Size="9" Style="line-height:10pt" Runat="Server" Text='<%# Eval("BookDescription") %>'/><br/> </asp:Panel></td> <td style="text-align:right"> <asp:Label Text='<%# Eval("BookPrice") %>' Runat="Server"/></td> <td style="text-align:right"> <asp:Label Text='<%# Eval("BookQty") %>' Runat="Server"/></td> </tr> </ItemTemplate>

<FooterTemplate>

Page 18: Assignment Instructions

</table> </FooterTemplate></asp:Repeater>Listing 8-27. Code for Repeater and TextBox to enter SELECT statement.

Notice that the textbox has the property setting TextMode="MultiLine" with 60 columns and a default of 2 rows. The label for the error message is coded withEnableViewState="False" so that error messages are cleared when re-posting the form.

Executing the SQL Command

When the "Submit" button is clicked, the Display_Repeater subprogram is called. As long as a valid SQL statement is entered into the textbox, coding of this subprogram is as simple as assigning the statement to the SelectCommand property of the Access Data Source.

Sub Display_Repeater (Src As Object, Args As EventArgs) BookSource.SelectCommand = SQLStatement.TextEnd SubListing 8-28. Assigning SELECT statement to SelectCommand property of AccessDataSource for Repeater.

However, if an invalid statement is entered, an execution error occurs. When using an AccessDataSource to populate the Repeater, there is no way to test for this error to handle it elegantly. The subprogram simply aborts and a system error is generated, leaving the user unsure of what happened. Therefore, a different tactic must be used to trap these errors and recover from them.

A run-time error can be trapped only by using a data access script to discover the error. This amounts to issuing the SQL statement through an OleDbCommand object, not through an AccessDataSource. Coding to accomplish this error checking in the Display_Repeater subprogram is shown below. It checks for an invalid SQL statement and displays a "friendly" error message if the statement cannot be properly executed.

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

<SCRIPT Runat="Server">

Sub Display_Repeater (Src As Object, Args As EventArgs)

Try Dim DBConnection As OleDbConnection = New OleDbConnection( _ "Provider=Microsoft.Jet.OLEDB.4.0;" & _ "Data Source=" & Server.MapPath("../Databases/BooksDB.mdb")) DBConnection.Open() Dim DBCommand = New OleDbCommand(SQLStatement.Text, DBConnection) DBCommand.ExecuteReader() DBConnection.Close() BookSourceSource.SelectCommand = SQLStatement.Text Catch ERRMsg.Text &= "-- Error in SQL statement --" ERRMsg.Visible = True End Try End Sub

</SCRIPT>Listing 8-29. Trapping run-time errors with Try...Catch statement.

Page 19: Assignment Instructions

Since a script is used to access the BooksDB.mdb database, the System.Data.OleDb namespace must be imported to the page.

Using Try...Catch

The Visual Basic Try...Catch structure is used to encapsulate code that can cause execution errors. Its general format is shown below.

Try

...execution code

Catch

...error code

End Try

Figure 8-20. General format for Try...Catch structure.

Code that can cause run-time execution errors is placed inside the Try section; code to respond to an error is placed inside the Catch section. If there is execution failure in the Trycode, the Catch code is run instead. In the above example, database access with the SQL statement is in the Try section; display of an error message appears in the Catchsection.

Notice that the OleDbCommand directly executes the entered SQL statement, that is, the Text property of the SQLStatement TextBox. The Command's ExecuteReader() method is the one that can cause a run-time error which is trapped by the Try section of code.

No recordset is returned by the ExecuteReader() method. It is used only to test whether or not the entered SQL statement is valid and can be issued without error. If an error occurs, the remaining statements in the Try section of script are skipped and the Catch section of code is run. If no error is generated, then the SQL statement is valid. The final statement in the Try section assigns the entered SQL command to the SelectCommand property of the AccessDataSource and the subset of records are returned for display.

Similar techniques can be used with any of the information display controls.

Updating Records - GridView

All data-driven Web sites are driven by data management systems. These systems house the information displayed on Web pages and represent the transactions with customers and interactions with clients that promote the success of an organization. Even before a store-front appears on the Web, there must be collections of information representing products and services for sale. Before an internal work-flow procedure takes place, there must be repositories of data to share and data that trigger the flows. Before managers can evaluate the results of operations there must be data available about those operations. In short, any Web-based system must be founded on the information it gathers, processes, analyzes, and disseminates. It must be supported by its repositories of information and the database management systems that maintain them.

Page 20: Assignment Instructions

Database maintenance applications comprise the back-end infrastructure to collect and store organizational data and to keep them current. Basic maintenance activities include adding new records to database tables, deleting outdated records, and changing the content of current records to keep them up to date. Beginning with this tutorial, various ways of performing database maintenance are discussed, starting with built-in features supplied by the GridView control.

Basic GridView Editing

A GridView control along with a data source control include features to perform basic editing of database fields without the need to write script. In the following example, a GridView that displays selected products from the BooksDB.mdb database includes "Edit" buttons for the book records. When a button is clicked, displayed fields for the record are converted into textboxes or other appropriate controls to permit changing displayed values. "Update" and "Cancel" buttons appear to rewrite the changed values to the database or to cancel updating. The changed record is redisplayed in the GridView table.

Note that making actual changes to the BooksDB.mdb database is not permitted in these tutorials; however, all other functions work as expected.

Book Edit

  BookID BookType BookTitle BookAuthor BookPrice BookQty BookSale

Edit DB111 Database Oracle Database K. Loney $69.99 10

Edit DB222 Database Databases in Depth C. J. Date $29.95 6

Edit DB333 Database Database Processing D. Kroenke $136.65 12

Edit DB444 DatabaseAccess Database Design

S. Roman $34.95 25

Edit DB555 Database SQL Server 2005 P. Debetta $29.99 0

Figure 9-1. Updating a database with a GridView.

It requires very little code to produce a default GridView that permits editing. Code for the above editable GridView and its AccessDataSource are shown below.

<asp:AccessDataSource id="BookSource" Runat="Server" DataFile="../Databases/BooksDB.mdb" SelectCommand="SELECT BookID, BookType, BookTitle, BookAuthor, BookPrice, BookQty, BookSale FROM Books WHERE BookType = 'Database' ORDER BY BookID" UpdateCommand="UPDATE Books SET BookType=@BookType, BookTitle=@BookTitle, BookAuthor=@BookAuthor, BookPrice=@BookPrice, BookQty=@BookQty, BookSale=@BookSale WHERE BookID=@BookID"/>

<h3>Book Edit</h3>

<asp:GridView id="EditGrid" DataSourceID="BookSource" Runat="Server" AutoGenerateEditButton="True"

Page 21: Assignment Instructions

DataKeyNames="BookID" HeaderStyle-Font-Size="10pt" RowStyle-Font-Size="10pt"/>Listing 9-1. Code for default GridView with editing.

A column of "Edit" buttons is added to the GridView by setting its AutoGenerateEditButton property to "True". A click on one of these buttons changes the row from display to edit mode and replaces the "Edit" button with "Update" and "Cancel" buttons.

A changed record that is rewritten to the database is identified by a field having unique values that distinguish the updated record from all other records in the table. It is necessary to identify this primary key field to the GridView through its DataKeyNames property. Although it is not necessary for this field to be formally designated as a primary key in the database, it is proper practice to do so. In the above example, the BookID field is the primary key for the Books table and is designated as such in the DataKeyNames property of the GridView.

For certain database tables, more than one field might be needed to provide a unique identifier for records. In these cases, all fields that are combined to produce a unique key are given in a comma-separated list in the DataKeyNames property.

When the chosen row is put into edit mode, DataKeyNames fields are not rendered as textboxes for making changes. Changing a record key can wreck all kinds of havoc with a database. A changed key misidentifies the record being updated, possibly leading to an updating error or causing the wrong record to be updated. Also, if records in other database tables are linked through the key field, then they become disassociated from the changed record. Therefore, record keys should never be changed. Instead, it is better practice to delete the record and create a new one with a new key, at the same time changing related values in associated tables.

GridView Updating

The AccessDataSource for an editable GridView requires two SQL commands—a SELECT statement is needed to select records for display, and an UPDATE statement is required to rewrite changes to the database. The SELECT statement is given by the control's SelectCommand property. The UPDATE statement is given in the UpdateCommand property. TheSelectCommand statement is similar to that used in previous examples of displaying database records in a GridView. In this example, it selects only records with "Database" as theBookType value. The BookDescription field is not displayed in this example simply to reduce the amount of information appearing on this page.

The UPDATE command for updating the Books table contains database field names and associated parameters, or binding values, identified by the prefix "@".

UpdateCommand="UPDATE Books SET BookType=@BookType, BookTitle=@BookTitle, BookAuthor=@BookAuthor, BookPrice=@BookPrice, BookQty=@BookQty, BookSale=@BookSale WHERE BookID=@BookID"Listing 9-2. Code for UpdateCommand with parameters.

The update specification BookType=@BookType, for example, gives the database field to update (BookType) along with a reference to its update value (@BookType). As shown in the following illustration, this update value is taken from the textbox edit field whose original value is bound to it by the SELECT statement that populates the GridView.

Page 22: Assignment Instructions

Figure 9-2. Relationships between field names and parameters.

It is important to remember that fields identified as DataKeyNames have ReadOnly properties and cannot take part in updating since edit boxes are not created for these fields. In the above example, BookID is a key field. When a record is placed in edit mode, no edit box is created for this field. Therefore, the BookID value cannot be changed and the update assignment BookID=@BookID cannot be part of the UPDATE statement's list of update assignments.

It is also important that a WHERE clause appear in the UPDATE statement to identify which record to update. This clause identifies a DataKeyNames field to match to a database field for rewriting the record. In this example, the record updated is the one where the BookID field in the Books table is matched by the value of the @BookID parameter in the update row of the GridView (BookID=@BookID). If a WHERE clause is not provided, all records in the table mistakenly receive the same changed values.

Updating of the database takes place when the "Update" button is clicked for the row being edited and the UPDATE statement is issued through the AccessDataSource. Then theSELECT statement is reissued to repopulate the GridView with changed values. An option is to click the "Cancel" button to abort updating and return the row to display mode without changes.

Formatted GridView Editing

A GridView can be formatted with bound columns for more control over its display. The following GridView displays the entire Books table as a paged recordset with various bound-field controls used for display and editing. For illustration purposes, it permits editing of only the BookType and BookDescription fields.

Book Edit

Edit ID Type Title Author Description Price Qty Sale

DB111 Database

Oracle Database

K. Loney

Get thorough coverage of Oracle Database 10g from the most comprehensive reference available, published by Oracle Press. With in-depth details on all the new features, this powerhouse resource provides an overview of database architecture and Oracle Grid Computing technology, and covers SQL, SQL*Plus, PL/SQL,

$69.99 10

Page 23: Assignment Instructions

dynamic PL/SQL, object-oriented features, and Java programming in the Oracle environment. You'll also find valuable database administration and application development techniques, plus an alphabetical reference covering major Oracle commands, keywords, features, and functions, with cross-referencing of topics.

DB222 Database

Databases in Depth

C. J. Date

In Database in Depth, author and well-known database authority Chris Date lays out the fundamentals of the relational model. Don't let a lack to formal education in database theory hold you back. Instead, let Chris's clear explanation of relational concepts, set theory, the difference between model and implementation, relational algebra, normalization, and much more set you apart and well above the competition when it comes to getting work done with a relational database.

$29.95 6

DB333 Database

Database Processing

D. Kroenke

Revised to reflect the needs of today's users, this 10th edition of Database Processing assures that you will learn marketable skills. By presenting SQL SELECT statements near the beginning of the book readers will know early on how to query data and obtain results-seeing firsthand some of the ways that database technology is useful in the marketplace. By utilizing free software downloads, you will be able to actively use a DBMS product by the end of the 2nd chapter. Each topic appears in the context of accomplishing practical tasks. Its spiral approach to database design provides users with enhanced information not available in other database books on the market.

$136.65 12

DB444 Database

Access Database Design

S. Roman

When using software products with graphical interfaces, we frequently focus so much on the details of how to use the interface that we forget about the general concepts that allow us to understand and use the software effectively. This is particularly true of a powerful database product like Microsoft Access. Novice, and sometimes even experienced, programmers are so concerned with

$34.95 25

Page 24: Assignment Instructions

how something is done in Access that they often lose sight of the general principles that underlie their database applications. Access Database Design and Programming takes you behind the details of the Access interface, focusing on the general knowledge necessary for Access power users or developers to create effective database applications.

1 2 3 4 5 6 7 8

Figure 9-3. Updating a database with a formatted GridView.

Full coding for this application appears below. The AccessDataSource for the GridView is similar to the previous one except that it selects all records from the Books table. Also theUPDATE statement updates only the BookType and BookDescription fields. A second AccessDataSource supplies BookTypes for the DropDownList that appears in edit mode.

The GridView has its AutoGenerateColumns property set to False since bound fields and template fields are defined for the columns. Also, AutoGenerateEditButtons is set toFalse (the default value) since explicitly coded edit buttons replace the automatic text buttons to edit, update, and cancel editing for a row. The BookID field is the record key and declared as such in the DataKeyNames property. Paging is turned on for four records per page. Note that when a row is put into edit mode an EditRowsStyle property setting becomes available to style this row.

<asp:AccessDataSource id="BookSource" Runat="Server" DataFile="../Databases/BooksDB.mdb" SelectCommand="SELECT * FROM Books ORDER BY BookID" UpdateCommand="UPDATE Books SET BookType=@BookType, BookDescription=@BookDescription WHERE BookID=@BookID"/>

<asp:AccessDataSource id="TypeSource" Runat="Server" DataFile="../Databases/BooksDB.mdb" SelectCommand="SELECT DISTINCT BookType FROM Books ORDER BY BookType"/>

<h3>Book Edit</h3>

<asp:GridView id="EditGrid" DataSourceID="BookSource" Runat="Server" AutoGenerateColumns="False" DataKeyNames="BookID" AutoGenerateEditButton="False" AllowPaging="True" PageSize="4" CellPadding="3" HeaderStyle-Font-Size="10pt" HeaderStyle-BackColor="#707070" HeaderStyle-ForeColor="#FFFFFF" RowStyle-Font-Size="10pt" RowStyle-VerticalAlign="Top" EditRowStyle-BackColor="Yellow">

Page 25: Assignment Instructions

<Columns> <asp:TemplateField HeaderText="Edit"> <ItemTemplate> <asp:Button CommandName="Edit" Runat="Server" Text="Edit" Font-Size="8pt" Width="45px"/> </ItemTemplate> <EditItemTemplate> <asp:Button CommandName="Update" Runat="Server" Text="Update" Font-Size="8pt" Width="45px"/> <asp:Button CommandName="Cancel" Runat="Server" Text="Cancel" Font-Size="8pt" Width="45px"/> </EditItemTemplate> </asp:TemplateField> <asp:BoundField ReadOnly="True" HeaderText="ID" DataField="BookID"/> <asp:TemplateField HeaderText="Type"> <ItemTemplate> <asp:Label Text='<%# Eval("BookType") %>' Runat="Server"/> </ItemTemplate> <EditItemTemplate> <asp:DropDownList id="EditType" Runat="Server" DataSourceID="TypeSource" DataTextField="BookType" DataValueField="BookType" SelectedValue='<%# Bind("BookType") %>' Font-Size="8pt"/> </EditItemTemplate> </asp:TemplateField> <asp:BoundField ReadOnly="True" HeaderText="Title" DataField="BookTitle"/> <asp:BoundField ReadOnly="True" HeaderText="Author" DataField="BookAuthor"/> <asp:TemplateField HeaderText="Description"> <ItemTemplate> <asp:Panel Width="150px" Height="30px" Runat="Server" ScrollBars="Vertical"> <asp:Label Text='<%# Eval("BookDescription") %>' Runat="Server"/> </asp:Panel> </ItemTemplate> <EditItemTemplate> <asp:TextBox id="EditDescription" Runat="Server" Text='<%# Bind("BookDescription") %>' TextMode="MultiLine" Rows="3" Width="150px" Font-Name="Arial" Font-Size="8pt"/> </EditItemTemplate> </asp:TemplateField> <asp:BoundField ReadOnly="True" HeaderText="Price" DataField="BookPrice"

Page 26: Assignment Instructions

DataFormatString="{0:N}" ItemStyle-HorizontalAlign="Right"/> <asp:BoundField ReadOnly="True" HeaderText="Qty" DataField="BookQty" DataFormatString="{0:D}" ItemStyle-HorizontalAlign="Right"/> <asp:CheckBoxField ReadOnly="True" HeaderText="Sale" DataField="BookSale"/> </Columns>

</asp:GridView>Listing 9-3. Code for editable GridView.

Creating Edit Buttons

Rather than using default text buttons to edit, update, and cancel editing, an <asp:TemplateField> is defined to create a set of explicitly coded buttons to take on these functions. Since AutoGenerateEditButton="False" is coded for the GridView, an alternate set of buttons must be provided. They are provided here in the form of <asp:Button> controls. Coding for this TemplateField is repeated below.

<asp:TemplateField HeaderText="Edit"> <ItemTemplate> <asp:Button CommandName="Edit" Runat="Server" Text="Edit" Font-Size="8pt" Width="45px"/> </ItemTemplate> <EditItemTemplate> <asp:Button CommandName="Update" Runat="Server" Text="Update" Font-Size="8pt" Width="45px"/> <asp:Button CommandName="Cancel" Runat="Server" Text="Cancel" Font-Size="8pt" Width="45px"/> </EditItemTemplate></asp:TemplateField>Listing 9-4. Code for GridView Template column with edit buttons.

In addition to a HeaderTemplate, ItemTemplate, and FooterTemplate, a TemplateField can include an <EditItemTemplate>. Whereas its ItemTemplate describes the normal display appearance of a column; the EditItemTemplate describes its appearance when the row is placed in edit mode.

The ItemTemplate includes an <asp:Button> control taking the place of the normal "Edit" text button. This button's CommandName="Edit" property must be coded in order for the replacement button to function like its replaced text button. The EditItemTemple describes the column's appearance when in edit mode. In this case, <asp:Button> controls replace the normal "Update" and "Cancel" text buttons. They must contain CommandName="Update" and CommandName="Cancel" properties, respectively, to serve as functional replacements for the text buttons.

Editing with a BoundField

Page 27: Assignment Instructions

Several of the columns are formatted with <asp:BoundField> controls. These include the columns to display the BookID, BookTitle, BookAuthor, BookPrice, BookQty, andBookSale fields. Code for the BoundField used to display the BookID key field is repeated below. Other BoundField columns are similarly coded.

<asp:BoundField ReadOnly="True" HeaderText="ID" DataField="BookID"/>Listing 9-5. Code for GridView Bound column supplying record key defined in DataKeyNames.

This is a field named in the DataKeyNames list. Because it is a record key field, it must include the ReadOnly="True" property to prohibit display of an edit box when the row is placed in edit mode. The other fields formatted with BoundField controls—BookTitle, BookAuthor, BookPrice, BookQty, and BookSale—also have ReadOnly="True" settings to disallow changing their values even though they are not key fields. Recall that only the BookType and BookDescription fields are editable in this example.

Editing with a TemplateField

TemplateFields are used in place of BoundFields for display of the BookType and BookDescription columns. The reason for using TemplateFields is for greater control over their display and editing appearances.

In the case of the BookType field, a DropDownList supplies the BookType values from which to choose a replacement. Using a DropDownList of available types ensures that only valid types are chosen, something that cannot be guaranteed if editing is done in a standard free-entry textbox. Code for this TemplateField and its associated AccessDataSource are repeated below.

<asp:AccessDataSource id="TypeSource" Runat="Server" DataFile="../Databases/BooksDB.mdb" SelectCommand="SELECT DISTINCT BookType FROM Books ORDER BY BookType"/>

...<asp:TemplateField HeaderText="Type"> <ItemTemplate> <asp:Label Text='<%# Eval("BookType") %>' Runat="Server"/> </ItemTemplate> <EditItemTemplate> <asp:DropDownList id="EditType" Runat="Server" DataSourceID="TypeSource" DataTextField="BookType" DataValueField="BookType" SelectedValue='<%# Bind("BookType") %>' Font-Size="8pt"/> </EditItemTemplate></asp:TemplateField>Listing 9-6. Binding a data source to a GridView Template control.

The ItemTemplate displays the current BookType value for a record. This value is displayed through a Label control bound to the database value with a standard <%# Eval("BookType") %> binding expression.

The EditItemTemplate presents a DropDownList of BookType values drawn from the database for choosing a replacement value. Since this DropDownList is populated from the database, an

Page 28: Assignment Instructions

AccessDataSource must be associated with this control to extract all unique (DISTINCT) book types from the Books table. In addition, the DropDownList preselects the type that matches the database BookType for the particular record in the edit row. This matching selection takes place by coding a SelectedValue property for the DropDownList and binding (see below) to it the current BookType value for the record being edited.

Binding Updatable Fields

Notice the format of the binding that takes place for the SelectedValue property of the DropDownList.

SelectedValue='<%# Bind("BookType") %>'Listing 9-7. Binding an updatable DropDownList control.

In the previous Label control that simply displays the current value of the BookType, a binding expression in the form <%# Eval("fieldname")%> is used. This expression displays a value from a database; however, it does not permit updating of a changed value back to the database. The Eval() form of binding expression cannot be associated with a@fieldname parameter in an UPDATE statement.

Importantly, GridView fields that permit updating must be bound to their values with binding expressions in the form <%# Bind("fieldname") %>; that is, updatable values must usebinding rather than evaluating for their display.

<%# Bind("fieldname") %>

Figure 9-4. General format for binding an updatable field.

This type of binding takes place for updatable BoundField controls, although it takes place behind the scenes and is not specified. For other types of bound controls, such as those appearing in the EditItemTemplate of a TemplateField, this binding must be explicit.

It also is important to remember that when a Bind() expression is used for an updatable field, the bound control must be assigned an id value. This is the reason for the assignmentid="EditType" in the DropDownList in the above example. This id does not have to match the name of the bound data field, but it must be unique among all id values assigned to editable controls.

The TemplateField for the BookDescription field also contains an editable control. Its code is repeated below.

<asp:TemplateField HeaderText="Description"> <ItemTemplate> <asp:Panel Width="150px" Height="30px" Runat="Server" ScrollBars="Vertical"> <asp:Label Text='<%# Eval("BookDescription") %>' Runat="Server"/> </asp:Panel> </ItemTemplate> <EditItemTemplate> <asp:TextBox id="EditDescription" Runat="Server" Text='<%# Bind("BookDescription") %>' TextMode="MultiLine" Rows="3" Width="150px"

Page 29: Assignment Instructions

Font-Name="Arial" Font-Size="8pt"/> </EditItemTemplate></asp:TemplateField>Listing 9-8. Binding an updatable TextBox control.

The ItemTemplate defines a Label control to display the current database value for the record, linking to the value through the <%# Eval("BookDescription") %> binding expression. The Label appears inside a Panel control that is given style settings to create a scrollable display area.

The EditItemTemplate defines a multiline TextBox to display the editable book description. The TextBox uses the binding expression <%# Bind("BookDescription") %> and is assigned an id since changes to the description are updated in the database.

Using TemplateFields for DataKeys

In the current example, the BookID key field is formatted with a BoundField control. A key field also can be formatted as a TemplateField. The following field does not appear in the example, but can be used in place of the BoundField.

<asp:TemplateField HeaderText="ID"> <ItemTemplate> <asp:Label Text='<%# Eval("BookID") %>' Runat="Server"/> </ItemTemplate> <EditItemTemplate> <asp:Label Text='<%# Eval("BookID") %>' Runat="Server"/> </EditItemTemplate></asp:TemplateField>Listing 9-9. Binding a key field in a GridView Template.

When a TemplateField formats a DateKeyNames field, its value normally is displayed in a Label control in the EditItemTemplate, which does not permit editing and makes it unnecessary to code a ReadOnly="True" property. The data field is bound to the control with an Eval() expression rather than a Bind() expression since the value is not updatable.

Editing with a CheckBoxField

The BookSale field is defined as an <asp:CheckBoxField> to match its format in the database. It is also identical in format and function to the field supplied in the default GridView. In this example, the BookSale field is not updatable; therefore, it must be assigned a ReadOnly="True" property.

<asp:CheckBoxField ReadOnly="True" HeaderText="Sale" DataField="BookSale"/>Listing 9-10. Binding a CheckBoxField control.

An alternative to using the GridView's CheckBoxField is to create a TemplateField with an <asp:CheckBox> control. This alternate coding, although not used in the current example, is shown below.

<asp:TemplateField

Page 30: Assignment Instructions

HeaderText="Sale"> <ItemTemplate> <asp:CheckBox Checked='<%# Eval("BookSale") %>' Runat="Server"/> </ItemTemplate> <EditItemTemplate> <asp:CheckBox Checked='<%# Eval("BookSale") %>' Runat="Server"/> </EditItemTemplate></asp:TemplateField>Listing 9-11. Binding a CheckBox control.

The current setting for the CheckBox is given by assigning its Checked property from the database. Recall that this is a "Yes/No" field in the Access database that has a value ofTrue or False depending on whether it is checked or not. Therefore, the Checked property takes on a value of True or False, checking or not checking the CheckBox. Notice that this value is assigned with an Eval() expression in the EditItemTemplate since changing its value is not permitted. Were this to be an updatable field, it would be bound with aBind() expression and an id would be assigned to the field.

Update Parameters

In the present example, only the BookType and BookDescription fields are updated by rewriting the record with the matching BookID (primary key) value. The SQL UPDATEstatement assigned to the UpdateCommand property of the AccessDataSource reflects this updating.

<asp:AccessDataSource id="BookSource" Runat="Server" DataFile="../Databases/BooksDB.mdb" SelectCommand="SELECT * FROM Books ORDER BY BookID" UpdateCommand="UPDATE Books SET BookType=@BookType, BookDescription=@BookDescription WHERE BookID=@BookID"/>Listing 9-12. Using parameters in an UpdateCommand.

The general rule for coding the UpdateCommand is that a parameter appearing in an UPDATE clause to represent an updatable field must be one of the following:

a BoundField without a ReadOnly="True" property and without a DataKeyNames entry, a TemplateField in the EditItemTemplate with a Bind() binding

expression, without a ReadOnly="True" property, and without a DataKeyNames entry, an ImageField, CheckBoxField, or

HyperLinkField without a ReadOnly="True" property, with a Bind() binding expression, and without a DataKeyNames entry,

and a parameter name appearing in a WHERE clause to signify a key field used to update the matching record must be one of the following:

a BoundField with ReadOnly="True" and a DataKeyNames entry, a TemplateField with an Eval() binding expression and a DataKeyNames entry, an ImageField, CheckBoxField, or HyperLinkField with an Eval() binding expression and

a DataKeyNames entry.

Page 31: Assignment Instructions

In short, the data field appearing in a WHERE clause cannot also appear in the UPDATE clause since this would mean updating, or changing, the value used to locate the record being updated.

GridView Edit Events and Handlers

When performing GridView updating it is wise not to permit invalid or unreasonable data to enter the database. Prior to updating, then, changed data should be tested to avoid errors, either run-time errors caused by processing of invalid data or corruptions to the database from unreasonable data values.

The following GridView opens all data fields for editing and performs update validations to ensure that entered data are correct. When the "Update" button is clicked, enteredBookPrice and BookQty values are checked for nonnumeric characters. Also, the BookQty value is verified to be no larger that 100 units. If a validation test is not passed, the update event is cancelled and an error message is displayed.

Book Edit w/Validation

 

Edit ID Type Title Author Description Price Qty Sale

DB111 Database Oracle Database

K. Loney

Get thorough coverage of Oracle Database 10g from the most comprehensive reference available, published by Oracle Press. With in-depth details on all the new features, this powerhouse resource provides an overview of database architecture and Oracle Grid Computing technology, and covers SQL, SQL*Plus, PL/SQL, dynamic PL/SQL, object-oriented features, and Java programming in the Oracle environment. You'll also find valuable database administration and application development techniques, plus an alphabetical reference covering major Oracle commands, keywords, features, and functions, with cross-referencing of topics.

$69.99 10

DB222 Database Databases in Depth

C. J. Date

In Database in Depth, author and well-known database authority Chris Date lays out the fundamentals of the relational model. Don't let a lack to formal education in database theory hold you back. Instead, let Chris's clear explanation of relational concepts, set theory, the difference between model and implementation, relational algebra, normalization, and much more set you apart and well above the competition when it comes to getting work done with a relational database.

$29.95 6

DB333 Database Database Processing

D. Kroenke

Revised to reflect the needs of today's users, this 10th edition of Database Processing assures that you will learn marketable skills. By presenting SQL SELECT statements near the beginning of the book readers will know early on how to query data and obtain results-seeing firsthand some of the ways that database

$136.65 12

Page 32: Assignment Instructions

technology is useful in the marketplace. By utilizing free software downloads, you will be able to actively use a DBMS product by the end of the 2nd chapter. Each topic appears in the context of accomplishing practical tasks. Its spiral approach to database design provides users with enhanced information not available in other database books on the market.

DB444 Database Access Database Design

S. Roman

When using software products with graphical interfaces, we frequently focus so much on the details of how to use the interface that we forget about the general concepts that allow us to understand and use the software effectively. This is particularly true of a powerful database product like Microsoft Access. Novice, and sometimes even experienced, programmers are so concerned with how something is done in Access that they often lose sight of the general principles that underlie their database applications. Access Database Design and Programming takes you behind the details of the Access interface, focusing on the general knowledge necessary for Access power users or developers to create effective database applications.

$34.95 25

1 2 3 4 5 6 7 8

Figure 9-5. Updating a database with validation.

Notice in the following listing that most fields are defined as TemplateFields for greater control over their layout and formatting. All of these update fields have their data bindings made with Bind() expressions to supply values for their like-named parameters in the SQL UPDATE statement.

<SCRIPT Runat="Server">

Sub Edit_Row (Src As Object, Args As GridViewEditEventArgs)

EditMSG.Text = "Row " & Args.NewEditIndex + 1 & " edit"

End Sub

Sub Validate_Data (Src As Object, Args As GridViewUpdateEventArgs)

If Not IsNumeric(Args.NewValues("BookPrice")) Then Args.Cancel = True EditMSG.Text = "-- Book Price is not numeric. Record not updated." End If If Not IsNumeric(Args.NewValues("BookQty")) Then Args.Cancel = True EditMSG.Text = "-- Book Quantity is not numeric. Record not updated" End If If Args.Cancel = False Then If Args.NewValues("BookPrice") < 0 _ OR Args.NewValues("BookPrice") > 200 Then Args.Cancel = True

Page 33: Assignment Instructions

EditMSG.Text = "-- Book Price out of range. Record not updated" End If End If If Args.Cancel = False Then If Args.NewValues("BookQty") < 0 Then Args.Cancel = True EditMSG.Text = "-- Book Quantity out of range. Record not updated" End If End If End Sub

Sub Display_Message (Src As Object, Args As GridViewUpdatedEventArgs) EditMSG.Text = " Record " & Args.Keys("BookID") & " updated"

End Sub

</SCRIPT>

<form Runat="Server">

<asp:AccessDataSource id="BookSource" Runat="Server" DataFile="../Databases/BooksDB.mdb" SelectCommand="SELECT * FROM Books ORDER BY BookID" UpdateCommand="UPDATE Books SET BookType=@BookType, BookTitle=@BookTitle, BookAuthor=@BookAuthor, BookDescription=@BookDescription, BookPrice=@BookPrice, BookQty=@BookQty, BookSale=@BookSale WHERE BookID=@BookID"/>

<asp:AccessDataSource id="TypeSource" Runat="Server" DataFile="../Databases/BooksDB.mdb" SelectCommand="SELECT DISTINCT BookType FROM Books ORDER BY BookType"/>

<h3>Book Edit w/Validation</h3>

<asp:Label id="EditMSG" Text="&nbsp;" ForeColor="Red" Runat="Server"EnableViewState="False"/>

<asp:GridView id="EditGrid" DataSourceID="BookSource" Runat="Server" AutoGenerateEditButton="False" DataKeyNames="BookID" OnRowEditing="Edit_Row" OnRowUpdating="Validate_Data" OnRowUpdated="Display_Message" AutoGenerateColumns="False" AllowPaging="True" PageSize="4" CellPadding="3" HeaderStyle-Font-Size="10pt" HeaderStyle-BackColor="#707070" HeaderStyle-ForeColor="#FFFFFF" RowStyle-Font-Size="9pt" RowStyle-VerticalAlign="Top" EditRowStyle-BackColor="Yellow"> <Columns> <asp:TemplateField

Page 34: Assignment Instructions

HeaderText="Edit"> <ItemTemplate> <asp:Button CommandName="Edit" Runat="Server" Text="Edit" Font-Size="8pt" Width="45px"/> </ItemTemplate> <EditItemTemplate> <asp:Button CommandName="Update" Runat="Server" Text="Update" Font-Size="8pt" Width="45px"/> <asp:Button CommandName="Cancel" Runat="Server" Text="Cancel" Font-Size="8pt" Width="45px"/> </EditItemTemplate> </asp:TemplateField> <asp:TemplateField HeaderText="ID"> <ItemTemplate> <asp:Label Text='<%# Eval ("BookID") %>' Runat="Server"/> </ItemTemplate> <EditItemTemplate> <asp:Label Text='<%# Eval ("BookID") %>' Runat="Server"/> </EditItemTemplate> </asp:TemplateField> <asp:TemplateField HeaderText="Type"> <ItemTemplate> <asp:Label Text='<%# Eval("BookType") %>' Runat="Server"/> </ItemTemplate> <EditItemTemplate> <asp:DropDownList id="EditType" Runat="Server" DataSourceID="TypeSource" DataTextField="BookType" DataValueField="BookType" SelectedValue='<%# Bind("BookType") %>' Font-Size="8pt"/> </EditItemTemplate> </asp:TemplateField> <asp:TemplateField HeaderText="Title"> <ItemTemplate> <asp:Label Text='<%# Eval("BookTitle") %>' Runat="Server"/> </ItemTemplate> <EditItemTemplate> <asp:TextBox id="EditTitle" Runat="Server" Text='<%# Bind("BookTitle") %>' Font-Size="8pt" Width="80px"/> </EditItemTemplate> </asp:TemplateField> <asp:TemplateField HeaderText="Author"> <ItemTemplate> <asp:Label Text='<%# Eval("BookAuthor") %>' Runat="Server"/> </ItemTemplate> <EditItemTemplate> <asp:TextBox id="EditAuthor" Runat="Server" Text='<%# Bind("BookAuthor") %>' Font-Size="8pt" Width="80px"/> </EditItemTemplate> </asp:TemplateField> <asp:TemplateField

Page 35: Assignment Instructions

HeaderText="Description"> <ItemTemplate> <asp:Panel Runat="Server" Width="150px" Height="30px" Runat="Server" ScrollBars="Vertical"> <asp:Label Text='<%# Eval("BookDescription") %>' Runat="Server"/> </asp:Panel> </ItemTemplate> <EditItemTemplate> <asp:TextBox id="EditDescription" Runat="Server" Text='<%# Bind("BookDescription") %>' TextMode="MultiLine" Rows="3" Width="150px" Font-Name="Arial" Font-Size="8pt"/> </EditItemTemplate> </asp:TemplateField> <asp:TemplateField HeaderText="Price" ItemStyle-HorizontalAlign="Right"> <ItemTemplate> <asp:Label HorizontalAlign="Right" Runat="Server" Text='<%# String.Format("{0:N2}", Eval("BookPrice")) %>'/> </ItemTemplate> <EditItemTemplate> <asp:TextBox id="EditPrice" Runat="Server" Text='<%# Bind("BookPrice") %>' Font-Size="8pt" Width="50" MaxLength="7" Style="text-align:right"/> </EditItemTemplate> </asp:TemplateField> <asp:TemplateField HeaderText="Qty" ItemStyle-HorizontalAlign="Right"> <ItemTemplate> <asp:Label Text='<%# String.Format("{0:D}", Eval("BookQty")) %>' Runat="Server"/> </ItemTemplate> <EditItemTemplate> <asp:TextBox id="EditQuantity" Runat="Server" Text='<%# Bind("BookQty") %>' Font-Size="8pt" Width="30" MaxLength="2" Style="text-align:right"/> </EditItemTemplate> </asp:TemplateField>

<asp:CheckBoxField HeaderText="Sale" DataField="BookSale"/> </Columns>

</asp:GridView>

</form>Listing 9-13. Code to update Books table with validation.

GridView Edit Event

When a row's "Edit" button is clicked, a GridView issues a RowEditing event that can be intercepted and programmed by including an OnRowEditing event handler as shown in the above listing. This event handler calls a named subprogram, Edit_Row in this example, through

Page 36: Assignment Instructions

which event properties are exposed. This event, its event handler, and its subprogram argument are shown in the following listing.

Event Event Handler Signature Description

RowEditing OnRowEditing GridViewEditEventArgs A record is being edited but has not yet been updated to its data source.

Figure 9-6. GridView RowEditing event, event handler, and subprogram argument.

For the RowEditing event, there are two properties exposed to a subprogram called by the OnRowEditing event handler and received through argument GridViewEditEventArgs. Script references to these properties are in the formats shown below.

argument.NewEditIndexargument.Cancel="False|True"

Figure 9-7. General formats for referencing GridView RowEditing properties.

The NewEditIndex property returns the index of the row being editing (counting from 0). The Cancel property can be set to True to halt editing and return the row to display mode from edit mode. The following subprogram is called by the OnRowEditing handler for the example GridView to report the row number being edited when the "Edit" button is clicked.

Sub Edit_Row (Src As Object, Args As GridViewEditEventArgs)

EditMSG.Text = "Row " & Args.NewEditIndex + 1 & " edit"

End SubListing 9-14. Determining GridView row indexes.

Properties are exposed through the subprogram's argument, named Args in this example. Notice that 1 is added to the Args.NewEditIndex property for the edit row. Since GridView row indexing begins with 0, this addition reports a row number that is more obvious when viewing the GridView. This message is displayed in the UpdateMSG Label appearing above the GridView. Editing is not cancelled, but awaits the user's click on the now-displayed "Update" and "Cancel" buttons.

GridView Update Events

When a row's "Update" button is clicked, a GridView issues two events which can be intercepted and programmed with event handlers. A subprogram can be called on aRowUpdating event, when the update process begins, and on a RowUpdated event, when updating is completed. These two events, their event handlers, and their subprogram arguments are shown below.

Event Event Handler Signature Description

RowUpdating OnRowUpdating GridViewUpdateEventArgs A record is being updated but has not yet been rewritten to the data source.

Page 37: Assignment Instructions

RowUpdated OnRowUpdated GridViewUpdatedEventArgs An updated record has been rewritten to the data source.

Figure 9-8. GridView update events, event handlers, and subprogram arguments.

To trap a RowUpdating event in a GridView, an OnRowUpdating event handler is coded; to trap a RowUpdated event, an OnRowUpdated handler is coded. These event handlers are associated with subprograms containing arguments GridViewUpdateEventArgs and GridViewUpdatedEventArgs, respectively.

Subprogram arguments for both RowUpdating and RowUpdated events include properties that can be investigated by scripts. Script references to these properties are in the general formats shown below.

--GridViewUpdateEventArgs Properties--

argument.OldValues("field")argument.NewValues("field")argument.Keys("field")argument.RowIndexargument.Cancel="False|True"

--GridViewUpdatedEventArgs Properties--

argument.OldValues("field")argument.NewValues("field")argument.Keys("field")argument.AffectedRowsargument.KeepInEditMode="False|True"

Figure 9-9. General formats for referencing GridView RowUpdating and RowUpdated properties.

For both RowUpdating and RowUpdated events, the OldValues property contains a collection of field names and values from the original GridView row; the NewValues property contains a collection of names and values from the changed row; the Keys property contains names and values of key fields for the row. For the RowUpdating event, a Cancelproperty is available and can be set to True during updating, prior to the RowUpdated event, in order to cancel updating. The RowIndex property returns the index of the row (counting from 0) that is being updated. For the RowUpdated event, the AffectedRows property gives the number of rows updated. Normally, when the "Update" button is clicked and a row is updated, the GridView switches back to display mode. A row can be kept in edit mode by setting the KeepInEditMode property to True.

The following Validate_Data subprogram is called on a RowUpdating event for the example GridView to test entered values for the BookPrice and BookQty fields. If the data are invalid (nonnumeric) or not within a reasonable range (less than 0), then updating is cancelled and an error message is displayed.

Sub Validate_Data (Src As Object, Args As GridViewUpdateEventArgs)

If Not IsNumeric(Args.NewValues("BookPrice")) Then Args.Cancel = True EditMSG.Text = "-- Book Price is not numeric. Record not updated." End If

Page 38: Assignment Instructions

If Not IsNumeric(Args.NewValues("BookQty")) Then Args.Cancel = True EditMSG.Text = "-- Book Quantity is not numeric. Record not updated" End If

If Args.Cancel = False Then If Args.NewValues("BookPrice") < 0 _ OR Args.NewValues("BookPrice") > 200 Then Args.Cancel = True EditMSG.Text = "-- Book Price out of range. Record not updated" End If End If

If Args.Cancel = False Then If Args.NewValues("BookQty") < 0 Then Args.Cancel = True EditMSG.Text = "-- Book Quantity out of range. Record not updated" End If End If

End SubListing 9-15. Validating GridView values during updating.

Changed values are tested with references in the format Args.NewValues("field"), using the field names given by the DataField properties of BoundFields or the binding names in TemplateFields. If a validation test fails, then the GridView's RowUpdating event is cancelled (Args.Cancel="True") and an error message is displayed in the page's EditMSGLabel. If all validation tests are passed (Args.Cancel is still False), then the record is automatically updated.

These are just a couple of the validation tests that could, and probably should, be made on user-entered data. If other fields were open to editing, then other tests are appropriate. These current checks just serve as examples of the kinds of tests that can be performed. In a production environment it is often the case that more code is written to perform data validation that to carry out actual processing performed on the data.

One final subprogram supports current GridView updating. The following Display_Message subprogram is called on a RowUpdated event (when the RowUpdating event has not been cancelled). It displays a record-updated message in the EditMSG Label.

Sub Display_Message (Src As Object, Args As GridViewUpdatedEventArgs)

EditMSG.Text = " Record " & Args.Keys("BookID") & " updated"

End SubListing 9-16. Trapping the GridView updated event.

Again, caution is needed to match the event handler, OnRowUpdated in this case, with the appropriate argument, GridViewUpdatedEventArgs for this subprogram.

GridView Delete Events and Handlers

In addition to record editing, a GridView supports record deletion. By coding its AutoGenerateDeleteButton="True" property, a "Delete" text button is displayed in a column of the grid that, when clicked, deletes the associated record. You can, if needed, create a

Page 39: Assignment Instructions

"Delete" button without an accompanying "Edit" button. The default appearance of the "Delete" button is shown below.

Book Edit w/Deletion

 

  BookID BookType BookTitle BookAuthor BookPrice BookQty BookSale

Edit Delete DB111 Database Oracle Database K. Loney $69.99 10

Edit Delete DB222 Database Databases in Depth C. J. Date $29.95 6

Edit Delete DB333 Database Database Processing D. Kroenke $136.65 12

Edit Delete DB444 DatabaseAccess Database Design

S. Roman $34.95 25

Edit Delete DB555 Database SQL Server 2005 P. Debetta $29.99 0

Figure 9-10. Adding a delete function to a GridView.

Complete coding for the above GridView, AccessDataSource, and script processing is shown below.

<SCRIPT Runat="Server">

Sub Display_Message (Src As Object, Args As GridViewDeletedEventArgs)

EditMSG.Text = "Record " & Args.Keys("BookID") & " deleted"

End Sub

</SCRIPT>

<form Runat="Server">

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

SelectCommand="SELECT BookID, BookType, BookTitle, BookAuthor, BookPrice, BookQty, BookSale FROM Books WHERE BookType='Database' ORDER BY BookID"

UpdateCommand="UPDATE Books SET BookType=@BookType, BookTitle=@BookTitle, BookAuthor=@BookAuthor, BookPrice=@BookPrice, BookQty=@BookQty, BookSale=@BookSale WHERE BookID=@BookID"

DeleteCommand="DELETE FROM Books WHERE BookID = @BookID"/>

<h3>Book Edit w/Deletion</h3>

<asp:Label id="EditMSG" Text="&nbsp;" ForeColor="Red" Runat="Server"EnableViewState="False"/>

<asp:GridView id="EditGrid" DataSourceID="BookSource" Runat="Server" DataKeyNames="BookID" AutoGenerateEditButton="True"

Page 40: Assignment Instructions

AutoGenerateDeleteButton="True" OnRowDeleted="Display_Message" HeaderStyle-Font-Size="10pt" RowStyle-Font-Size="10pt"/>

</form>Listing 9-17. Code for default GridView with delete feature.

When the record-deletion feature is added to a GridView, its associated AccessDataSource must include a DeleteCommand with an SQL DELETE statement. This is a parameterized statement using the DataKeyNames field to identify the record to be deleted.

GridView Delete Events

A GridView recognizes RowDeleting and RowDeleted events which can be trapped for subprogram calls. These events, their handlers, and their subprogram arguments are listed below.

Event Event Handler Signature Description

RowDeleting OnRowDeleting GridViewDeleteEventArgs A record is being deleted but has not yet been deleted from the data source.

RowDeleted OnRowDeleted GridViewDeletedEventArgs A record has been deleted from the data source.

Figure 9-11. GridView delete events, event handlers, and subprogram arguments.

A GridView can include OnRowDeleting and OnRowDeleted event handlers to trap and program RowDeleting and RowDeleted events. Called subprograms have the argumentsGridViewDeleteEventArgs and GridViewDeletedEventArgs, respectively. The following properties are accessible in subprograms called by these two events.

--GridViewDeleteEventArgs Properties--

argument.Values("field")argument.Keys("field")argument.RowIndexargument.Cancel="False|True"

--GridViewDeletedEventArgs Properties--

argument.Values("field")argument.Keys("field")argument.AffectedRows

Figure 9-12. General formats for referencing GridView RowDeleting and RowDeleted properties.

The Values property returns the value in a field that is being deleted or has been deleted. The Keys property returns the value of a key field. The RowIndex property gives the row number (counting from 0) of the row being deleted. Prior to the RowDeleted event, the Cancel property can be set to True to halt record deletion. After deletion, the AffectedRowsproperty gives the number of rows deleted.

Page 41: Assignment Instructions

In the example GridView, an OnRowDeleted event handler calls the Display_Message subprogram with its GridViewDeletedEventArgs argument. The subprogram displays a record-deleted message that includes the key of the deleted record—Args.Keys("BookID").

Delete Buttons

Rather than using the default "Delete" text link, an <asp:Button CommandName="Delete"> control can be coded in a TemplateField, as is done for previous edit, update, and cancel buttons. Unlike the case with update buttons, though, there is only a single delete button coded in the <ItemTemplate> of the Template Field. There is no associated<EditItemTemplate> needed because a delete button does not cause a switch to an editing row. It immediately deletes its associated row.

<asp:TemplateField HeaderText="Edit"> <ItemTemplate> <asp:Button CommandName="Edit" Runat="Server" Text="Edit" Font-Size="8pt" Width="45px"/> <asp:Button CommandName="Delete" Runat="Server" Text="Delete" Font-Size="8pt" Width="45px"/> </ItemTemplate> <EditItemTemplate> <asp:Button CommandName="Update" Runat="Server" Text="Update" Font-Size="8pt" Width="45px"/> <asp:Button CommandName="Cancel" Runat="Server" Text="Cancel" Font-Size="8pt" Width="45px"/> </EditItemTemplate></asp:TemplateField>Listing 9-18. Code for GridView Template column with delete button.

It is probably not a good idea to immediately delete a record when the "Delete" button is clicked. The button could be inadvertently clicked and cause loss of information that is difficult, if not impossible, to recreate. This is why record deletion processes often include a confirmation step asking the user to verify deletion. Delete confirmation is not built into a GridView; it must be coded and scripted. This task is taken up in a later tutorial. However, you might recognize that the separate RowDeleting and RowDeleted events offer the opportunity to insert a confirmation step between the former and latter events.

A GridView does not include a built-in feature to add records to a database. This database maintenance need can be realized with a DetailsView or FormView control, either stand-alone or in combination with a GridView. These editing controls are described in subsequent tutorials.

Editing Records - DetailsView

A DetailsView control includes features to perform basic editing of database records in the same manner as in a GridView. In addition, a DetailsView can include features for adding new records to a database. In the following example, a default DetailsView that displays books from the BooksDB.mdb database includes an "Edit" button for updating a book record. In addition, "Delete" and "New" buttons delete existing records from the database and display data entry fields for adding new records to the database.

Page 42: Assignment Instructions

Note that making actual changes to the BooksDB.mdb database is not permitted in these tutorials; however, all other functions work as expected.

Book Edit

 

BookID DB111

BookType Database

BookTitle Oracle Database

BookAuthor K. Loney

BookDescription Get thorough coverage of Oracle Database 10g from the most comprehensive reference available, published by Oracle Press. With in-depth details on all the new features, this powerhouse resource provides an overview of database architecture and Oracle Grid Computing technology, and covers SQL, SQL*Plus, PL/SQL, dynamic PL/SQL, object-oriented features, and Java programming in the Oracle environment. You'll also find valuable database administration and application development techniques, plus an alphabetical reference covering major Oracle commands, keywords, features, and functions, with cross-referencing of topics.

BookPrice $69.99

BookQty 10

BookSale

Edit Delete New

1 2 3 4 5 6 7 8 9 10 ...

Figure 9-13. Editing a database with a default DetailsView.

Complete coding for the AccessDataSource, DetailsView, and processing scripts for this application are shown below. You should recognize similarities to the setup for editing with a GridView.

<SCRIPT Runat="Server">

Sub Display_Updated_Msg (Src As Object, Args As DetailsViewUpdatedEventArgs) EditMSG.Text = "Record " & Args.Keys("BookID") & " updated"End Sub

Sub Display_Deleted_Msg (Src As Object, Args As DetailsViewDeletedEventArgs) EditMSG.Text = "Record " & Args.Keys("BookID") & " deleted"End Sub

Sub Display_Inserted_Msg (Src As Object, Args As DetailsViewInsertedEventArgs) EditMSG.Text = "Record " & Args.Values("BookID") & " added"End Sub

</SCRIPT>

<form Runat="Server">

<asp:AccessDataSource id="BookSource" Runat="Server" DataFile="../Databases/BooksDB.mdb" SelectCommand="SELECT * FROM Books ORDER BY BookID"

Page 43: Assignment Instructions

InsertCommand="INSERT INTO Books (BookID, BookType, BookTitle, BookAuthor, BookDescription, BookPrice, BookQty, BookSale) VALUES (@BookID, @BookType, @BookTitle, @BookAuthor, BookDescription=@BookDescription, @BookPrice, @BookQty, @BookSale)" UpdateCommand="UPDATE Books SET BookType=@BookType, BookTitle=@BookTitle, BookAuthor=@BookAuthor, BookDescription=@BookDescription, BookPrice=@BookPrice, BookQty=@BookQty, BookSale=@BookSale WHERE BookID=@BookID" DeleteCommand="DELETE FROM Books WHERE BookID = @BookID"/>

<h3>Book Edit</h3>

<asp:Label id="EditMSG" ForeColor="Red" Runat="Server"EnableViewState="False" Text=" "/>

<asp:DetailsView id="EditView" DataSourceID="BookSource" Runat="Server" DataKeyNames="BookID" AutoGenerateEditButton="True" AutoGenerateDeleteButton="True" AutoGenerateInsertButton="True" OnItemUpdated="Display_Updated_Msg" OnItemDeleted="Display_Deleted_Msg" OnItemInserted="Display_Inserted_Msg" AllowPaging="True" RowStyle-Font-Size="10pt" RowStyle-VerticalAlign="Top"/>Listing 9-19. Code for default DetailsView with editing.

"Edit," "Delete," and "Insert" buttons are added to the DetailsView by coding AutoGenerateEditButton="True", AutoGenerateDeleteButton="True", andAutoGenerateInsertButton="True" properties. Also, the DataKeyNames property gives the database field (BookID) that serves as the unique identifier, the primary key, for the records. In this example, the recordset is paged.

When all editing functions are implemented for a DetailsView, its AccessDataSource requires a SelectCommand (SELECT statement) to select records for display in the DetailsView, a parameterized UpdateCommand (UPDATE statement) to rewrite changes to the database, a parameterized InsertCommand (INSERT statement) to add records to the database, and a parameterized DeleteCommand (DELETE statement) to delete records from the database.

You should be familiar with formatting for the UPDATE and DELETE statements since they are similar to those used for editing a GridView. The parameterized INSERT statement uses similar formatting. Database fields are associated with edit box parameters to write a new record with new data values to the database. In this case, the primary key field also is written as part of the record; that is, field BookID is associated with parameter @BookID in the matching list of INSERT fields and field VALUES.

Editing Events and Event Handlers

Page 44: Assignment Instructions

Update, insert, and delete buttons carry out their tasks automatically unless scripts are provided to intercept processing. Subprograms can be called by these buttons by coding event handlers in the DetailsView. The following events can be scripted.

Event Event Handler Signature Description

ItemInserting OnItemInserting DetailsViewInsertEventArgs A new record is being added to the data source but has not yet been written.

ItemInserted OnItemInserted DetailsViewInsertedEventArgs A new record has been written to the data source.

ItemUpdating OnItemUpdating DetailsViewUpdateEventArgs A record is being updated but has not yet been rewritten to the data source.

ItemUpdated OnItemUpdated DetailsViewUpdatedEventArgs An updated record has been rewritten to the data source.

ItemDeleting OnItemDeleting DetailsViewDeleteEventArgs A record is being deleted from the data source but has not yet been deleted.

ItemDeleted OnItemDeleted DetailsViewDeletedEventArgs A record has been deleted from the data source.

Figure 9-14. DetailsView event handlers and subprogram arguments.

In the current example, OnItemUpdated, OnItemDeleted, and OnItemInserted event handlers are added to the DetailsView in order to display editing messages associated with completion of these events. As in the case with a GridView, subprograms are exposed to event properties. The list of accessible properties for the six events of DetailsView editing are shown below. Most of these should be familiar from identical properties for the GridView.

--DetailsViewInsertEventArgs Properties--

argument.Values("field")argument.Cancel="False|True"

--DetailsViewInsertedEventArgs Properties--

argument.Values("field")argument.AffectedRowsargument.KeepInInsertMode="False|True"

--DetailsViewUpdateEventArgs Properties--

argument.OldValues("field")argument.NewValues("field")argument.Keys("field")argument.Cancel="False|True"

Page 45: Assignment Instructions

--DetailsViewUpdatedEventArgs Properties--

argument.OldValues("field")argument.NewValues("field")argument.Keys("field")argument.AffectedRowsargument.KeepInEditMode="False|True"

--DetailsViewDeleteEventArgs Properties--

argument.Values("field")argument.Keys("field")argument.Cancel="False|True"

--DetailsViewDeletedEventArgs Properties--

argument.Values("field")argument.Keys("field")argument.AffectedRows

Figure 9-15. General formats for referencing DetailsView event properties.

Subprograms for the example DetailsView report the key field for updated and deleted records—Args.Keys("BookID")—and the key value for an inserted record—Args.Values("BookID"). In this latter case, the BookID field is not a key field until after it has been inserted into the database. Therefore, it is just another field value until after insertion.

The <asp:CommandField> Control

Default link buttons are supplied for editing functions by specifying "True" for the AutoGenerateInsertButton, AutoGenerateUpdateButton, and AutoGenerateDeleteButtonproperties of the DetailsView. These buttons appear as text links at the bottom of the DetailsView. An alternative is to set these properties to False (the default value) and to define an <asp:CommandField> to display selected buttons at selected locations in selected styles.

The following DetailsView incorporates a CommandField to override default editing buttons. The remaining data fields take on default appearances as in the previous DetailsView.

Book Edit

 

BookID DB111

BookType Database

BookTitle Oracle Database

BookAuthor K. Loney

BookDescription Get thorough coverage of Oracle Database 10g from the most comprehensive reference available, published by Oracle Press. With in-depth details on all the new features, this powerhouse resource provides an overview of database architecture and Oracle Grid

Page 46: Assignment Instructions

Computing technology, and covers SQL, SQL*Plus, PL/SQL, dynamic PL/SQL, object-oriented features, and Java programming in the Oracle environment. You'll also find valuable database administration and application development techniques, plus an alphabetical reference covering major Oracle commands, keywords, features, and functions, with cross-referencing of topics.

BookPrice $69.99

BookQty 10

BookSale

    

1 2 3 4 5 6 7 8 9 10 ...

Figure 9-16. A DetailsView with CommandField buttons.

The general format for a CommandField, which appears inside the <Fields> tag of the DetailsView, is shown below along with associated properties for the DetailsView and CommandField.

<asp:DetailsView AutoGenerateInsertButton="False|True" AutoGenerateEditButton="False|True" AutoGenerateDeleteButton="False|True" OnItemDeleting="subprogram" OnItemDeleted="subprogram" OnItemInserting="subprogram" OnItemInserted="subprogram" OnItemUpdating="subprogram" OnItemUpdated="subprogram" InsertRowStyle-property="value"... EditRowStyle-property="value"... DeleteRowStyle-property="value"... ...>

<Fields>

<asp:CommandField ButtonType="Link|Button" ShowCancelButton="False|True" ShowDeleteButton="False|True" ShowEditButton="False|True" ShowInsertButton="False|True" ShowHeader="False|True" CancelText="string" DeleteText="string" EditText="string" InsertText="string" HeaderText="string" NewText="string" UpdateText="string"

Page 47: Assignment Instructions

HeaderStyle-property="value"... ItemStyle-property="value"... ControlStyle-property="value"... /> ...

</Fields>

</asp:DetailsView>

Figure 9-17. General format for CommandField row type of DetailsView control.

An <asp:CommandField> control appears in the row of the DetailsView wherever it is coded. If editable rows are automatically generated as in the current example, the CommandField appears at the bottom. It defines a row where one or more editing buttons can be placed by coding ShowInsertButton="True," ShowEditButton="True," and/orShowDeleteButton="True" properties for the DetailsView. By default, the buttons are displayed as text links unless ButtonType="Button" is coded. Standard text labels are displayed for the buttons; these can be overridden with CancelText, DeleteText, EditText, InsertText, NewText, and UpdateText properties with different labels for the buttons. Style settings for buttons are given by coding ControlStyle properties for the CommandField.

For updating a displayed record, an "Edit" button is displayed. When clicked, a second set of "Update" and "Cancel" buttons appears to rewrite changes to the database or to cancel the task. For adding a new record to the database, a "New" button is displayed. When clicked, a second set of "Insert" and "Cancel" buttons appears and all fields are converted into data-entry types. Clicking the "Insert" button adds the new record to the database. For deleting a record, a "Delete" button is displayed. When clicked, the record is immediately deleted from the database.

Shown below is code for the example DetailsView with CommandField buttons displayed.

<asp:DetailsView id="EditView" DataSourceID="BookSource" Runat="Server" DataKeyNames="BookID" AutoGenerateEditButton="False" AutoGenerateDeleteButton="False" AutoGenerateInsertButton="False" OnItemUpdated="Display_Updated_Msg" OnItemDeleted="Display_Deleted_Msg" OnItemInserted="Display_Inserted_Msg" AllowPaging="True" RowStyle-Font-Size="10pt"

RowStyle-VerticalAlign="Top">

<Fields>

<asp:CommandField ButtonType="Button" HeaderStyle-BackColor="#E0E0E0" ItemStyle-BackColor="#E0E0E0" ShowHeader="True" ShowInsertButton="True" ShowEditButton="True" ShowDeleteButton="True" ControlStyle-Font-Size="9pt" ControlStyle-Width="50px"/>

Page 48: Assignment Instructions

</Fields>

</asp:DetailsView>Listing 9-20. Code for DetailsView with CommandField to display edit buttons.

Incidentally, a CommandField can be supplied for a GridView. However, since a GridView does not support new record insertions, ShowInsertButton should be set to False so that it does not display and leave the false impression that the “Insert” button is functional.

Formatted DetailsView Editing

The basic DetailsView can be expanded with bound columns for more control over display of data and editing fields. The following formatted DetailsView provides the same functionality as previous examples with the addition of validation of entered data identical to the processing done through the previous GridView control.

Book Edit

  

   

ID: DB111

Type: Database

Title: Oracle Database

Author: K. Loney

Description:

Get thorough coverage of Oracle Database 10g from the most comprehensive reference available, published by Oracle Press. With in-depth details on all the new features, this powerhouse resource provides an overview of database architecture and Oracle Grid Computing technology, and covers SQL, SQL*Plus, PL/SQL, dynamic PL/SQL, object-oriented features, and Java programming in the Oracle environment. You'll also find valuable database administration and application development techniques, plus an alphabetical reference covering major Oracle commands, keywords, features, and functions, with cross-referencing of topics.

Price: $69.99

Qty: 10

Sale:

1 2 3 4 5 6 7 8 9 10 ...

Figure 9-18. DetailsView editing with bound columns and data validation.

Coding the DetailsView

Coding for the DetailsView and its AccessDataSource for the above example is shown below. The AccessDataSource is identical to the previous example. A second AccessDataSource supplies values for DropDownLists of book types during updates and additions. A default DetailsView is overridden with bound data fields and template fields. Also, neither auto-generated

Page 49: Assignment Instructions

buttons nor a CommandField is used to display editing buttons. Instead, editing buttons are coded as standard Button controls inside a TemplateField.

<asp:AccessDataSource id="BookSource" Runat="Server" DataFile="../Databases/BooksDB.mdb" SelectCommand="SELECT * FROM Books ORDER BY BookID" InsertCommand="INSERT INTO Books (BookID, BookType, BookTitle, BookAuthor, BookDescription, BookPrice, BookQty, BookSale) VALUES (@BookID, @BookType, @BookTitle, @BookAuthor, BookDescription=@BookDescription, @BookPrice, @BookQty, @BookSale)" UpdateCommand="UPDATE Books SET BookType=@BookType, BookTitle=@BookTitle, BookAuthor=@BookAuthor, BookDescription=@BookDescription, BookPrice=@BookPrice, BookQty=@BookQty, BookSale=@BookSale WHERE BookID=@BookID" DeleteCommand="DELETE FROM Books WHERE BookID = @BookID"/>

<asp:AccessDataSource id="TypeSource" Runat="Server" DataFile="../Databases/BooksDB.mdb" SelectCommand="SELECT DISTINCT BookType FROM Books ORDER BY BookType"/>

<h3>Book Edit</h3>

<asp:Label id="EditMSG" Text=" " ForeColor="Red" Runat="Server"EnableViewState="False"/>

<asp:DetailsView id="EditView" DataSourceID="BookSource" Runat="Server" DataKeyNames="BookID" AutoGenerateRows="False" AutoGenerateInsertButton="False" AutoGenerateEditButton="False" AutoGenerateDeleteButton="False" OnItemInserting="Validate_Insert_Data" OnItemInserted="Display_Insert_Msg" OnItemUpdating="Validate_Update_Data" OnItemUpdated="Display_Update_Msg" OnItemDeleted="Display_Delete_Msg" AllowPaging="True" BorderStyle="Solid" BorderWidth="1" CellPadding="3" GridLines="None" RowStyle-Font-Size="11pt" RowStyle-VerticalAlign="Top"> <Fields> <asp:TemplateField HeaderStyle-BackColor="#E0E0E0" ItemStyle-BackColor="#E0E0E0"> <ItemTemplate> <asp:Button CommandName="New" Text="New" Runat="Server" Font-Size="8pt" Width="45px"/> <asp:Button CommandName="Edit" Text="Edit" Runat="Server" Font-Size="8pt" Width="45px"/> <asp:Button CommandName="Delete" Text="Delete" Runat="Server" Font-Size="8pt" Width="45px"/>

Page 50: Assignment Instructions

</ItemTemplate> <EditItemTemplate> <asp:Button CommandName="Update" Text="Update" Runat="Server" Font-Size="8pt" Width="45px"/> <asp:Button CommandName="Cancel" Text="Cancel" Runat="Server" Font-Size="8pt" Width="45px"/> </EditItemTemplate> <InsertItemTemplate> <asp:Button CommandName="Insert" Text="Insert" Runat="Server" Font-Size="8pt" Width="45px"/> <asp:Button CommandName="Cancel" Text="Cancel" Runat="Server" Font-Size="8pt" Width="45px"/> </InsertItemTemplate> </asp:TemplateField> <asp:BoundField ReadOnly="True" DataField="BookID" HeaderText="ID: " HeaderStyle-BackColor="#E0E0E0" HeaderStyle-Font-Bold="True"/> <asp:TemplateField HeaderText="Type: " HeaderStyle-BackColor="#E0E0E0" HeaderStyle-Font-Bold="True"> <ItemTemplate> <asp:Label Text='<%# Eval("BookType") %>' Runat="Server"/> </ItemTemplate> <EditItemTemplate> <asp:DropDownList id="EditType" Runat="Server" DataSourceID="TypeSource" DataTextField="BookType" DataValueField="BookType" SelectedValue='<%# Bind("BookType") %>'/> </EditItemTemplate> </asp:TemplateField> <asp:BoundField DataField="BookTitle" HeaderText="Title: " HeaderStyle-BackColor="#E0E0E0" HeaderStyle-Font-Bold="True"/> <asp:BoundField DataField="BookAuthor" HeaderText="Author: " HeaderStyle-BackColor="#E0E0E0" HeaderStyle-Font-Bold="True"/> <asp:TemplateField HeaderText="Description: " HeaderStyle-BackColor="#E0E0E0" HeaderStyle-Font-Bold="True"> <ItemTemplate> <asp:Panel Width="400px" Height="110px" BorderWidth="1px" BorderColor="#C0C0C0" ScrollBars="Auto" Runat="Server"> <asp:Label Text='<%# Eval("BookDescription") %>' Font-Size="10pt" Runat="Server"/> </asp:Panel> </ItemTemplate> <EditItemTemplate> <asp:TextBox id="EditDescription" Runat="Server" Text='<%# Bind("BookDescription") %>'

Page 51: Assignment Instructions

TextMode="MultiLine" Height="110" Width="400px" Font-Name="Arial" Font-Size="9pt"/> </EditItemTemplate> </asp:TemplateField> <asp:BoundField DataField="BookPrice" HtmlEncode="False" DataFormatString="{0:C}" HeaderText="Price: " HeaderStyle-BackColor="#E0E0E0" HeaderStyle-Font-Bold="True"/> <asp:BoundField DataField="BookQty" HtmlEncode="False" DataFormatString="{0:D}" HeaderText="Qty: " HeaderStyle-BackColor="#E0E0E0" HeaderStyle-Font-Bold="True"/> <asp:CheckBoxField DataField="BookSale" HeaderText="Sale: " HeaderStyle-BackColor="#E0E0E0" HeaderStyle-Font-Bold="True"/> </Fields>

</asp:DetailsView>Listing 9-21. Code for DetailsView with Bound and Template fields.

Default display of editing buttons is turned off by assigning "False" to the AutoGenerateInsertButton, AutoGenerateEditButton, and AutoGenerateDeleteButton properties. This same effect is achieved by not coding these properties. Also, event handlers are coded for the ItemInserting, ItemInserted, ItemUpdating, ItemUpdated, and ItemDeleted events. Subprograms are called on these events to validate entered data and to display process completion messages.

Default edit buttons are replaced by <asp:Button> controls coded in a TemplateField. This coding is repeated below.

<asp:TemplateField HeaderStyle-BackColor="#E0E0E0" ItemStyle-BackColor="#E0E0E0">

<ItemTemplate> <asp:Button CommandName="New" Text="New" Runat="Server" Font-Size="8pt" Width="45px"/> <asp:Button CommandName="Edit" Text="Edit" Runat="Server" Font-Size="8pt" Width="45px"/> <asp:Button CommandName="Delete" Text="Delete" Runat="Server" Font-Size="8pt" Width="45px"/> </ItemTemplate>

<EditItemTemplate> <asp:Button CommandName="Update" Text="Update" Runat="Server" Font-Size="8pt" Width="45px"/> <asp:Button CommandName="Cancel" Text="Cancel" Runat="Server" Font-Size="8pt" Width="45px"/> </EditItemTemplate>

Page 52: Assignment Instructions

<InsertItemTemplate> <asp:Button CommandName="Insert" Text="Insert" Runat="Server" Font-Size="8pt" Width="45px"/> <asp:Button CommandName="Cancel" Text="Cancel" Runat="Server" Font-Size="8pt" Width="45px"/> </InsertItemTemplate>

</asp:TemplateField>Listing 9-22. Code for DetailsView Template field with command buttons.

An <ItemTemplate> includes the "New," "Edit," and "Delete" buttons that initiate editing. An <EditItemTemplate> includes the "Update" and "Cancel" buttons common for display of update fields. An <InsertItemTemplate> includes controls for the "Insert" and "Cancel" buttons displayed on record additions. You need to made sure that the properCommandName values are assigned to these buttons to work like the default text buttons.

The remainder of the DetailsView's display and editing fields are similar to those used previously in a GridView. BoundField, TemplateField, and other controls bind to database fields for updating existing records and adding new records to the database. Notice that none of the TemplateFields include <InsertItemTemplate> sections. This template can be defined along with an <EditItemTemplate> if the format of the insert field is different from that of an update field.

DetailsView Event Scripting

Scripts for the five events trapped for the DetailsView are shown below: ItemInserting, ItemInserted, ItemUpdating, ItemUpdated, and ItemDeleted.

<SCRIPT Runat="Server">

'-- OnItemInserting --Sub Validate_Insert_Data (Src As Object, Args As DetailsViewInsertEventArgs) If Args.Values("BookID") = "" Then Args.Cancel = True EditMSG.Text = "-- Missing BookID. Record not added." End If If Not IsNumeric(Args.Values("BookPrice")) Then Args.Cancel = True EditMSG.Text = "-- Book Price is not numeric. Record not added." End If If Not IsNumeric(Args.Values("BookQty")) Then Args.Cancel = True EditMSG.Text = "-- Book Quantity is not numeric. Record not added" End If If Args.Cancel = False Then If Args.Values("BookPrice") < 0 _ OR Args.Values("BookPrice") > 200 Then Args.Cancel = True EditMSG.Text = "-- Book Price out of range. Record not added" End If End If If Args.Cancel = False Then If Args.Values("BookQty") < 0 Then

Page 53: Assignment Instructions

Args.Cancel = True EditMSG.Text = "-- Book Quantity out of range. Record not added" End If End If

End Sub

'-- OnItemInserted --Sub Display_Insert_Msg (Src As Object, Args As DetailsViewInsertedEventArgs) EditMSG.Text = " Record " & Args.Values("BookID") & " added"End Sub

'-- OnItemUpdating --Sub Validate_Update_Data (Src As Object, Args As DetailsViewUpdateEventArgs)

If Not IsNumeric(Args.NewValues("BookPrice")) Then Args.Cancel = True EditMSG.Text = "-- Book Price is not numeric. Record not updated." End If If Not IsNumeric(Args.NewValues("BookQty")) Then Args.Cancel = True EditMSG.Text = "-- Book Quantity is not numeric. Record not updated" End If If Args.Cancel = False Then If Args.NewValues("BookPrice") < 0 _ OR Args.NewValues("BookPrice") > 200 Then Args.Cancel = True EditMSG.Text = "-- Book Price out of range. Record not updated" End If End If If Args.Cancel = False Then If Args.NewValues("BookQty") < 0 Then Args.Cancel = True EditMSG.Text = "-- Book Quantity out of range. Record not updated" End If End If

End Sub

'-- OnItemUpdated --Sub Display_Update_Msg (Src As Object, Args As DetailsViewUpdatedEventArgs) EditMSG.Text = " Record " & Args.Keys("BookID") & " updated"End Sub

'-- OnItemDeleted --Sub Display_Delete_Msg (Src As Object, Args As DetailsViewDeletedEventArgs) EditMSG.Text = " Record " & Args.Keys("BookID") & " deleted"End Sub

</SCRIPT>Listing 9-23. Scripts for editing events of DetailsView.

When a new record's "Insert" button is clicked and an ItemInserting event is raised, the Validate_Insert_Data subprogram is called. A collection of Values properties is available for testing for an inserted record. The record must be supplied with an BookID value since this is the key for the record. Also, numeric fields must contain numeric data. If any field fails a validation test, inserting is cancelled. On an ItemInserted event, the Display_Insert_Msg subprogram is called and a record-added message is displayed.

Page 54: Assignment Instructions

Editing for the ItemUpdating event is similar except that no test is made for a valid BookID field since this field is not available for editing. On an ItemUpdated event, a record-updated message is displayed.

On an ItemDeleted event, a record-deleted message is displayed. There is no built-in confirmation step giving the user the option to cancel editing. This is a common precaution to take, and later examples implement this confirmation.

Editing Records - FormView

A FormView control can include editing features to add new records to a database, update existing records, and delete existing records. These functions can be performed without scripting, or scripts can be added for pre- or post-processing activities. In the following example, a FormView includes "Edit," "New," and "Delete" buttons for editing books from theBooksDB.mdb database.

Note that making actual changes to the BooksDB.mdb database is not permitted in these tutorials; however, all other functions work as expected.

Book Edit

 

Edit New Delete

ID DB111

Type Database

Title Oracle Database

Author K. Loney

Description Get thorough coverage of Oracle Database 10g from the most comprehensive reference available, published by Oracle Press. With in-depth details on all the new features, this powerhouse resource provides an overview of database architecture and Oracle Grid Computing technology, and covers SQL, SQL*Plus, PL/SQL, dynamic PL/SQL, object-oriented features, and Java programming in the Oracle environment. You'll also find valuable database administration and application development techniques, plus an alphabetical reference covering major Oracle commands, keywords, features, and functions, with cross-referencing of topics.

Price $69.99

Qty 10

Sale

1 2 3 4 5 6 7 8 9 10 ...

Figure 9-19. Editing with a FormView.

Coding the FormView

Page 55: Assignment Instructions

Code for the FormView and its AccessDataSources for the above example is shown below. This code may appear more extensive than for a DetailsView with the same functionality. However, much of the code pertains to formatting the forms inside XHTML tables. There still remain an ItemTemplate, EditItemTemplate, and InsertItemTemplate as comparable templates for arranging display and edit fields.

The FormView uses an AccessDataSource that includes three SQL statements. The SelectCommand, InsertCommand, and UpdateCommand statements are identical to those used previously to edit a selected record in a DetailsView. You might notice a missing DeleteCommand. In this example, record deletion is scripted rather than taking place automatically through the AccessDataSource. This deletion process is described below. A second AccessDataSource supplies values for DropDownLists of book types during updates and additions.

A embedded Cascading Style Sheet is included in the code. Use of the style sheet makes it easier to consistently style the three separate tables in the ItemTemplate, EditItemTemplate, and InsertItemTemplate.

<style type="text/css"> table#ItemTable th {font-size:11pt; text-align:left; vertical-align:top; background-color:#E0E0E0} table#ItemTable td {font-size:11pt; vertical-align:top} table#EditTable th {font-size:11pt; text-align:left; vertical-align:top; background-color:#E0E0E0} table#EditTable td {font-size:11pt; vertical-align:top} table#AddTable th {font-size:11pt; text-align:left; vertical-align:top; background-color:#E0E0E0} table#AddTable td {font-size:11pt; vertical-align:top} .buttons {background-color:#E0E0E0}</style>

<asp:AccessDataSource id="BookSource" Runat="Server" DataFile="../Databases/BooksDB.mdb" SelectCommand="SELECT * FROM Books ORDER BY BookID" InsertCommand="INSERT INTO Books (BookID, BookType, BookTitle, BookAuthor, BookDescription, BookPrice, BookQty, BookSale) VALUES (@BookID, @BookType, @BookTitle, @BookAuthor, BookDescription=@BookDescription, @BookPrice, @BookQty, @BookSale)" UpdateCommand="UPDATE Books SET BookType=@BookType, BookTitle=@BookTitle, BookAuthor=@BookAuthor, BookDescription=@BookDescription, BookPrice=@BookPrice, BookQty=@BookQty, BookSale=@BookSale WHERE BookID=@BookID"/>

<asp:AccessDataSource id="TypeSource" Runat="Server" DataFile="../Databases/BooksDB.mdb" SelectCommand="SELECT DISTINCT BookType FROM Books ORDER BY BookType"/>

<h3>Book Edit</h3>

<asp:Label id="EditMSG" Text="&nbsp;" ForeColor="Red" Runat="Server"EnableViewState="False"/>

<asp:Panel id="ConfirmDelete" Visible="False" EnableViewState="False" Runat="Server"> <asp:Label Text="Delete this record? " ForeColor="Red" Runat="Server"/>

Page 56: Assignment Instructions

<asp:Button Text="Yes" OnClick="Delete_Record" Runat="Server" Font-Size="7pt" Height="17px" Width="30px"/> <asp:Button Text="No" OnClick="Cancel_Delete" Runat="Server" Font-Size="7pt" Height="17px" Width="30px"/></asp:Panel>

<asp:FormView id="FormView" DataSourceID="BookSource" Runat="Server" DataKeyNames="BookID" OnItemInserting="Validate_Insert_Data" OnItemInserted="Display_Insert_Msg" OnItemUpdating="Validate_Update_Data" OnItemUpdated="Display_Update_Msg" OnItemDeleting="Confirm_Delete" AllowPaging="True" GridLines="Both"> <ItemTemplate> <table id="ItemTable" border="0" cellpadding="3" cellspacing="0"> <tr> <th></th> <td class="buttons"> <asp:LinkButton CommandName="Edit" Text="Edit" Runat="Server"/> <asp:LinkButton CommandName="New" Text="New" Runat="Server"/> <asp:LinkButton CommandName="Delete" Text="Delete" Runat="Server"/> </td> </tr> <tr> <th>ID</th> <td><asp:Label Text='<%# Eval("BookID") %>' Runat="Server"/></td> </tr> <tr> <th>Type</th> <td><asp:Label Text='<%# Eval("BookType") %>' Runat="Server"/></td> </tr> <tr> <th>Title</th> <td><asp:Label Text='<%# Eval("BookTitle") %>' Runat="Server"/></td> </tr> <tr> <th>Author</th> <td><asp:Label Text='<%# Eval("BookAuthor") %>' Runat="Server"/></td> </tr> <tr> <th>Description</th> <td><asp:Panel Width="350px" Height="95px" BorderWidth="1px" BorderColor="#C0C0C0" ScrollBars="Auto" Runat="Server"> <asp:Label Text='<%# Eval("BookDescription") %>' Font-Size="10pt" Runat="Server"/> </asp:Panel></td> </tr> <tr> <th>Price</th> <td><asp:Label Text='<%# String.Format("{0:C}", Eval("BookPrice")) %>' Runat="Server"/></td> </tr> <tr> <th>Qty</th> <td><asp:Label Text='<%# String.Format("{0:D}", Eval("BookQty")) %>' Runat="Server"/></td> </tr> <tr> <th>Sale</th> <td><asp:CheckBox Checked='<%# Eval("BookSale") %>' Enabled="False"

Page 57: Assignment Instructions

Runat="Server"/></td> </tr> </table> </ItemTemplate> <EditItemTemplate> <table id="EditTable" border="0" cellpadding="3" cellspacing="0"> <tr> <th></th> <td class="buttons"> <asp:LinkButton CommandName="Update" Text="Update" Runat="Server"/> <asp:LinkButton CommandName="Cancel" Text="Cancel" Runat="Server"/> </td> </tr> <tr> <th>ID</th> <td><asp:Label Text='<%# Eval("BookID") %>' Runat="Server"/></td> </tr> <tr> <th>Type</th> <td><asp:DropDownList id="BookType" Runat="Server" DataSourceID="TypeSource" DataTextField="BookType" DataValueField="BookType" SelectedValue='<%# Bind("BookType") %>'/></td> </tr> <tr> <th>Title</th> <td><asp:TextBox id="EditTitle" Runat="Server" Text='<%# Bind("BookTitle") %>'/></td> </tr> <tr> <th>Author</th> <td><asp:TextBox id="EditAuthor" Runat="Server" Text='<%# Bind("BookAuthor") %>'/></td> </tr> <tr> <th>Description</th> <td><asp:TextBox id="EditDescription" Runat="Server Text='<%# Bind("BookDescription") %>'" TextMode="MultiLine" Height="95" Width="350px" Font-Name="Arial" Font-Size="9pt"/></td> </tr> <tr> <th>Price</th> <td><asp:TextBox id="EditPrice" Runat="Server" Text='<%# Bind("BookPrice") %>' Width="50px"/></td> </tr> <tr> <th>Quantity</th> <td><asp:TextBox id="EditQty" Runat="Server" Text='<%# Bind("BookQty") %>' Width="50px"/></td> </tr> <tr> <th>Sale</th> <td><asp:CheckBox id="EditSale" Runat="Server" Checked='<%# Bind("BookSale") %>'/></td> </tr> </table> </EditItemTemplate>

Page 58: Assignment Instructions

<InsertItemTemplate> <table id="AddTable" border="0" cellpadding="3" cellspacing="0"> <tr> <th></th> <td class="buttons"> <asp:LinkButton CommandName="Insert" Text="Insert" Runat="Server"/> <asp:LinkButton CommandName="Cancel" Text="Cancel" Runat="Server"/> </td> </tr> <tr> <th>ID</th> <td><asp:TextBox id="AddBookID" Runat="Server" Text='<%# Bind("BookID") %>'/></td> </tr> <tr> <th>Type</th> <td><asp:DropDownList id="AddType" Runat="Server" DataSourceID="TypeSource" DataTextField="BookType" DataValueField="BookType" SelectedValue='<%# Bind("BookType") %>'/></td> </tr> <tr> <th>Title</th> <td><asp:TextBox id="AddTitle" Runat="Server" Text='<%# Bind("BookTitle") %>'/></td> </tr> <tr> <th>Author</th> <td><asp:TextBox id="AddAuthor" Runat="Server" Text='<%# Bind("BookAuthor") %>'/></td> </tr> <tr> <th>Description</th> <td><asp:TextBox id="AddDescription" Runat="Server" Text='<%# Bind("BookDescription") %>' TextMode="MultiLine" Rows="5" Width="350px" Font-Name="Arial" Font-Size="9pt"/></td> </tr> <tr> <th>Price</th> <td><asp:TextBox id="AddPrice" Runat="Server" Text='<%# Bind("BookPrice") %>'/></td> </tr> <tr> <th>Qty</th> <td><asp:TextBox id="AddQty" Runat="Server" Text='<%# Bind("BookQty") %>'/></td> </tr> <tr> <th>Sale</th> <td><asp:CheckBox id="AddSale" Runat="Server" Checked='<%# Bind("BookSale") %>'/></td> </tr> </table> </InsertItemTemplate>

</asp:FormView>Listing 9-24. Code for FormView with editing.

The FormView requires three templates, one for viewing a record (ItemTemplate), one for updates (EditItemTemplate), and one for additions (InsertItemTemplate). All three templates

Page 59: Assignment Instructions

include <table> tags to arrange labels and controls for formatted display. All templates must include buttons to trigger editing activity. LinkButtons are used in this example. These buttons must include proper CommandNames for editing (Edit, New, and Delete), for updating (Update and Cancel), and for inserting (Insert and Cancel). Data display controls for updating and inserting records must use editable controls along with Bind() expressions so that changed or inserted values can be posted back to the database.

Like the GridView and DetailsView, a FormView immediately deletes a record when the "Delete" button is clicked. For this example, a confirmation step is added. When the button is clicked, a pair of "Yes" and "No" buttons appear to confirm or cancel deletion. These buttons are coded inside a Panel control that is initially hidden.

<asp:Panel id="ConfirmDelete" Visible="False" EnableViewState="False" Runat="Server"> <asp:Label Text="Delete this record? " ForeColor="Red" Runat="Server"/> <asp:Button Text="Yes" OnClick="Delete_Record" Runat="Server" Font-Size="7pt" Height="17px" Width="30px"/> <asp:Button Text="No" OnClick="Cancel_Delete" Runat="Server" Font-Size="7pt" Height="17px" Width="30px"/></asp:Panel>Listing 9-25. Code for delete confirmation display for FormView.

This Panel is not part of the FormView. It appears outside and immediately preceding the FormView. It is made visible by a Confirm_Delete subprogram that is called on the ItemDeleting event that occurs when the "Delete" button is clicked. This subprogram is described below.

FormView Events and Event Handlers

"Insert," "Update," and "Delete" buttons for the FormView have events associated with them for calling subprograms to intercept these processes. These events, their event handlers, and arguments of called subprograms are listed below. Except for argument names, these events and handlers are the same as those associated with a DetailsView.

Event Event Handler Signature Description

ItemInserting OnItemInserting FormViewInsertEventArgs A new record is being added to the data source but has not yet been written.

ItemInserted OnItemInserted FormViewInsertedEventArgs A new record has been written to the data source.

ItemUpdating OnItemUpdating FormViewUpdateEventArgs A record is being updated but has not yet been rewritten to the data source.

ItemUpdated OnItemUpdated FormViewUpdatedEventArgs An updated record has been rewritten to the data source.

ItemDeleting OnItemDeleting FormViewDeleteEventArgs A record is being deleted from the data source but has not yet been deleted.

ItemDeleted OnItemDeleted FormViewDeletedEventArgs A record has been deleted from the data source.

Figure 9-20. FormView event handlers and subprogram argument signatures.

Page 60: Assignment Instructions

As in the case with the DetailsView, subprograms called by these event handlers expose event properties that can be investigated. These properties are listed below and are identical to those exposed through DetailsView events.

--FormViewInsertEventArgs Properties--

argument.Values("fieldname")argument.Cancel="False|True"

--FormViewInsertedEventArgs Properties--

argument.Values("field")argument.AffectedRowsargument.KeepInInsertMode="False|True"

--FormViewUpdateEventArgs Properties--

argument.OldValues("field")argument.NewValues("field")argument.Keys("field")argument.Cancel="False|True"

--FormViewUpdatedEventArgs Properties--

argument.OldValues("field")argument.NewValues("field")argument.Keys("field")argument.AffectedRowsargument.KeepInEditMode="False|True"

--FormViewDeleteEventArgs Properties--

argument.Values("field")argument.Keys("field")argument.Cancel="False|True"

--FormViewDeletedEventArgs Properties--

argument.Values("field")argument.Keys("field")argument.AffectedRows

Figure 9-21. General formats for referencing FormView event properties.

FormView Event Scripting

Scripts for the OnInserting, OnInserted, OnUpdating, OnUpdated, and OnDeleting events trapped for the example FormView are shown below. Except for added functionality for the OnDeleting event, the remaining subprograms are identical to those used with the previous DetailsView.

<SCRIPT Runat="Server">

'-- OnItemInserting --Sub Validate_Insert_Data (Src As Object, Args As FormViewInsertEventArgs)

Page 61: Assignment Instructions

If Args.Values("BookID") = "" Then Args.Cancel = True EditMSG.Text = "-- Missing BookID. Record not added." End If If Not IsNumeric(Args.Values("BookPrice")) Then Args.Cancel = True EditMSG.Text = "-- Book Price is not numeric. Record not added." End If If Not IsNumeric(Args.Values("BookQty")) Then Args.Cancel = True EditMSG.Text = "-- Book Quantity is not numeric. Record not added" End If If Args.Cancel = False Then If Args.Values("BookPrice") < 0 _ OR Args.Values("BookPrice") > 200 Then Args.Cancel = True EditMSG.Text = "-- Book Price out of range. Record not added" End If End If If Args.Cancel = False Then If Args.Values("BookQty") < 0 Then Args.Cancel = True EditMSG.Text = "-- Book Quantity out of range. Record not added" End If End If

End Sub

'-- OnItemInserted --Sub Display_Insert_Msg (Src As Object, Args As FormViewInsertedEventArgs) EditMSG.Text = " Record " & Args.Values("BookID") & " added"End Sub

'-- OnItemUpdating --Sub Validate_Update_Data (Src As Object, Args As FormViewUpdateEventArgs)

If Not IsNumeric(Args.NewValues("BookPrice")) Then Args.Cancel = True EditMSG.Text = "-- Book Price is not numeric. Record not updated." End If If Not IsNumeric(Args.NewValues("BookQty")) Then Args.Cancel = True EditMSG.Text = "-- Book Quantity is not numeric. Record not updated" End If If Args.Cancel = False Then If Args.NewValues("BookPrice") < 0 Then Args.Cancel = True EditMSG.Text = "-- Book Price out of range. Record not updated" End If End If If Args.Cancel = False Then If Args.NewValues("BookQty") < 0 _ OR Args.NewValues("BookPrice") > 200 Then Args.Cancel = True EditMSG.Text = "-- Book Quantity out of range. Record not updated"

Page 62: Assignment Instructions

End If End If

End Sub

'-- OnItemUpdated --Sub Display_Update_Msg (Src As Object, Args As FormViewUpdatedEventArgs) EditMSG.Text = " Record " & Args.Keys("BookID") & " updated"End Sub

'-- OnItemDeleting --Sub Confirm_Delete (Src As Object, Args As FormViewDeleteEventArgs) Args.Cancel = True ConfirmDelete.Visible = True EditMSG.Visible = False ViewState("BookID") = Args.Keys("BookID") End Sub

Sub Delete_Record (Src As Object, Args As EventArgs) BookSource.DeleteCommand = "DELETE FROM Books " & _ "WHERE BookID = '" & ViewState("BookID") & "'" BookSource.Delete() EditMSG.Text = " Record " & ViewState("BookID") & " deleted" End Sub

Sub Cancel_Delete (Src As Object, Args As EventArgs) ConfirmDelete.Visible = False

End Sub

</SCRIPT>Listing 9-26. Scripts for editing events of FormView.

When a new record's "Insert" button is clicked and an ItemInserting event is raised, the Validate_Insert_Data subprogram is called. The record must be supplied with a BookIDvalue since this is the key for the record. Also, numeric fields must contain numeric data. If any field fails a validation test, inserting is cancelled. On an ItemInserted event, theDisplay_Insert_Msg subprogram is called and a record-added message is displayed.

Editing for the ItemUpdating event is similar except that no test is made for a valid BookID field since this field is not available for editing. On an ItemUpdated event, a record-updated message is displayed.

Delete Confirmation

When the "Delete" button is clicked and an ItemDeleting event occurs, the Confirm_Delete subprogram is called to immediately cancel the delete event and to make theConfirmDelete Panel visible with its confirmation buttons. Since the delete event is cancelled, the Args.Keys("BookID") reference to the BookID key for this record is lost to subsequent subprograms to delete the record should it be confirmed. Therefore, this key is saved to a View State variable, ViewState("BookID"), for subsequent retrieval.

Sub Confirm_Delete (Src As Object, Args As FormViewDeleteEventArgs)

Page 63: Assignment Instructions

Args.Cancel = True ConfirmDelete.Visible = True EditMSG.Visible = False ViewState("BookID") = Args.Keys("BookID")

End SubListing 9-27. Code to activate delete confirmation Panel of FormView.

This is the case if the "Yes" button is clicked and the Delete_Record subprogram is called. This subprogram composes a DELETE statement, using the savedViewState("BookID") to identify the key of the record to be deleted. The statement is assigned to the DeleteCommand property of AccessDataSource for the FormView, and itsDelete() method is called to delete the record. This scripted delete operation is the reason a DeleteCommand statement is not needed in the AccessDataSource.

Sub Delete_Record (Src As Object, Args As EventArgs)

BookSource.DeleteCommand = "DELETE FROM Books " & _ "WHERE BookID = '" & ViewState("BookID") & "'" BookSource.Delete() EditMSG.Text = " Record " & ViewState("BookID") & " deleted"

End SubListing 9-28. Script to delete a FormView record.

If, on the other hand, the "No" confirmation button is clicked, no action is taken on the record. Instead, the Cancel_Delete subprogram is called simply to hide the confirmation Panel.

Sub Cancel_Delete (Src As Object, Args As EventArgs)

ConfirmDelete.Visible = False

End SubListing 9-29. Script to cancel deletion of a FormView record.

This method of coding the FormView with its AccessDataSources and scripts can be applied to a DetailsView or GridView to supply record-deletion confirmation in these controls.

Using Command Events

In the above FormView the delete confirmation buttons appear outside the FormView itself. As a visual option you may wish to incorporate the "Yes" and "No" buttons inside the control as you can see by clicking the "Delete" button in the following FormView.

Book Edit

 

     

ID DB111

Type Database

Page 64: Assignment Instructions

Title Oracle Database

Author K. Loney

Description Get thorough coverage of Oracle Database 10g from the most comprehensive reference available, published by Oracle Press. With in-depth details on all the new features, this powerhouse resource provides an overview of database architecture and Oracle Grid Computing technology, and covers SQL, SQL*Plus, PL/SQL, dynamic PL/SQL, object-oriented features, and Java programming in the Oracle environment. You'll also find valuable database administration and application development techniques, plus an alphabetical reference covering major Oracle commands, keywords, features, and functions, with cross-referencing of topics.

Price $69.99

Qty 10

Sale

1 2 3 4 5 6 7 8 9 10 ...

Figure 9-22. Editing with a FormView.

Coding for the FormView is similar to the previous example. One difference is that the LinkButton controls are coded here as Button controls. A second difference is in the section of the ItemTemplate where confirmation buttons are included along with the "Edit," "New," and "Delete" buttons. In the previous example, these "Yes" and "No" buttons are enclosed inside a Panel to control their visibility. Here they are enclosed inside a Label control. A Label is used so the confirmation buttons appear on the same line as the edit buttons (a Panel control causes a line break). As before, the buttons are initially hidden since the ConfirmDelete Label that contains them is hidden.

<asp:FormView id="FormView" DataSourceID="BookSource" Runat="Server" ... OnItemDeleting="Confirm_Delete" OnItemCommand="Get_Command" >

<ItemTemplate> <table id="ItemTable" border="0" cellpadding="3" cellspacing="0"> <tr> <th></th> <td class="buttons"> <asp:Button CommandName="Edit" Text="Edit" Font-Size="9pt" Width="50" Runat="Server"/> <asp:Button CommandName="New" Text="New" Font-Size="9pt" Width="50" Runat="Server"/> <asp:Button CommandName="Delete" Text="Delete" Font-Size="9pt" Width="50" Runat="Server"/>&nbsp; <asp:Label id="ConfirmDelete" Visible="False" EnableViewState="False" Runat="Server"> <asp:Label Text="Delete this record? " ForeColor="Red" Runat="Server"/> <asp:Button Text="Yes" CommandName="Yes" Font-Size="8pt" Width="30px" Runat="Server"/> <asp:Button Text="No" CommandName="No" Font-Size="8pt" Width="30px" Runat="Server"/> </asp:Label>

Page 65: Assignment Instructions

</td> </tr> ... <EditItemTemplate> <table id="EditTable" border="0" cellpadding="3" cellspacing="0"> <tr> <th></th> <td class="buttons"> <asp:Button CommandName="Update" Text="Update" Font-Size="9pt" Width="50" Runat="Server"/> <asp:Button CommandName="Cancel" Text="Cancel" Font-Size="9pt" Width="50" Runat="Server"/> </td> </tr> <tr> ... <InsertItemTemplate> <table id="AddTable" border="0" cellpadding="3" cellspacing="0"> <tr> <th></th> <td class="buttons"> <asp:Button CommandName="Insert" Text="Insert" Font-Size="9pt" Width="50" Runat="Server"/> <asp:Button CommandName="Cancel" Text="Cancel" Font-Size="9pt" Width="50" Runat="Server"/> </td> </tr> ...

</asp:FormView>Listing 9-30. Coding a FormView with command buttons.

Because the confirmation buttons are now inside, and part of, the FormView, they are given CommandName properties ("Yes" and "No") just like other buttons in a FormView. Also, the FormView is given an OnItemCommand event handler to respond to these command buttons by calling the Get_Command subprogram.

These command names replace the subprogram calls of the previous example. Subprograms Confirm_Delete, Delete_Record, and Cancel_Delete are no longer required, replaced by two new subprograms described below.

The other event handlers for the FormView—OnItemInserting, OnItemInserted, OnItemUpdating, OnItemUpdated, and OnItemDeleting—are associated with the special command names New, Insert, Edit, Update, and Delete. The Command names Yes and No, however, do not have special event handlers. Any buttons with undefined command names are handled by the general-purpose OnItemCommand event handler. In this example, the undefined command names "Yes" and "No" call the Get_Command subprogram through this handler. If preferred, any or all of the special event handlers can be replaced by the OnItemCommand hander to sort out any and all of the command names appearing in a FormView.

When the "Delete" button is clicked, the OnItemDeleting event handler calls the Confirm_Delete subprogram just as in the previous example. This subprogram is coded differently, however, since its purpose now is to make visible the confirmation buttons inside the FormView. A different method is required from the previous example of directly setting theVisible property of the ConfirmDelete Panel that resides outside the FormView.

Page 66: Assignment Instructions

Sub Confirm_Delete (Src As Object, Args As FormViewDeleteEventArgs)

Args.Cancel = True Dim ConfirmLabel As Label = FormView.FindControl("ConfirmDelete") ConfirmLabel.Visible = True ViewState("BookID") = Args.Keys("BookID")

End SubListing 9-31. Finding a FormView control.

First, the confirmation Label must be "found" in the FormView. The FormView's FindControl() method locates a control by its id value, after which it is assigned to a variable of the same type (a Label type in this instance). Once this control has been found and assigned to a variable, its Visible property is set to True, thereby revealing the "Yes" and "No" confirmation buttons. As before, the key of the currently displayed record is saved to ViewState("BookID") for deletion of this record, if requested, in a different subprogram.

When either of the two confirmation buttons is clicked, an ItemCommand event is raised and the Get_Command subprogram is called by the FormView's OnItemCommand event handler. This subprogram has the argument FormViewCommandEventArgs and is shown below.

Sub Get_Command (Src As Object, Args As FormViewCommandEventArgs)

If Args.CommandName = "Yes" Then BookSource.DeleteCommand = "DELETE FROM Books" & _ "WHERE BookID = '" & ViewState("BookID") & "'" BookSource.Delete() EditMSG.Text = " Record " & ViewState("BookID") & " deleted" End If

End SubListing 9-32. Trapping a FormView command event.

Action needs to be taken only on a click of the "Yes" button to delete the associated record. Therefore, if the command name passed to the subprogram (Args.CommandName) is"Yes," then an SQL DELETE statement is issued through the FormView's AccessDataSource. The record identified in ViewState("BookID") is deleted. If the "No" button is clicked (which calls this same subprogram), no action is taken. In either case, the confirmation buttons are hidden since their container Label is coded with EnableViewState="False" to revert to hidden status any time a page post-back occurs.

Data Entry with a FormView

Sometimes it is not necessary to perform full-featured editing; it's just a matter of performing data entry. This need crops up routinely on Web sites with forms to fill out for taking orders, conducting surveys, registering for memberships, and other common types of information gathering. In these cases, the need is simply to display a data entry form and to write collected information to a database. Although any of the information editing controls can perform this task, the FormView is particularly attractive because of its flexibility in formatting the data collection instrument.

The following FormView demonstrates a data entry form. Here it is used to perform data entry to add records to the Books table of the BooksDB.mdb database; however, it can be adapted for use in any type of data entry situation.

Page 67: Assignment Instructions

Note that making actual changes to the BooksDB.mdb database is not permitted in these tutorials; however, all other functions work as expected.

Book Entry

ID

Type           

Title

Author

Description

Price

Quantity

Sale

 

Figure 9-23. Data entry with a FormView.

Setting the DefaultMode

A FormView normally operates in three modes. Its <ItemTemplate> configures the form for display of a data source record, its <EditItemTemplate> displays edit boxes for making changes to the record, and its <InsertItemTemplate> displays blank edit boxes for adding a new record. For presenting a data entry form, however, only the<InsertItemTemplate> is of interest. At the same time, there is only need to display "Insert" and "Cancel" buttons to activate data entry.

Code for the FormView to display a data entry form is shown below along with its AccessDataSource controls.

<style type="text/css"> table#AddTable th {font-size:11pt; text-align:left; vertical-align:top; background-color:#E0E0E0} table#AddTable td {font-size:11pt; vertical-align:top} table#AddTable th {font-size:11pt; text-align:left; vertical-align:top; background-color:#E0E0E0} .buttons {background-color:#E0E0E0} .errmsg {width:180px}</style>

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

Page 68: Assignment Instructions

InsertCommand="INSERT INTO Books (BookID, BookType, BookTitle, BookAuthor,BookDescription, BookPrice, BookQty, BookSale) VALUES (@BookID, @BookType, @BookTitle, @BookAuthor, @BookDescription, @BookPrice, @BookQty, @BookSale)"/>

<asp:AccessDataSource id="TypeSource" Runat="Server" DataFile="../Databases/BooksDB.mdb" SelectCommand="SELECT DISTINCT BookType FROM Books ORDER BY BookType"/>

<h3>Book Entry</h3>

<asp:FormView id="DEFormView" DataSourceID="BookSource" Runat="Server" DefaultMode="Insert" OnItemInserting="Validate_Insert_Data"> <InsertItemTemplate> <table id="AddTable" border="0" cellpadding="1"> <tr> <th>ID</th> <td><asp:TextBox id="BookID" Text='<%# Bind("BookID") %>' Runat="Server" Width="70" MaxLength="6"/></td> <td class="errmsg"> <asp:Label id="ERRBookID" ForeColor="#FF0000" Runat="Server" EnableViewState="False"/></td> </tr> <tr> <th>Type</th> <td><asp:DropDownList id="BookType" Runat="Server" DataSourceID="TypeSource" DataTextField="BookType" DataValueField="BookType" SelectedValue='<%# Bind("BookType") %>'/></td> <td></td> </tr> <tr> <th>Title</th> <td><asp:TextBox id="BookTitle" Runat="Server" Text='<%# Bind("BookTitle") %>' Width="300" MaxLength="50"/></td> <td class="errmsg"> <asp:Label id="ERRBookTitle" ForeColor="#FF0000" Runat="Server" EnableViewState="False"/></td> </tr> <tr> <th>Author</th> <td><asp:TextBox id="BookAuthor" Runat="Server" Text='<%# Bind("BookAuthor") %>' Width="300" MaxLength="50"/></td> <td class="errmsg"> <asp:Label id="ERRBookAuthor" ForeColor="#FF0000" Runat="Server" EnableViewState="False"/></td> </tr> <tr> <th>Description</th> <td><asp:TextBox id="BookDescription" Runat="Server" Text='<%# Bind("BookDescription") %>' TextMode="MultiLine" Rows="5" Width="300px" Font-Name="Arial" Font-Size="9pt"/></td> <td class="errmsg"> <asp:Label id="ERRBookDescription" ForeColor="#FF0000" Runat="Server" EnableViewState="False"/></td>

Page 69: Assignment Instructions

</tr> <tr> <th>Price</th> <td><asp:TextBox id="BookPrice" Runat="Server" Text='<%# Bind("BookPrice") %>' Width="60" MaxLength="6" Style="text-align:right"/></td> <td class="errmsg"> <asp:Label id="ERRBookPrice" ForeColor="#FF0000" Runat="Server" EnableViewState="False"/></td> </tr> <tr> <th>Quantity</th> <td><asp:TextBox id="BookQty" Runat="Server" Text='<%# Bind("BookQty") %>' Width="60" MaxLength="2" Style="text-align:right"/></td> <td class="errmsg"> <asp:Label id="ERRBookQty" ForeColor="#FF0000" Runat="Server" EnableViewState="False"/></td> </tr> <tr> <th>Sale</th> <td><asp:CheckBox id="BookSale" Checked='<%# Bind("BookSale") %>' Runat="Server"/></td> </tr> <tr> <th></th> <td class="buttons"> <asp:Button id="InsertButton" CommandName="Insert" Text="Insert" Font-Size="9pt" Width="50" Runat="Server"/> <asp:Button id="CancelButton" CommandName="Cancel" Text="Cancel" Font-Size="9pt" Width="50" Runat="Server"/> </td> </tr> </table> </InsertItemTemplate> </asp:FormView>

<asp:Label id="AddMSG" ForeColor="#FF0000" EnableViewState="False" Runat="Server"/>Listing 9-33. Coding a FormView for data entry.

Normally, a FormView opens in ReadOnly mode to display the first of the selected records from its AccessDataSource in its <ItemTemplate>. In this case, however, there is no set of selected records for display since the AccessDataSource does not have a SelectCommand property. Neither does the FormView have an <ItemTemplate>. With no data to bind to the form and with no way to display it, the FormView simply would not appear on the page.

The need is to open the FormView in its Insert mode rather than its default "ReadOnly" mode. This is accomplished by adding the DefaultMode="Insert" property to the FormView as is done in the above example. Now, the form is opened as a set of input areas as it would appear when clicking the standard FormView's "New" button and switching to insert mode. Also, if necessary, you can open the FormView in Edit mode by setting its DefaultMode="Edit" property, assuming you also have a SelectCommand statement that retrieves an initial record.

Since the FormView opens in Insert mode, it is necessary to provide appropriate command buttons to activate the form. Buttons with CommandName="Insert" andCommandName="Cancel" are provided.

Page 70: Assignment Instructions

An OnItemInserting event handler calls a subprogram when the ItemInserting event occur to validate input values.

Notice that all input areas have <#% Bind(field) %> binding expressions to link with the parameters of the InsertCommand. These expressions do not bind "forward" from a data source to an insert box for display; they bind "backwards" from the input areas to the INSERT statement for inserting entered values.

Each input area has an associated message display Label to its right. These separate message areas make it possible to display separate data entry errors. Also, there is a message Label at the end of the FormView to report success or failure in adding a record to the database.

Scripting the FormView

Most of the scripting effort is to validate entered data to ensure its correctness and reasonableness prior to writing the record to the database. Much of the code is adapted from previous scripts.

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

<SCRIPT Runat="Server">

Sub Validate_Insert_Data (Src As Object, Args As FormViewInsertEventArgs) Dim MSGLabel As Label If Args.Values("BookID") = "" Then Args.Cancel = True MSGLabel = DEFormView.FindControl("ERRBookID") MSGLabel.Text = "&bull; Missing BookID" End If Dim DBConnection As OleDbConnection Dim DBCommand As OleDbCommand Dim SQLString As String DBConnection = New OleDbConnection( _ "Provider=Microsoft.Jet.OLEDB.4.0;" & _ "Data Source=" & Server.MapPath("../Databases/BooksDB.mdb")) DBConnection.Open() SQLString = "SELECT Count(*) FROM Books " & _ "WHERE BookID = '" & Args.Values("BookID") & "'" DBCommand = New OleDbCommand(SQLString, DBConnection) If DBCommand.ExecuteScalar() <> 0 Then MSGLabel = DEFormView.FindControl("ERRBookID") MSGLabel.Text = "&bull; Duplicate BookID" Args.Cancel = True End If DBConnection.Close() If Args.Values("BookTitle") = "" Then Args.Cancel = True MSGLabel = DEFormView.FindControl("ERRBookTitle") MSGLabel.Text = "&bull; Missing title" End If If Args.Values("BookAuthor") = "" Then Args.Cancel = True

Page 71: Assignment Instructions

MSGLabel = DEFormView.FindControl("ERRBookAuthor") MSGLabel.Text = "&bull; Missing author" End If If Args.Values("BookDescription") = "" Then Args.Cancel = True MSGLabel = DEFormView.FindControl("ERRBookDescription") MSGLabel.Text = "&bull; Missing description" End If If Not IsNumeric(Args.Values("BookPrice")) Then Args.Cancel = True MSGLabel = DEFormView.FindControl("ERRBookPrice") MSGLabel.Text = "&bull; Price not numeric" Else If Args.Values("BookPrice") < 0 _ OR Args.Values("BookPrice") > 200 Then Args.Cancel = True MSGLabel = DEFormView.FindControl("ERRBookPrice") MSGLabel.Text = "&bull; Price out of range" End If End If If Not IsNumeric(Args.Values("BookQty")) Then Args.Cancel = True MSGLabel = DEFormView.FindControl("ERRBookQty") MSGLabel.Text = "&bull; Qty not numeric" Else If Args.Values("BookQty") < 0 Then Args.Cancel = True MSGLabel = DEFormView.FindControl("ERRBookQty") MSGLabel.Text = "&bull; Qty out of range" End If End If If Args.Cancel = True Then AddMSG.Text = "Record " & Args.Values("BookID") & " Not Added" Else AddMSG.Text = "Record " & Args.Values("BookID") & " Added" End If End Sub

</SCRIPT>Listing 9-34. Scripting a FormView used for data validation.

When the "Insert" button is clicked and an Inserting event occurs, the Validate_Insert_Data subprogram is called. This subprogram validates submitted data for all except the DropDownList and CheckBox fields. A typical check is shown below for the BookID TextBox.

Dim MSGLabel As Label

If Args.Values("BookID") = "" Then Args.Cancel = True MSGLabel = DEFormView.FindControl("ERRBookID") MSGLabel.Text = "-- Missing BookID"End If

...Listing 9-35. Scripting a FormView to validate a data entry field.

Page 72: Assignment Instructions

In this case, the BookID textbox is tested for an entry. If it is missing, then the inserting event is cancelled (Args.Cancel="True") and an error message is displayed in the associated Label.

Since message labels appear inside the FormView, they must be "found" prior to their use. Here, the Label with id="ERRBookID" is located withDEFormView.FindControl("ERRBookID") and assigned to a script-generated Label (MSGLabel) for assignment of the "Missing BookID" message.

Since there are six different message areas, there is the need to find all six and to assign them to Labels. It is not necessary, however, to create six different scripted Labels. AllFindControl() methods can assign to the same shared control as is done here by declaring MSGLabel as the shared global Label.

It should be meantioned that the FormView's FindControl() method cannot be applied in this same format to GridView or DetailsView controls. The reason is that the FormView has only one "row" to search for a control. These other controls have multiple rows whose index numbers must be used to identify on which row to look for a control. TheseFindControl() differences are pointed out in later tutorials.

A test of made of the BookID entry to avoid writing a duplicate record to the database. This test involves connecting to the database and returning a Count(*) of the number of records with a BookID matching the entered value. If the count is 0, then there are no matching records and the new record can be added. If the count is not 0, then a matching record was found and a duplicate-record message is displayed.

DBConnection = New OleDbConnection( _ "Provider=Microsoft.Jet.OLEDB.4.0;" & _ "Data Source=" & Server.MapPath("../Databases/BooksDB.mdb"))DBConnection.Open()SQLString = "SELECT Count(*) FROM Books " & _ "WHERE BookID = '" & Args.Values("BookID") & "'"DBCommand = New OleDbCommand(SQLString, DBConnection)If DBCommand.ExecuteScalar() <> 0 Then MSGLabel = DEFormView.FindControl("ERRBookID") MSGLabel.Text = "&bull; Duplicate BookID" Args.Cancel = TrueEnd IfDBConnection.Close()Listing 9-36. Testing for a duplicate BookID.

Whether or not the entered information is added to the database, a message is displayed indicating the results. This message area is located outside the FormView, so it can be referenced directly to display the message.

If Args.Cancel = True Then AddMSG.Text = "Record " & Args.Values("BookID") & " Not Added"Else AddMSG.Text = "Record " & Args.Values("BookID") & " Added"End IfListing 9-37. Displaying a record-addition results message.

Master/Detail Editing - DetailsView

Record editing with a single DetailsView or FormView works fine for small database tables requiring few pages to navigate. For large recordsets, though, it may be difficult to locate

Page 73: Assignment Instructions

particular records by paging through them sequentially. What is required is a table of contents to quickly locate records of interest, and a form for editing those that are found. These requirements are easily provided in a master/detail setup. In the following example, a GridView presents an index to all records in the BooksDB.mdb database; a linked DetailsView gives editing access to individual records.

Note that making actual changes to the BooksDB.mdb database is not permitted in these tutorials; however, all other functions work as expected.

Book Master

ID Title

DB111 Oracle Database

DB222 Databases in Depth

Page 74: Assignment Instructions

DB333 Database Processing

DB444 Access Database Design

DB555 SQL Server 2005

Page 75: Assignment Instructions

GR111 Adobe Photoshop CS2

GR222 Learning Web Design

GR333 Macromedia Flash Professional

Page 76: Assignment Instructions

GR444 Digital Photographer Handbook

GR555 Creating Motion Graphics

HW111 How Computers Work

Page 77: Assignment Instructions

HW222 Upgrading and Repairing PCs

1 2 3

Book Detail 

   

ID: DB111

Type: Database

Title: Oracle Database

Author: K. Loney

Description: Get thorough coverage of Oracle Database 10g from the most comprehensive reference available, published by Oracle Press. With in-depth details on all the new features, this powerhouse resource provides an overview of database architecture and Oracle Grid Computing technology, and covers SQL, SQL*Plus, PL/SQL, dynamic PL/SQL, object-oriented features, and Java programming in the Oracle environment. You'll also find valuable database administration and application development techniques, plus an alphabetical reference covering major Oracle commands, keywords, features, and functions, with cross-referencing of topics.

Price: $69.99

Qty: 10

Sale:

Page 78: Assignment Instructions

Figure 9-24. Master/detail record editing.

Coding the GridView

Code for the master GridView and its AccessDataSource are shown below. A TemplateField containing <asp:LinkButton> controls both displays BookIDs and configures them as command links for selecting books. The CommandName for the LinkButtons is "Select" to tieBookIDs to the SelectCommand property of the AccessDataSource for the DetailsView.

<asp:AccessDataSource id="MasterSource" Runat="Server" DataFile="../Databases/BooksDB.mdb" SelectCommand="SELECT BookID, BookTitle FROM Books ORDER BY BookID"/>

<asp:GridView id="GridView" DataSourceID="MasterSource" Runat="Server" DataKeyNames="BookID" AutoGenerateColumns="False" Caption="<b>Book Master</b>" CaptionAlign="Left" AllowPaging="True" PageSize="12" SelectedIndex="0" SelectedRowStyle-BackColor="#E9E9E9" HeaderStyle-BackColor="#E0E0E0" Style="float:left; margin-bottom:200px"> <Columns> <asp:TemplateField HeaderText="ID" HeaderStyle-Width="40" HeaderStyle-Font-Size="10pt" ItemStyle-Font-Size="10pt"> <ItemTemplate> <asp:LinkButton CommandName="Select" Runat="Server" Text='<%# Eval("BookID") %>'/> </ItemTemplate> </asp:TemplateField> <asp:BoundField DataField="BookTitle" HeaderText="Title" HeaderStyle-Width="180" HeaderStyle-Font-Size="10pt" Itemstyle-Font-Size="10pt"/> </Columns>

</asp:GridView>Listing 9-38. Coding a master GridView.

Coding the DetailsView

The DetailsView employs an AccessDataSource whose SelectCommand is linked to the GridView selection through its SelectParameters. The GridView's SelectedValue becomes the @BookID parameter for displaying the chosen record. A second AccessDataSource fills DropDownLists of book types during updating and inserting operations.

Page 79: Assignment Instructions

<asp:AccessDataSource id="DetailsSource" Runat="Server" DataFile="../Databases/BooksDB.mdb" SelectCommand="SELECT * FROM Books WHERE BookID = @BookID" InsertCommand="INSERT INTO Books (BookID, BookType, BookTitle, BookAuthor, BoodDescription, BookPrice, BookQty, BookSale) VALUES (@BookID, @BookType, @BookTitle, @BookAuthor, @BoodDescription, @BookPrice, @BookQty, @BookSale)"

UpdateCommand="UPDATE Books SET BookType=@BookType, BookTitle=@BookTitle, BookAuthor=@BookAuthor, BoodDescription=@BoodDescription, BookPrice=@BookPrice, BookQty=@BookQty, BookSale=@BookSale WHERE BookID=@BookID"> <SelectParameters> <asp:ControlParameter ControlID="GridView" Name="BookID" PropertyName="SelectedValue"/> </SelectParameters>

</asp:AccessDataSource>

<asp:AccessDataSource id="TypeSource" Runat="Server" DataFile="../Databases/BooksDB.mdb" SelectCommand="SELECT DISTINCT BookType FROM Books ORDER BY BookType"/>Listing 9-39. Linking a DetailsView to a master GridView via AccessDataSource parameters.

Code for the DetailsView is shown below. Editing buttons are supplied through a TemplateField containing Button controls configured as command buttons. Also, a set of delete confirmation buttons appears inside the DetailsView's ItemTemplate.

<asp:DetailsView id="DetailsView" Runat="Server" DataSourceID="DetailsSource" AutoGenerateRows="False" DataKeyNames="BookID" OnItemInserting="Validate_Insert_Data" OnItemInserted="Display_Insert_Msg" OnItemUpdating="Validate_Update_Data" OnItemUpdated="Display_Update_Msg" OnItemDeleting="Confirm_Delete" OnItemCommand="Get_Command" Caption="<b>Book Detail</b>" CaptionAlign="Left" BorderStyle="Outset" BorderWidth="1" CellPadding="3" GridLines="None" RowStyle-Font-Size="10pt" RowStyle-VerticalAlign="Top">

<Fields> <asp:TemplateField HeaderStyle-BackColor="#E0E0E0" HeaderStyle-Font-Bold="True" HeaderStyle-HorizontalAlign="Right" ItemStyle-VerticalAlign="Top" ItemStyle-BackColor="#E0E0E0" ItemStyle-ForeColor="#FF0000"> <ItemTemplate> <asp:Button CommandName="Edit" Text="Edit" Runat="Server" Font-Size="8pt" Width="45"/>

Page 80: Assignment Instructions

<asp:Button CommandName="New" Text="New" Runat="Server" Font-Size="8pt" Width="45"/> <asp:Button CommandName="Delete" Text="Delete" Runat="Server" Font-Size="8pt" Width="45"/> <asp:Label id="ConfirmDelete" Visible="False" EnableViewState="False" Runat="Server"> <asp:Label Text="<br/>Delete this record? " ForeColor="Red" EnableViewState="False" Runat="Server"/> <asp:Button Text="Yes" CommandName="Yes" Runat="Server" Font-Size="7pt" Width="30px"/> <asp:Button Text="No" CommandName="No" Runat="Server" Font-Size="7pt" Width="30px"/> </asp:Label> </ItemTemplate> <EditItemTemplate> <asp:Button CommandName="Update" Text="Update" Runat="Server" Font-Size="8pt" Width="45"/> <asp:Button CommandName="Cancel" Text="Cancel" Runat="Server" Font-Size="8pt" Width="45"/> </EditItemTemplate> <InsertItemTemplate> <asp:Button CommandName="Insert" Text="Insert" Runat="Server" Font-Size="8pt" Width="45"/> <asp:Button CommandName="Cancel" Text="Cancel" Runat="Server" Font-Size="8pt" Width="45"/> </InsertItemTemplate> </asp:TemplateField> <asp:BoundField ReadOnly="True" DataField="BookID" HeaderText="ID: " HeaderStyle-BackColor="#E0E0E0" HeaderStyle-Font-Bold="True"/> <asp:TemplateField HeaderText="Type: " HeaderStyle-BackColor="#E0E0E0" HeaderStyle-Font-Bold="True"> <ItemTemplate> <asp:Label Text='<%# Eval("BookType") %>' Runat="Server"/> </ItemTemplate> <EditItemTemplate> <asp:DropDownList id="EditType" Runat="Server" DataSourceID="TypeSource" DataTextField="BookType" DataValueField="BookType" SelectedValue='<%# Bind("BookType") %>'/> </EditItemTemplate> </asp:TemplateField> <asp:BoundField DataField="BookTitle" HeaderText="Title: " HeaderStyle-BackColor="#E0E0E0" HeaderStyle-Font-Bold="True"/> <asp:BoundField DataField="BookAuthor" HeaderText="Author: " HeaderStyle-BackColor="#E0E0E0" HeaderStyle-Font-Bold="True"/> <asp:TemplateField

Page 81: Assignment Instructions

HeaderText="Description: " HeaderStyle-BackColor="#E0E0E0" HeaderStyle-Font-Bold="True"> <ItemTemplate> <asp:Panel Width="210px" Height="75px" BorderWidth="1px" ScrollBars="Auto" BorderColor="#C0C0C0" Runat="Server"> <asp:Label Font-Size="10pt" Runat="Server" Text='<%# Eval("BookDescription") %>'/> </asp:Panel> </ItemTemplate> <EditItemTemplate> <asp:TextBox id="EditDescription" Text='<%# Bind("BookDescription") %>' Runat="Server" TextMode="MultiLine" Height="75px" Width="210px" Font-Name="Arial" Font-Size="9pt"/> </EditItemTemplate> </asp:TemplateField> <asp:BoundField DataField="BookPrice" HtmlEncode="False" DataFormatString="{0:C}" HeaderText="Price: " HeaderStyle-BackColor="#E0E0E0" HeaderStyle-Font-Bold="True"/> <asp:BoundField DataField="BookQty" HtmlEncode="False" DataFormatString="{0:D}" HeaderText="Qty: " HeaderStyle-BackColor="#E0E0E0" HeaderStyle-Font-Bold="True"/> <asp:CheckBoxField DataField="BookSale" HeaderText="Sale: " HeaderStyle-BackColor="#E0E0E0" HeaderStyle-Font-Bold="True"/> </Fields>

</asp:DetailsView>

<asp:Label id="EditMSG" EnableViewState="False" ForeColor="Red" Runat="Server"/>Listing 9-40. Code for DetailsView to display records selected from GridView.

Bound fields are used for several data fields, for example the BookPrice field. When this control is placed in edit or insert mode, a default textbox appears for changing or adding the field. To have more control over the size and properties of this entry box, you may wish to use a TemplateField instead, enclosing an <asp:TextBox>. This gives you the option of sizing the textbox, formatting its text, and including the MaxLength property to restrict the number of characters typed. A BoundField is a convenient editing device, but it may not provide the formatting, styling, and data-entry options you seek.

DetailsView Scripting

Page 82: Assignment Instructions

Scripting for the DetailsView uses similar techniques as before. Click events are trapped by OnItemInserting, OnItemInserted, OnItemUpdating, OnItemUpdated,OnItemDeleting, and OnItemCommand handlers. During inserting and updating, subprograms validate entered data; during deletion, confirmation buttons are presented. All subprograms display appropriate messages about the current editing status.

<SCRIPT Runat="Server">

Sub Validate_Insert_Data (Src As Object, Args As DetailsViewInsertEventArgs) If Args.Values("BookID") = "" Then Args.Cancel = True EditMSG.Text = "&bull; Missing BookID" End If If Args.Values("BookTitle") = "" Then Args.Cancel = True EditMSG.Text = "&bull; Missing title" End If If Args.Values("BookAuthor") = "" Then Args.Cancel = True EditMSG.Text = "&bull; Missing author" End If If Args.Values("BookDescription") = "" Then Args.Cancel = True EditMSG.Text = "&bull; Missing description" End If If Not IsNumeric(Args.Values("BookPrice")) Then Args.Cancel = True EditMSG.Text = "&bull; Price is not numeric" Else If Args.Values("BookPrice") < 0 _ OR Args.Values("BookPrice") > 200 Then Args.Cancel = True EditMSG.Text = "&bull; Price is out of range" End If End If If Not IsNumeric(Args.Values("BookQty")) Then Args.Cancel = True EditMSG.Text = "&bull; Quantity is not numeric" Else If Args.Values("BookQty") < 0 Then Args.Cancel = True EditMSG.Text = "&bull; Quantity is out of range" End If End If

End Sub

Sub Display_Insert_Msg (Src As Object, Args As DetailsViewInsertedEventArgs)

EditMSG.Text = "&bull; Record " & Args.Values("BookID") & " added" GridView.DataBind() End Sub

Sub Validate_Update_Data (Src As Object, Args As DetailsViewUpdateEventArgs)

Page 83: Assignment Instructions

If Args.NewValues("BookTitle") = "" Then Args.Cancel = True EditMSG.Text = "&bull; Missing title" End If If Args.NewValues("BookDescription") = "" Then Args.Cancel = True EditMSG.Text = "&bull; Missing description" End If If Not IsNumeric(Args.NewValues("BookPrice")) Then Args.Cancel = True EditMSG.Text = "&bull;Price is not numeric" Else If Args.NewValues("BookPrice") < 0 _ OR Args.NewValues("BookPrice") > 200 Then Args.Cancel = True EditMSG.Text = "&bull; Price is out of range" End If End If If Not IsNumeric(Args.NewValues("BookQty")) Then Args.Cancel = True EditMSG.Text = "&bull;Quantity is not numeric" Else If Args.NewValues("BookQty") < 0 Then Args.Cancel = True EditMSG.Text = "&bull; Quantity is out of range" End If End If End Sub

Sub Display_Update_Msg (Src As Object, Args As DetailsViewUpdatedEventArgs)

EditMSG.Text = "&bull; Record " & Args.Keys("BookID") & " updated" End Sub

Sub Confirm_Delete (Src As Object, Args As DetailsViewDeleteEventArgs) Args.Cancel = True Dim ConfirmLabel As Label ConfirmLabel = DetailsView.Rows(Args.RowIndex).FindControl("ConfirmDelete") ConfirmLabel.Visible = True ViewState("BookID") = Args.Keys("BookID") End Sub

Sub Get_Command (Src As Object, Args As DetailsViewCommandEventArgs)

If Args.CommandName = "Yes" Then DetailsSource.DeleteCommand = "DELETE FROM Books " & _ "WHERE BookID = '" & _ ViewState("BookID") & "'" DetailsSource.Delete() GridView.DataBind() EditMSG.Text = "&bull; Record " & ViewState("BookID") & " deleted" End If End Sub

</SCRIPT>

Page 84: Assignment Instructions

Listing 9-41. Script to validate and update records through master/detail GridView/DetailsView.

You will notice a slight difference in coding from previous scripts to locate the ConfirmDelete Label containing "Yes" and "No" confirmation buttons. In coding this technique for a previous FormView, the control's FindControl("id") method is used. For a DetailsView, however, this method's format is different as shown below.

Sub Confirm_Delete (Src As Object, Args As DetailsViewDeleteEventArgs) Args.Cancel = True Dim ConfirmLabel As Label ConfirmLabel = DetailsView.Rows(Args.RowIndex).FindControl("ConfirmDelete") ConfirmLabel.Visible = True ViewState("BookID") = Args.Keys("BookID") End SubListing 9-42. Finding controls in the first row of a DetailsView.

Whereas a FormView contains a single Row object that is searched for a control, a DetailsView contains multiple Rows, one for each visible row of the control. These rows are indexed beginning with 0, and this index is used to point to the particular row to search to find an enclosed control.

Arguments passed to subprograms when events occur on a row include the RowIndex property for that row. In the current DetailsView, the confirmation buttons and their container Label appear in the same row as the "Delete" button. Therefore, when this button is clicked to call the Confirm_Delete subprogram, this subprogram's Args.RowIndex property points to the same row containing the confirmation Label. This row, then, is searched for the ConfirmDelete Label.

Incidentally, in this particular example, it is known in advance that the confirmation label is on row 0. Therefore, this fixed index can be used to find the Label:

DetailsView.Rows(0).FindControl("ConfirmDelete")

Notice in the example GridView that when a new record is added or an existing record is deleted that the GridView is bound again from its AccessDataSource:GridView.DataBind(). This re-binding ensures that the GridView correctly represents the changed contents of the Books table after records are added and deleted.

A FormView can be easily substituted for the above DetailsView. The one from the previous FormView editing tutorial can be used by linking it to the GridView. Slight script changes are needed for subprogram arguments and for locating embedded controls. Still, it is nearly a matter of swapping one control for another.

Master/Detail Editing - FormView

When using a DetailsView along with a GridView for master/detail editing, you are limited to display of record details in a vertical column of individual fields. Although there is nothing particularly wrong with this alignment, it reduces somewhat the flexibility you have in arranging fields for display. With a FormView and its templates, however, a single cell (a singleRow object) gives you more choices on how to arrange headings and fields within this display area.

In the following example, a GridView handles record updating and deleting for the BooksDB.mdb database. Since a GridView does not supply record inserting functionality, a FormView sitting atop the GridView takes on this function. Fields are arranged horizontally along

Page 85: Assignment Instructions

a row to match the display format of the GridView. This is not a true master/detail arrangement since the FormView and GridView are not linked. They operate as two distinct controls. However, their appearance is suggestive of a master/detail application.

Note that making actual changes to the BooksDB.mdb database is not permitted in these tutorials; however, all other functions work as expected.

Book Edit

Edit ID Type Title Author Description Price Qty Sale

 DB111 Databas

eOracle Database

K. Loney

Get thorough coverage of Oracle Database 10g from the most comprehensive reference available, published by Oracle Press. With in-depth details on all the new features, this powerhouse resource provides an overview of database architecture and Oracle Grid Computing technology, and covers SQL, SQL*Plus, PL/SQL, dynamic PL/SQL, object-oriented features, and Java programming in the Oracle environment. You'll also find valuable database administration and application development techniques, plus an alphabetical reference covering major Oracle commands, keywords, features, and functions, with cross-referencing of topics.

$69.99 10

 DB222 Databas

eDatabases in Depth

C. J. Date

In Database in Depth, author and well-known database authority Chris Date lays out the fundamentals of the relational model. Don't let a lack to formal education in database theory hold you back. Instead, let Chris's clear explanation of relational concepts, set theory, the difference between model and implementation, relational algebra, normalization, and much more set you apart and well above the competition when it comes to getting work done with a relational database.

$29.95 6

 DB333 Databas

eDatabase Processing

D. Kroenke

Revised to reflect the needs of today's users, this 10th edition of Database Processing assures that you will learn marketable skills. By presenting SQL SELECT statements

$136.65 12

Page 86: Assignment Instructions

near the beginning of the book readers will know early on how to query data and obtain results-seeing firsthand some of the ways that database technology is useful in the marketplace. By utilizing free software downloads, you will be able to actively use a DBMS product by the end of the 2nd chapter. Each topic appears in the context of accomplishing practical tasks. Its spiral approach to database design provides users with enhanced information not available in other database books on the market.

 DB444 Databas

eAccess Database Design

S. Roman

When using software products with graphical interfaces, we frequently focus so much on the details of how to use the interface that we forget about the general concepts that allow us to understand and use the software effectively. This is particularly true of a powerful database product like Microsoft Access. Novice, and sometimes even experienced, programmers are so concerned with how something is done in Access that they often lose sight of the general principles that underlie their database applications. Access Database Design and Programming takes you behind the details of the Access interface, focusing on the general knowledge necessary for Access power users or developers to create effective database applications.

$34.95 25

 DB555 Databas

eSQL Server 2005

P. Debetta

Get a developer-focused introduction to the new programmability features in the next version of Microsoft SQL Server-including integration with the Microsoft .NET Framework-and learn powerful new ways to manipulate your servers. Whether you're a developer currently working with T-SQL or Microsoft Visual Studio.NET, or you're responsible for database administration, you'll see how to draw from your existing skills and knowledge to exploit new SQL Server technology. With introductory-level code samples

$29.99 0

Page 87: Assignment Instructions

written in both T-SQL and C#, you'll understand how to take advantage of the cross-platform interoperability, native support for XML and Web services, shared language base, and other programming innovations to build better solutions from business intelligence to enterprise data management.

1 2 3 4 5 6

Figure 9-25. Master/detail editing with a FormView and GridView.

Coding the FormView

Two AccessDataSource controls are needed for the FormView. One is for inserting new records into the database, the second is to supply book types for the DropDownList of choices. Although the FormView does not display selected records (it is used only to add new records), it still requires a SelectCommand with a record selection to make the FormView visible. If its binding is null, the form does not display. Therefore, an arbitrary selection is made of the first record in the Books table (SELECT TOP 1 * FROM Books). This record is never displayed because the FormView's ItemTemplate does not include binding fields. However, it ensures that the FormView is always visible.

<asp:AccessDataSource id="AddSource" Runat="Server" DataFile="../Databases/BooksDB.mdb" SelectCommand="SELECT TOP 1 * FROM Books" InsertCommand="INSERT INTO Books (BookID, BookType, BookTitle, BookAuthor, BookDescription, BookPrice, BookQty, BookSale) VALUES (@BookID, @BookType, @BookTitle, @BookAuthor, @BookDescription, @BookPrice, @BookQty, @BookSale)"/>

<asp:AccessDataSource id="TypeSource" Runat="Server" DataFile="../Databases/BooksDB.mdb" SelectCommand="SELECT DISTINCT BookType FROM Books ORDER BY BookType"/>Listing 9-43. AccessDataSources for FormView used with a master GridView.

Coding for the FormView is shown below along with the message display and delete confirmation areas. Notice that exacting measurements are used to position the FormView precisely in alignment with the GridView so that it visually appears almost as part of the GridView. Also notice that the record confirmation area is positioned outside the FormView and the GridView to which it applies. In this location it is not necessary to search for it with the FindControl() method. It is directly accessible on the page. Also, the "Yes" and "No" buttons can directly call subprograms rather than being configured as GridView command buttons. A CSS style sheet is provided for the XHTML tables used in the FormView's templates.

Since the FormView is used only for record additions, it requires only those event handlers related to ItemInserting and ItemInserted events. The former calls a subprogram to validate entered data, the latter to rebind the DataGrid to reflect additions.

Page 88: Assignment Instructions

<h3>Book Edit</h3>

<asp:Label id="EditMSG" Height="25" ForeColor="Red" Runat="Server"EnableViewState="False"/>

<asp:Label id="ConfirmDelete" Visible="False" Runat="Server"EnableViewState="False" Height="25"> <asp:Label Text="Delete this record? " Runat="Server" ForeColor="Red" EnableViewState="False"/> <asp:Button Text="Yes" OnClick="Delete_Record" Runat="Server" Font-Size="7pt" Width="30px"/> <asp:Button Text="No" OnClick="Cancel_Delete" Runat="Server" Font-Size="7pt" Width="30px"/></asp:Label>

<style type="text/css"> table#Head {border-collapse:collapse} table#Head th {font-size:11pt; background-color:#E0E0E0} table#Head td {font-size:11pt} table#Edit {width:630px; border-collapse:collapse} table#Insert {border-collapse:collapse} table#Insert td {font-size:10pt}</style>

<asp:FormView id="AddForm" DataSourceID="AddSource" Runat="Server" InsertRowStyle-BackColor="#00EE00" OnItemInserting="Validate_Insert_Data" OnItemInserted="Insert_Record"> <HeaderTemplate> <table id="Head" border="1"> <tr> <th><asp:Label Text="Edit" Width="70px" Runat="Server"/></th> <th><asp:Label Text="ID" Width="45px" Runat="Server"/></th> <th><asp:Label Text="Type" Width="80px" Runat="Server"/></th> <th><asp:Label Text="Title" Width="80px" Runat="Server"/></th> <th><asp:Label Text="Author" Width="70px" Runat="Server"/></th> <th><asp:Label Text="Description" Width="150px" Runat="Server"/></th> <th><asp:Label Text="Price" Width="40px" Runat="Server"/></th> <th><asp:Label Text="Qty" Width="25px" Runat="Server"/></th> <th><asp:Label Text="Sale" Width="42px" Runat="Server"/></th> </tr> </table> </HeaderTemplate> <ItemTemplate> <table id="Edit" border="1"> <tr> <td><asp:Button Text="New" CommandName="New" Runat="Server" Font-Size="7pt" Width="35px"/></td> </tr> </table> </ItemTemplate> <InsertItemTemplate> <table id="Insert" border="1"> <tr> <td nowrap> <asp:Button Text="Insert" CommandName="Insert" Runat="Server" Font-Size="7pt" Width="35px"/> <asp:Button Text="Cancel" CommandName="Cancel" Runat="Server" Font-Size="7pt" Width="35px"/></td> <td><asp:TextBox id="AddBookID" Runat="Server"

Page 89: Assignment Instructions

Text='<%# Bind("BookID")% >' Font-Size="8pt" Width="45" MaxLength="5"/></td> <td><asp:DropDownList id="AddType" Runat="Server" DataSourceID="TypeSource" DateTextField="BookType" DataValueField="BookType" SelectedValue='<%# Bind("BookType") %>' Font-Size="8pt" Width="78"/></td> <td><asp:TextBox id="AddTitle" Runat="Server" Text='<%# Bind("BookTitle") %>' Font-Size="8pt" Width="80"/></td> <td><asp:TextBox id="AddAuthor" Runat="Server" Text='<%# Bind("BookAuthor") %>' Font-Size="8pt" Width="70"/></td> <td><asp:TextBox id="AddDescription" Runat="Server" Text='<%# Bind("BookDescription") %>' TextMode="MultiLine" Rows="2" Font-Name="Arial" Font-Size="8pt" Width="150"/></td> <td><asp:TextBox id="AddPrice" Runat="Server" Text='<%# Bind("BookPrice") %>' Font-Size="8pt" Width="40" MaxLength="6" Style="text-align:right"/></td> <td><asp:TextBox id="AddQuantity" Runat="Server" Text='<%# Bind("BookQty") %>' Font-Size="8pt" Width="25" MaxLength="2" Style="text-align:right"/></td> <td style="text-align:center"> <asp:CheckBox id="AddSale" Runat="Server" Checked='<%# Bind("ItemSale") %>' Width="40"/></td> </tr> </table> </InsertItemTemplate>

</asp:FormView>Listing 9-44. Code for FormView used with a master GridView.

The FormView's ItemTemplate, where normally a selected record is displayed, is used instead to display only the "New" command button for record insertion. Its InsertItemTemplate contains related "Insert" and "Cancel" buttons along with empty fields for data entry for a new record. Here is where the FormView provides layout flexibility to match its display with that of the companion GridView. Also, by placing all elements inside <table> tags, provision is made for their exact sizing and placement.

Coding the GridView

The GridView, like the FormView, requires two AccessDataSource controls. The one shown below services selection, updating, and deletion of records. A DeleteCommand is not included since it is composed in a separate subprogram. The second AccessDataSource required to populate the DropDownList of book types is the same one used for the FormView.

<asp:AccessDataSource id="EditSource" Runat="Server" DataFile="../Databases/BooksDB.mdb" SelectCommand="SELECT * FROM Books ORDER BY BookID" UpdateCommand="UPDATE Books SET BookType=@BookType, BookTitle=@BookTitle, BookAuthor=@BookAuthor, BookDescription=@BookDescription, BookPrice=@BookPrice, BookQty=@BookQty, BookSale=@BookSale WHERE BookID=@BookID"

Page 90: Assignment Instructions

/>Listing 9-45. AccessDataSource for master GridView.

Coding for the GridView is shown below. Notice that all fields are defined explicitly as server controls located inside TemplateFields for precise sizing and arrangement to match the layout of the FormView. Editing buttons are Button controls configured as command buttons for the RowUpdating, RowUpdated, and RowDeleting events.

<asp:GridView id="EditGrid" DataSourceID="EditSource" Runat="Server" AutoGenerateColumns="False" DataKeyNames="BookID" ShowHeader="False" AllowPaging="True" PageSize="5" EditRowStyle-BackColor="#FFFF00" PagerStyle-BackColor="#E0E0E0" RowStyle-VerticalAlign="Top" RowStyle-Font-Size="10pt" OnRowUpdating="Validate_Update_Data" OnRowUpdated="Update_Record" OnRowDeleting="Confirm_Delete"> <Columns> <asp:TemplateField ItemStyle-Wrap="False"> <ItemTemplate> <asp:Button Text="Edit" CommandName="Edit" Runat="Server" Font-Size="7pt" Width="35px"/> <asp:Button Text="Delete" CommandName="Delete" Runat="Server" Font-Size="7pt" Width="35px"/> </ItemTemplate> <EditItemTemplate> <asp:Button Text="Update" CommandName="Update" Runat="Server" Font-Size="7pt" Width="35px"/> <asp:Button Text="Cancel" CommandName="Cancel" Runat="Server" Font-Size="7pt" Width="35px"/> </EditItemTemplate> </asp:TemplateField> <asp:TemplateField> <ItemTemplate> <asp:Label Text='<%# Eval("BookID") %>' Runat="Server" Width="45"/> </ItemTemplate> <EditItemTemplate> <asp:Label Text='<%# Eval("BookID") %>' Runat="Server" Width="45"/> </EditItemTemplate> </asp:TemplateField> <asp:TemplateField> <ItemTemplate> <asp:Label Text='<%# Eval("BookType") %>' Runat="Server" Width="80"/> </ItemTemplate> <EditItemTemplate> <asp:DropDownList id="EditType" Runat="Server" DataSourceID="TypeSource" DateTextField="BookType" DataValueField="BookType" SelectedValue='<%# Bind("BookType") %>'

Page 91: Assignment Instructions

Font-Size="8pt"/> </EditItemTemplate> </asp:TemplateField> <asp:TemplateField> <ItemTemplate> <asp:Label Text='<%# Eval("BookTitle") %>' Runat="Server" Width="80"/> </ItemTemplate> <EditItemTemplate> <asp:TextBox id="EditTitle" Runat="Server" Text='<%# Bind("BookTitle") %>' Width="80" Font-Size="8pt"/> </EditItemTemplate> </asp:TemplateField> <asp:TemplateField> <ItemTemplate> <asp:Label Text='<%# Eval("BookAuthor") %>' Runat="Server" Width="70"/> </ItemTemplate> <EditItemTemplate> <asp:TextBox id="EditAuthor" Runat="Server" Text='<%# Bind("BookAuthor") %>' Width="70" Font-Size="8pt"/> </EditItemTemplate> </asp:TemplateField> <asp:TemplateField> <ItemTemplate> <asp:Panel Width="150" Height="30" ScrollBars="Vertical" Runat="Server"> <asp:Label Text='<%# Eval("BookDescription") %>' Runat="Server"/> </asp:Panel> </ItemTemplate> <EditItemTemplate> <asp:TextBox id="EditDescription" Runat="Server" Text='<%# Bind("BookDescription") %>' TextMode="MultiLine" Rows="2" Font-Name="Arial" Width="150" Font-Size="8pt"/> </EditItemTemplate> </asp:TemplateField> <asp:TemplateField ItemStyle-HorizontalAlign="Right"> <ItemTemplate> <asp:Label Text='<%# String.Format("{0:N}", Eval("BookPrice")) %>' Width="40" Runat="Server"/> </ItemTemplate> <EditItemTemplate> <asp:TextBox id="EditPrice" Runat="Server" Text='<%# Bind("BookPrice") %>' Font-Size="8pt" Width="40" MaxLength="6" Style="text-align:right"/> </EditItemTemplate> </asp:TemplateField> <asp:TemplateField ItemStyle-HorizontalAlign="Right"> <ItemTemplate> <asp:Label Text='<%# String.Format("{0:D}", Eval("BookQty")) %>' Width="25" Runat="Server"/> </ItemTemplate>

Page 92: Assignment Instructions

<EditItemTemplate> <asp:TextBox id="EditQty" Runat="Server" Text='<%# Bind("BookQty") %>' Font-Size="8pt" Width="25" MaxLength="2" Style="text-align:right"/> </EditItemTemplate> </asp:TemplateField> <asp:TemplateField ItemStyle-HorizontalAlign="Center"> <ItemTemplate> <asp:CheckBox Checked='<%# Eval("BookSale") %>' Runat="Server" Enabled="False" Width="40"/> </ItemTemplate> <EditItemTemplate> <asp:CheckBox id="EditSale" Runat="Server" Checked='<%# Bind("BookSale") %>' Width="40"/> </EditItemTemplate> </asp:TemplateField> </Columns>

</asp:GridView>Listing 9-46. Code for master GridView.

Scripting the Controls

Subprograms called from both the FormView and GridView are shown below. This coding should be familiar from previous scripts implementing the same functionality.

<%@ Import Namespace="System.Drawing" %>

<SCRIPT Runat="Server">

Sub Validate_Update_Data (Src As Object, Args As GridViewUpdateEventArgs)

If Args.NewValues("BookTitle") = "" Then Args.Cancel = True EditMSG.Text = "&bull; Missing title" End If If Args.NewValues("BookAuthor") = "" Then Args.Cancel = True EditMSG.Text = "&bull; Missing author" End If If Args.NewValues("BookDescription") = "" Then Args.Cancel = True EditMSG.Text = "&bull; Missing description" End If If Not IsNumeric(Args.NewValues("BookPrice")) Then Args.Cancel = True EditMSG.Text = "&bull; Price not numeric" Else If Args.NewValues("BookPrice") < 0 _ OR Args.NewValues("BookPrice") > 200 Then Args.Cancel = True EditMSG.Text = "&bull; Price is out of range"

Page 93: Assignment Instructions

End If End If If Not IsNumeric(Args.NewValues("BookQty")) Then Args.Cancel = True EditMSG.Text = "&bull; Quantity not numeric" Else If Args.NewValues("BookQty") < 0 Then Args.Cancel = True EditMSG.Text = "&bull; Quantity is out of range" End If End If End Sub

Sub Update_Record (Src As Object, Args As GridViewUpdatedEventArgs)

EditMSG.Text = "&bull; Record " & Args.Keys("BookID") & " updated" End Sub

Sub Validate_Insert_Data (Src As Object, Args As FormViewInsertEventArgs)

If Args.Values("BookID") = "" Then Args.Cancel = True EditMSG.Text = "&bull; Missing BookID" End If If Args.Values("BookTitle") = "" Then Args.Cancel = True EditMSG.Text = "&bull; Missing title" End If If Args.Values("BookAuthor") = "" Then Args.Cancel = True EditMSG.Text = "&bull; Missing author" End If If Args.Values("BookDescription") = "" Then Args.Cancel = True EditMSG.Text = "&bull; Missing description" End If If Not IsNumeric(Args.Values("BookPrice")) Then Args.Cancel = True EditMSG.Text = "&bull; Price not numeric" Else If Args.Values("BookPrice") < 0 _ OR Args.Values("BookPrice") > 200 Then Args.Cancel = True EditMSG.Text = "&bull; Price out of range" End If End If If Not IsNumeric(Args.Values("BookQty")) Then Args.Cancel = True EditMSG.Text = "&bull; Quantity not numeric" Else If Args.Values("BookQty") < 0 Then Args.Cancel = True EditMSG.Text = "&bull; Quantity out of range" End If End If

Page 94: Assignment Instructions

End Sub

Sub Insert_Record (Src As Object, Args As FormViewInsertedEventArgs)

EditGrid.DataBind() EditMSG.Text = "&bull; Record " & Args.Values("BookID") & " added" End Sub

Sub Confirm_Delete (Src As Object, Args As GridViewDeleteEventArgs)

Args.Cancel = True ConfirmDelete.Visible = True Dim Row As GridViewRow = EditGrid.Rows(Args.RowIndex) Row.BackColor = Color.FromName("#FF3333") Row.ForeColor = Color.FromName("#FFFFFF") ViewState("RowIndex") = Args.RowIndex ViewState("BookID") = Args.Keys("BookID") End Sub

Sub Delete_Record (Src As Object, Args As EventArgs)

EditSource.DeleteCommand = "DELETE FROM Books WHERE " & _ BookID = '" & ViewState("BookID") & "'" EditSource.Delete() Dim Row As GridViewRow = EditGrid.Rows(ViewState("RowIndex")) Row.BackColor = Color.FromName("#FFFFFF") Row.ForeColor = Color.FromName("#000000") EditMSG.Text = "&bull; Record " & ViewState("BookID") & " deleted" End Sub

Sub Cancel_Delete (Src As Object, Args As EventArgs)

Dim Row As GridViewRow = EditGrid.Rows(ViewState("RowIndex")) Row.BackColor = Color.FromName("#FFFFFF") Row.ForeColor = Color.Fromname("#000000") ConfirmDelete.Visible = False End Sub

</SCRIPT>Listing 9-47. Script to validate and update records through master/detail GridView/FormView.

It is noteworthy that clicking edit buttons highlights the affected row with a background color giving a visual clue to the operation: green for inserting, yellow for updating, and red for deleting. For the FormView, this row styling is handled by coding the InsertRowStyle-BackColor="#00EE00" property, for the GridView by coding the EditRowStyle-BackColor="#FFFF00" property. For a GridView delete operation, however, there is no comparable row styling property. This is because, normally, a record is immediately deleted with no editing row appearing for styling. By trapping the RowDeleting event prior to deletion, though, the normal ItemTemplate row can be singled out for highlighting.

Locating GridView Rows

Highlighting the GridView row of the record marked for deletion takes place in the three subprograms associated with record deletion: Confirm_Delete, Delete_Record,

Page 95: Assignment Instructions

andCancel_Delete. The technique involves determining on which row the "Delete" button was clicked and styling that row with a background color.

For a subprogram called by a GridView command button, its argument supplies an index (beginning with 0) of the row on which a clicked button appears (Args.RowIndex in the current subprograms). This index is used to identify this row from among the GridView's Rows collection and to assign it to a GridViewRow object. This object is then highlighted with background and foreground colors. Code to locate and highlight a clicked row when the Confirm_Delete subprogram is called on a click of the "Delete" button is reproduced below.

Sub Confirm_Delete (Src As Object, Args As GridViewDeleteEventArgs)

Args.Cancel = True ConfirmDelete.Visible = True Dim Row As GridViewRow = EditGrid.Rows(Args.RowIndex) Row.BackColor = Color.FromName("#FF3333") Row.ForeColor = Color.FromName("#FFFFFF") ViewState("RowIndex") = Args.RowIndex ViewState("BookID") = Args.Keys("BookID") End SubListing 9-48. Finding and saving a reference to a GridView row.

Since subsequent Delete_Record and Cancel_Delete subprograms are called outside the context of the GridView (they are not called by GridView command buttons but by independent "Yes" and "No" buttons), a RowIndex is not available to these subprograms to change the row background color back to normal. Therefore, the Confirm_Deletesubprogram saves the current RowIndex value in a View State variable, ViewState("RowIndex"). This row identifier is then used in the Delete_Record and Cancel_Deletesubprograms to reset the row's background color. Affected statements in these two subprograms are repeated below.

Sub Delete_Record (Src As Object, Args As EventArgs)

EditSource.DeleteCommand = "DELETE FROM Books WHERE " & _ BookID = '" & ViewState("BookID") & "'" EditSource.Delete() Dim Row As GridViewRow = EditGrid.Rows(ViewState("RowIndex")) Row.BackColor = Color.FromName("#FFFFFF") Row.ForeColor = Color.FromName("#000000") EditMSG.Text = "&bull; Record " & ViewState("BookID") & " deleted" End Sub

Sub Cancel_Delete (Src As Object, Args As EventArgs)

Dim Row As GridViewRow = EditGrid.Rows(ViewState("RowIndex")) Row.BackColor = Color.FromName("#FFFFFF") Row.ForeColor = Color.Fromname("#000000") ConfirmDelete.Visible = False End SubListing 9-49. Styling a GridView row.

It requires quite a bit of trial and error to align a FormView with a GridView in the above manner. However, if you have the patience, the combination makes a very effective "master/detail" editing form that can be adapted to many editing needs.

Page 96: Assignment Instructions

Editing with a Repeater

A Repeater can be used for database editing, although additional scripting is required beyond that needed for a GridView, DetailsView, or FormView. Also, a Repeater does not incorporate many of the built-in editing features of these other controls. Still, it is not too difficult to adapt this information display control to an editing control as shown in the following example of editing the Books table of the BooksDB.mdb database. What becomes problematic in using a Repeater for editing is the lack of a paging mechanism. This absence limits the Repeater to editing databases of relatively modest sizes.

Note that making actual changes to the BooksDB.mdb database is not permitted in these tutorials; however, all other functions work as expected.

Book Edit

 

ID Type Title Author Description Price QtySale

Edit

           

             

             

             

             

             

             

Page 97: Assignment Instructions

             

             

             

             

             

             

             

             

             

             

             

             

Page 98: Assignment Instructions

             

             

             

             

             

             

             

             

             

             

             

             

Page 99: Assignment Instructions

Figure 9-26. Editing a database with a Repeater.

The Repeater Form

The Repeater displays all records and fields in the Books table within a set of data input areas. The data items can be edited directly in the fields and the "Update" button for that record is clicked to rewrite the information to the database. Also associated with each record is a "Delete" button for removing the record from the database. The top row of the Repeater displays blank input areas and an "Insert" button for entering data and creating a new record. Data validation is not performed in this example.

Two AccessDataSource controls are needed. One populates the Repeater with records from the database. The second supplies book types for the drop-down lists. Code for all controls is shown below.

<asp:AccessDataSource id="BookSource" Runat="Server" DataFile="../Databases/BooksDB.mdb" SelectCommand = "SELECT * FROM Books ORDER BY BookID"/>

<asp:AccessDataSource id="TypeSource" Runat="Server" DataFile="../Databases/BooksDB.mdb" SelectCommand = "SELECT DISTINCT BookType FROM Books ORDER BY BookType"/>

<h3>Book Edit</h3>

<asp:Label id="EditMessage" Text="&nbsp;" ForeColor="#FF0000"Runat="Server"/><br/>

<asp:Repeater id="RepeaterEdit" Runat="Server" DataSourceID="BookSource" OnItemCommand="Edit_Record"> <HeaderTemplate> <table id="RepeaterTable" border="1" style="border-collapse:collapse"> <tr style="background-color:#E0E0E0"> <th>ID</th> <th>Type</th> <th>Title</th> <th>Author</th> <th>Description</th> <th>Price</th> <th>Qty</th> <th>Sale</th> <th>Edit</th> </tr> <tr> <td><asp:TextBox id="BookID" Runat="Server" Font-Size="8pt" Width="45px"/></td> <td><asp:DropDownList id="BookType" Runat="Server" DataSourceID="TypeSource" DataTextField="BookType" DataValueField="BookType" Font-Size="8pt"/></td> <td><asp:TextBox id="BookTitle" Runat="Server" Font-Size="8pt" Width="80px"/></td> <td><asp:TextBox id="BookAuthor" Runat="Server" Font-Size="8pt" Width="90px"/></td> <td><asp:TextBox id="BookDescription" Runat="Server" TextMode="MultiLine" Font-Name="Arial"

Page 100: Assignment Instructions

Font-Size="7pt" Width="150" Rows="2"/></td> <td><asp:TextBox id="BookPrice" Runat="Server" Font-Size="8pt" Width="50px" Style="text-align:right"/></td> <td><asp:TextBox id="BookQty" Runat="Server" Font-Size="8pt" Width="25px" Style="text-align:right"/></td> <td><asp:CheckBox id="BookSale" Runat="Server"/></td> <td nowrap> <asp:Button Text="Insert" CommandName="Insert" Runat="Server" Font-Size="7pt" Width="45"/></td> </tr> <tr> <td colspan="9" style="border-bottom:solid 1 black"></td> </tr> </HeaderTemplate> <ItemTemplate> <tr> <td><asp:TextBox id="BookID" ReadOnly="True" Runat="Server" Text='<%# Eval("BookID") %>' Font-Size="8pt" Width="45px"/></td> <td><asp:DropDownList id="BookType" Runat="Server" DataSourceID="TypeSource" DataTextField="BookType" DataValueField="BookType" SelectedValue='<%# Eval("BookType") %>' Font-Size="8pt"/></td> <td><asp:TextBox id="BookTitle" Runat="Server" Text='<%# Eval("BookTitle") %>' Font-Size="8pt" Width="80px"/></td> <td><asp:TextBox id="BookAuthor" Runat="Server" Text='<%# Eval("BookAuthor") %>' Font-Size="8pt" Width="90px"/></td> <td><asp:TextBox id="BookDescription" Runat="Server" Text='<%# Eval("BookDescription") %>' TextMode="MultiLine" Font-Name="Arial" Font-Size="7pt" Width="150" Rows="2"/></td> <td><asp:TextBox id="BookPrice" Runat="Server" Text='<%# Eval("BookPrice") %>' Font-Size="8pt" Width="50px" Style="text-align:right"/></td> <td><asp:TextBox id="BookQty" Runat="Server" Text='<%# Eval("BookQty") %>' Font-Size="8pt" Width="25px" Style="text-align:right"/></td> <td><asp:CheckBox id="BookSale" Runat="Server" Checked='<%# Eval("BookSale") %>'/></td> <td nowrap> <asp:Button Text="Update" CommandName="Update" Runat="Server" Font-Size="7pt" Width="45"/> <asp:Button Text="Delete" CommandName="Delete" Runat="Server" Font-Size="7pt" Width="45"/></td> </tr> </ItemTemplate> <FooterTemplate> </table> </FooterTemplate> </asp:Repeater>Listing 9-50. Code for editing with a Repeater control.

Page 101: Assignment Instructions

You should note that data binding expressions are in the format <%# Eval("field") %>. The Eval() method is used rather than the Bind() method needed when working with a GridView, DetailsView, and FormView.

Locating Repeater Controls

A Repeater responds to user events such as clicks on its enclosed buttons by including an OnItemCommand event handler. In the current example, this event handler calls subprogram Edit_Record when any of the "Insert," "Update," or "Delete" buttons is clicked. The subprogram called by an OnItemCommand event handler must have the argumentRepeaterCommandEventArgs. Also, the buttons must include CommandName properties to identify themselves to the called subprogram.

Rows in a Repeater are indexed. The header row is index -1; other rows are indexed beginning with 0. This row index is passed to the OnItemCommand subprogram through itsRepeaterCommandEventArgs argument to identify the row on which a button is clicked and to differentiate among the several rows of controls comprising the Repeater. The row index along with a control's id give a unique identifier to each control. It becomes a matter of using the Repeater's FindControl() method to locate individual controls for editing. The first portion of the Edit_Record subprogram illustrates this process.

Sub Edit_Record (Src As Object, Args As RepeaterCommandEventArgs)

Dim BookID As TextBox = Args.Item.FindControl("BookID") Dim BookType As DropDownList = Args.Item.FindControl("BookType") Dim BookTitle As TextBox = Args.Item.FindControl("BookTitle") Dim BookAuthor As TextBox = Args.Item.FindControl("BookAuthor") Dim BookDescription As TextBox = Args.Item.FindControl("BookDescription") Dim BookPrice As TextBox = Args.Item.FindControl("BookPrice") Dim BookQty As TextBox = Args.Item.FindControl("BookQty") Dim BookSale As CheckBox = Args.Item.FindControl("BookSale")

...End SubListing 9-51. Finding Repeater controls on a clicked row.

The argument passed to the subprogram includes a reference to the row (Item) on which a button is clicked. This reference includes an index for the row (Item.ItemIndex ), and it contains the collection of controls making up the row. These controls are accessed by the row's FindControl() method, supplying the id of the control of interest. For example, the reference Args.Item.FindControl("BookID") in the above example locates the TextBox with the identification "BookID" on whichever row a button is clicked. When this reference is assigned to a variable of the same control type, then the script can access properties associated with that particular control in the Repeater.

Dim BookID As TextBox = Args.Item.FindControl("BookID")Listing 9-52. Finding a TextBox control on a Repeater row.

The above statement permits use of the reference BookID.Text to access the value contained in the TextBox with id="BookID" on whichever row a command button is clicked. In the preceding script, all controls along the reference row are found and assigned to variables of like types. This makes it easy for the script to refer to all data values appearing along that row of the Repeater and to involve them in database insert, update, and delete operations.

Page 102: Assignment Instructions

Repeater Editing

The remainder of the Edit_Record subprogram to perform insert, update, and delete operations is shown below.

Sub Edit_Record (Src As Object, Args As RepeaterCommandEventArgs)

Dim BookID As TextBox = Args.Item.FindControl("BookID") Dim BookType As DropDownList = Args.Item.FindControl("BookType") Dim BookTitle As TextBox = Args.Item.FindControl("BookTitle") Dim BookAuthor As TextBox = Args.Item.FindControl("BookAuthor") Dim BookDescription As TextBox = Args.Item.FindControl("BookDescription") Dim BookPrice As TextBox = Args.Item.FindControl("BookPrice") Dim BookQty As TextBox = Args.Item.FindControl("BookQty") Dim BookSale As CheckBox = Args.Item.FindControl("BookSale") If Args.CommandName = "Update" Then Dim SQLString As String = "UPDATE Books SET " & _ "BookType = '" & BookType.SelectedValue & "', " & _ "BookTitle = '" & BookTitle.Text & "', " & _ "BookAuthor = '" & BookAuthor.Text & "', " & _ "BookDescription = '" & BookDescription.Text & "', " & _ "BookPrice = '" & BookPrice.Text & "', " & _ "BookQty = '" & BookQty.Text & "', " & _ "BookSale = " & BookSale.Checked & " " & _ "WHERE BookID = '" & BookID.Text & "'" BookSource.UpdateCommand = SQLString BookSource.Update() EditMessage.Text = "Record " & BookID.Text & " updated" End If

If Args.CommandName = "Insert" Then Dim SQLString As String = "INSERT INTO Books " & _ "(BookID, BookType, BookTitle, BookAuthor, BookDescription, " & _ "BookPrice, BookQty, BookSale) VALUES (" & _ "'" & BookID.Text & "', " & _ "'" & BookType.SelectedValue & "', " & _ "'" & BookTitle.Text & "', " & _ "'" & BookAuthor.Text & "', " & _ "'" & BookDescription.Text & "', " & _ "'" & BookPrice.Text & "', " & _ "'" & BookQty.Text & "', " & _ BookSale.Checked & ")" BookSource.InsertCommand = SQLString BookSource.Insert() EditMessage.Text = "Record " & BookID.Text & " inserted" End If If Args.CommandName = "Delete" Then Dim SQLString As String = "DELETE FROM Books " _ & "WHERE BookID = '" & BookID.Text & "'" BookSource.DeleteCommand = SQLString BookSource.Delete() EditMessage.Text = "Record " & BookID.Text & " deleted" End If

Page 103: Assignment Instructions

End SubListing 9-53. Repeater script to edit records.

The subprogram needs to know which button on the row was clicked in order to perform appropriate processing. Recall that the buttons include CommandNames to identify whether to perform "Insert," "Update," or "Delete" operations. The CommandName of the clicked button is contained in the CommandName argument passed to the subprogram. Therefore, by testing Args.CommandName the subprogram can determine which processing action to take.

Inserting Records

If the clicked button has the CommandName of "Insert," then the section of the script to add a new record to the database is performed. This section is repeated below.

If Args.CommandName = "Insert" Then Dim SQLString As String = "INSERT INTO Books " & _ "(BookID, BookType, BookTitle, BookAuthor, BookDescription, " & _ "BookPrice, BookQty, BookSale) VALUES (" & _ "'" & BookID.Text & "', " & _ "'" & BookType.SelectedValue & "', " & _ "'" & BookTitle.Text & "', " & _ "'" & BookAuthor.Text & "', " & _ "'" & BookDescription.Text & "', " & _ "'" & BookPrice.Text & "', " & _ "'" & BookQty.Text & "', " & _ BookSale.Checked & ")" BookSource.InsertCommand = SQLString BookSource.Insert() EditMessage.Text = "Record " & BookID.Text & " inserted"

End IfListing 9-54. Repeater script to insert a new record.

Processing involves composing an SQL statement to INSERT a new record into the Books table of the database. The SQL statement is assigned to the InsertCommand property of the AccessDataSource for the Repeater, and its Insert() method is called.

Recall from above that controls along the insert row of the Repeater (along the row containing the insert button that is clicked) have been found and assigned to reference variables. For TextBoxes along the row, their Text properties point to their entered values; for the DropDownList, its SelectedValue property is accessed; for the CheckBox, its Checkedproperty is its value. These row values are concatenated inside fixed text strings to compose an appropriate INSERT statement that is issued through the AccessDataSource. After record insertion, the Repeater is automatically re-bound to the data source to display the added record.

Updating Records

A similar process is followed to update existing records. This portion of the Edit_Records subprogram is repeated below.

If Args.CommandName = "Update" Then

Page 104: Assignment Instructions

Dim SQLString As String = "UPDATE Books SET " & _ "BookType = '" & BookType.SelectedValue & "', " & _ "BookTitle = '" & BookTitle.Text & "', " & _ "BookAuthor = '" & BookAuthor.Text & "', " & _ "BookDescription = '" & BookDescription.Text & "', " & _ "BookPrice = '" & BookPrice.Text & "', " & _ "BookQty = '" & BookQty.Text & "', " & _ "BookSale = " & BookSale.Checked & " " & _ "WHERE BookID = '" & BookID.Text & "'" BookSource.UpdateCommand = SQLString BookSource.Update() EditMessage.Text = "Record " & BookID.Text & " updated"

End IfListing 9-55. Repeater script to update an existing record.

If the CommandName of the clicked button is "Update," then an SQL UPDATE statement is composed, inserting data values from controls along the same row as the clicked button. Notice that the BookID (key) field is not updated since it is used to locate the database record to update. In fact, the BookID TextBoxes on the edit rows do not permit changing these numbers. They all have ReadOnly="True" property settings.

After the UPDATE statement is composed, it is assigned to the UpdateCommand property of the AccessDataSource and its Update() method is called. After the record is changed, the Repeater redisplays the changes.

Deleting Records

Deleting the record on the row containing the clicked "Delete" button is a matter of composing an SQL DELETE statement that includes the value from the BookID TextBox on that row. This statement is assigned to the DeleteCommand property of the AccessDataSource and its Delete() method is called. The redisplayed Repeater is absent the deleted record.

If Args.CommandName = "Delete" Then Dim SQLString As String = "DELETE FROM Books " _ & "WHERE BookID = '" & BookID.Text & "'" BookSource.DeleteCommand = SQLString BookSource.Delete() EditMessage.Text = "Record " & BookID.Text & " deleted" End IfListing 9-56. Repeater script to delete an existing record.

Record Validation

In this example application no validation is perform on inserted or updated fields. These routines are left out to simplify description of the editing routines. It is a simple matter, however, to insert validation routines inside the appropriate sections of code. The following portion of script for inserting new records is augmented with selected validation tests that have been used in previous examples.

If Args.CommandName = "Insert" Then

Dim ValidRecord As Boolean = True

Page 105: Assignment Instructions

If BookID.Text = "" Then ValidRecord = False EditMessage.Text = "-- Missing BookID. Record not added." End If

If Not IsNumeric(BookPrice.Text) Then ValidRecord = False EditMessage.Text = "-- Price is not numeric. Record not added." End If

If Not IsNumeric(BookQty.Text) Then ValidRecord = False EditMessage.Text = "-- Quantity is not numeric. Record not added" End If

If ValidRecord = True Then

Dim SQLString As String = "INSERT INTO Books " & _ "(BookID, BookType, BookTitle, BookAuthor, BookDescription, " & _ "BookPrice, BookQty, BookSale) VALUES (" & _ "'" & BookID.Text & "', " & _ "'" & BookType.SelectedValue & "', " & _ "'" & BookTitle.Text & "', " & _ "'" & BookAuthor.Text & "', " & _ "'" & BookDescription.Text & "', " & _ "'" & BookPrice.Text & "', " & _ "'" & BookQty.Text & "', " & _ BookSale.Checked & ")" BookSource.InsertCommand = SQLString BookSource.Insert() EditMessage.Text = "Record " & BookID.Text & " inserted"

End If

End IfListing 9-57. Repeater script to validate an inserted record.

Prior to writing a new record to the database, three tests are made on the BookID, BookPrice, and BookQty fields of the insert row. Initially, a validation flag (ValidRecord) is set toTrue. If any of the validation tests fails, then the flag is changed to False and no new record is written to the database. A similar script setup can be used for editing records.

Editing with a DataList

An DataList can be set up to operate in editing mode much like the Repeater. The major difference is in the fact that a DataList has an EditItemTemplate that is activated when a record is placed in edit mode. The following is one example of using a DataList for record editing. Like the Repeater, it is problematic in using a DataList for editing since the lack of a paging mechanism limits the DataList to editing databases of relatively modest sizes.

Note that making actual changes to the BooksDB.mdb database is not permitted in these tutorials; however, all other functions work as expected.

Book Edit

 

Page 106: Assignment Instructions

ID Type Title Author Description Price QtySale

Edit

           

ID: DB111

Type: Database

Title: Oracle Database

Author: K. Loney

Description: Get thorough coverage of Oracle Database 10g from the most comprehensive reference available, published by Oracle Press. With in-depth details on all the new features, this powerhouse resource provides an overview of database architecture and Oracle Grid Computing technology, and covers SQL, SQL*Plus, PL/SQL, dynamic PL/SQL, object-oriented features, and Java programming in the Oracle environment. You'll also find valuable database administration and application development techniques, plus an alphabetical reference covering major Oracle commands, keywords, features, and functions, with cross-referencing of topics.

Price: $69.99

Quantity: 10

Sale:

 

ID: DB222

Type: Database

Title: Databases in Depth

Author: C. J. Date

Description: In Database in Depth, author and well-known database authority Chris Date lays out the fundamentals of the relational model. Don't let a lack to formal education in database theory hold you back. Instead, let Chris's clear explanation of relational concepts, set theory, the difference between model and implementation, relational algebra, normalization, and much more set you apart and well above the competition when it comes to getting work done with a relational database.

Price: $29.95

Quantity: 6

Sale:

 

ID: DB333

Type: Database

Title: Database Processing

Author: D. Kroenke

Description: Revised to reflect the needs of today's users, this 10th edition of Database Processing assures that you will learn marketable skills. By presenting SQL SELECT statements near the beginning of the book readers will know early on how to query data and obtain results-seeing firsthand some of the ways that database technology is useful in the marketplace. By utilizing free software downloads, you will be able to actively use a DBMS product by the end of the 2nd chapter. Each topic appears in the context of accomplishing practical tasks. Its spiral approach to database design provides users with enhanced information not available in other database books on the market.

Price: $136.65

Quantity: 12

Sale:

 

ID: DB444

Type: Database

Title: Access Database Design

Author: S. Roman

Description: When using software products with graphical interfaces, we frequently focus so much on the

ID: DB555

Type: Database

Title: SQL Server 2005

Author: P. Debetta

Description: Get a developer-focused introduction to the new programmability features in the next

ID: GR111

Type: Graphics

Title: Adobe Photoshop CS2

Author: Adobe

Description: With this book, you learn by doing, getting your feet wet immediately as you

Page 107: Assignment Instructions

details of how to use the interface that we forget about the general concepts that allow us to understand and use the software effectively. This is particularly true of a powerful database product like Microsoft Access. Novice, and sometimes even experienced, programmers are so concerned with how something is done in Access that they often lose sight of the general principles that underlie their database applications. Access Database Design and Programming takes you behind the details of the Access interface, focusing on the general knowledge necessary for Access power users or developers to create effective database applications.

Price: $34.95

Quantity: 25

Sale:

 

version of Microsoft SQL Server-including integration with the Microsoft .NET Framework-and learn powerful new ways to manipulate your servers. Whether you're a developer currently working with T-SQL or Microsoft Visual Studio.NET, or you're responsible for database administration, you'll see how to draw from your existing skills and knowledge to exploit new SQL Server technology. With introductory-level code samples written in both T-SQL and C#, you'll understand how to take advantage of the cross-platform interoperability, native support for XML and Web services, shared language base, and other programming innovations to build better solutions from business intelligence to enterprise data management.

Price: $29.99

Quantity: 0

Sale:

 

progress through a series of hands-on projects that build on your growing Photoshop knowledge. Simple step-by-step instructions, review questions at the end of each chapter, and a companion CD with all of the book's project files make learning a breeze as the Adobe Creative Team takes you on a self-paced tour of the image-editing powerhouse. This bestselling guide has been completely revised to cover all of Photoshop CS2's new features and creative tools. This comprehensive guide starts with an introductory tour of the software and then progresses on through lessons on everything from Photoshop's interface to more complex topics like color management, Web graphics, and photo retouching.

Price: $29.99

Quantity: 4

Sale:

 

ID: GR222

Type: Graphics

Title: Learning Web Design

Author: J. Niederst

Description: In Learning Web Design, author Jennifer Niederst shares the knowledge she's gained from years of web design experience, both as a designer and a teacher. This book starts from the beginning--defining the Internet, the Web, browsers, and URLs--so you don't need to have any previous knowledge about how the Web works. After reading this

ID: GR333

Type: Graphics

Title: Macromedia Flash Professional

Author: T. Green

Description: Offering breakthrough video capabilities and powerful run-time effects, Macromedia Flash Professional 8 is poised to upend the Web video market, posing a serious challenge to Microsoft, RealNetworks, and Apple's efforts in the process. Here to make sure you're ready for it is the official Macromedia training for the program.

ID: GR444

Type: Graphics

Title: Digital Photographer Handbook

Author: M. Freeman

Description: Michael Freeman has a well-deserved reputation for effectively explaining the concepts behind digital picture-taking to a variety of audiences. Here, he turns his attention to the professionals and advanced hobbyists who are making the move from traditional to digital and want help mastering the technology and

Page 108: Assignment Instructions

book, you'll have a solid foundation in HTML, graphics, and design principles that you can immediately put to use in creating effective web pages.

Price: $39.95

Quantity: 8

Sale:

 

Through 20-plus hours of project-based instruction, you'll learn how to program for the enhanced Flash Player; use ActionScript to create dynamic interactivity; take advantage of new Flash 8 features like the improved script editor, revamped library interface, and new Undo feature; and more. Simple step-by-step instructions peppered with plenty of visual aids and a CD that includes lesson files and a trial version of Flash Professional 8 leave you with a solid understanding of Flash development and the techniques required to tap your creative potential by producing dynamic, interactive content.

Price: $44.99

Quantity: 17

Sale:

 

meeting their clients' new requirements. Freeman thoroughly answers the most frequently asked questions about the basics of digital capture, from cameras and computers to storage options, printers, and scanners. Photographers will learn the different file formats and how to save images for print or publishing on the web. They'll explore valuable software tools and basic image processing programs that fix common problems, and see how to improve pictures using an assortment of cropping and filtering techniques.

Price: $24.95

Quantity: 22

Sale:

 

ID: GR555

Type: Graphics

Title: Creating Motion Graphics

Author: T. Meyer

Description: Trish and Chris Meyer share their years of real-world production experience in this vital update to the acclaimed Creating Motion Graphics with After Effects series. More than a step-by-step review of the features, you learn how this program integrates into real-world production workflows from award-winning artists who make their living using After Effects. This full-color guide is packed with visual examples, and the enclosed DVD-ROM is loaded with source material and projects that encourage you to practice their

ID: HW111

Type: Hardware

Title: How Computers Work

Author: R. White

Description: How Computers Work, 7th Edition, has been one of the bestselling computer books for the last 10 years. This four-color visual tutorial is a must-have for any computer user, from novice to expert. The rich graphics and intricate details about the inner workings of computers have been admired for years by consumers, instructors, professionals, and readers of all ages. A fresh cover and interior provide the reader with superior usability and it is the most aesthetically-pleasing edition yet!

ID: HW222

Type: Hardware

Title: Upgrading and Repairing PCs

Author: S. Mueller

Description: Push your PC's performance to the limit. Know the differences between: Pentium II, Pentium MMX, Pentium Pro, and earlier CPU chips and choose the best chip for your needs; understand compatibility and feature sets of processor upgrade sockets, motherboards, and chipsets; use Universal Serial Bus ports and devices to simplify peripheral installation, configuration, and improve performance; squeeze the most performance, life, and

Page 109: Assignment Instructions

techniques.

Price: $59.95

Quantity: 13

Sale:

 

Price: $29.99

Quantity: 8

Sale:

 

reliability out of your hard drives; prevent memory headaches: pick the right speed and type of SIMMs and DIMMs, run more programs at once, and work with bigger files; and integrate hot new hardware including 3D graphics accelerators, fast SDRAM memory, Zoomed Video and CardBus PC Cards for your notebook, and NLX motherboards with support for Single Edge Contact processors and Accelerated Graphics Ports.

Price: $59.99

Quantity: 5

Sale:

 

ID: HW333

Type: Hardware

Title: USB System Architecture

Author: D. Anderson

Description: Universal Serial Bus System Architecture, Second Edition, based on the 2.0 version of the Universal Serial Bus specification, provides in-depth coverage and a detailed discussion of USB. It focuses on the USB protocol, signaling environment, and electrical specifications, along with the hardware/software interaction required to configure and access USB devices.

Price: $49.99

Quantity: 1

Sale:

 

ID: HW444

Type: Hardware

Title: Designing Embedded Hardware

Author: J. Catsoulis

Description: Designing Embedded Hardware is a book about designing small machines for embedded applications. There are many books on the market dedicated to writing code for particular microprocessors, or that stress the philosophy of embedded system design without providing any practical information. This book steers a middle path, telling you what you need to know to create your own products, and distilling much of the lore of embedded systems design into a single volume. It shows you how to build a complete embedded system, add peripherals, and connect your system to other devices.

ID: HW555

Type: Hardware

Title: Contemporary Logic Design

Author: R. Katz

Description: Contemporary Logic Design introduces a wide range of software tools including schematic capture, logic simulation, Boolean minimization, multi-level minimization and state assignment. Links the traditional techniques of logic design (such as Karnaugh maps and breadboard techniques) with real-world design examples. Provides comprehensive, early coverage of programmable logic including ROMs, PALs, and PLAs. Includes a variety of examples, exercises, problems, and case studies that illustrate real design problems and challenge the reader to develop practical solutions

Page 110: Assignment Instructions

Price: $44.95

Quantity: 3

Sale:

 

using modern design tools.

Price: $102.95

Quantity: 2

Sale:

 

ID: SW111

Type: Software

Title: Java How to Program

Author: Deitel

Description: This edition is completely up-to-date with The Java 2 Platform Standard Edition (J2SE) 1.5. Now includes topics such as autoboxing, enumerations, enhanced for loops, static import statements, variable-length argument lists, and much more. Presents each new concept in the context of a complete, working program, immediately followed by one or more windows showing the program's input/output dialog. A valuable reference for programmers and anyone interested in learning the Java programming language.

Price: $98.59

Quantity: 9

Sale:

 

ID: SW222

Type: Software

Title: C Programming Language

Author: B. Kernighan

Description: The original authors of C and the first UNIX system present this concise and powerful guide to ANSI standard C programming. This version, building on Kerninghan and Ritchie's classic The C Programming Language, brings readers up-to-date with the finalized ANSI standard for C while teaching users how to take advantage of noted C features like economy of expression, its full set of operators and more. One reader claimed "Just about every C programmer I respect learned C from this book," while another raved th

Price: $44.25

Quantity: 12

Sale:

 

ID: SW333

Type: Software

Title: Programming C#

Author: J. Liberty

Description: The programming language C# was built with the future of application development in mind. Pursuing that vision, C#'s designers succeeded in creating a safe, simple, component-based, high-performance language that works effectively with Microsoft's .NET Framework. Now the favored language among those programming for the Microsoft platform, C# continues to grow in popularity as more developers discover its strength and flexibility. And, from the start, C# developers have relied on Programming C# both as an introduction to the language and a means of further building their skills. The new fourth edition of Programming C#--the top-selling C# book on the market--has been updated to the C# ISO standard as well as changes to Microsoft's implementation of the language.

Price: $44.95

Quantity: 0

Sale:

 

ID: SW444 ID: SW555 ID: SY111

Page 111: Assignment Instructions

Type: Software

Title: Programming PHP

Author: R. J. Lerdorf

Description: PHP is a simple yet powerful open source scripting language for creating dynamic web content. The millions of web sites powered by PHP are testament to its popularity and ease of use. PHP is used by both programmers, who appreciate its flexibility and speed, and web designers, who value its accessibility and convenience. Programming PHP is an authoritative guide to PHP 4, the latest version of the language, and is filled with the unique knowledge of the creator of PHP, Rasmus Lerdorf. This book explains PHP language syntax and programming techniques in a clear and concise manner, with numerous examples that illustrate both correct usage and common idioms. The book also includes style tips and practical programming advice that will help you become not just a PHP programmer, but a good PHP programmer.

Price: $39.95

Quantity: 17

Sale:

 

Type: Software

Title: Visual Basic.NET Programming

Author: P. Vick

Description: Visual Basic .NET builds on the legendary simplicity of Visual Basic to add the immense power of true object-oriented programming and Microsoft's .NET Framework. Now, for the first time, one of the language's architects has written a definitive technical reference and tutorial for Visual Basic .NET. Moving far beyond the online documentation, Paul Vick presents the language's complete specification, along with descriptions, reference materials, and code samples direct from the Visual Basic .NET design team.

Price: $49.99

Quantity: 13

Sale:

 

Type: Systems

Title: Operating System Concepts

Author: A. Silberschatz

Description: By staying current, remaining relevant, and adapting to emerging course needs, this text has continued to define the operating systems course. This Seventh Edition not only presents the latest and most relevant systems, it also digs deeper to uncover those fundamental concepts that have remained constant throughout the evolution of today's operating systems. With this strong conceptual foundation in place, students can more easily understand the details related to specific systems.

Price: $95.75

Quantity: 1

Sale:

 

ID: SY222

Type: Systems

Title: The UNIX Operating System

Author: J. D. Peek

Description: Learning the Unix Operating System is a handy book for someone just starting with Unix or Linux, and it's an ideal primer for Mac and PC users of the Internet who need to know a little about Unix on the systems they

ID: SY333

Type: Systems

Title: Windows Server 2003

Author: W. R. Stanek

Description: Here's the practical, pocket-sized reference for IT professionals supporting Windows .NET Server. Designed for quick referencing, this portable guide covers all the essentials for performing everyday system-administration

ID: SY444

Type: Systems

Title: Linux in a Nutshell

Author: S. Figgins

Description: Whether you're using Linux for personal software projects, for a small office or home office, to provide services to a small group of colleagues, or to administer a site responsible for millions of email and web connections each

Page 112: Assignment Instructions

visit. The fifth edition is the most effective introduction to Unix in print, covering Internet usage for email, file transfers, web browsing, and many major and minor updates to help the reader navigate the ever-expanding capabilities of the operating system.

Price: $19.95

Quantity: 12

Sale:

 

tasks. Topics include managing workstations and servers, using Microsoft Active Directory services, creating and administering user and group accounts, managing files and directories, data security and auditing, data back-up and recovery, network administration using TCP/IP, WINS, and DNS, and more.

Price: $29.99

Quantity: 25

Sale:

 

day, you need quick access to information on a wide range of tools. This book covers all aspects of administering and making effective use of Linux systems. Among its topics are booting, package management, and revision control. But foremost in Linux in a Nutshell are the utilities and commands that make Linux one of the most powerful and flexible systems available.

Price: $44.95

Quantity: 14

Sale:

 

ID: SY555

Type: Systems

Title: Mastering Active Directory

Author: R. R. King

Description: Active Directory represents an enormous advance in network administration. It provides a vast set of powerful tools and technologies for managing a network within a native Windows environment. Mastering Active Directory for Windows Server 2003 is the resource you need to take full advantage of all it has to offer. You get a sound introduction to network directory services, then detailed, practical instruction in the work of implementing Active Directory and using all of its tools. This edition has been completely updated to address features new to Active Directory for Windows Server 2003.

Price: $49.99

Quantity: 8

Sale:

ID: WB111

Type: Web

Title: Ajax in Action

Author: D. Crane

Description: Ajax in Action helps you implement that thinking--it explains how to distribute the application between the client and the server while retaining the integrity of the system. You will learn how to ensure your app is flexible and maintainable, and how good, structured design can help avoid problems like browser incompatibilities. Along the way it helps you unlearn many old coding habits. Above all, it opens your mind to the many advantages gained by placing much of the processing in the browser. If you are a web developer who has prior experience with web technologies, this book is for you.

Price: $22.67

Quantity: 14

Sale:

ID: WB222

Type: Web

Title: Professional ASP.NET 2.0

Author: B. Evjen

Description: ASP.NET allows web sites to display unique pages for each visitor rather than show the same static HTML pages. The release of ASP.NET 2.0 is a revolutionary leap forward in the area of web application development. It brings with it a wealth of new and exciting built-in functions that reduce the amount of code you'll need to write for even the most common applications. With more than 50 new server controls, the number of classes inside ASP.NET 2.0 has more than doubled, and, in many cases, the changes in this new version are dramatic. This book will alert you to every new feature and capability that ASP.NET 2.0 provides so that you'll be prepared to put these new technologies into

Page 113: Assignment Instructions

   

action.

Price: $32.99

Quantity: 21

Sale:

 

ID: WB333

Type: Web

Title: Cascading Style Sheets

Author: E. A. Meyer

Description: This second edition of Cascading Style Sheets: The Definitive Guide completes the discussion of CSS2, explores CSS2.1, and introduces emerging elements of CSS3. Eric A. Meyer uses his trademark wit and humor to explore properties, tags, attributes, and implementation, as well as real-life issues, such as browser support and design guidelines. This book addresses experienced web authors and scripters, as well as novice authors who may be implementing CSS from scratch. Cascading Style Sheets: The Definitive Guide, Second Edition also includes a new foreword by Molly Holzschlag, a steering committee member for the Web Standards Project.

Price: $39.95

Quantity: 6

Sale:

 

ID: WB444

Type: Web

Title: DOM Scripting

Author: J. Keith

Description: There are three main technologies married together to create usable, standards-compliant web designs: XHTML for data structure, Cascading Style Sheets for styling your data, and JavaScript for adding dynamic effects and manipulating structure on the fly using the Document Object Model. This book is about the latter of the three. DOM Scripting: Web Design with JavaScript and the Document Object Model gives you everything you need to start using JavaScript and the Document Object Model to enhance your web pages with client-side dynamic effects. Jermey starts off by giving you a basic crash course in JavaScript and the DOM, then moves on to provide you with several real world examples built up from scratch including dynamic image galleries and dynamic menus, and shows you how to manipulate web page style using the CSS DOM, and create markup on the fly.

Price: $23.09

Quantity: 8

Sale:

 

ID: WB555

Type: Web

Title: Microsoft ASP.NET 2.0

Author: D. Esposito

Description: Get an expert, developer-focused introduction to the next big innovation in ASP.NET Web programming. Programming authority Dino Esposito takes you inside ASP.NET 2.0 technology, explaining how its updated, more powerful infrastructure allows you to create rich and dynamic Web applications quickly and with less code. Esposito examines how developer best practices with ASP.NET 1.x and classic ASP helped drive the architectural changes in ASP.NET 2.0, and he offers prerelease insights into its final architecture. With concise concept and feature explanations and more than 70 fully functional examples, this comprehensive introduction gives you everything you need to dig into ASP.NET 2.0 right now.

Price: $29.99

Quantity: 12

Sale:

 

Page 114: Assignment Instructions

Figure 9-27. Editing a database with a DataList.

The DataList displays all fields in all Books records of the BooksDB.mdb database. The header row displays blank input areas along with an "Insert" button for creating a new product record. Item rows display records with "Edit" and "Delete" buttons. The "Edit" button switches the fields to input areas for changing records and displays "Update" and "Cancel" buttons. The "Delete" button removes the record from the table.

Coding the DataList

Code for the DataList is shown below along with its AccessDataSource. A second AccessDataSource is needed to populate DropDownLists of book types during record insertion and updating.

<asp:AccessDataSource id="BookSource" Runat="Server" DataFile="../Databases/BooksDB.mdb" SelectCommand = "SELECT * FROM Books ORDER BY BookID"/>

<asp:AccessDataSource id="TypeSource" Runat="Server" DataFile="../Databases/BooksDB.mdb" SelectCommand = "SELECT DISTINCT BookType FROM Books ORDER BY BookType"/>

<h3>Book Edit</h3>

<asp:Label id="EditMessage" Text=" " ForeColor="#FF0000" EnableViewState="False" Runat="Server"/>

<asp:DataList id="DataListEdit" DataSourceID="BookSource" Runat="Server" OnItemCommand="Edit_Record" CellSpacing="0" CellPadding="3" RepeatColumns="3" RepeatDirection="Horizontal" GridLines="Both" BorderWidth="1"> <HeaderTemplate> <table id="Add" border="1"> <tr> <th>ID</th> <th>Type</th> <th>Title</th> <th>Author</th> <th>Description</th> <th>Price</th> <th>Qty</th> <th>Sale</th> <th>Edit</th> </tr> <tr> <td><asp:TextBox id="BookID" Runat="Server" Font-Size="8pt" Width="45px"/></td> <td><asp:DropDownList id="BookType" DataSourceID="TypeSource" Runat="Server" DataTextField="BookType" DataValueField="BookType" Font-Size="8pt"/></td> <td><asp:TextBox id="BookTitle" Runat="Server"

Page 115: Assignment Instructions

Font-Size="8pt" Width="80px"/></td> <td><asp:TextBox id="BookAuthor" Runat="Server" Font-Size="8pt" Width="90px"/></td> <td><asp:TextBox id="BookDescription" Runat="Server" TextMode="MultiLine" Width="150" Rows="2" Font-Name="Arial" Font-Size="7pt"/></td> <td><asp:TextBox id="BookPrice" Runat="Server" Font-Size="8pt" Width="50px" Style="text-align:right"/></td> <td><asp:TextBox id="BookQty" Runat="Server" Font-Size="8pt" Width="25px" Style="text-align:right"/></td> <td><asp:CheckBox id="BookSale" Runat="Server"/></td> <td nowrap> <asp:Button Text="Insert" CommandName="Insert" Runat="Server" Font-Size="7pt" Width="45"/></td> </tr> <tr> <td colspan="9" style="border-bottom:solid 1 black"></td> </tr> </table> </HeaderTemplate> <ItemTemplate> <table id="Display" border="0" cellpadding="1"> <tr> <th><b>ID: </b></th> <td><asp:Label id="BookIDDelete" Runat="Server" Text='<%# Eval("BookID") %>'/></td> </tr> <tr> <th><b>Type: </b></th> <td><asp:Label Runat="Server" Text='<%# Eval("BookType") %>'/></td> </tr> <tr> <th><b>Title: </b></th> <td><asp:Label Runat="Server" Text='<%# Eval("BookTitle") %>'/></td> </tr> <tr> <th><b>Author: </b></th> <td><asp:Label Runat="Server" Text='<%# Eval("BookAuthor") %>'/></td> </tr> <tr style="vertical-align:top"> <th><b>Description: </b></th> <td><asp:Panel Width="150" Height="100" ScrollBars="Vertical" Runat="Server"> <asp:Label Runat="Server" Text='<%# Eval("BookDescription") %>'/> </asp:Panel></td> <tr> <th><b>Price: </b></th> <td><asp:Label Runat="Server" Text='<%# Eval("BookPrice") %>'/></td> </tr> <tr> <th><b>Quantity: </b></th> <td><asp:Label Runat="Server" Text='<%# Eval("BookQty") %>'/></td> </tr> <tr>

Page 116: Assignment Instructions

<th><b>Sale: </b></th> <td><asp:CheckBox Runat="Server" Checked='<%# Eval("BookSale") %>'/></td> </tr> </table> <asp:Button Text="Edit" CommandName="Edit" Runat="Server" Font-Size="7pt" Width="45"/> <asp:Button Text="Delete" CommandName="Delete" Runat="Server" Font-Size="7pt" Width="45"/> </ItemTemplate> <EditItemTemplate> <table id="Edit" border="0" cellpadding="0"> <tr> <th>ID:</th> <td><asp:TextBox id="BookID" Runat="Server" ReadOnly="True" Text='<%# Eval("BookID") %>' Font-Size="8pt" Width="45px"/></td> </tr> <tr> <th>Type:</th> <td><asp:DropDownList id="BookType" Runat="Server" DataSourceID="TypeSource" DataTextField="BookType" DataValueField="BookType" SelectedValue='<%# Eval("BookType") %>' Font-Size="8pt"/></td> </tr> <tr> <th>Title:</th> <td><asp:TextBox id="BookTitle" Runat="Server" Text='<%# Eval("BookTitle") %>' Font-Size="8pt" Width="145px"/></td> </tr> <tr> <th>Author:</th> <td><asp:TextBox id="BookAuthor" Runat="Server" Text='<%# Eval("BookAuthor") %>' Font-Size="8pt" Width="145px"/></td> </tr> <tr style="vertical-align:top"> <th>Description:</th> <td><asp:TextBox id="BookDescription" Runat="Server" Text='<%# Eval("BookDescription") %>' TextMode="MultiLine" Font-Name="Arial" Font-Size="8pt" Width="145" Rows="5"/></td> <tr> <th>Price:</th> <td><asp:TextBox id="BookPrice" Runat="Server" Text='<%# Eval("BookPRice") %>' Font-Size="8pt" Width="50px" Style="text-align:right"/></td> </tr> <tr> <th>Qty:</th> <td><asp:TextBox id="BookQty" Runat="Server" Text='<%# Eval("BookQty") %>' Font-Size="8pt" Width="25px" Style="text-align:right"/></td> </tr> <tr> <th>Sale:</th>

Page 117: Assignment Instructions

<td><asp:CheckBox id="BookSale" Runat="Server" Checked='<%# Eval("BookSale") %>'/></td> </tr> </table> <asp:Button Text="Update" CommandName="Update" Runat="Server" Font-Size="7pt" Width="45"/> <asp:Button Text="Cancel" CommandName="Cancel" Runat="Server" Font-Size="7pt" Width="45"/> </EditItemTemplate>

</asp:DataList>Listing 9-58. Code for editing with a DataList.

As in the case with the Repeater, data binding expressions are in the format <%# Eval("field") %>. It is not necessary to use the Bind() method that is needed when working with a GridView, DetailsView, or FormView.

Like a Repeater, a DataList recognizes an ItemCommand event when an enclosed command button is clicked. Its OnItemCommand event handler calls a subprogram for this event. The DataList also recognizes OnItemEdit, OnItemUpdate, OnItemDelete, and OnItemCancel events for scripting them separately. In this example, the general-purposeOnItemCommand handler calls the Edit_Record subprogram to handle all command events.

A <HeaderTemplate> is defined to contain the input areas for adding a new record. It is formatted as a table to control formatting of the items. An insert button is defined as a command button with the CommandName "Insert".

The <ItemTemplate>, as well, is formatted as a table to align items appearing in the cells. Each record has accompanying edit and delete command buttons with "Edit" and"Delete" command names that call the Edit_Record subprogram through the OnItemCommand event handler. The edit button causes display of the <EditItemTemplate> and associated "Update" and "Cancel" buttons for updating the record.

DataList Editing

The script for the DataList is similar to the one used for the previous Repeater. The Edit_Record subprogram, being called from a DataList, has the argumentDataListCommandEventArgs. The subprogram's first task is to locate controls for the selected record in the same manner as is done for the Repeater. These data input controls appear in the HeaderTemplate and EditItemTemplate when the "Edit" button is clicked. Then the subprogram performs activities depending on the CommandName of the button that is clicked.

Sub Edit_Record (Src As Object, Args As DataListCommandEventArgs) Dim BookID As TextBox = Args.Item.FindControl("BookID") Dim BookType As DropDownList = Args.Item.FindControl("BookType") Dim BookTitle As TextBox = Args.Item.FindControl("BookTitle") Dim BookAuthor As TextBox = Args.Item.FindControl("BookAuthor") Dim BookDescription As TextBox = Args.Item.FindControl("BookDescription") Dim BookPrice As TextBox = Args.Item.FindControl("BookPrice") Dim BookQty As TextBox = Args.Item.FindControl("BookQty") Dim BookSale As CheckBox = Args.Item.FindControl("BookSale") If Args.CommandName = "Edit" Then

Page 118: Assignment Instructions

DataListEdit.EditItemIndex = Args.Item.ItemIndex DataListEdit.DataBind() End If If Args.CommandName = "Cancel" Then ' Set the EditItemIndex property to -1 to exit editing mode. Be sure ' to rebind the DataList to the data source to refresh the control. DataListEdit.EditItemIndex = -1 DataListEdit.DataBind() End If If Args.CommandName = "Insert" Then Dim SQLString As String = "INSERT INTO Books " & _ "(BookID, BookType, BookTitle, BookAuthor, BookDescription, " & _ "BookPrice, BookQty, BookSale) VALUES (" & _ "'" & BookID.Text & "', " & _ "'" & BookType.SelectedValue & "', " & _ "'" & BookTitle.Text & "', " & _ "'" & BookAuthor.Text & "', " & _ "'" & BookDescription.Text & "', " & _ "'" & BookPrice.Text & "', " & _ "'" & BookQty.Text & "', " & _ BookSale.Checked & ")" BookSource.InsertCommand = SQLString BookSource.Insert() EditMessage.Text = "Record " & BookID.Text & " inserted" End If If Args.CommandName = "Update" Then Dim SQLString As String = "UPDATE Books SET " & _ "BookType = '" & BookType.SelectedValue & "', " & _ "BookTitle = '" & BookTitle.Text & "', " & _ "BookAuthor = '" & BookAuthor.Text & "', " & _ "BookDescription = '" & BookDescription.Text & "', " & _ "BookPrice = '" & BookPrice.Text & "', " & _ "BookQty = '" & BookQty.Text & "', " & _ "BookSale = " & BookSale.Checked & " " & _ "WHERE BookID = '" & BookID.Text & "'" BookSource.UpdateCommand = SQLString BookSource.Update() DataListEdit.EditItemIndex = -1 DataListEdit.DataBind() EditMessage.Text = "Record " & BookID.Text & " updated" End If If Args.CommandName = "Delete" Then Dim BookIDDelete As Label = Args.Item.FindControl("BookIDDelete") Dim SQLString As String = "DELETE FROM Books " & _ "WHERE BookID = '" & BookIDDelete.Text & "'" BookSource.DeleteCommand = SQLString BookSource.Delete() EditMessage.Text = "Record " & BookIDDelete.Text & " deleted" End If

Page 119: Assignment Instructions

End SubListing 9-59. Script for DataList editing.

When an "Edit" button is clicked, a record's EditItemTemplate is revealed as a set of input controls for changing data values. This switch from the ItemTemplate is activated by setting the EditItemIndex property of the record. This portion of the script is repeated below.

If Args.CommandName = "Edit" Then

DataListEdit.EditItemIndex = Args.Item.ItemIndex DataListEdit.DataBind()

End IfListing 9-60. Switching a DataList row to edit mode.

The current ItemIndex of the selected item is the DataList's index number for the record, numbered beginning with 0 for the HeaderTemplate. This index number is passed to the subprogram through its argument list. It identifies the ItemTemplate that should be replaced by its EditItemTemplate by setting the DataList's EditItemIndex to the same value. Then the DataList is re-bound to its AccessDataSource.

The reverse process is following when the "Cancel" button is clicked. The currently displayed EditItemTemplate is replaced by its ItemTemplate. Setting the EditItemIndex to -1 displays the ItemTemplate for any item currently in edit mode.

If Args.CommandName = "Cancel" Then

DataListEdit.EditItemIndex = -1 DataListEdit.DataBind()

End IfListing 9-61. Switching a DataList row to display mode.

The remainder of the scripts are virtually the same as for the Repeater when the "Insert," "Update," and "Delete" buttons are clicked. One difference is in the delete routine. Deletion takes place with the ItemTemplate displayed. Within this template, the BookID required to identify the record is displayed in a Label control, not with a TextBox as occurs when the EditItemTemplate is being displayed. Therefore, the FindControl() method must find a Label.

If Args.CommandName = "Delete" Then Dim BookIDDelete As Label = Args.Item.FindControl("BookIDDelete") Dim SQLString As String = "DELETE FROM Books " & _ "WHERE BookID = '" & BookIDDelete.Text & "'" BookSource.DeleteCommand = SQLString BookSource.Delete() EditMessage.Text = "Record " & BookIDDelete.Text & " deleted" End IfListing 9-62. Finding a control in a DataList row.

This Edit_Record subprogram does not perform data validation. Routines can be easily added as described for the Repeater.

Editing with a DataGrid

Page 120: Assignment Instructions

A DataGrid can be set up for editing like the previous DataList, with an ItemTemplate and an EditItemTemplate to which it switches in edit mode. The easiest method, however, is to configure it like a Repeater, with only editing fields displayed. This layout is shown in the following example. Fortunately, a DataGrid has a built-in pager so that it is not necessary to display the entire database.

Note that making actual changes to the BooksDB.mdb database is not permitted in these tutorials; however, all other functions work as expected.

Book Edit

 

ID Type           

Title Author Description Price Qty Sale

           

           

           

           

           

1 2 3 4 5 6

Figure 9-28. Editing a database with a DataGrid.

Coding the DataGrid

The DataGrid displays five records at a time from the Books table using the DataGrid's built-in paging feature. Data items are edited directly in the TextBoxes and other controls across the rows of the grid. All buttons are configured as command buttons to call the Edit_Record subprogram through the DataGrid's OnItemCommand event handler. The currentEdit_Record subprogram does not perform data validation although routines can be added as described for the Repeater.

<asp:AccessDataSource id="BookSource" Runat="Server" DataFile="../Databases/BooksDB.mdb" SelectCommand = "SELECT * FROM Books ORDER BY BookID"/>

<asp:AccessDataSource id="TypeSource" Runat="Server"

Page 121: Assignment Instructions

DataFile="../Databases/BooksDB.mdb" SelectCommand = "SELECT DISTINCT BookType FROM Books ORDER BY BookType"/>

<h3>Book Edit</h3>

<asp:Label id="EditMessage" Text=" " ForeColor="#FF0000" Runat="Server"EnableViewState="False"/>

<asp:DataGrid id="DataGridEdit" DataSourceID="BookSource" Runat="Server" OnItemCommand="Edit_Record" AutoGenerateColumns="False" AllowPaging="True" PageSize="5" OnPageIndexChanged="Change_Page" PagerStyle-Mode="NumericPages" PagerStyle-BackColor="#E0E0E0" HeaderStyle-BackColor="#E0E0E0" HeaderStyle-Font-Size="10pt" HeaderStyle-Font-Bold="True" HeaderStyle-HorizontalAlign="Center" HeaderStyle-VerticalAlign="Top" ItemStyle-VerticalAlign="Top"> <Columns> <asp:TemplateColumn> <HeaderTemplate> ID<br/> <asp:TextBox id="BookID" Runat="Server" Font-Size="8pt" Width="45px"/> </HeaderTemplate> <ItemTemplate> <asp:TextBox id="BookID" ReadOnly="True" Runat="Server" Text='<%# Eval("BookID") %>' Font-Size="8pt" Width="45px"/> </ItemTemplate> </asp:TemplateColumn> <asp:TemplateColumn> <HeaderTemplate> Type<br/> <asp:DropDownList id="BookType" Runat="Server" DataSourceID="TypeSource" DataTextField="BookType" DataValueField="BookType" Font-Size="8pt"/> </HeaderTemplate> <ItemTemplate> <asp:DropDownList id="BookType" Runat="Server" DataSourceID="TypeSource" DataTextField="BookType" DataValueField="BookType" SelectedValue='<%# Eval("BookType") %>' Font-Size="8pt"/> </ItemTemplate> </asp:TemplateColumn> <asp:TemplateColumn> <HeaderTemplate> Title<br/> <asp:TextBox id="BookTitle" Runat="Server" Font-Size="8pt" Width="80px"/> </HeaderTemplate>

Page 122: Assignment Instructions

<ItemTemplate> <asp:TextBox id="BookTitle" Runat="Server" Text='<%# Eval("BookTitle") %>' Font-Size="8pt" Width="80px"/> </ItemTemplate> </asp:TemplateColumn> <asp:TemplateColumn> <HeaderTemplate> Author<br/> <asp:TextBox id="BookAuthor" Runat="Server" Font-Size="8pt" Width="100px"/> </HeaderTemplate> <ItemTemplate> <asp:TextBox id="BookAuthor" Runat="Server" Text='<%# Eval("BookAuthor") %>' Font-Size="8pt" Width="100px"/> </ItemTemplate> </asp:TemplateColumn> <asp:TemplateColumn> <HeaderTemplate> Description<br/> <asp:TextBox id="BookDescription" Runat="Server" TextMode="MultiLine" Cols="30" Rows="2" Style="font-family:arial; font-size:8pt"/> </HeaderTemplate> <ItemTemplate> <asp:TextBox id="BookDescription" Runat="Server" Text='<%# Eval("BookDescription") %>' TextMode="MultiLine" Cols="30" Rows="2" Style="font-family:arial; font-size:8pt"/> </ItemTemplate> </asp:TemplateColumn> <asp:TemplateColumn> <HeaderTemplate> Price<br/> <asp:TextBox id="BookPrice" Runat="Server" Text="0.00" Font-Size="8pt" Width="50px" Style="text-align:right"/> </HeaderTemplate> <ItemTemplate> <asp:TextBox id="BookPrice" Runat="Server" Text='<%# Eval("BookPrice") %>' Font-Size="8pt" Width="50px" Style="text-align:right"/> </ItemTemplate> </asp:TemplateColumn> <asp:TemplateColumn> <HeaderTemplate> Qty<br/> <asp:TextBox id="BookQty" Runat="Server" Text="0" Font-Size="8pt" Width="30px" Style="text-align:right"/> </HeaderTemplate> <ItemTemplate> <asp:TextBox id="BookQty" Runat="Server" Text='<%# Eval("BookQty") %>' Font-Size="8pt" Width="30px"

Page 123: Assignment Instructions

Style="text-align:right"/> </ItemTemplate> </asp:TemplateColumn> <asp:TemplateColumn> <HeaderTemplate> Sale<br/> <asp:CheckBox id="BookSale" Runat="Server"/> </HeaderTemplate> <ItemTemplate> <asp:CheckBox id="BookSale" Runat="Server" Checked='<%# Eval("BookSale") %>'/> </ItemTemplate> </asp:TemplateColumn> <asp:TemplateColumn> <HeaderTemplate> <br/> <asp:Button Text="Insert" CommandName="Insert" Runat="Server" Font-Size="7pt" Width="45"/> </HeaderTemplate> <ItemTemplate> <asp:Button Text="Update" CommandName="Update" Runat="Server" Font-Size="7pt" Width="45"/> </ItemTemplate> </asp:TemplateColumn> <asp:TemplateColumn> <HeaderTemplate> <br/> </HeaderTemplate> <ItemTemplate> <asp:Button Text="Delete" CommandName="Delete" Runat="Server" Font-Size="7pt" Width="45"/> </ItemTemplate> </asp:TemplateColumn> </Columns>

</asp:DataGrid>Listing 9-63. Code for editing with a DataGrid.

DataGrid Editing

The Edit_Record subprogram to perform insert, update, and delete operations is shown below. It is nearly identical to the like-named subprograms for the previous Repeater and DataList. Likewise, data validation routines can be added to the subprogram to test data being edited and inserted.

Sub Edit_Record (Src As Object, Args As DataGridCommandEventArgs)

Dim BookID As TextBox = Args.Item.FindControl("BookID") Dim BookType As DropDownList = Args.Item.FindControl("BookType") Dim BookTitle As TextBox = Args.Item.FindControl("BookTitle") Dim BookAuthor As TextBox = Args.Item.FindControl("BookAuthor") Dim BookDescription As TextBox = Args.Item.FindControl("BookDescription") Dim BookPrice As TextBox = Args.Item.FindControl("BookPrice") Dim BookQty As TextBox = Args.Item.FindControl("BookQty") Dim BookSale As CheckBox = Args.Item.FindControl("BookSale")

Page 124: Assignment Instructions

If Args.CommandName = "Insert" Then Dim SQLString As String = "INSERT INTO Products " & _ "(BookID, BookType, BookTitle, BookAuthor, BookDescription, " & _ "BookPrice, BookQty, BookSale) VALUES (" & _ "'" & BookID.Text & "', " & _ "'" & BookType.SelectedValue & "', " & _ "'" & BookTitle.Text & "', " & _ "'" & BookAuthor.Text & "', " & _ "'" & BookDescription.Text & "', " & _ "'" & BookPrice.Text & "', " & _ "'" & BookQty.Text & "', " & _ BookSale.Checked & ")" BookSource.InsertCommand = SQLString BookSource.Insert() EditMessage.Text = "Record " & BookID.Text & " inserted" End If If Args.CommandName = "Update" Then Dim SQLString As String = "UPDATE Products SET " & _ "BookType = '" & BookType.SelectedValue & "', " & _ "BookTitle = '" & BookTitle.Text & "', " & _ "BookAuthor = '" & BookAuthor.Text & "', " & _ "BookDescription = '" & BookDescription.Text & "', " & _ "BookPrice = '" & BookPrice.Text & "', " & _ "BookQty = '" & BookQty.Text & "', " & _ "BookSale = " & BookSale.Checked & " " & _ "WHERE BookID = '" & BookID.Text & "'" BookSource.UpdateCommand = SQLString BookSource.Update() EditMessage.Text = "Record " & BookID.Text & " updated" End If If Args.CommandName = "Delete" Then Dim SQLString As String = "DELETE FROM Products " & _ "WHERE BookID = '" & BookID.Text & "'" BookSource.DeleteCommand = SQLString BookSource.Delete() EditMessage.Text = "Record " & BookID.Text & " deleted" End If

End Sub

Sub Change_Page (Src As Object, Args As DataGridPageChangedEventArgs)

DataGridEdit.CurrentPageIndex = Args.NewPageIndex DataGridEdit.DataBind()

End SubListing 9-64. Script for DataGrid editing.

For a paged DataGrid, the Change_Page subprogram is called by the OnPageIndexChanged event handler to set the CurrentPageIndex to the NewPageIndex that is passed to the subprogram by a click on a page button.

Page 125: Assignment Instructions

The Repeater, DataList, and DataGrid represent "older" ASP.NET technologies for displaying and editing bound recordsets. They are being replaced by the new GridView, DetailsView, and FormView controls with built-in editing functionality that requires little or no scripting. These older controls have been updated slightly to work with data source controls, eliminating much of the scripting previously required. However, they still have limitations in the ease-of-use category. The choice normally should be to use the newer controls where possible.

Record Editing with Scripts

Database editing has become a much simpler process with the GridView, DetailsView, and FormView controls new to ASP.NET. Much of the back-end processing is encapsulated in these controls and their data source controls to greatly reduce the amount of scripting required. Still, developers may wish to design their own editing forms and to write script to handle database additions, changes, and deletions. The following example uses standard controls and scripts to edit the BooksDB.mdb database in emulation of methods employed for the GridView, DetailsView, and FormView.

Note that making actual changes to the BooksDb.mdb database is not permitted in these tutorials; however, all other functions work as expected.

Book Edit

Books:                                                               

ID: DB444

Type: Database

Title: Access Database Design

Author: S. Roman

Description:

When using software products with graphical interfaces, we frequently focus so much on the details of how to use the interface that we forget about the general concepts that allow us to understand and use the software effectively. This is particularly true of a powerful database product like Microsoft Access. Novice, and sometimes even experienced, programmers are so concerned with how something is done in Access that they often lose sight of the general principles that underlie their database applications. Access Database Design and Programming takes you behind the details of the Access interface, focusing on the general knowledge necessary for Access power users or developers to create effective database applications.

Price: $34.95

Qty: 25

Sale:

   

Figure 9-29. Database editing with scripts.

Page 126: Assignment Instructions

Coding the Form Controls

All editing functionality is built into this single application. However, separate forms are supplied for record displays, inserts, updates, and deletes. Each form is enclosed inside a Panel control which is made visible or hidden as different editing functions are called. In effect, the separate forms overlay one another to give the appearance of a single form. Coding for these forms is shown below. An embedded CSS style sheet is provided to format the XHTML and controls.

<style type="text/css">table#Display {border-collapse:collapse; width:500px}table#Display th {font-size:11pt; background-color:#E0E0E0; text-align:left; vertical-align:top}table#Display td {font-size:11pt}table#Update {border-collapse:collapse; width:500px}table#Update th {font-size:11pt; background-color:#E0E0E0; text-align:left; vertical-align:top}table#Update td {font-size:11pt}table#Insert {border-collapse:collapse; width:500px}table#Insert th {font-size:11pt; background-color:#E0E0E0; text-align:left; vertical-align:top}table#Insert td {font-size:11pt}table#Delete {border-collapse:collapse; width:500px}table#Delete th {font-size:11pt; background-color:#E0E0E0; text-align:left; vertical-align:top}table#Delete td {font-size:11pt}.buttons {background-color:#E0E0E0}</style>

<h3>Book Edit</h3>

<b>Books: </b><asp:DropDownList id="BookList" Runat="Server"/><asp:Button Text="Select" OnClick="Display_Record" Runat="Server"/><br/>

<asp:Panel Width="500" HorizontalAlign="Right" Runat="Server"> <asp:Label id="UpdateMSG" Text="&nbsp;" Runat="Server" EnableViewState="False" ForeColor="#FF0000"/></asp:Panel>

<!-- Code for Display Panel -->

<asp:Panel id="DisplayPanel" Visible="True" Runat="Server"><table id="Display" border="1"><tr> <th>ID:</th> <td><asp:Label id="DisplayID" Runat="Server"/></td></tr><tr> <th>Type:</th> <td><asp:Label id="DisplayType" Runat="Server"/></td></tr><tr> <th>Title:</th> <td><asp:Label id="DisplayTitle" Runat="Server"/></td></tr><tr> <th>Author:</th> <td><asp:Label id="DisplayAuthor" Runat="Server"/></td></tr><tr> <th>Description:</th>

Page 127: Assignment Instructions

<td><asp:Label id="DisplayDescription" Runat="Server" Style="width:400px; height:100px; overflow:auto"/></td></tr><tr> <th>Price:</th> <td><asp:Label id="DisplayPrice" Runat="Server"/></td></tr><tr> <th>Qty:</th> <td><asp:Label id="DisplayQty" Runat="Server"/></td></tr><tr> <th>Sale:</th> <td><asp:CheckBox id="DisplaySale" Enabled="False" Runat="Server"/></td></tr><tr> <th></th> <td class="buttons"> <asp:Button Text=" Edit Record " Runat="Server" OnClick="Load_Update_Form"/> <asp:Button Text="Insert Record" Runat="Server" OnClick="Load_Insert_Form"/> <asp:Button Text="Delete Record" Runat="Server" OnClick="Load_Delete_Form"/> </td></tr></table></asp:Panel>

<!-- Code for Insert Panel -->

<asp:Panel id="InsertPanel" Visible="False" Runat="Server"><table id="Insert" border="1"><tr> <th>ID:</th> <td><asp:TextBox id="AddID" Runat="Server" Width="50" MaxLength="5" EnableViewState="False"/></td></tr><tr> <th>Type:</th> <td><asp:DropDownList id="AddType" Runat="Server"/></td></tr><tr> <th>Title:</th> <td><asp:TextBox id="AddTitle" Runat="Server" Width="300" MaxLength="50" EnableViewState="False"/></td></tr><tr> <th>Author:</th> <td><asp:TextBox id="AddAuthor" Runat="Server" Width="100" MaxLength="20" EnableViewState="False"/></td></tr><tr> <th>Description:</th> <td><asp:TextBox id="AddDescription" Runat="Server" TextMode="MultiLine" Width="400px" Rows="4" EnableViewState="False"/></td></tr><tr> <th>Price:</th> <td><asp:TextBox id="AddPrice" Runat="Server" Width="50" MaxLength="6"/ EnableViewState="False"></td></tr>

Page 128: Assignment Instructions

<tr> <th>Qty:</th> <td><asp:TextBox id="AddQty" Runat="Server" Width="50" MaxLength="2" EnableViewState="False"/></td></tr><tr> <th>Sale:</th> <td><asp:CheckBox id="AddSale" Runat="Server" EnableViewState="False"/></td></tr><tr> <th></th> <td class="buttons"> <asp:Button Text="Insert" OnClick="Insert_Record" Runat="Server"/> <asp:Button Text="Cancel" OnClick="Display_Record" Runat="Server"/> </td></tr></table></asp:Panel>

<!-- Code for Update Panel -->

<asp:Panel id="UpdatePanel" Visible="False" Runat="Server"><table id="Update" border="1"><tr> <th>ID:</th> <td><asp:Label id="UpdateID" Runat="Server"/></td></tr><tr> <th>Type:</th> <td><asp:DropDownList id="UpdateType" Runat="Server"/></td></tr><tr> <th>Title:</th> <td><asp:TextBox id="UpdateTitle" Runat="Server" Width="300" MaxLength="50"/></td></tr><tr> <th>Author:</th> <td><asp:TextBox id="UpdateAuthor" Runat="Server" Width="100" MaxLength="20"/></td></tr><tr> <th>Description:</th> <td><asp:TextBox id="UpdateDescription" Runat="Server" TextMode="MultiLine" Font-Name="Arial" Width="400px" Rows="4"/></td></tr><tr> <th>Price:</th> <td><asp:TextBox id="UpdatePrice" Runat="Server" Width="50" MaxLength="6"/></td></tr><tr> <th>Qty:</th> <td><asp:TextBox id="UpdateQty" Runat="Server" Width="50" MaxLength="2"/></td></tr><tr> <th>Sale:</th> <td><asp:CheckBox id="UpdateSale" Runat="Server"/></td></tr><tr>

Page 129: Assignment Instructions

<th></th> <td class="buttons"> <asp:Button Text="Update" OnClick="Update_Record" Runat="Server"/> <asp:Button Text="Cancel" OnClick="Display_Record" Runat="Server"/>

</td></tr></table></asp:Panel>

<!-- Code for Delete Panel -->

<asp:Panel id="DeletePanel" Visible="False" Runat="Server"><table id="Delete" border="1"><tr> <th>ID:</th> <td><asp:Label id="DeleteID" Runat="Server"/></td></tr><tr> <th>Type:</th> <td><asp:Label id="DeleteType" Runat="Server"/></td></tr><tr> <th>Title:</th> <td><asp:Label id="DeleteTitle" Runat="Server"/></td></tr><tr> <th>Author:</th> <td><asp:Label id="DeleteAuthor" Runat="Server"/></td></tr><tr> <th>Description:</th> <td><asp:Label id="DeleteDescription" Runat="Server" Style="width:400px; height:100px; overflow:auto"/></td></tr><tr> <th>Price:</th> <td><asp:Label id="DeletePrice" Runat="Server"/></td></tr><tr> <th>Qty:</th> <td><asp:Label id="DeleteQty" Runat="Server"/></td></tr><tr> <th>Sale:</th> <td><asp:CheckBox id="DeleteSale" Enabled="False" Runat="Server"/></td></tr><tr> <th></th> <td class="buttons"> <asp:Label Text="Delete this record?" Runat="Server" ForeColor="#FF0000"/> <asp:Button Text="Yes" OnClick="Delete_Record" Runat="Server"/> <asp:Button Text=" No " OnClick="Display_Record" Runat="Server"/></td></tr></table></asp:Panel>Listing 9-65. Code for editing with server controls.

Scripting the Initial Record Display

Page 130: Assignment Instructions

Selection of a record for display or editing is made through a DropDownList and accompanying Button. When the page first opens, the DisplayPanel is initially visible and the first product in the list is selected for display. Code for the Page_Load subprogram and other called subprograms to produce this initial display are shown below.

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

<SCRIPT Runat="Server">

Dim DBConnection As OleDbConnectionDim DBCommand As OleDbCommandDim DBReader As OleDbDataReaderDim SQLString As String

Sub Page_Load

If Not Page.IsPostBack Then Bind_Book_DropDown BookList.SelectedIndex = 0 Display_Record(Nothing, Nothing) End If

End Sub

Sub Bind_Book_DropDown

'-- Load book titles into drop-down list DBConnection = New OleDbConnection( _ "Provider=Microsoft.Jet.OLEDB.4.0;" & _ "Data Source=" & Server.MapPath("../Databases/BooksDB.mdb")) DBConnection.Open() SQLString = "SELECT BookID, BookTitle FROM Books ORDER BY BookTitle" DBCommand = New OleDbCommand(SQLString, DBConnection) DBReader = DBCommand.ExecuteReader() BookList.DataSource = DBReader BookList.DataTextField = "BookTitle" BookList.DataValueField = "BookID" BookList.DataBind() DBReader.Close() DBConnection.Close() End Sub

Sub Display_Record (Source As Object, Args As EventArgs)

DBConnection = New OleDbConnection( _ "Provider=Microsoft.Jet.OLEDB.4.0;" & _ "Data Source=" & Server.MapPath("../Databases/BooksDB.mdb")) DBConnection.Open() SQLString = "SELECT * FROM Books WHERE " & _ "BookID='" & BookList.SelectedValue & "'" DBCommand = New OleDbCommand(SQLString, DBConnection) DBReader = DBCommand.ExecuteReader() DBReader.Read() DisplayID.Text = DBReader("BookID") DisplayType.Text = DBReader("BookType") DisplayTitle.Text = DBReader("BookTitle") DisplayAuthor.Text = DBReader("BookAuthor") DisplayDescription.Text = DBReader("BookDescription") DisplayPrice.Text = FormatCurrency(DBReader("BookPrice"))

Page 131: Assignment Instructions

DisplayQty.Text = DBReader("BookQty") DisplaySale.Checked = DBReader("BookSale") DBReader.Close() DBConnection.Close() InsertPanel.Visible = False UpdatePanel.Visible = False DeletePanel.Visible = False DisplayPanel.Visible = True End Sub

...</SCRIPT>Listing 9-66. Script to select a record for editing.

It is important to remember to import the System.Data.OleDb namespace to the page in order to support script access to the database. Also, the database connection object, command object, data reader object, and SQLString variable are globally declared since they are used in numerous subprograms.

The Page_Load subprogram builds the initial page display. It calls the Bind_Book_DropDown subprogram to load the DropDownList with book titles and IDs, sets the SelectedIndexof the list to 0 to programmaticaly select the first item in the list, and calls the Display_Record subprogram to display this selected record. The Bind_Book_DropDown subprogram binds book titles to the DataTextField property of the DropDownList and binds book IDs to the DataValueField property. Thus, book titles are shown in the list while book IDs are used for record retrieval.

After loading the DropDownList and selecting the first item in the list, the Display_Record subprogram is called. This subprogram loads the DisplayPanel with all fields from the selected record.

The Display_Record subprogram is called on different occasions by different buttons and by different subprograms to redisplay the display form. Because it is called by buttons, the subprogram must include a signature appropriate for button calls: Display_Record (Source As Object, Args As EventArgs). However, this subprogram is also called by other subprograms for which button arguments are not automatically supplied. Therefore, subprograms which call Display_Record must pass along null arguments (Nothing) in place of expected button arguments. For this reason, the call to this subprogram is coded as Display_Record(Nothing, Nothing) in the Page_Load subprogram.

Sub Page_Load

If Not Page.IsPostBack Then

Bind_Book_DropDown BookList.SelectedIndex = 0 Display_Record(Nothing, Nothing)

End If

End SubListing 9-67. Calling a subprogram with null arguments.

Page 132: Assignment Instructions

The Display_Record subprogram opens a connection to the database and issues an SQL SELECT statement to retrieve the record with an BookID matching that of the number selected from the DropDownList. After reading this single record, the subprogram assigns its fields to the output controls in the display form. All of these controls are TextBoxes except for the CheckBox showing the value of the BookSale field. Notice that every time Display_Record is called it hides all other Panels (which may be visible during an insert, update, and delete operation) and makes the DisplayPanel visible.

InsertPanel.Visible = FalseUpdatePanel.Visible = FalseDeletePanel.Visible = FalseDisplayPanel.Visible = TrueListing 9-68. Switching visibility of editing Panels.

Inserting a Record

The DisplayPanel shows record editing buttons for additions, changes, and deletions of records. When the "Insert Record" button is clicked, the Load_Insert_Form subprogram is called to replace the DisplayPanel with the InsertPanel containing the form and appropriate buttons for added a new record to the database. Its code is shown below.

Sub Load_Insert_Form (Source As Object, Args As EventArgs)

'-- Load book types into drop-down list DBConnection = New OleDbConnection( _ "Provider=Microsoft.Jet.OLEDB.4.0;" & _ "Data Source=" & Server.MapPath("../Databases/BooksDB.mdb")) DBConnection.Open() SQLString = "SELECT DISTINCT BookType FROM Books ORDER BY BookType" DBCommand = New OleDbCommand(SQLString, DBConnection) DBReader = DBCommand.ExecuteReader() AddType.DataSource = DBReader AddType.DataTextField = "BookType" AddType.DataValueField = "BookType" AddType.DataBind() DBReader.Close() DBConnection.Close() DisplayPanel.Visible = False InsertPanel.Visible = True

End SubListing 9-69. Loading an insert form.

Part of the insert form is a DropDownList (id="AddType") containing book types from which to choose. Therefore, this list must be loaded prior to displaying the insert form. Once the BookType values are bound to the control, the DisplayPanel is hidden and the InsertPanel is made visible.

At this point, users can fill in new information on the insert form and click its "Insert" button to write the new information to the database. This action calls the Insert_Recordsubprogram whose code is shown below.

Sub Insert_Record (Source As Object, Args As EventArgs)

DBConnection = New OleDbConnection( _

Page 133: Assignment Instructions

"Provider=Microsoft.Jet.OLEDB.4.0;" & _ "Data Source=" & Server.MapPath("../Databases/BooksDB.mdb")) DBConnection.Open() SQLString = "INSERT INTO Books (BookID, BookType, BookTitle, " & _ "BookAuthor, BookDescription, BookPrice, BookQty, " & _ "BookSale) VALUES (" & _ "'" & AddID.Text & "', " & _ "'" & AddType.SelectedValue & "', " & _ "'" & AddTitle.Text & "', " & _ "'" & AddAuthor.Text & "', " & _ "'" & AddDescription.Text & "', " & _ " " & AddPrice.Text & ", " & _ " " & AddQty.Text & ", " & _ " " & AddSale.Checked & ")" DBCommand = New OleDbCommand(SQLString, DBConnection) DBCommand.ExecuteNonQuery() DBConnection.Close() Bind_Book_DropDown BookList.SelectedValue = AddID.Text Display_Record(Nothing, Nothing) UpdateMSG.Text = "-- Record " & AddID.Text & " inserted"

End SubListing 9-70. Script to insert a record from insert form.

An SQL INSERT statement is composed from data values taken from the input form. The statement is issued through the command object's ExecuteNonQuery() method, the method used when no records are returned from an SQL query. After writing the new record to the database, the DropDownList of book titles and IDs is recreated by theBind_Book_DropDown subprogram to reflect this addition to the database. The DropDownList's SelectedValue property is set to the new book ID value so that when theDisplay_Record subprogram is called to display this record the DropDownList will show this new book.

Changing a Record

When the "Edit Record" button is clicked in the DisplayPanel, the Load_Update_Form subprogram is called to replace the DisplayPanel, with the UpdatePanel containing the form and appropriate buttons for updated an existing record in the database. Its code is shown below.

Sub Load_Update_Form (Source As Object, Args As EventArgs)

'-- Load book types into drop-down list DBConnection = New OleDbConnection( _ "Provider=Microsoft.Jet.OLEDB.4.0;" & _ "Data Source=" & Server.MapPath("../Databases/BooksDB.mdb")) DBConnection.Open() SQLString = "SELECT DISTINCT BookType FROM Books ORDER BY BookType" DBCommand = New OleDbCommand(SQLString, DBConnection) DBReader = DBCommand.ExecuteReader() UpdateType.DataSource = DBReader UpdateType.DataTextField = "BookType" UpdateType.DataValueField = "BookType" UpdateType.DataBind() DBReader.Close()

Page 134: Assignment Instructions

'-- Load current record SQLString = "SELECT * FROM Books WHERE " & _ "BookID='" & BookList.SelectedValue & "'" DBCommand = New OleDbCommand(SQLString, DBConnection) DBReader = DBCommand.ExecuteReader() DBReader.Read() UpdateID.Text = DBReader("BookID") UpdateType.SelectedValue = DBReader("BookType") UpdateTitle.Text = DBReader("BookTitle") UpdateAuthor.Text = DBReader("BookAuthor") UpdateDescription.Text = DBReader("BookDescription") UpdatePrice.Text = DBReader("BookPrice") UpdateQty.Text = DBReader("BookQty") UpdateSale.Checked = DBReader("BookSale") DBReader.Close() DBConnection.Close() DisplayPanel.Visible = False UpdatePanel.Visible = True End SubListing 9-71. Loading an update form.

Again, a DropDownList of book types must be loaded for selecting changed values of this data item. Then a book record is retrieved to populate the update form. The identify of this record is taken from the current selection showing in the book title DropDownList. After controls on the update form are populated, the DisplayPanel is hidden and the UpdatePanelis revealed. Now, changes can be made to the record (with the exception of the BookID key field), and the record rewritten to the database. Rewriting occurs with a click on the "Update" button appearing at the bottom of the update form, and a call to the Update_Record subprogram shown below.

Sub Update_Record (Source As Object, Args As EventArgs)

DBConnection = New OleDbConnection( _ "Provider=Microsoft.Jet.OLEDB.4.0;" & _ "Data Source=" & Server.MapPath("../Databases/BooksDB.mdb")) DBConnection.Open() SQLString = "UPDATE Books SET " & _ "BookType='" & UpdateType.SelectedValue & "', " & _ "BookTitle='" & UpdateTitle.Text & "', " & _ "BookAuthor='" & UpdateAuthor.Text & "', " & _ "BookDescription='" & UpdateDescription.Text & "', " & _ "BookPrice=" & UpdatePrice.Text & ", " & _ "BookQty=" & UpdateQty.Text & ", " & _ "BookSale=" & UpdateSale.Checked & _ " WHERE BookID='" & UpdateID.Text & "'" DBCommand = New OleDbCommand(SQLString, DBConnection) DBCommand.ExecuteNonQuery() DBConnection.Close() BookList.SelectedValue = UpdateID.Text Display_Record(Nothing, Nothing) UpdateMSG.Text = "-- Record " & UpdateID.Text & " updated"

End SubListing 9-72. Script to update a record from update form.

Page 135: Assignment Instructions

An SQL UPDATE statement is composed from values taken from the update form, and is issued through the command object's ExecuteNonQuery() method to update the database. The current book ID from the UpdateID Label is assigned as the SelectedValue of the selection DropDownList so that this book is retrieved when the Display_Record subprogram is called to display the changed record.

Deleting a Record

When the "Delete Record" button is clicked in the DisplayPanel, the Load_Delete_Form subprogram is called to replace the DisplayPanel with the DeletePanel containing the form and appropriate buttons for deleting an existing record in the database. Its code is shown below.

Sub Load_Delete_Form (Source As Object, Args As EventArgs)

DBConnection = New OleDbConnection( _ "Provider=Microsoft.Jet.OLEDB.4.0;" & _ "Data Source=" & Server.MapPath("../Databases/BooksDB.mdb")) DBConnection.Open() SQLString = "SELECT * FROM Books WHERE " & _ "BookID = '" & BookList.SelectedValue & "'" DBCommand = New OleDbCommand(SQLString, DBConnection) DBReader = DBCommand.ExecuteReader() DBReader.Read() DeleteID.Text = DBReader("BookID") DeleteType.Text = DBReader("BookType") DeleteTitle.Text = DBReader("BookTitle") DeleteAuthor.Text = DBReader("BookAuthor") DeleteDescription.Text = DBReader("BookDescription") DeletePrice.Text = FormatCurrency(DBReader("BookPrice")) DeleteQty.Text = DBReader("BookQty") DeleteSale.Checked = DBReader("BookSale") DBReader.Close() DBConnection.Close() DisplayPanel.Visible = False DeletePanel.Visible = True End SubListing 9-73. Loading a delete form.

The record to be deleted is retrieved from the database and populates the delete form. The currently visible DisplayPanel is hidden and the DeletePanel is made visible, revealing the buttons for record deletion. When the "Yes" button is clicked in this Panel, the Delete_Record subprogram is called.

Sub Delete_Record (Source As Object, Args As EventArgs)

DBConnection = New OleDbConnection( _ "Provider=Microsoft.Jet.OLEDB.4.0;" & _ "Data Source=" & Server.MapPath("../Databases/BooksDb.mdb")) DBConnection.Open() SQLString = "DELETE FROM Books WHERE BookID = '" & DeleteID.Text & "'" DBCommand = New OleDbCommand(SQLString, DBConnection) DBCommand.ExecuteNonQuery() DBConnection.Close()

Page 136: Assignment Instructions

Bind_Book_DropDown BookList.SelectedIndex = 0 Display_Record(Nothing, Nothing) UpdateMSG.Text = "-- Record " & DeleteID.Text & " deleted" End SubListing 9-74. Script to delete a record from delete form.

This subprogram takes the book ID displayed on the delete form and uses it to compose an SQL DELETE statement to delete the record. Since deleting a record affects the books displayed in the DropDownList, the list needs re-binding with the Bind_Book_DropDown subprogram. Then the Display_Record subprogram is called to redisplay the initial record in the database.

The above editing subprograms do not contain validation routines to check on the correctness of added or changed data. These can be added to existing subprograms without too much complication.

As you can see, there is quite a bit of extra coding needed to create your own editing forms. As a general rule, you probably will prefer to stick with the GridView, DetailsView, and FormView controls for their built-in editing conveniences. However, producing your own editing applications is an available option.

Directory Access

Up to this point, most of your work has involved accessing and maintaining the central repositories of information that feed Web sites. Database activity is one of the central activities in site content management, involving the collection, organization, storage, retrieval, and maintenance of the primary content appearing on Web pages. Yet, there are other important surrounding tasks that go along with database management. Particularly, you need to set up and work with the directory and file structures within which all this activity takes place.

It may be that you have direct physical access to the Web server that hosts your site. If this is the case, then you can sit down at the server console and create and manage the folders and files you need. If you are hosted by a remote Internet Service Provider (ISP), you probably have FTP access to your main Web directory to which you can transfer files and otherwise work with your site structure. A third option is to create your own Web pages to handle directory and file access, much in the same way that your pages handle database access. In fact, ASP.NET supplies a collection of software classes that permit you to work with your site structure and its contents through your own Web pages in nearly identical fashion to having direct console or FTP access. It is not necessary to rely on physical access or third-party hosts to manage your site.

The directory structure assumed previously for working with the BooksDB.mdb database and associated pictures is continued here. Recall that the main Web directory is on the physical path c:\eCommerce and that all Web pages appear in the c:\eCommerce\WebSite subdirectory. All databases and files are in the c:\eCommerce\Databases subdirectory and all book pictures are in the c:\eCommerce\BookPictures subdirectory. Therefore, the relative path from a Web page to the main directory is Server.MapPath(../), the relative path to the Databases directory is Server.MapPath(../Databases), and the relative path to the BookPictures directory is Server.MapPath(../BookPictures). (See Figure 3-8 for an illustration of this directory structure.)

Page 137: Assignment Instructions

The Directory Class

The ASP.NET Directory class is your access to the directory structure of your site and its applications. Through various methods supplied by this class, scripts can manipulate directories in much the same way as you do this locally through the Windows Desktop and Windows Explorer. That is, you can create and delete directories, view the contents of directories, and move and copy directories. With proper permissions, you can work remotely with your directory structure through a Web page just as if your were sitting at the server console.

Directory access is provided through the SystemIO namespace which must be imported for any page using the Directory class.

<%@ Import Namespace="System.IO" %>Listing 10-1. Importing a namespace for working with directory system.

Also, you need to be very cautious when using Directory methods. They can easily erase your entire directory structure with no recourse for getting it back.

Determining Directory Content

The contents of a directory can be determined with three methods shown in Figure 10-1.

Directory.GetDirectories(path)

Directory.GetFiles(path [, search pattern])

Directory.GetFileSystemEntries(path)

Figure 10-1. General formats for accessing directory contents.

The GetDirectories() method returns the paths to all subdirectories inside the specified directory; the GetFiles() method returns the paths to all files within the specified directory; the GetFileSystemEntries() method returns the paths to all folders and files within a specified directory.

All of these Directory methods return an array of strings, each element of which is the physical path to a folder or file. Therefore, you need to set up an array to capture the paths, and a loop to extract them individually from the array. Figure 10-2 shows the general structure of a script to build and extract elements from an array populated by Directory methods.

Dim array() As Stringarray = Directory.method(path)

Dim element As StringFor Each element In array ...display elementNext

Figure 10-2. General formats for retrieving and displaying directory contents.

Page 138: Assignment Instructions

Displaying Subdirectory Paths

The Directory.GetDirectories() method returns an array of paths to all subdirectories within a specified directory. The following script illustrates how to retrieve and displays all of the subfolders inside the eCommerce folder used for many of the examples in these tutorials.

Show eCommerce SubFolders: Figure 10-3. Listing of subdirectory paths inside a directory.<%@ Import Namespace="System.IO" %>

<SCRIPT Runat="Server">

Sub Show_Folders (Src As Object, Args As EventArgs) '-- Get directory listing into an array Dim FolderArray() As String FolderArray = Directory.GetDirectories("c:\eCommerce")

'-- Iterate the array items and display directory paths Dim Folder As String For Each Folder in FolderArray Folders.Text &= Folder & "<br/>" Next

End Sub

</SCRIPT>

<form Runat="Server">

<b>Show eCommerce SubFolders: </b><asp:Button Text="Show Folders" OnClick="Show_Folders" Runat="Server"/><p><asp:Label id="Folders" EnableViewState="False" Runat="Server"/></p>

</form>Listing 10-2. Code to get subdirectory paths in a directory.

First, Visual Basic array FolderArray() is declared as string data type since paths are retrieved as strings. The Directory.GetDirectories() method is assigned to the array, filling it with physical paths to all subdirectories within the specified c:\eCommerce directory. Then a For Each...Next loop is established to iterate all elements of the array, concatenating each path string to the Folders label for display.

Displaying File Paths

The Directory.GetFiles() method, in similar fashion, returns the physical paths to files in a specified directory. The following example shows all files residing in the c:\eCommerce\Databases directory.

Show Databases Files: Figure 10-4. Listing of file paths inside a directory.<%@ Import Namespace="System.IO" %>

<SCRIPT Runat="Server">

Page 139: Assignment Instructions

Sub Show_Files (Src As Object, Args As EventArgs)

Dim FileArray() As String FileArray = Directory.GetFiles("c:\eCommerce\Databases") Dim File As String For Each File in FileArray Files.Text &= File & "<br/>" Next

End Sub

</SCRIPT>

<form Runat="Server">

<b>Show Databases Files: </b><asp:Button Text="Show Files" OnClick="Show_Files" Runat="Server"/><p><asp:Label id="Files" EnableViewState="False" Runat="Server"/></p>

</form>Listing 10-3. Code to get file paths in a directory.

When displaying file paths you can specify a search pattern to restrict retrieval to files that match the pattern. For example, using the wildcard character "*" you can retrieve,

"a*"     - all file names beginning with the letter "a""*r.*"   - all file names ending with the letter "r""*.jpg"  - all file names ending with the ".jpg" extension

The following button reports all of the files inside the c:\eCommerce\Databases directory which have the ".mdb" extension.

Show .mdb Files: Figure 10-5. Listing of filtered file paths inside a directory.<%@ Import Namespace="System.IO" %>

<SCRIPT Runat="Server">

Sub Show_MDB_Files (Src As Object, Args As EventArgs)

Dim FileArray() As String FileArray = Directory.GetFiles("c:\eCommerce\Databases", "*.mdb") Dim File As String For Each File in FileArray MDBFiles.Text &= File & "<br/>" Next

End Sub

</SCRIPT>

<form Runat="Server">

<b>Show .mdb Files: </b><asp:Button Text="Show Files" OnClick="Show_MDB_Files" Runat="Server"/><p><asp:Label id="MDBFiles" EnableViewState="False" Runat="Server"/></p>

Page 140: Assignment Instructions

</form>Listing 10-4. Code to get filtered file paths in a directory.

It is sometimes useful just to get a list of the folder or file names without the complete path. This is easily accomplished by using the Visual Basic Replace() method to remove the path portions of the names. In the following script only the names of the files in a directory are reported.

Show File Names: Figure 10-6. Listing of files names in a directory.<%@ Import Namespace="System.IO" %>

<SCRIPT Runat="Server">

Sub Show_Files (Src As Object, Args As EventArgs)

Dim FileArray() As String FileArray = Directory.GetFiles("c:\eCommerce\Databases") Dim File As String For Each File in FileArray File = Replace(File, "c:\eCommerce\Databases\", "") Files.Text &= File & "<br/>" Next

End Sub

</SCRIPT>

<form Runat="Server">

<b>Show File Names: </b><asp:Button Text="Show Files" OnClick="Show_Files" Runat="Server"/><p><asp:Label id="Files" EnableViewState="False" Runat="Server"/></p>

</form>Listing 10-5. Code to get file names in a directory.

Displaying Folder and File Paths

You can get the entire contents of a directory—subdirectories and files—by using the Directory.GetFileSystemEntries() method. The following script retrieves both folder and file names inside the eCommerce directory.

Show Directory Contents: Figure 10-7. Listing of all contents inside a directory.<%@ Import Namespace="System.IO" %>

<SCRIPT Runat="Server">

Sub Show_Contents (Src As Object, Args As EventArgs)

Dim ContentsArray() As String ContentsArray = Directory.GetFileSystemEntries("c:\eCommerce") Dim Item As String For Each Item in ContentsArray

Page 141: Assignment Instructions

Item = Replace(Item, "c:\eCommerce\", "") Contents.Text &= Item & "<br/>" Next

End Sub

</SCRIPT>

<form Runat="Server">

<b>Show Directory Contents: </b><asp:Button Text="Get Contents" OnClick="Show_Contents" Runat="Server"/><p><asp:Label id="Contents" EnableViewState="False" Runat="Server"/></p>

</form>Listing 10-6. Code to get listing of all contents in a directory.

You might consider producing a general-purpose form for viewing the contents of any directory entered into a textbox. This application is shown below wherein a default directory is pre-entered into the textbox. You can, however, view the contents of the Databases, BookPictures, and WebSite subdirectories by appending these folder names to the path.

Show Directory Contents:

Path:   Figure 10-8. Page to view the contents of specified directories.<%@ Import Namespace="System.IO" %>

<SCRIPT Runat="Server">

Sub Show_Contents (Src As Object, Args As EventArgs)

If Left(Path.Text, 12) = "c:\eCommerce" Then Try Dim ContentsArray() As String ContentsArray = Directory.GetFileSystemEntries(Path.Text) Dim Item As String For Each Item in ContentsArray Item = Replace(Item, Path.Text & "\", "") Contents.Text &= Item & "<br/>" Next Catch Contents.Text = "&bull; Invalid path" End Try Else Contents.Text = "&bull; Invalid path" End If End Sub

</SCRIPT>

<form Runat="Server">

<p><b>Show Directory Contents: </b></p><b>Path: </b><asp:TextBox id="Path" Text="c:\eCommerce" Runat="Server"/><asp:Button Text="Get Contents" OnClick="Show_Contents" Runat="Server"/>

Page 142: Assignment Instructions

<p><asp:Label id="Contents" EnableViewState="False" Runat="Server"/></p>

</form>Listing 10-7. Code to view the contents of specified directories.

There are a couple of tests to make sure that proper directory paths are entered. First, the input path (Path.Text) is checked to make sure that it begins with with the characters "c:\eCommerce" in order to restrict listings to this particular root directory. Also, retrieval and display are contained inside a Try...Catch block to trap any script execution errors. For example, entering the path "c:\eCommerce\Database" (rather than "Databases") produces an error that otherwise would abort the script and display a system error on screen.

Displaying Directory Structures and Files

With a bit more elaborate coding you can automatically produce an entire directory structure as a set of links to view files in any of the directories. This approach is used in the following example that maps the structure of the c:\eCommerce directory.

Show Directory Contents:

Directories: Files:

eCommerce            databases            folder1            folder2Figure 10-9. Page to view the contents of all directories.

This application is based on the use of an <asp:PlaceHolder> and the building of dynamic LinkButton controls for the main directory and each of its subdirectories (and any of their subdirectories). These LinkButtons call a subprogram to display the file contents of the directories.

<%@ Import Namespace="System.IO" %>

<SCRIPT Runat="Server">

Sub Page_Load

ViewState("Folder") = "c:\eCommerce" Create_RootFolder_Link Create_SubFolder_Links

End Sub

Create_RootFolder_Link PHFolders.Controls.Clear() '-- Get folder name Dim FolderName As String = ViewState("Folder") Dim SlashPos As Integer = InStrRev(FolderName, "\") FolderName = Right(FolderName, Len(FolderName) - SlashPos) '-- Create root folder link Dim FolderLink As New LinkButton

Page 143: Assignment Instructions

FolderLink.Text = FolderName FolderLink.CommandName = ViewState("Folder") AddHandler FolderLink.Command, AddressOf Show_Files PHFolders.Controls.Add(FolderLink) '-- Create line break Dim LineBreak As New Literal LineBreak.Text = "<br/>" PHFolders.Controls.Add(LineBreak) End Sub

Sub Create_SubFolder_Links Dim FolderArray() As String FolderArray = Directory.GetDirectories(ViewState("Folder")) Dim FolderPath As String For Each FolderPath in FolderArray '-- Get folder name Dim SlashPos As Integer = InStrRev(FolderPath, "\") Dim FolderName As String = Right(FolderPath, Len(FolderPath) - SlashPos) '-- Determine number of indention spaces Dim SlashCount As Integer = 0 Dim i As Integer For i = 1 To Len(FolderPath) If Mid(FolderPath, i, 1) = "\" Then SlashCount += 1 End If Next Dim Spaces As String = "" For i = 1 To SlashCount - 1 Spaces &= "&nbsp;&nbsp;&nbsp;" Next '-- Create indention spaces Dim Spacing As New Literal Spacing.Text = Spaces PHFolders.Controls.Add(Spacing) '-- Create subdirectory link Dim FolderLink As New LinkButton FolderLink.Text = FolderName FolderLink.CommandName = FolderPath FolderLink.EnableViewState = False AddHandler FolderLink.Command, AddressOf Show_Files PHFolders.Controls.Add(FolderLink) '-- Create line break Dim LineBreak As New Literal LineBreak.Text = "<br/>" PHFolders.Controls.Add(LineBreak) '-- Reset folder path and recall this subprogram ViewState("Folder") = FolderPath Create_SubFolder_Links Next

End Sub

Page 144: Assignment Instructions

Sub Show_Files (Src As Object, Args As CommandEventArgs) Dim FileArray() As String FileArray = Directory.GetFiles(Args.CommandName) Dim File As String For Each File in FileArray File = Replace(File, Args.CommandName & "\", "") Files.Text &= File & "<br/>" Next End Sub

</SCRIPT>

<form Runat="Server">

<p><b>Show Directory Contents: </b></p>

<table border="0" cellpadding="3" style="width:100%"><tr style="text-align:left; background-color:#E0E0E0"> <th>Directories:</th> <th>Files:</th></tr><tr style="vertical-align:top"> <td style="width:50%"> <asp:PlaceHolder id="PHFolders" Runat="Server"/> </td> <td style="width:50%> <asp:Label id="Files" EnableViewState="False" Runat="Server"/> </td></tr></table>

</form>Listing 10-8. Code to view the contents of all directories.

As you can see from the listing, output is arranged in a table, the left column of which houses the PlaceHolder containing dynamically-built directory links, and the right column of which contains a Label for display files in the chosen directory.

As dynamic controls, the LinkButtons must be created on every page-load and post-back. Therefore, their creation is governed by the Page_Load subprogram.

Sub Page_Load

ViewState("Folder") = "c:\eCommerce" Create_RootFolder_Link Create_SubFolder_Links

End SubListing 10-9. Code to initiate building of directory structure.

The top-level directory for this particular listing is assigned to a ViewState variable as the starting point for creating LinkButtons. You can, in fact, choose any initial directory path when using this application. Then, the Create_RootFolder_Link subprogram is called to create the single LinkButton for this root directory; subprogram Create_SubFolder_Linksis called to create the lower-level links.

Create_RootFolder_Link

Page 145: Assignment Instructions

PHFolders.Controls.Clear() '-- Get folder name Dim FolderName As String = ViewState("Folder") Dim SlashPos As Integer = InStrRev(FolderName, "\") FolderName = Right(FolderName, Len(FolderName) - SlashPos) '-- Create root folder link Dim FolderLink As New LinkButton FolderLink.Text = FolderName FolderLink.CommandName = ViewState("Folder") AddHandler FolderLink.Command, AddressOf Show_Files PHFolders.Controls.Add(FolderLink) '-- Create line break Dim LineBreak As New Literal LineBreak.Text = "<br/>" PHFolders.Controls.Add(LineBreak) End SubListing 10-10. Code for Create_RootFolder_Link subprogram.

Since the links are built each time the page loads, the PlaceHolder is cleared of its controls so that new links are not added to those created on previous page loads.

The link text appearing on the page is the folder name, which must be extracted from the path to this folder held in ViewState("Folder"); that is, "eCommerce" must be extracted from "c:\eCommerce". This is a matter of locating the final back-slash character ("\") in the path and extracting the remaining string to its right.

A LinkButton for this folder is created as a new LinkButton control. Then, its Text property is set to the folder name previously extracted from the path string. The CommandName for the LinkButton is the full path to the folder: ViewState("Folder"). The subprogram called when this button is clicked is Show_Files. When this subprogram is called, theCommandName is passed as an argument to the subprogram, which displays the files contained on this directory path.

After adding the LinkButton to the PlaceHolder, the script adds a final Literal control whose Text property is a <br/> tag to produce a line break following the folder name.

Once this initial LinkButton is created, a call is made to subprogram Create_SubFolder_Links in order to build links to all subdirectories under the root directory. The structure of this subprogram is similar to the Create_RootFolder_Link subprogram with the main difference being that it repeatedly calls itself to traverse the subdirectories of the root directory. It is a recursive subprogram.

Sub Create_SubFolder_Links Dim FolderArray() As String FolderArray = Directory.GetDirectories(ViewState("Folder")) Dim FolderPath As String For Each FolderPath in FolderArray '-- Get folder name Dim SlashPos As Integer = InStrRev(FolderPath, "\") Dim FolderName As String = Right(FolderPath, Len(FolderPath) - SlashPos)

Page 146: Assignment Instructions

'-- Determine number of indention spaces Dim SlashCount As Integer = 0 Dim i As Integer For i = 1 To Len(FolderPath) If Mid(FolderPath, i, 1) = "\" Then SlashCount += 1 End If Next Dim Spaces As String = "" For i = 1 To SlashCount - 1 Spaces &= "&nbsp;&nbsp;&nbsp;" Next '-- Create indention spaces Dim Spacing As New Literal Spacing.Text = Spaces PHFolders.Controls.Add(Spacing) '-- Create subdirectory link Dim FolderLink As New LinkButton FolderLink.Text = FolderName FolderLink.CommandName = FolderPath FolderLink.EnableViewState = False AddHandler FolderLink.Command, AddressOf Show_Files PHFolders.Controls.Add(FolderLink) '-- Create line break Dim LineBreak As New Literal LineBreak.Text = "<br/>" PHFolders.Controls.Add(LineBreak) '-- Reset folder path and recall this subprogram ViewState("Folder") = FolderPath Create_SubFolder_Links Next

End SubListing 10-11. Code for Create_SubFolder_Links subprogram.

The first step is to get all subdirectories subordinate to root folder ViewState("Folder"), assigned here to FolderArray(). Then the elements of FolderArray() are iterated to build a LinkButton for each directory path in the array. As before, this means assigning the folder name as the LinkButton Text, assigning the full path as the CommandName, and creating a command link to subprogram Show_Files. Then, this LinkButton and a trailing line break are added to the PlaceHolder.

When adding links to the PlaceHolder, spaces are used to indent the links to visually represent the directory structure. Each level is indented three additional spaces. The amount of indention is given by the number of back-slash characters appearing in a path. Loops are set up to count the number of these characters and then to build a string of spaces (variableSpaces) representing this amount of indention. (One fewer than the total number of back-slashes is used to add spacing since the root path itself contains a back-slash that is not spaced.)

The final step in the iteration is to reassign to ViewState("Folder") the current directory for which a LinkButton was just created, then to re-call subprogramCreate_SubFolder_Links. The purpose is to iterate and create LinkButtons for any subdirectories of the current directory prior to returning to the iteration and continuing through the original directory. This recursive calling of the

Page 147: Assignment Instructions

subprogram proceeds through as many levels of subdirectories as are subordinate to the root directory.

Once the structure of LinkButtons is created, each is clicked to call subprogram Show_Files. Notice that this subprogram has the appropriate signature for a call by a command button (CommandEventArgs).

Sub Show_Files (Src As Object, Args As CommandEventArgs) Dim FileArray() As String FileArray = Directory.GetFiles(Args.CommandName) Dim File As String For Each File in FileArray File = Replace(File, Args.CommandName & "\", "") Files.Text &= File & "<br/>" Next End SubListing 10-12. Code for Show_Files subprogram.

Recall that the CommandNames assigned to LinkButtons are paths to the named directories. Therefore, argument Args.CommandName is the path identifying which directory's files to display: Directory.GetFiles(Args.CommandName). As in previous examples, the retrieved file paths are assigned to an array which is iterated to display the file names. File names are extracted from the paths by replacing the directory-path portion of the file-path string.

Creating and Deleting Directories

The Directory class provides two methods used to create and delete directories under script control. Their general syntax is shown in Figure 10-10.

Directory.CreateDirectory(path)

Directory.Delete(path, True|False)

Figure 10-10. General formats for creating and deleting directories.

The path is the physical path to a folder or a relative path converted to a physical path with Server.MapPath(). This path can be a literal string or a reference to a variable containing a path string.

When creating directories, all directories on the path to the final directory are also created if they do not exist. If the named directory already exists, it is not created.

When deleting a directory, the True or False parameter specifies whether to delete the directory if it contains subdirectories and/or files. The default value is False, meaning that the folder must be empty to be deleted. If True is specified, the directory and its subfolders and files are deleted.

Creating Directories

Page 148: Assignment Instructions

A directory is created by including its name on the path of the Directory.CreateDirectory() method. This statement can be issued through any script. An approach you might consider, though, is writing a more generalized script to create any subdirectories within your main directory. A simple example is shown below.

Create Directory

Path:     

 

Figure 10-11. Page to create a new subdirectory.

The path and new directory name are entered into the textbox. The current path to the root directory is precoded for appending any subdirectory structure. Remember that if the newly named directory already exists, it is not created. Therefore, no harm comes from inadvertently clicking the button more than one time. As you can see in the following code listing, the entered path is checked to make sure that the new directory path is valid and is, in fact, created inside this root directory.

<%@ Import Namespace="System.IO" %>

<SCRIPT Runat="Server">

Sub Create_Directory (Src As Object, Args As EventArgs)

Dim ValidPath As Boolean = True Dim CheckPath As String If Left(Path.Text, 13) = "c:\eCommerce\" Then CheckPath = Replace(Path.Text, "c:\eCommerce\", "") Else ValidPath = False Message.Text = "&bull; Invalid path" End If If Len(CheckPath) = 0 Then ValidPath = False Message.Text = "&bull; Missing path" End If Dim ValidChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ_\" Dim i As Integer For i = 1 To Len(CheckPath) If InStr(ValidChars, Mid(UCase(CheckPath), i, 1)) = 0 Then Message.Text = "&bull; Invalid character """ & Mid(CheckPath, i, 1) & _ """ in path" ValidPath = False Exit For End If Next If ValidPath = True Then Directory.CreateDirectory(Path.Text) Message.Text = "&bull; New directory <b>" & Path.Text & "</b> created" End If End Sub

Page 149: Assignment Instructions

Sub Reset_Directory (Src As Object, Args As EventArgs)

Path.Text = "c:\eCommerce\" Message.Text = ""

End Sub

</SCRIPT>

<form Runat="Server">

<h3>Create Directory</h3>

<b>Path: </b><asp:TextBox id="Path" Text="c:\eCommerce\" Width="250" Runat="Server"/><asp:Button Text="Create" OnClick="Create_Directory" Runat="Server"/><br/><br/><asp:Label id="Message" ForeColor="#FF0000" Runat="Server"/><br/>

</form>Listing 10-13. Code to create a directory.

In this example, the left-most 13 characters of the path string should be "c:\eCommerce\". If this is the case, the remaining portion of the path is saved to variable CheckPath for additional tests for a valid directory name. The Visual Basic Replace() function removes the root path and places the remaining characters in CheckPath.

Dim ValidPath As Boolean = True

Dim CheckPath As StringIf Left(Path.Text, 13) = "c:\eCommerce\" Then CheckPath = Replace(Path.Text, "c:\eCommerce\", "")Else ValidPath = False Message.Text = "&bull; Invalid path"End If

If Len(CheckPath) = 0 Then ValidPath = False Message.Text = "&bull; Missing path"End IfListing 10-14. Testing for a valid root directory.

There is also a test for a valid directory name. Here, each character in the directory name (CheckPath) is compared with the range of valid characters stored in string ValidChars. Note that a back-slash is included as a valid character since the new directory may be on a subdirectory path.

Dim ValidChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ_\"Dim i As IntegerFor i = 1 To Len(CheckPath) If InStr(ValidChars, Mid(UCase(CheckPath), i, 1)) = 0 Then Message.Text = "&bull; Invalid character """ & Mid(CheckPath, i, 1) & _ """ in path" ValidPath = False Exit For End IfNextListing 10-15. Testing for a valid root directory.

Page 150: Assignment Instructions

The valid characters are more restrictive than necessary for acceptable directory names. However, both upper- and lower-case characters are valid since names are converted to upper-case for comparisons.

Deleting Directories

A similar approach can be taken to delete directories. You need to be cautious, however, since a directory, along with its subdirectories and files, can be easily deleted without any way to recover.

Detecting Directories

Before creating or deleting a directory, a test should be made as to whether it exists. When attempting to delete a directory, for instance, a run-time error is generated if the directory cannot be found. The Directory.Exists() method is used to make this determination.

Directory.Exists(path)

Figure 10-12. General format for checking for existence of a directory.

This method returns True or False depending on whether the supplied path, physical or relative, exists. Creating a new directory or deleting an existing directory, then, generally follows the coding models shown below.

If Not Directory.Exists("path") Then Directory.CreateDirectory("path")End If

If Directory.Exists("path") Then Directory.Delete("path", True|False)End IfListing 10-16. Code to test for an existing directory and to create or delete a directory.

Moving Directories

Directories and their contents can be moved from one location to another by using the Directory.Move() method.

Directory.Move(source path, destination path)

Figure 10-13. General format for moving directories.

The source path is the path to a current directory; the destination path is the path to the new target directory. Any new directories specified in the destination path are created if they do not exist. If the destination path already exists then an error results. Therefore, a test for an existing path should be made when moving directories.

With the following statements the c:\eCommerce\BookPictures folder is moved from along side the Databases folder to inside the Databases folder.

Page 151: Assignment Instructions

If Not Directory.Exists("c:\eCommerce\Databases\BookPictures") Then Directory.Move("c:\eCommerce\BookPictures", _ "c:\eCommerce\Databases\BookPictures")End IfListing 10-17. Code to move a directory.

Note that the destination path includes the new BookPictures folder that is to be created inside the Databases folder.

Copying Directories

The Directory class does not include a copy method to duplicate a folder structure. You can, though, create a new directory with the same name on a different path and then copy files individually with the File class (covered in the next tutorial).

File Access

The ASP.NET File class provides access to the files comprising an application. Through various methods supplied by this class scripts can create and delete files, move and copy files, read and write files, and view file attributes. Again, caution is advised when using File methods since files can be easily deleted without recourse to recovering them.

As in the case with Directory methods, file access is provided through the System.IO namespace which must be imported for the page.

Deleting Files

The File class provides the Delete() method to remove a file from a directory. Its general syntax is shown in Figure 10-14.

File.Delete(path)

Figure 10-14. General format for deleting a file.

The path is the physical path to a folder or it can be a relative path converted with Server.MapPath(). This path can be a literal string or a reference to a variable containing a path string.

File.Delete("c:\eCommerce\BookPictures\Picture.jpg")File.Delete(Server.MapPath("../BookPictures/Picture.jpg"))Listing 10-18. Code to delete a file.

Detecting Files

Before working with a file, a test should be made as to whether it exists. When attempting to delete a file, for instance, a run-time error is generated if the file does not exist. TheFile.Exists() method is used to make this determination.

Page 152: Assignment Instructions

File.Exists(path)

Figure 10-15. General format for detecting a file.

This method returns True or False depending on whether the identified file path exists. The code to delete a file, then, takes on the following general format.

If File.Exists("path") Then File.Delete("path")End If

Listing 10-19. Code to check for and delete an existing file.

Moving Files

Files can be moved from one location to another by using the File.Move() method whose general format is shown below.

File.Move(source path, destination path)

Figure 10-16. General format for moving a file.

The source path is the path to a current file; the destination path is the path to the new file. If the specified source path does not already exist, or if the destination pathalready exists, then an error results.

With the following statements the file Picture.gif is moved up a level from the c:\eCommerce\BookPictures folder to the c:\eCommerce folder. During the move the file is deleted from its original location.

If File.Exists("c:\eCommerce\BookPictures\Picture.jpg") _AND Not File.Exists("c:\eCommerce\Picture.jpg") Then

File.Move("c:\eCommerce\BookPictures\Picture.jpg", _ "c:\eCommerce\Picture.jpg")

End IfListing 10-20. Code to move a file.

Note that the destination path needs to include the name of the file being moved. If you use a different file name in the destination path, then the file is renamed when it is moved.

Copying Files

In a like manner, files can be copied from one folder to another using the File.Copy() method.

File.Copy(source path, destination path)

Figure 10-17. General format for copying a file.

Page 153: Assignment Instructions

The source path is the physical or relative path to the file being copied, and the destination path specifies a different path with the same or different file name. Unlike using theMove() method, the copied file is not deleted from its original location.

In the following example, a picture file is copied to its parent directory and is assigned a different file name in the destination directory.

If File.Exists("c:\eCommerce\BookPictures\Picture.jpg") _AND Not File.Exists("c:\eCommerce\Picture.jpg") Then

File.Copy("c:\eCommerce\BookPictures\Picture.jpg", _ "c:\eCommerce\Picture.jpg")

End IfListing 10-21. Code to copy a file.

Renaming Files

The ASP.NET File class does not provide a method for renaming files except when moving or copying them. However, you can use the Visual Basic Rename() function to do this.

Rename(source path, destination path)

Figure 10-18. General format for Visual Basic file Rename function.

The old and new file names must be part of the complete source and destination paths.

Rename("c:\eCommerce\BookPictures\Picture.jpg", _ "c:\eCommerce\BookPictures\MyPicture.jpg")

Listing 10-22. Code to rename a file.

File Attributes

Three informative file attributes can be retrieved with the File methods shown in Figure 10-19. The GetCreationTime() method returns the date and time at which a file was originally created; the GetLastAccessTime() method returns the date and time when the file was last accessed. The GetLastWriteTime() method returns the date and time at which the file was last written to the directory.

File.GetCreationTime(path)File.GetLastAccessTime(path)File.GetLastWriteTime(path)

Figure 10-19. General formats for retrieving file attributes.

Below, for example, this information is displayed for the BooksDB.mdb database.

Show BooksDB.mdb Attributes: 

Page 154: Assignment Instructions

Figure 10-20. Selected attributes of BooksDB.mdb database.<%@ Import Namespace="System.IO" %>

<SCRIPT Runat="Server">

Sub Show_Attributes (Src As Object, Args As EventArgs)

Attributes.Text &= "<b>Creation Date: </b>" & _ File.GetCreationTime("c:\eCommerce\Databases\BooksDB.mdb") & "<br/>" Attributes.Text &= "<b>Last Access Date: </b>" & _ File.GetLastAccessTime("c:\eCommerce\Databases\BooksDB.mdb") & "<br/>" Attributes.Text &= "<b>Last Write Date: </b>" & _ File.GetLastWriteTime("c:\eCommerce\Databases\BooksDB.mdb") & "<br/>" End Sub

</SCRIPT>

<form Runat="Server">

<b>Show BooksDB.mdb Attributes: </b><asp:Button text="Get Attributes" OnClick="Show_Attributes" Runat="Server"/><p><asp:Label id="Attributes" EnableViewState="False" Runat="Server"/></p>

</form>Listing 10-23. Code to report file attributes.

File size is not one of the attributes supplied by the File class. This property needs to be accessed through the FileInfo class, requiring you to create a FileInfo object prior to referencing its Length property on the specified file.

Show File Sizes: Figure 10-21. File sizes in Databases directory.<%@ Import Namespace="System.IO" %>

<SCRIPT Runat="Server">

Sub Show_File_Sizes (Src As Object, Args As EventArgs) Dim FileArray() As String FileArray = Directory.GetFiles("c:\eCommerce\Databases") Dim File As String For Each File in FileArray Dim FileName As String FileName = Replace(File, "c:\eCommerce\Databases\", "") Dim MyFileInfo As New FileInfo("c:\eCommerce\Databases\" & FileName) Size.Text &= FileName & " = " & MyFileInfo.Length & " bytes<br/>" Next End Sub

</SCRIPT>

<form Runat="Server">

<b>Show File Sizes: </b>

Page 155: Assignment Instructions

<asp:Button Text="Get Sizes" OnClick="Show_File_Sizes" Runat="Server"/><p><asp:Label id="Size" EnableViewState="False" Runat="Server"/></p>

</form>Listing 10-24. Code to report file sizes.

Reading and Writing Files

Among all the fancy data storage methods available under ASP.NET there is still comfort in working with plain vanilla text files. They are trusted friends. ASP.NET supplies file objects that make it very easy to read and write text files.

The System.IO namespace must be imported for working with files. This namespace gives access to the StreamReader and StreamWriter classes for reading and writing text files.

Reading Text Files

The StreamReader class provides several methods for reading text files which are summarized in Figure 10-22.

Dim FileReader As StreamReader

FileReader = File.OpenText(path)FileLine = FileReader.ReadLine()FileContents = FileReader.ReadToEnd()

FileReader.Close()

Figure 10-22. General formats for reading text files.

FileReader is a variable that is declared as a StreamReader object. The File class' OpenText() method then assigns the text file located on the path to this FileReader object. The StreamReader's ReadLine() method reads a single line of text from the file, not including the line-break character. The line of text is normally assigned to a string variable for script handling. The StreamReader's ReadToEnd() method reads all the contents of a file including line-break characters. This collection of all lines in the file can be assigned to a string variable. The StreamReader's Close() method is called after finishing with the file.

For the following examples the Books.txt file located in the c:\eCommerce\Databases directory is used. Its layout is shown below along with the first six records in the file.

ID,Type,Title,Author,Price,QtyDB111,Database,Oracle Database,K. Loney,69.99,10DB222,Database,Databases in Depth,C. J. Date,29.95,6DB333,Database,Database Processing,D. Kroenke,136.65,12DB444,Database,Access Database Design,S. Roman,34.95,25DB555,Database,SQL Server 2005,P. Debetta,29.99,0...

Figure 10-23. Contents of Books.txt file.

Page 156: Assignment Instructions

Reading an Entire Text File

One of the ways to read a text file is to use the ReadToEnd() method to capture the entire file at once. The file is read from beginning to end as a continuous stream of text, ignoring any line-break characters in the file. This method is used in the following example of reading the Books.txt and reporting its contents in a Label control. Note that the output does not differentiate the separate lines of text appearing in the file.

Read Books.txt File: 

Figure 10-24. Reading and displaying the Books.txt file as a continuous stream.<%@ Import Namespace="System.IO" %>

<SCRIPT Runat="Server">

Sub Read_File (Src As Object, Args As EventArgs)

Dim FileReader As StreamReader FileReader = File.OpenText("c:\eCommerce\Databases\Books.txt") FileContents.Text = FileReader.ReadToEnd() FileReader.Close()

End Sub

</SCRIPT>

<form Runat="Server">

<b>Read Books.txt File: </b><asp:Button Text="Read File" OnClick="Read_File" Runat="Server"/>

<p><asp:Label id="FileContents" EnableViewState="False" Runat="Server"/></p>

</form>Listing 10-25. Code to read and display the Books.txt file as a continuous stream.

First, FileReader is created as a StreamReader object; then the File.OpenText() method is used to connect to the file and assign it to the FileReader. This reader'sReadToEnd() method captures the entire file (minus any line-break characters) and assign it to the FileContents Label for display. An alternative, of course, is to read the file into a script variable for other types of processing. Lastly, the FileReader is closed.

Reading Lines of Text from a File

More often than not, it is necessary to differentiate the separate lines of text appearing in a file. A script, for instance, may process each line individually rather than read the entire file at once. The following example reads the Books.txt file a line at a time, thereby formatting its output to resemble the structure of the file.

Read Books.txt Lines: Figure 10-25. Reading and displaying the Books.txt file as individual lines.<%@ Import Namespace="System.IO" %>

Page 157: Assignment Instructions

<SCRIPT Runat="Server">

Sub Read_Lines (Src As Object, Args As EventArgs) Dim FileReader As StreamReader FileReader = File.OpenText("c:\eCommerce\Databases\Books.txt") Dim LineIn As String LineIn = FileReader.ReadLine() '-- read initial line While LineIn <> Nothing FileContents.Text &= LineIn & "<br/>" LineIn = FileReader.ReadLine() '-- read next line End While FileReader.Close()

End Sub

</SCRIPT>

<form Runat="Server">

<b>Read Books.txt Lines: </b><asp:Button Text="Read Lines" OnClick="Read_Lines" Runat="Server"/>

<p><asp:Label id="FileContents" EnableViewState="False" Runat="Server"/></p>

</form>Listing 10-26. Code to read and display the Personnel.txt file as individual lines.

Consider the general script structure for reading individual lines from a text file. The "look-ahead" reading technique is used to read an initial line from the file (into variable LineIn in this example). Then a While...End While loop is set up to process that line and read the next line at the bottom of the loop. Processing and reading continues until there are no more lines to read.

One of the ways to test for the end of the file is to test for a null line returned from the ReadLine() method. This test is employed in the above example by checking whether variableLineIn contains Nothing. This is an indication that no data lines remain in the file, providing the condition for ending the process-and-read loop.

Within the example loop, the contents of variable LineIn includes the string of text from a line in the file, absent its line-break character. An XHTML <br/> tag is appended to this text, which is concatenated to the output Label to display this individual text line.

End-of-File Markers

The keyword Nothing represents a null character, i.e., a blank line or no-more-lines in the file. If, however, a file contains legitimate blank lines, then another technique needs to be used to indicate the end of the file. Often, a special trailing record containing an end-of-file marker is used. This could be, for example, a line coded as "//EOF" (or any other special characters not likely to begin a legitimate record). Then the condition for ending the processing loop includes a test for this special string.

LineIn = FileReader.ReadLine()While LineIn <> "//EOF" ...process the line LineIn = FileReader.ReadLine()

Page 158: Assignment Instructions

End WhileListing 10-27. Testing for an end-of-file marker.

Splitting Fields into an Array

In the preceding example, entire lines of text are treated as a single entity. No consideration is given to the fact that lines are composed of individual data fields separated by commas. For certain applications, this may not be a consideration. For example, when duplicating a file by reading and writing entire lines of text, it is not important that individual data fields be distinguished. For other applications, however, it may be necessary to decompose the lines into their individual fields.

In the following example, lines from the Books.txt file are broken down into their component fields so they can be formatted for display in an XHTML table.

Read Books.txt Fields: Figure 10-26. Reading and displaying the data fields from Books.txt.<%@ Import Namespace="System.IO" %>

<SCRIPT Runat="Server">

Sub Read_File_Fields (Src As Object, Args As EventArgs)

Dim FileReader As StreamReader FileReader = File.OpenText("c:\eCommerce\Databases\Books.txt")

Dim LineIn As String Dim FieldArray() As String Dim Field As String

LineIn = FileReader.ReadLine() If Not LineIn = Nothing Then FieldArray = Split(LineIn, ",")

'-- Display table header FileContents.Text &= "<table border=""1"">" FileContents.Text &= "<tr style=""background-color:#F0F0F0"">" For Each Field in FieldArray FileContents.Text &= "<th>" & Field & "</th>" Next FileContents.Text &= "</tr>"

'-- Display table rows LineIn = FileReader.ReadLine() While LineIn <> Nothing FieldArray = Split(LineIn, ",")

'-- Display a table row FileContents.Text &= "<tr>" For Each Field in FieldArray FileContents.Text &= "<td>" & Field & "</td>" Next FileContents.Text &= "</tr>"

LineIn = FileReader.ReadLine() End While

Page 159: Assignment Instructions

FileContents.Text &= "</table>" End If FileReader.Close()

End Sub

</SCRIPT>

<form Runat="Server">

<b>Read Books.txt Fields: </b><asp:Button Text="Read File" OnClick="Read_File_Fields" Runat="Server"/>

<p><asp:Label id="FileContents" EnableViewState="False" Runat="Server"/></p>

</form>Listing 10-28. Code to read and display the data fields from Books.txt.

An input line is separated into its component fields by using the Visual Basic Split() function whose general format is shown below.

array = Split(string, [delimiter])

Figure 10-27. General format for Visual Basic Split() function.

This function tests for a delimiter character to break apart a text string and to assign the individual substrings to an array. The default delimiter is a blank space and does not need to be specified; otherwise, the delimiter character is coded as a string in the function. Then it is a matter of iterating this array to display the individual fields of data. Array elements can be identified by their indexes (array(0), array(1), array(2), etc.), or a For Each...Next loop can be established to iterate all elements of the array.

In the above example, each line from the file is split into array FieldArray and a For Each...Next loop accesses the individual array elements. For instance, when the first line of the file—containing field names—is read into variable LineIn, this string is split into separate elements of FieldArray, which is then iterated to produce a row of column headings for the output table.

... LineIn = FileReader.ReadLine() If Not LineIn = Nothing Then FieldArray = Split(LineIn, ",")

'Display table header FileContents.Text &= "<table border=""1"">" FileContents.Text &= "<tr style=""background-color:#F7F7F7"">" For Each Field in FieldArray FileContents.Text &= "<th>" & Field & "</th>" Next FileContents.Text &= "</tr>" ... End If

...Listing 10-29. Splitting an input string into an array to produce table headings.

The same technique is used to split individual lines from the file into an array of data fields which are iterated to produce individual table rows.

Page 160: Assignment Instructions

Writing Text Files

The StreamWriter provides methods for writing text files. These methods are summarized in Figure 10-28.

Dim FileWriter As StreamWriter

FileWriter = File.CreateText(path)FileWriter = File.AppendText(path)FileWriter.WriteLine(string)FileWriter.Write(string)

FileWriter.Close()

Figure 10-28. General formats for writing text files.

FileWriter is a variable that is declared as a StreamWriter object. The File class' CreateText() method assigns the text file located on the path to this FileWriter object. This method creates the text file if it does not exist, and it overwrites the text file if it does exist. The AppendText() method adds lines to an existing file. Again, the file is created if it does not exist.

The StreamWriter's WriteLine(string) method writes a string of text to the file and appends a line-break character. The Write(string) method writes a string of text without a line-break character. This method can be used several times in a row to compose a line in the file before finally writing a line-break character. It also can write a string representing the entire file contents at one time. The StreamWriter's Close() method is called after finishing with the file.

A simple example of creating a new text file and writing three lines to the file is shown in Listing 10-30.

Sub Write_File (Src As Object, Args As EventArgs)

Dim FileWriter As StreamWriter FileWriter = File.CreateText(c:\eCommerce\Databases\NewFile.txt") FileWriter.WriteLine("First line of file") FileWriter.WriteLine("Second line of file") FileWriter.WriteLine("Third line of file") FileWriter.Close()

End SubListing 10-30. Code to create a new text file.

Since the WriteLine() method is used, each line is written with a line-break character resulting in a file with three separate lines of text. If the file does not exist, it is created; if the file does exist, it is overwritten. If the subprogram uses the File.AppendText() method rather than the File.CreateText() method, then lines written to the file are added to the end of current lines in the file.

Creating Text Files

Page 161: Assignment Instructions

It is fairly easy to create a page application that permits users to create text files. All that is required is a text input area for entering lines of text along with a subprogram to append lines to the end of a file.

File Name:     

Input Line:      Line addedFigure 10-29. Creating and appending lines to a new text file.<%@ Import Namespace="System.IO" %>

<SCRIPT Runat="Server">

Sub Create_File (Src As Object, Args As EventArgs)

If FileName.Text <> "" Then If Not File.Exists("c:\eCommerce\Databases\" & FileName.Text) Dim NewFile As StreamWriter NewFile = File.CreateText("c:\eCommerce\Databases\" & FileName.Text) NewFile.Close() InputPanel.Visible = True Else FileExistsMsg.Text = "File already exists. Not created." End If End If

End Sub

Sub Add_To_File (Src As Object, Args As EventArgs)

Dim FileWriter As StreamWriter FileWriter = File.AppendText("c:\eCommerce\Databases\" & FileName.Text) FileWriter.WriteLine(FileLine.Text) FileWriteMsg.Text = "Line added" FileLine.Text = "" FileWriter.Close()

End Sub

</SCRIPT>

<form Runat="Server">

File Name: <asp:TextBox id="FileName" Runat="Server"/><asp:Button Text="Create File" OnClick="Create_File" Runat="Server"/><asp:Label id="FileExistsMsg" Runat="Server" EnableViewState="False" ForeColor="#FF0000"/><br/><br/>

<asp:Panel id="InputPanel" Visible="False" Runat="Server"> Input Line: <asp:TextBox id="FileLine" EnableViewState="False" Runat="Server"/> <asp:Button Text="Add To File" Runat="Server" OnClick="Add_To_File"/> <asp:Label id="FileWriteMsg" Runat="Server" EnableViewState="False" ForeColor="#FF0000"/></asp:Panel>

</form>Listing 10-31. Code to create a new text file.

Page 162: Assignment Instructions

A textbox is presented for the user to enter a new file name. If the file does not current exist, it is created and the text input area is displayed by making its enclosing Panel visible. Each input line is appended to those already added to the file.

Database Backup

File creation, reading, and writing methods are illustrated by adding a database backup feature for the BooksDB.mdb database. Creation of a text file of records from the Books table can be used to reload the table if necessary. The following example has its save and delete routines disabled.

Records from the Books table are input and formatted within a text area for visual verification of file contents. These records are then saved as text file Books.txt to thec:\eCommerce\Databases folder. The saved file can be visually verified by opening and displaying it.

BooksDB.mdb Backup

       

Figure 10-30. Reading, writing, and deleting a text file.

Page Layout

The page is formatted with a row of buttons along with two display areas. A multiline <asp:TextBox> holds the records from the Books table, formatted for text output. An<asp:Label> provides an area for displaying the saved file. Initially, this file display Label is hidden.

<h3>BooksDB.mdb Backup</h3>

<asp:Button Text="Load Table" OnClick="Load_Table" Runat="Server"/><asp:Button Text="Save Table" OnClick="Save_Table" Runat="Server"/><asp:Button Text="Show File" OnClick="Show_File" Runat="Server"/>

Page 163: Assignment Instructions

<asp:Button Text="Delete File" OnClick="Delete_File" Runat="Server"/><asp:Label id="Message" ForeColor="#FF0000" Runat="Server" EnableViewState="False"/><br/>

<asp:TextBox id="FileBox" Runat="Server" TextMode="MultiLine" Columns="70" Rows="20" Font-Size="8pt" Font-Name="Courier New" Wrap="False"/>

<asp:Panel id="FilePanel" Visible="False" Runat="Server"Width="510px" Height="300px" BorderStyle="Solid" BorderWidth="1px"ScrollBars="Auto">

<asp:Label id="FileDisplay" Runat="Server"/>

</asp:Panel>Listing 10-32. Server controls to read, write, and delete a text file.

Loading the Books Table

The "Load Table" button calls the Load_Table subprogram to retrieve all records from the Books table and to format them as text strings for writing to the display area. These formatted records are displayed in the multiline TextBox.

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

<SCRIPT Runat="Server">

Sub Load_Table (Src As Object, Args As EventArgs)

Dim DBConnection As OleDbConnection Dim DBCommand As OleDbCommand Dim DBReader As OleDbDataReader Dim SQLString As String Dim RecordString As String

DBConnection = New OleDbConnection( _ "Provider=Microsoft.Jet.OLEDB.4.0;" & _ "Data Source=" & Server.MapPath("../Databases/BooksDB.mdb")) DBConnection.Open() SQLString = "SELECT * FROM Books ORDER BY BookID" DBCommand = New OleDbCommand(SQLString, DBConnection) DBReader = DBCommand.ExecuteReader() FileBox.Text = "" While DBReader.Read() RecordString = "" RecordString &= DBReader("BookID") & "|" RecordString &= DBReader("BookType") & "|" RecordString &= DBReader("BookTitle") & "|" RecordString &= DBReader("BookAuthor") & "|" RecordString &= DBReader("BookDescription") & "|" RecordString &= DBReader("BookPrice") & "|" RecordString &= DBReader("BookQty") FileBox.Text &= RecordString & Chr(10) End While DBReader.Close()

Page 164: Assignment Instructions

DBConnection.Close() FileBox.Visible = True FilePanel.Visible = False Message.Text = "Table loaded"

End Sub

</SCRIPT>Listing 10-33. Script to display a text file.

The individual fields of a Books record are concatenated into variable RecordString to compose a display line. This variable is then appended to the TextBox display area. In order to visually and physically separate the data fields along a line, a special delimiter character is used. In this example, the "|" (pipe) character is written since this character is not likely to appear as an actual character in any of the fields. A line-break character—Chr(10)—is added to the end of the line to produce a line break in the TextBox and also in the text file that is written from the TextBox.

Writing the BooksBK.txt File

The formatted Books records appearing in the multiline TextBox are written to a BooksBK.txt backup file by clicking the "Save Table" button. This button click calls the Save_Tablesubprogram.

Sub Save_Table (Src As Object, Args As EventArgs)

If FileBox.Text <> "" Then Dim FileWriter As StreamWriter FileWriter = File.CreateText(Server.MapPath("../Databases/BooksBK.txt")) FileWriter.Write(FileBox.Text) FileWriter.Close() Message.Text = "File saved" Else Message.Text = "No records to save" End If

End SubListing 10-34. Script to write a text file.

The File.CreateText() method is used to overwrite an existing file or to create a new file if BooksBK.txt does not exist. The StreamWriter's Write() method is used to write the contents of the TextBox to the file all at one time. The resulting file is comprised of separate lines of text because the line-break character, Chr(10), is appended to each line when filling the TextBox. Then the file is closed. No file is written if the TextBox does not contain the records from the Books table.

Showing the BooksBK.txt File

The "Show File" button is clicked to retrieve the saved file and display it for visual verification. This button calls the Show_File subprogram.

Sub Show_File (Src As Object, Args As EventArgs)

If File.Exists(Server.MapPath("../Databases/BooksBK.txt")) Then Dim FileReader As StreamReader

Page 165: Assignment Instructions

Dim LineIn As String Dim FieldArray() As String Dim Item As String FileReader = File.OpenText(Server.MapPath("../Databases/BooksBK.txt")) FileDisplay.Text = "" LineIn = FileReader.ReadLine() While LineIn <> "" FieldArray = Split(LineIn, "|") For Each Item in FieldArray FileDisplay.Text &= Item & "<br/>" Next FileDisplay.Text &= "<br/>" LineIn = FileReader.ReadLine() End While FileReader.Close() FileBox.Visible = False FilePanel.Visible = True Else Message.Text = "File does not exist" End If End SubListing 10-35. Script to display a text file.

This subprogram uses the look-ahead method of reading through the BooksBK.txt file. Each line of the file is split into array FieldArray() so that individual fields can be handled separately. In this case, each field is written as a separate line to the FileDisplay output Label. After reading through and formatting the entire file, the FilePanel enclosing the Label is made visible for viewing the formatted file.

Deleting the BooksBK.txt File

Finally, subprogram Delete_File can be called to delete the BooksBK.txt file. This script uses the File.Delete() method of the File class to delete the file, after making sure that the file exists.

Sub Delete_File (Src As Object, Args As EventArgs)

If File.Exists(Server.MapPath("../Databases/BooksBK.txt")) Then File.Delete(Server.MapPath("../Databases/BooksBK.txt")) Message.Text = "File deleted" FileBox.Text = "" FileBox.Visible = True FileDisplay.Visible = False Else Message.Text = "File does not exist" End If

End SubListing 10-36. Script to delete a file.

Uploading Files

A very useful file access feature under ASP.NET is its file upload capability. You can present a form containing a text box and browse button for users to navigate their local PC directories to locate a file for uploading. By clicking a button users can copy the local file to a server directory.

Page 166: Assignment Instructions

This feature is illustrated by creating a file upload form for copying book pictures to the c:\eCommerce\BookPictures directory. The upload function is disabled, but you should get a good sense of how it works. The following form restricts uploads to JPG files no larger that 100 KB.

Picture Upload

Select file:  

 Figure 10-31. File uploading.

The <asp:FileUpload> Control

File uploading takes place through an <asp:FileUpload> control. This control produces a textbox along with a "Browse" button for navigating local computer directories to locate a file for uploading. The general format for this control is shown below.

<asp:FileUpload id="id" Runat="Server"/>

Figure 10-32. General format for <asp:FileUpload> control.

The control needs to be accompanied by a button to call a subprogram to copy the selected file to a server directory and to report on characteristics of the uploaded file. Code for the above control, its activation button, and its upload message Label is shown below.

<h3>Picture Upload</h3>

<b>Select file: </b><asp:FileUpload id="FileUp" Width="300px" Runat="Server"/><asp:Button Text="Upload" OnClick="Upload_File" Runat="Server"/><br/><br/><asp:Label id="Message" Text=" " ForeColor="#FF0000" Runat="Server" EnableViewState="False"/>Listing 10-37. Code for file uploading.

File Upload Properties and Methods

An <asp:FileUpload> control has several properties and methods to manage file uploading. These are listed in Figure 10-33.

Properties: Control.HasFile Control.FileName Control.PostedFile.ContentLength

Methods: Control.SaveAs("path")

Figure 10-33. File upload properties and methods.

Page 167: Assignment Instructions

Whether or not a file has been selected and appears in the input box is given by the control's HasFile property. This property returns True or False for a script to determine whether to proceed with file uploading.

The FileName property gives the name of the file selected for uploading. This is a reference only to the file name, not to the entire file path showing in the input area.

The PostedFile property is a reference to the file chosen for uploading. This file object's ContentLength property returns the size of the file in bytes.

The control's SaveAs("path") method writes the uploaded file to a server directory given by the path string. The FileName property is appended to this path to copy the file to the designated directory.

File Upload Scripting

The following script references these properties and methods to upload a picture file to the "c:\eCommerce\BookPictures\" directory. This subprogram performs tests on missing file selections, non-JPG file types, and file sizes in addition to uploading a file.

Sub Upload_File (Src As Object, Args As EventArgs)

If Not FileUp.HasFile Then '-- Missing file selection Message.Text = "Please choose a file to upload." ElseIf InStr(UCase(FileUp.FileName), ".JPG") = 0 Then '-- Selection of non-JPG file Message.Text = "You can upload only JPG files." ElseIf FileUp.PostedFile.ContentLength > 100000 Then '-- File too large Message.Text = "Uploaded file size must be less than 100 KB." Else '-- File upload FileUp.SaveAs(Server.MapPath("../BookPictures/") & FileUp.FileName) Message.Text = "<b>File Uploaded</b><br/>" Message.Text &= "File Name: " & FileUp.FileName & "<br/>" Message.Text &= "File Size: " & FileUp.PostedFile.ContentLength & _ " bytes<br/>" End If

End SubListing 10-38. Script for file uploading.

First, a check is made that a file has been chosen for uploading by testing the FileUp.HasFile property. If the textbox is empty, then a message is written to the Message Label and the script ends.

A second check is made to ensure that only JPG files are uploaded. The FileUp.FileName property is checked to make sure that it contains the substring ".JPG". If not, then a message is written to the Message control and the script ends. The file name is converted to upper-case characters for the test to ensure that both ".JPG" and ".jpg" files are accepted.

Page 168: Assignment Instructions

A third check is made on the size of the upload file as given by its FileUp.PostedFile.ContentLength property. Files that are larger than 100,000 bytes in length are rejected.

The file is saved to a server directory by appending the FileUp.FileName property to a directory path in calling the SaveAs() method. If a file of the same name appears in the directory, it is overwritten.

Managing Picture Files

A useful sidebar to file uploading is giving users the ability to view the file that is uploaded. This feature is provided in the following example which displays all files in thec:\eCommerce\BookPictures directory along with a button to delete any of the picture files.

Contents of BookPictures Directory

BU111.jpg BU222.jpg BU333.jpg DB111.jpg

DB222.jpg DB333.jpg DP111.jpg DP222.jpg

GR111.jpg GR222.jpg GR333.jpg GR444.jpg

Page 169: Assignment Instructions

GR555.jpg OS111.jpg OS222.jpg OS333.jpg

WB111.jpg WB222.jpg WB333.jpg WB444.jpg

Figure 10-34. Picture file display.

Pictures are displayed in an <asp:DataList> control whose code is shown below.

<asp:DataList id="PictureFiles" Runat="Server" RepeatColumns="4" RepeatDirection="Horizontal" CellPadding="3" GridLines="Both" OnItemCommand="Delete_File">

<ItemTemplate> <asp:Image ImageUrl='<%# "../BookPictures/" & Container.DataItem %>' Width="45" ImageAlign="Left" Runat="Server"/> <asp:Label Text='<%# Container.DataItem %>' Runat="Server"/><br/> <asp:Button Text="Delete" Runat="Server" Font-Size="7pt" CommandName='<%# Container.DataItem %>'/> </ItemTemplate>

</asp:DataList>Listing 10-39. Code for DataList display of picture files.

Binding Files to a Display Control

Files cannot be bound to a DataList or to other display controls. There is no data source control for file binding as there is for database binding. An intermediate step is needed, then, to import file names into a data structure that, in turn, can be bound to the control. A Visual Basic array is a common way to store file names that can serve as a binding source for a display control.

In the following script that accompanies the example DataList control, a list of file names is taken from the c:\eCommerce\BookPictures directory and assigned to an array. This array is then

Page 170: Assignment Instructions

bound to the DataList as properties for the Image, Label, and Button controls to display and delete these files. This binding is coded in a separate Bind_Picture_Filessubprogram since it takes place on two different occasions. File names are bound to the control when the page first loads; rebinding takes place after a file is deleted.

<%@ Import Namespace="System.IO" %>

<SCRIPT Runat="Server">

Sub Page_Load

If Not Page.IsPostBack Then Bind_Picture_Files End If

End Sub

Sub Bind_Picture_Files

Dim FileArray() As String FileArray = Directory.GetFiles(Server.MapPath("../BookPictures"), "*.jpg")

Dim i As Integer For i = 0 to UBound(FileArray) FileArray(i) = Replace(FileArray(i), Server.MapPath("../BookPictures/"), "") Next

PictureFiles.DataSource = FileArray PictureFiles.DataBind()

End Sub

Sub Delete_File (Src As Object, Args As DataListCommandEventArgs)

File.Delete(Server.MapPath("../BookPictures/") & Args.CommandName) Bind_Picture_Files

End Sub

</SCRIPT>Listing 10-40. Script to bind picture files to DataList.

Accessing Directories and Files

In the above Bind_Picture_Files subprogram, an array named FileArray() is declared and becomes the target for the Directory.GetFiles() method. This method produces an array of paths to the files in the BookPictures directory, restricted to files with the ".jpg" extension.

Once an array of data values exists, it can be bound to a display control. In the current example, however, only the file names are needed, not the full paths appearing inFileArray(). Therefore, the file paths are converted to file names by iterating the array and replacing the path portion of the strings with a null character. This iteration produces an array of file names for all the .jpg files in the BookPictures directory.

Binding an Array to a Display Control

Page 171: Assignment Instructions

An array is bound to an output control by assigning it as the DataSource property of the control and calling the control's DataBind() method. Binding of array values takes a binding expression in the following format.

<%# Container.DataItem %>

Figure 10-35. Binding expression for array values.

The Container.DataItem expression is the array equivalent to the Eval() and Bind() expressions used in database binding. For each element in the array, a row is produced in the output control.

In the present example, the 30 elements in FileArray() produce 30 ItemTemplates in the DataList. FileArray() values (picture file names) are bound to the ImageUrl property of the Image controls, to the Text property of the Label controls, and to the CommandName property of the "Delete" Buttons. This ItemTemplate portion of the DataList is repeated in the following listing.

<ItemTemplate> <asp:Image ImageUrl='<%# "../BookPictures/" & Container.DataItem %>' Width="45" ImageAlign="Left" Runat="Server"/> <asp:Label Text='<%# Container.DataItem %>' Runat="Server"/><br/> <asp:Button Text="Delete" Font-Size="7pt" Runat="Server" CommandName='<%# Container.DataItem %>'/></ItemTemplate>Listing 10-41. Code to bind picture files to a DataList.

Deleting Picture Files

A DataList, like other bound controls such as a GridView or DetailsView, recognizes Command events to which subprograms can be attached. Notice in the following partial listing that the DataList traps for this event with its OnItemCommand event handler which calls subprogram Delete_Picture when a Command event is triggered. This event takes place whenever a DataList button is clicked, in this case when the "Delete" Button associated with a picture is clicked.

Sub Delete_File (Src As Object, Args As DataListCommandEventArgs)

File.Delete(Server.MapPath("../BookPictures/") & Args.CommandName) Bind_Picture_Files

End Sub

<asp:DataList id="PictureFiles" Runat="Server" OnItemCommand="Delete_File"> ...Listing 10-42. Code to raise a Command event to call a subprogram to delete a picture file.

The subprogram receives the button's CommandName through its DataListCommandEventArgs argument list. Since button CommandNames are the names of picture files, the subprogram receives the name of a file to delete through its Arg.CommandName argument. This argument is used in the Directory class' File.Delete() method, supplying the full path to the file to delete. Since the contents of the BookPictures directory change with a deletion, the Bind_Picture_Files subprogram is called to re-bind the directory listing to the DataList.

Page 172: Assignment Instructions

Sending Email

A recurring need for many Web sites is to generate and send email messages. A commercial site, for instance, normally sends order confirmations to customers who purchase products or services. Sites with memberships routinely correspond with members to keep them up to date on site policies and features. In other cases, automated email feedback is provided when inquiries are made. There are numerous occasions in which Web pages need to handle routine email correspondence to relieve individuals from having to manually produce email themselves.

ASP.NET pages can be configured to send email through the server's SMTP (Simple Mail Transfer Protocol) service. This is, as the name implies, a limited email service; however, it is sufficient for generating and sending automated emails from Web pages. The SMTP service is included as a component of Microsoft Windows Server and XP Professional. It may have to be installed and initiated if it were not part of a default installation.

If your are running ASP.NET under XP Professional on your desktop computer (localhost), you may need to configure it to permit relay of email messages. Perform the following steps:

1. Open IIS Administrative Tools.2. Stop Default SMTP Virtual Server service.3. Open properties window of Default SMTP Virtual Server.4. Click "Access" tab and click "Relay..." button.5. Click "Only the list below" button and add the single computer at IP address 127.0.0.1.6. Click "OK" buttons to close "Access" tabs and properties window.7. Restart Default SMTP Virtual Server service.

Web pages that produce automated email messages must import the System.Net.Mail namespace. This namespace contains classes used to send electronic mail to an SMTP server for delivery.

<%@ Import Namespace="System.Net.Mail" %>Listing 10-43. Importing the System.Net.Mail namespace to produce email messages.

Three classes are included in the namespace. The MailMessage class represents the content of a mail message. The SmtpClient class transmits email to the SMTP host server designated for mail delivery. Email attachments are sent using the Attachment class.

The SmtpClient Class

All email messages are sent through an SmtpClient object. The general format for creating this object is given in Figure 10-36.

Dim EmailClient As SmtpClientEmailClient = New SmtpClient(host)

or

Dim EmailClient = New SmtpClient(host)

Figure 10-36. General format to create an SmtpClient object.

Page 173: Assignment Instructions

The host is a string identifying the host server through which SMTP services are provided for sending email. In most cases, these services reside on the same server that hosts the Web page producing the message. Therefore, the string is typically "localhost".

The SmtpClient object supplies the Send() method through which email messages are sent through the host server. The easiest and most direct way to produce and send an email message is through four string parameters that can be coded in the Send() method.

EmailClient.Send(from, to, subject, body)

Figure 10-37. General format to create an SmtpClient object.

In this format, from is the "From" address, to is the "To" address, subject is the "Subject" line, and body is the "Body" of the message. All four parameters must be present and must appear in the order given.

The following example script is run on a button click to compose and send an email through the SmtpClient. Here, the four Send() parameters are declared and valued as hard-coded strings.

<%@ Import Namespace="System.Net.Mail" %>

<SCRIPT Runat="Server">

Sub Send_Email (Src As Object, Args As EventArgs)

Dim MailFrom As String = "[email protected]" Dim MailTo As String = "[email protected]" Dim MailSubject As String = "Subject Line." Dim MailBody As String = "This is the message." Dim MailClient = New SmtpClient("localhost") MailClient.Send(MailFrom, MailTo, MailSubject, MailBody)

End Sub

</SCRIPT>

<form Runat="Server">

<asp:Button Text="Send Email" OnClick="Send_Email" Runat="Server"/>

</form>Listing 10-44. Composing and sending a simple email.

This technique makes it fairly easy to compose and send an email with the four items collected from a form. You can use the following example to send an email to yourself.

Send Email

To:

From:

Subject:

Page 174: Assignment Instructions

Message:

 Figure 10-38. Sending an email through a form.<%@ Import Namespace="System.Net.Mail" %>

<SCRIPT Runat="Server">

Sub Send_Email (Src As Object, Args As EventArgs) If MailFrom.Text <> "" _ AND MailTo.Text <> "" _ AND Subject.Text <> "" _ AND Body.Text <> "" Then Try Dim MailClient = New SmtpClient("localhost") MailClient.Send(MailFrom.Text, MailTo.Text, Subject.Text, Body.Text) MailMsg.Text = "Email sent" Catch MailMsg.Text = "Error in sending email!" End Try End If

End Sub

</SCRIPT>

<form Runat="Server">

<h3>Send Email</h3>

<table border="1" style="border-collapse:collapse"><tr> <td style="font-weight:bold; background-color:#E0E0E0">To:</td> <td><asp:TextBox id="MailTo" Width="200" Runat="Server"/></td></tr><tr> <td style="font-weight:bold; background-color:#E0E0E0">From:</td> <td><asp:TextBox id="MailFrom" Width="200" Runat="Server"/></td></tr><tr> <td style="font-weight:bold; background-color:#E0E0E0">Subject:</td> <td><asp:TextBox id="Subject" Width="300" Runat="Server"/></td></tr><tr> <td style="font-weight:bold; background-color:#E0E0E0">Message:</td> <td><asp:TextBox id="Body" Runat="Server" TextMode="MultiLine" Rows="5" Width="300" Font-Name="Arial"/></td></tr></table><br/><asp:Button Text="Send Email" OnClick="Send_Email" Runat="Server"/><asp:Label id="MailMsg" ForeColor="#FF0000" Runat="Server"/>

Page 175: Assignment Instructions

</form>Listing 10-45. Sending an email through a form.

The MailMessage Class

A more formal way of composing messages sent through an SmtpClient, and one that provides more flexibility to define message components, is through a MailMessage object. This object defines the sender, recipients, subject, and message body required by the SmtpClient, which then sends the final MailMessage. A MailMessage object is created using the formats shown in Figure 10-39.

Dim MailMessage As MailMessageMailMessage = New MailMessage

or

Dim MailMessage = New MailMessage

Figure 10-39. General format to create an MailMessage object.

Once a MailMessage object is created and its sender, recipients, subject, body, and other properties are set, the message is sent through the SmtpClient.

Dim SmtpClient = New SmtpClient("localhost")Dim MailMessage = New MailMessage

...set MailMessage sender, recipients, subject, and body properties

SmtpClient.Send(MailMessage)Listing 10-46. Sending a MailMessage through an SmtpClient.

Properties that can be defined for a MailMessage include From (sender address), ReplyTo (alternate sender address), To (recipients' addresses), CC (carbon-copy recipients' addresses), Bcc (blind carbon-copy recipients' addresses), Subject (subject line), Body (body of the message), and Attachments (attachments to the message).

Creating Email Addresses

Email addresses associated with a MailMessage must be declared as MailAddress objects. These objects are populated with sender and recipient email addresses and then assigned to the MailMessage as From and To properties. Different techniques are used to assign these two properties.

The From Property

A MailMessage's From property is set by creating a MailAddress object, assigning an address string to the object, and then assigning this object to the MailMessage's Fromproperty. The formats shown in Figure 10-40 can be used.

Page 176: Assignment Instructions

Dim FromAddress As MailAddressFromAddress = New MailAddress(address)MailMessage.From = FromAddress

or

Dim FromAddress = New MailAddress(address)MailMessage.From = FromAddress

or

MailMessage.From = New MailAddress(address)

Figure 10-40. General formats to create a MailAddress for assignment to a MailMessage's From property.

The following example should help clarify. Here, a MailMessage (MyEmail) is created for sending an email through an SmtpClient (MyClient). A new MailAddress (FromAddress) is created for the sender of the message, supplying an email address string. This address is assigned as the MailMessage's From property prior to sending the email.

Dim MyEmail = New MailMessageDim MyClient = New SmtpClient("localhost")

Dim FromAddress = New MailAddress("[email protected]")MyEmail.From = FromAddress...

MyClient.Send(MyEmail)Listing 10-47. Code to create a MailAddress for assignment to a MailMessage's From property.

This same technique is used to set the optional ReplyTo property of the message. The ReplyTo property supplies an email address other than the From address to use to reply to the message.

The To Property

A MailMessage's To property is set by creating a MailAddress object, assigning an address string to the object, and then adding (not assigning) this object to the MailMessage'sTo property. Creating a To address uses the same formats as for assignment to the From property; the difference is in how the address is associated with the MailMessage.

Dim ToAddress As MailAddressToAddress = New MailAddress(address)MailMessage.To.Add(ToAddress)

or

Dim ToAddress = New MailAddress(address)MailMessage.To.Add(ToAddress)

or

Page 177: Assignment Instructions

MailMessage.To.Add(New MailAddress(address))

Figure 10-41. General formats to create a MailAddress to add to a MailMessage's To collection.

A MailMessage's To property is a collection of email addresses to which is added a newly declared address through its Add() method. Again, an example should help clarifiy by continuation of the previous script.

Dim MyEmail = New MailMessageDim MyClient = New SmtpClient("localhost")

Dim FromAddress = New MailAddress("[email protected]")MyEmail.From = FromAddress

Dim ToAddress = New MailAddress("[email protected]")MyEmail.To.Add(ToAddress)...

MyClient.Send(MyEmail)Listing 10-48. Code to create a MailAddress to add to a MailMessage's To collection.

As you can see, any number of recipients can be designated by creating a MailAddress for each and adding the address to the MailMessage's To collection. It may be likely that a list of email recipients is stored in a database. In this case, you can retrieve the email field and create multiple MailAddresses that are added to a MailMessage. A skeletal script to perform this processing is shown below.

Dim MyEmail = New MailMessageDim MyClient = New SmtpClient("localhost")

Dim FromAddress = New MailAddress("[email protected]")MyEmail.From = FromAddress

'-- Create MailAddresses from a databaseCreate Connection object and open database Create Command object and issue SQL commandCreate DataReader objectWhile DataReader.Read()

Dim ToAddress = New MailAddress(DataReader("EmailField")) MyEmail.To.Add(ToAddress)

End WhileClose DataReaderClose Connection...

MyClient.Send(MyEmail)Listing 10-49. Code to create multiple MailAddresses from a database.

The CC and Bcc Properties

Similar methods are used to declare one or more carbon-copy (CC property) and blind carbon-copy (Bcc property) recipients of a MailMessage. That is, these properties, like the Toproperty, have Add() methods to include lists of recipients.

Dim MyEmail = New MailMessage

Page 178: Assignment Instructions

Dim MyClient = New SmtpClient("localhost")

Dim FromAddress = New MailAddress("[email protected]")MyEmail.From = FromAddress

Dim ToAddress = New MailAddress("[email protected]")MyEmail.To.Add(ToAddress)

Dim CCAddress = New MailAddress("[email protected]")MyEmail.CC.Add(CCAddress)

Dim BccAddress = New MailAddress("[email protected]")MyEmail.Bcc.Add(BccAddress)...

MyClient.Send(MyEmail)Listing 10-50. Code to create MailAddresses for assignment to a MailMessage's CC and Bcc properties.

The Subject Property

A MailMessage has a Subject property representing the subject line of an email message. A subject-line string is assigned directly to this property using the following formats.

Dim SubjectLine As StringSubjectLine = "subject string"MailMessage.Subject = SubjectLine

or

Dim SubjectLine As String = "subject string"MailMessage.Subject = SubjectLine

or

MailMessage.Subject = "subject string"

Figure 10-42. General formats to create a MailMessage subject line.Dim MyEmail = New MailMessageDim MyClient = New SmtpClient("localhost")

Dim FromAddress = New MailAddress("[email protected]")MyEmail.From = FromAddress

Dim ToAddress = New MailAddress("[email protected]")MyEmail.To.Add(ToAddress)

Dim CCAddress = New MailAddress("[email protected]")MyEmail.CC.Add(CCAddress)

Dim BccAddress = New MailAddress("[email protected]")MyEmail.Bcc.Add(BccAddress)

MyEmail.Subject = "This is the subject line"...

MyClient.Send(MyEmail)Listing 10-51. Code to create a MailMessage subject line.

Page 179: Assignment Instructions

The Body Property

A MailMessage has a Body property representing the main body of an email message. A message string is assigned directly to this property using the following formats.

Dim MailBody As StringMailBody = "text and XHTML string"MailMessage.Body = MailBody

or

Dim MailBody As String = "text and XHTML string"MailMessage.Body = MailBody

or

MailMessage.Body = "text and XHTML string"

Figure 10-43. General formats to create a MailMessage's Body content.Dim MyEmail = New MailMessageDim MyClient = New SmtpClient("localhost")

Dim FromAddress = New MailAddress("[email protected]")MyEmail.From = FromAddress

Dim ToAddress = New MailAddress("[email protected]")MyEmail.To.Add(ToAddress)

Dim CCAddress = New MailAddress("[email protected]")MyEmail.CC.Add(CCAddress)

Dim BccAddress = New MailAddress("[email protected]")MyEmail.Bcc.Add(BccAddress)

MyEmail.Subject = "This is the subject line"

MyEmail.Body = "This is the content of the email message."

MyClient.Send(MyEmail)Listing 10-52. Code to create a MailMessage's message body.

Finally, the point has been reached where all required components of a MailMessage have been declared and assigned to the MailMessage object. Now it is a matter of sending the MailMessage through the SmtpClient.

It is likely that the body of the message will contain more text than can be coded in a single string assignment. You can, though, concatenate any number of text strings to the Bodyproperty: MailMessage.Body &= "next text string", etc. A better way, however, is to build the body of the message in a separate string variable and assign the variable to theBody property prior to sending the message.

Dim BodyString As String = ""

BodyString &= "First sentence of message. "BodyString &= "Second sentence of message. "BodyString &= "Third sentence of message. "BodyString &= "Last sentence of message."

Page 180: Assignment Instructions

MailMessage.Body = BodyStringListing 10-53. Composing a message string for assignment to the MailMessage's Body property.

Message Formatting

Unless specified otherwise, the email message is sent as ASCII text, without formatting. Often you will wish to produce messages that are formatted as HTML pages, including XHTML tags to structure and style the message. This is easily accomplished by including tags inside the message strings and by setting the MailMessage's IsBodyHtml="True"property.

Dim BodyString As String = ""

BodyString &= "<h3>My Message</h3>"BodyString &= "<p>"BodyString &= "First line of message.<br/>"BodyString &= "Second line of message.<br/>"BodyString &= "Third line of message.<br/>"BodyString &= "Last line of message.<br/>"BodyString &= "</p>"

MailMessage.Body = BodyStringMailMessage.IsBodyHtml = "True"Listing 10-54. Composing an HTML-formatted message string for assignment to the MailMessage's Body property.

Message Priority

You can set a MailMessage's priority by assigning one of three priority levels to its Priority property. The levels are MailPriority.High, MailPriority.Normal, andMailPriority.Low.

MailMessage.Priority = MailPriority.HighListing 10-55. Setting a MailMessage's priority level.

Message Attachments

One or more attachments can be added to a MailMessage. First, an Attachment object is created giving the path to the file to be attached to the message. Then, this Attachmentobject is added to the MailMessage's Attachments collection. The path to the attachment file is its physical path on the server. You can use the Server.MapPath() method to convert a relative path to a physical path.

Dim AttachmentFile As AttachmentAttachmentFile = New Attachment(path)MailMessage.Attachments.Add(AttachmentFile)

or

Dim AttachmentFile = New Attachment(path)

Page 181: Assignment Instructions

MailMessage.Attachments.Add(AttachmentFile)

or

MailMessage.Attachments.Add(New Attachment(path))

Figure 10-44. General formats to create an attachment for a MailMessage.

In the following example, a Picture.gif file located in the same directory as the page sending the email is added as an attachment.

Dim MyAttachment = New Attachment(Server.MapPath("Picture.gif"))MailMessage.Attachments.Add(MyAttachment)Listing 10-56. Code to add an attachment to a MailMessage.

As you can infer from the Add() method, any number of attachments of any file types can be added to the Attachments collection to be sent along with the email message.

Sending Email with Attachments

When creating an email application for Web users, it is often the case that you need to make provision for users to attach their own files to an email. That is, the user selects one or more files from their local computers for appending to the email. In this case, it is necessary to first upload the files to the Web server (where they are accessible by ASP.NET) and then attach these files to the email message.

In the following example, a file upload feature is added to an email composition form. You can use this form to email yourself a message with attachments.

Send Email with Attachments

To:

From:

Subject:

Message:

Attachments:  

 

Figure 10-45. Sending an email with attachments through a form.

Page 182: Assignment Instructions

In the following code listing, an <asp:FileUpload> control is added to the form along with a Label display area to echo the names of the files selected for upload and attachment to the email message.

<h3>Send Email with Attachments</h3>

<table id="EmailTable" border="0" style="border-collapse:collapse"><tr> <th>To:</td> <td><asp:TextBox id="MailTo" Width="200" Runat="Server"/></td></tr><tr> <th>From:</td> <td><asp:TextBox id="MailFrom" Width="200" Runat="Server"/></td></tr><tr> <th>Subject:</td> <td><asp:TextBox id="Subject" Width="300" Runat="Server"/></td></tr><tr> <th>Message:</td> <td><asp:TextBox id="Body" Runat="Server" TextMode="MultiLine" Rows="5" Width="300" Font-Name="Arial"/></td></tr><tr> <th>Attachments:</td> <td><asp:FileUpload id="FileUp" Width="250px" Runat="Server"/> <asp:Button Text="Attach" OnClick="Upload_File" Runat="Server"/><br/> <asp:Label id="Attachments" Width="300" Runat="Server"/> </td></tr></table><br/><asp:Button Text="Send Email" OnClick="Send_Email" Runat="Server"/><asp:Label id="MailMsg" ForeColor="#FF0000" Runat="Server"/>Listing 10-57. Code to upload files as email attachments.

Uploading Files for Attachment

When the "Attach" button is clicked, subprogram Upload_File is called to upload the chosen file to the server. Here it is assumed that a directory named Attachments has been created for storing files uploaded as email attachments. The file name is concatenated to the display Label as visual feedback that the file has been uploaded. Any number of files can be uploaded.

<%@ Import Namespace="System.Net.Mail" %><%@ Import Namespace="System.IO" %>

<SCRIPT Runat="Server">

Sub Upload_File (Src As Object, Args As EventArgs)

If FileUp.HasFile Then FileUp.SaveAs(Server.MapPath("../Attachments/") & FileUp.FileName) Attachments.Text &= FileUp.FileName & "<br/>" End If

End Sub...

Page 183: Assignment Instructions

</SCRIPT>Listing 10-58. Script to uploaded files to Attachments directory.

Adding Files to the Attachments Collection

The "Send Mail" button calls subprogram Send_Mail to send the composed message along with any files uploaded to the Attachments directory. A new MailMessage and SmtpClient object are created, with information from the form assigned as the From, To, Subject, and Body properties of the message. Then, the uploaded files are assigned as attachments to the message.

<%@ Import Namespace="System.Net.Mail" %><%@ Import Namespace="System.IO" %>

<SCRIPT Runat="Server">

Sub Upload_File (Src As Object, Args As EventArgs) ...End Sub

Sub Send_Email (Src As Object, Args As EventArgs)

If MailFrom.Text <> "" _ AND MailTo.Text <> "" _ AND Subject.Text <> "" _ AND Body.Text <> "" Then

Dim MailClient = New SmtpClient("localhost") Dim Email = New MailMessage Dim FileArray() As String Dim FileItem As String Try Email.From = New MailAddress(MailFrom.Text) Email.To.Add(New MailAddress(MailTo.Text)) Email.Subject = Subject.Text Email.Body = Body.Text Email.IsBodyHtml = True If Attachments.Text <> "" Then FileArray = Directory.GetFiles(Server.MapPath("../Attachments/")) For Each FileItem in FileArray Email.Attachments.Add(New Attachment(FileItem)) Next End If MailClient.Send(Email) MailMsg.Text = "Email sent" Catch MailMsg.Text = "Error in sending email!" End Try If Attachments.Text <> "" Then Email.Attachments.Dispose() FileArray = Directory.GetFiles(Server.MapPath("../Attachments/")) For Each FileItem in FileArray File.Delete(FileItem) Next

Page 184: Assignment Instructions

End If MailFrom.Text = "" MailTo.Text = "" Subject.Text = "" Body.Text = "" Attachments.Text = "" End If

End Sub

</SCRIPT>Listing 10-59. Script to attach uploaded files to an email message.

The Directory.GetFiles() method retrieves all files in the Attachments directory (thus, the need for the System.IO namespace). The list of files is iterated and each file, in turn, becomes a new Attachment object in the MailMessage's Attachments collection.

Email.Attachments.Add(New Attachment(FileItem))

Finally, the email is sent through the SmtpClient. Notice that creation and sending of the email is enclose in a Try...Catch structure in order to trap any errors associated with sending the email.

Deleting Attachment Files

Once the email has been sent, there is no reason to retain the files previously uploaded to the Attachments directory. So, these files are deleted before exiting the subprogram. It is important to remember, however, that the MailMessage's Attachments collection is still associated with the MailMessage object, even after the email is sent. If an attempt is made to delete the files while this relationship still exists, an error is generated to the effect that the files cannot be deleted because they are still in use. Therefore, it is necessary apply the Dispose() method of the Attachments collection to release it for deletion.

Email.Attachments.Dispose()

Now, the File.Delete() method can be used to delete all files in the Attachments directory. Also, all of the form fields are blanked in preparation for a new message.

In this example, a single Attachments directory is used for all uploads and attachments. This works as long as a single user at a time sends an email. With multiple users, however, files belonging to different people are intermixed and you may end up deleting files that have been uploaded but not yet attached and mailed. A better solution is to create different attachment directories, as needed and in script, to service different users.

Software Components

Recall from previous discussions that a Web page can be characterized as implementing three application levels, or tiers. These tiers are

user interface - the Web page as it appears on-screen to the user and through which the user interacts with the page;

Page 185: Assignment Instructions

application (business) logic - page processing as encapsulated within subprograms and functions organized within scripts; and

data access - retrieval and updating of information residing in external data stores such as databases and files.

Up to this point, Web page development has been viewed as the detailed coding of all tasks—of all application tiers—necessary to carry out page processing. Server controls and XHTML code are placed on a Web page to represent the visual, on-screen appearance of the page and to supply mechanisms through which the user interacts with the page. Visual Basic subprograms and functions are coded within the script section of the page to carry out processing tasks in response to user requests. Various server controls and scripts issue SQL commands or activate stored procedures to handle data access chores surrounding the page. In other words, everything needed to carry out page processing is encapsulated on that very same page.

Web Development Issues

For small development projects it is not unusual to find all three tiers encapsulated on a single page. There is convenience in doing so; and in small shops it is likely that a single person serves as the page designer, the application programmer, and the database administrator, all expertise being confined to a single developer to create single pages containing multiple tasks spanning the three application tiers.

As Web-based applications become more complex, however, it becomes increasingly difficult to develop and maintain pages that encapsulate all three tiers. In the first place, and especially in larger shops, different groups of developers may have separate responsibilities for the tiers. One group of layout designers and graphic artists may deal exclusively with interface design issues, a second group of system designers and programmers may deal with the programming logic behind the application, and a third group of database administrators and specialists may have control over data access permissions and methods. Difficulties arise in coordinating the work of these multiple groups when producing Web pages that includes all three requirements.

A second problem that arises in larger and more complex applications is the duplication of code that results. It is often the case, for instance, that common data access and display methods appear multiple times across multiple pages. Several pages may make the same basic call to the same database and issue the same general SQL request for information to display. In other cases, the same application logic may be applied across multiple pages. In all of these cases the same basic code appears on many different pages. Not only do issues arise in faithfully reproducing the same code on multiple pages, but when changes are made in data access methods or business requirements, all instances of this code must be tracked down and faithfully changed, not a small task, even, on small projects.

The issue, then, is how best to deal with these complicating effects of larger projects and more complex code, of how best to manage the scalability of projects that grow from simple collections of all-inclusive Web pages to systems of multi-tiered applications. The solution, of course, is not to produce larger and more complex Web pages that only exacerbate the problems. Rather, the need is to decompose Web pages into smaller, more independent parts.

Developing Tier-Based Components

One of the objectives in Web page development, then, is to move away from this practice of including all processing details on all pages. The goal, instead, is to create separate

Page 186: Assignment Instructions

software components, separate files even, which independently carry out tasks surrounding the three basic tiers of an application.

For instance, the Web page itself—the .aspx file—contains the server controls and XHTML code to produce the screen display for the user interface. The processing scripts to implement the application logic for the page are in separate files—in .vb Visual Basic files—to which processing tasks are referred from the Web page. The data access methods to retrieve or update database information for the page are in a third file, again, activated by referrals from the Web page or from script files.

A Web page is, in effect, a bringing together of processing components that physically reside in separate files. Web page development, then, becomes a process of calling on and coordinating the work of these specialized components to carry out their processing tasks to support the particular task a hand.

Figure 11-1. Composing Web pages from processing components.

As implied in Figure 11-1, the Web page, the .aspx page, is the container for the user interface. Here, server controls and XHTML code appear to represent the public face of the application. Also, code is included to call upon application logic components to perform page processing. These components may be separate Visual Basic applications packaged in separate .vb files that are imported to the page when the application is put into production. In addition, server controls to perform data access call up external data access components, themselves packaged in separate files to return sets of records for display or to update databases with parameters passed to them. Application logic components, as well, may call upon data access components to perform database access associated with the business logic of the application. Thus, a Web page originates as a user interface page that calls upon external components to perform its business logic and data access tasks.

Three main advantages derive from component-based development. First, this approach means that separate individuals and separate groups can ply their trades in their separate specialties. The page designer can work on page layout and design, the programmer can implement and program business logic, and the database specialist can deal with database access, all without having to be directly involved in the production of a particular Web page. Plus, the page developer brings these specialties together on a single page without having to know the details of each.

Page 187: Assignment Instructions

Second, Web pages are much easier to maintain. If, for example, there is a change in database design or in the location of, or access permissions to, a database, these changes need only be made in the data access component, not to the Web page itself. Components are "black boxes" that hide their implementation details. Web pages call on these components to do their work, but are not concerned with the processing details inside these components. Therefore, major changes may take place in the components themselves, but have no impact on the pages that utilize these components.

Third, components promote sharing. Common application logic or common data access techniques can be called from all pages needing that processing without having to code that processing on all pages. There is, in effect, a separate library of processing routines that can be called upon, again, without having to know their implementation details.

Implementing Tier-Based Components

From an implementation standpoint, development of generalized, independent software components is of two varieties. What is known as code-behind development removes the application logic from Web pages and packages it into individual software classes, with public methods that can be called from Web pages to perform required processing. These classes are encapsulated as Visual Basic files composing a library of classes that can be called from any Web page. Page scripting, then, is reduced to the minimal code necessary to import classes, call methods, and handle display of any information returned from these calls.

A second variety of component development relates to database access. Again, external routines are involved to issue selection and updating requests to the database, freeing the Web page of these tasks. Web page controls and scripts issue requests for actions but do not work with the detailed code—the database connections, the SQL statements, or the procedure calls—against the database itself. These "physical" access details are encapsulated in the external routines; the page itself makes only "logical" requests for actions to take place, supplying the data or managing the results as it relates to the user interface.

In combination, application logic components and data access components comprise the middleware of Web applications. They stand between and facilitate connections between the front-end user interface and the back-end data stores that comprise Web-based systems. The following tutorials provide a brief introduction to creating Web applications based on software components.

Code-Behind Files

It is often the case that Web developers do not have the aesthetic sense nor the understandings of cognitive psychology to create attractive, user-friendly Web sites. For this reason, many companies divide the work of Web site development between two individuals or teams, one responsible for design features, the other for technical details. In other words, a Web page is divided into presentation and application logic components, each of which is handled by separate individuals or groups. In short, the HTML portion of a page is separated from the script portion of a page such that each can be designed and coded independently of the other.

This idea of separating presentation from application logic is inherent in ASP.NET pages that include both. The use of server controls enforces this separation. There is physical separation between the controls residing in the <HTML> section of a page and the programming of those controls in the <SCRIPT> section of the page. Still, ASP.NET goes a step further. Techniques exist to divide presentation from application logic as independent files. This separation permits

Page 188: Assignment Instructions

two teams of developers to work independently on these two different aspects of page design, and to have them brought together as a functional whole when the page is accessed over the Web.

This feature of ASP.NET is called code-behind development. Using the code-behind approach you can place your page's application logic (its scripting) into a separate file from your page's content presentation (its XHTML code and server controls). Even if you have responsibility for both aspects of page design, the use of code-behind encourages development of standardized processing components that can be reused on multiple Web pages.

Creating a Code-Behind Application

Consider first a very simple application created as a standard ASP.NET page. In this example, a button click delivers the current date displayed on the page.

<SCRIPT Runat="Server">

Sub Get_The_Date (Src As Object, Args As EventArgs) TheDate.Text = "Today is " & DateString & _ ", courtesy of your on-page script."End Sub

</SCRIPT>

<html><body><form Runat="Server">

<asp:Button Text="Get Date" OnClick="Get_The_Date" Runat="Server"/><asp:Label id="TheDate" Runat="Server"/>

</form></body></html>Listing 11-1. A simple Web page with code and script.

The first step in creating a code-behind application is to remove the script from the page leaving only the XHTML code and server controls—the user interface portion of the application—in the .aspx document. Assume the name of this document is MyDatePage.aspx. It contains a page directive that links this file with its paired MyDatePage.vb code-behind file that is described below.

<%@ Page Inherits="MyDateClass" src="MyDatePage.vb" %>

<html><body><form Runat="Server">

<asp:Button Text="Get Date" OnClick="Get_The_Date" Runat="Server"/><asp:Label id="TheDate" Runat="Server"/>

</form></body></html>Listing 11-2. MyDatePage.aspx: HTML portion of code-behind page.

Next, the script portion of the page is rewritten as a Visual Basic class file and saved with the extension .vb. Assume the name of this file is MyDatePage.vb.

Page 189: Assignment Instructions

Public Class MyDateClass Inherits Page Protected WithEvents TheDate As Label Sub Get_The_Date (Src As Object, Args As EventArgs) TheDate.Text = "Today is " & DateString & _ ", courtesy of your code-behind page." End Sub End ClassListing 11-3. MyDatePage.vb: script portion of code-behind page.

The class definition contains the statement "Inherits Page", meaning that the code-behind file inherits the Page class with all the necessary namespaces to create an ASP.NET page. The statement "Protected WithEvents TheDate As Label" is the link between the script on this page and the <asp:Label id="TheDate"> control on MyDatePage.aspxto which the output message is written. Any references in the class file to controls on the .aspx page must be explicitly declared in this format on the code-behind page. Other than these housekeeping statements, the file contains the same subprogram as appears on the original .aspx page.

The code-behind file can appear in the same directory as the .aspx page or it can be placed in a separate directory. Here, the page is located in the same directory as the page.

Application of the MyDatePage.vb code-behind file is illustration by the following button.

 

Figure 11-2. Using a code-behind file.

Note that is not necessary to separately compile the MyDatePage.vb since it is dynamically compiled along with MyDatePage.aspx when the latter is initially retrieved for display. If you decide to compile the class, say to distribute the application without revealing the source code, then the resulting .dll file is placed in the application's /bin directory. In this case, the Web page's src attribute is not required since all compiled classes reside in this same /bin directory.

Independence in Code-Behind Files

As you can see in the previous example, there is not total independence between the MyDatePage.aspx page and its MyDatePage.vb class. The latter file must be cognizant of the Label control to which it writes its output. In fact, any page that uses this class file must contain a like-named Label; the date cannot be written just anywhere on the page, nor can it be used for other purposes.

It is easy enough, though, to introduce total independence between the two files, making the MyDatePage.vb class usable by any page for any purpose. The needed change is to replace the subprogram with a function that returns the date to the calling page. Then, the page can do as it pleases with the date.

The following code is a rewrite of MyDatePage.vb in which the connection is broken to the display Label on the calling page.

Page 190: Assignment Instructions

Public Class MyDateClass2 Inherits Page Function Return_Date() Return "Today is " & DateString & _ ", courtesy of your code-behind function." End Function End ClassListing 11-4. MyDatePage.vb code-behind file with a function.

In addition, the Web page now requires its own script to respond to the button click and to call the function that is inherited with MyDateClass.

<%@ Page Inherits="MyDateClass" src="MyDatePage.vb" %>

<SCRIPT Runat="Server">

Sub Return_The_Date (Src As Object, Args As EventArgs) TheDate.Text = Return_Date() End Sub

</SCRIPT>

<html><body><form Runat="Server">

<asp:Button Text="Get Date" OnClick="Return_The_Date" Runat="Server"/><asp:Label id="TheDate" Runat="Server"/>

</form></body></html>Listing 11-5. MyDatePage.aspx page with a function call.

 

Figure 11-3. Using a code-behind file with a function call.

Of course, you can argue that it is just as easy to include the function itself on the Web page rather than inheriting it from an external file. This contention is true for this simple application. However, code-behind applications tend to involve more complex code that is used here. For any complex set of operations that could be applicable to numerous pages, there is efficiency in packaging them in code-behind files rather than duplicating the code on all pages, even if it requires modest on-page scripts to call up that functionality.

It is important to remembered that only one page directive is permitted on a .aspx page. Thus, only one code-behind file can be associated with any one page. However, any number of subprograms and functions can be coded in the code-behind file, thereby servicing any number of .aspx pages.

Business Components

One of the purposes of creating code-behind files is to use them as business components, independing processing routines that are sharable among different pages. In this way, a generally

Page 191: Assignment Instructions

useful processing routine does not have to be duplicated on all pages that need it; any page can inherit its class definition and take advantage of its processing. Therefore, the code-behind file needs to be independent of any page that uses it.

Component Sharing through Naming Conventions

One of the ways in which a code-behind file can be shared among multiple pages is to use common names for common controls. Imagine, for instance, that two ASP.NET pages require the same recordset from BooksDB.mdb; each page displays the recordset in a GridView. Rather than having to create two separate code-behind files for each of these pages, a single code-behind file can be used, assuming that the two GridViews have the same id. This is a necessary requirement since the code-behind recordset is bound to a particularid value.

The following code-behind file can be used as a general-purpose recordset retrieval function for the BooksDB.mdb Books table.

Imports System.Data.OleDb

Public Class DBClass Inherits Page

Protected WithEvents DisplayGrid As GridView

Sub Get_Data Dim DBConnection As OleDbConnection Dim DBCommand As OleDbCommand Dim DBReader As OleDbDataReader Dim SQLString As String DBConnection = New OleDbConnection( _ "Provider=Microsoft.Jet.OLEDB.4.0;" & _ "Data Source=" & Server.MapPath("../Databases/BooksDB.mdb")) DBConnection.Open() SQLString = "SELECT * FROM Books ORDER BY BookID" DBCommand = New OleDbCommand(SQLString, DBConnection) DBReader = DBCommand.ExecuteReader() DisplayGrid.DataSource = DBReader DisplayGrid.DataBind() DBReader.Close() DBConnection.Close() End Sub

End ClassListing 11-6. DBClass.vb code-behind data access file.

The Get_Data subprogram retrieves all fields from all records in the table. Notice that the retrieved recordset is bound to a GridView with id="DisplayGrid".

Just because the full set of database fields have been retrieved does not mean that particular pages have to use them all. By using different <asp:BoundField> controls for their GridViews, different pages can display different outputs. The following code represent two ASP.NET pages, each of which imports the above code-behind class but which use the retrieved recordset for different display purposes.

ID Title

Page 192: Assignment Instructions

DB111 Oracle Database

DB222 Databases in Depth

DB333 Database Processing

DB444 Access Database Design

DB555 SQL Server 2005

GR111 Adobe Photoshop CS2

GR222 Learning Web Design

GR333 Macromedia Flash Professional

GR444 Digital Photographer Handbook

GR555 Creating Motion Graphics

HW111 How Computers Work

HW222 Upgrading and Repairing PCs

HW333 USB System Architecture

HW444 Designing Embedded Hardware

HW555 Contemporary Logic Design

SW111 Java How to Program

SW222 C Programming Language

SW333 Programming C#

SW444 Programming PHP

SW555 Visual Basic.NET Programming

SY111 Operating System Concepts

SY222 The UNIX Operating System

SY333 Windows Server 2003

SY444 Linux in a Nutshell

SY555 Mastering Active Directory

WB111 Ajax in Action

WB222 Professional ASP.NET 2.0

WB333 Cascading Style Sheets

WB444 DOM Scripting

WB555 Microsoft ASP.NET 2.0<%@ Page Inherits="DBClass"  src="DBClass.vb" %>

<SCRIPT Runat="Server">

Sub Page_Load  Get_DataEnd Sub

</SCRIPT>

<html><body><form Runat="Server">

<asp:GridView id="DisplayGrid"Runat="Server"

Page 193: Assignment Instructions

  AutoGenerateColumns="False"  ShowHeader="True"  HeaderStyle-BackColor="#E0E0E0"  HeaderStyle-Font-Bold="True"  HeaderStyle-HorizontalAlign="Center">  <Columns>

  <asp:BoundField    DataField="BookID"    HeaderText="ID"/>

  <asp:BoundField    DataField="BookTitle"    HeaderText="Title"/>

  </Columns>

</asp:GridView>

</form></body></html>Figure 11-4. DBPage1.aspx - uses DBClass.vb code-behind data access file.

Title Price

Oracle Database $69.99

Databases in Depth $29.95

Database Processing $136.65

Access Database Design $34.95

SQL Server 2005 $29.99

Adobe Photoshop CS2 $29.99

Learning Web Design $39.95

Macromedia Flash Professional $44.99

Digital Photographer Handbook $24.95

Creating Motion Graphics $59.95

How Computers Work $29.99

Upgrading and Repairing PCs $59.99

USB System Architecture $49.99

Designing Embedded Hardware $44.95

Contemporary Logic Design $102.95

Java How to Program $98.59

C Programming Language $44.25

Programming C# $44.95

Programming PHP $39.95

Visual Basic.NET Programming $49.99

Operating System Concepts $95.75

The UNIX Operating System $19.95

Page 194: Assignment Instructions

Windows Server 2003 $29.99

Linux in a Nutshell $44.95

Mastering Active Directory $49.99

Ajax in Action $22.67

Professional ASP.NET 2.0 $32.99

Cascading Style Sheets $39.95

DOM Scripting $23.09

Microsoft ASP.NET 2.0 $29.99<%@ Page Inherits="DBClass"  src="DBClass.vb" %>

<SCRIPT Runat="Server">

Sub Page_Load  Get_DataEnd Sub

</SCRIPT>

<html><body><form Runat="Server">

<asp:GridView id="DisplayGrid"Runat="Server"  AutoGenerateColumns="False"  ShowHeader="True"  HeaderStyle-BackColor="#E0E0E0"  HeaderStyle-Font-Bold="True"  HeaderStyle-HorizontalAlign="Center">  <Columns>

  <asp:BoundField    DataField="BookTitle"    HeaderText="Title"/>

  <asp:BoundField    DataField="BookPrice"    HeaderText="Price"    ItemStyle-HorizontalAlign="Right"/>

  </Columns>

</asp:GridView>

</form></body></html>Figure 11-5. DBPage2.aspx - uses DBClass.vb code-behind data access file.

Both pages use identical page directives to import the DBClass.vb file. Both pages include a Page_Load script to call subprogram Get_Data when they load. Both pages have identical id="DisplayGrid" GridViews. The pages differ, however, in how they employ the retrieved recordset by the differing data columns that are bound to their GridViews.

Page 195: Assignment Instructions

Passing SQL Strings

It is not necessary that different pages be limited by the single recordset retrieved by the code-behind page. An ASP.NET page can, in fact, pass an SQL statement to the code-behind page and have a personalized recordset returned. A minor rewrite of the code-behind page is necessary.

Imports System.Data.OleDb

Public Class DBClass Inherits Page

Protected WithEvents DisplayGrid As GridView

Sub Get_Data (SQLString As String) Dim DBConnection As OleDbConnection Dim DBCommand As OleDbCommand Dim DBReader As OleDbDataReader DBConnection = New OleDbConnection( _ "Provider=Microsoft.Jet.OLEDB.4.0;" & _ "Data Source=" & Server.MapPath("../Databases/BooksDB.mdb")) DBConnection.Open() DBCommand = New OleDbCommand(SQLString, DBConnection) DBReader = DBCommand.ExecuteReader() DisplayGrid.DataSource = DBReader DisplayGrid.DataBind() DBReader.Close() DBConnection.Close() End Sub

End ClassListing 11-7. Rewrite of DBClass.vb code-behind data access file.

Here, the signature of the subprogram includes an SQLString argument through which an SQL statement is passed. Then that statement is issued through the Command object to retrieve that particular selection of fields. Different subsets of records can be retrieved and even different database tables can be accessed.

Title Qty

Programming C# 0

SQL Server 2005 0

Operating System Concepts 1

USB System Architecture 1

Oracle Database 10

The UNIX Operating System 12

C Programming Language 12

Microsoft ASP.NET 2.0 12

Database Processing 12

Visual Basic.NET Programming 13

Creating Motion Graphics 13

Linux in a Nutshell 14

Page 196: Assignment Instructions

Ajax in Action 14

Macromedia Flash Professional 17

Programming PHP 17

Contemporary Logic Design 2

Professional ASP.NET 2.0 21

Digital Photographer Handbook 22

Access Database Design 25

Windows Server 2003 25

Designing Embedded Hardware 3

Adobe Photoshop CS2 4

Upgrading and Repairing PCs 5

Databases in Depth 6

Cascading Style Sheets 6

DOM Scripting 8

How Computers Work 8

Mastering Active Directory 8

Learning Web Design 8

Java How to Program 9<%@ Page Inherits="DBClass"  src="DBClass.vb" %>

<SCRIPT Runat="Server">

Sub Page_LoadGet_Data ("SELECT BookTitle, BookQty FROM Books ORDER BY BookQty")End Sub

</SCRIPT>

<html><body><form Runat="Server">

<asp:GridView id="DisplayGrid"  Runat="Server"  AutoGenerateColumns="False"  ShowHeader="True"  HeaderStyle-BackColor="#E0E0E0"  HeaderStyle-Font-Bold="True"  HeaderStyle-HorizontalAlign="Center">  <Columns>

  <asp:BoundField    DataField="BookTitle"    HeaderText="Title"/>

  <asp:BoundField    DataField="BookQty"    HeaderText="Qty"    ItemStyle-HorizontalAlign="Right"/>

Page 197: Assignment Instructions

  </Columns>

</asp:GridView>

</form></body></html>Figure 11-6. Passing an SQL statement to the DBClass.vb code-behind file.

Passing Control Information

In previous examples, it is assumed that all controls to which the recordset is bound are GridViews with id="DisplayGrid". But this doesn't have to be the case. Any control can identify itself to the code-behind file and receive the recordset it needs. In the following example, an <asp:Repeater> control calls a code-behind subprogram and retrieves its personalized recordset. Again, only a slight change is needed in the code-behind file to make this generalization.

Imports System.Data.OleDb

Public Class DBClass Inherits Page

Sub Get_Data (SQLString As String, ControlID As String) Dim DBConnection As OleDbConnection Dim DBCommand As OleDbCommand Dim DBReader As OleDbDataReader Dim TheControl As Object = FindControl(ControlID) DBConnection = New OleDbConnection( _ "Provider=Microsoft.Jet.OLEDB.4.0;" & _ "Data Source=" & Server.MapPath("../Databases/BooksDB.mdb")) DBConnection.Open() DBCommand = New OleDbCommand(SQLString, DBConnection) DBReader = DBCommand.ExecuteReader() TheControl.DataSource = DBReader TheControl.DataBind() DBReader.Close() DBConnection.Close() End Sub

End ClassListing 11-8. Recoding of DBClass.vb code-behind file to accept passed control information.

The subprogram is passed the id of a control as argument ControlID along with an SQL statement. The subprogram uses the passed id to find that control on the page and assign it to variable TheControl. This variable is not explicitly typed since it is not known in advance what kind of control it is. TheControl is the object to which the retrieved recordset is bound, using the passed SQL statement to retrieve particular records and particular fields from particular tables.

The page that imports this code-behind page is shown below with a call to subprogram Get_Data, passing along an SQL statement and identification of the Repeater to which the returned recordset is bound.

Title Qty

Programming C# 0

Page 198: Assignment Instructions

SQL Server 2005 0

Operating System Concepts 1

USB System Architecture 1

Oracle Database 10

The UNIX Operating System 12

C Programming Language 12

Microsoft ASP.NET 2.0 12

Database Processing 12

Visual Basic.NET Programming 13

Creating Motion Graphics 13

Linux in a Nutshell 14

Ajax in Action 14

Macromedia Flash Professional 17

Programming PHP 17

Contemporary Logic Design 2

Professional ASP.NET 2.0 21

Digital Photographer Handbook 22

Access Database Design 25

Windows Server 2003 25

Designing Embedded Hardware 3

Adobe Photoshop CS2 4

Upgrading and Repairing PCs 5

Databases in Depth 6

Cascading Style Sheets 6

DOM Scripting 8

How Computers Work 8

Mastering Active Directory 8

Learning Web Design 8

Java How to Program 9<%@Page Inherits="DBClass"  src="DBClass.vb"%>

<SCRIPT Runat="Server">

Sub Page_Load  Get_Data ( _    "SELECT BookTitle, BookQty " & _    "FROM Books " & _    "ORDER BY BookQty", _    "MyRepeater")End Sub

</SCRIPT>

<html><body>

Page 199: Assignment Instructions

<form Runat="Server">

<asp:Repeater id="MyRepeater" Runat="Server">

<HeaderTemplate><table border="1"><tr style="background-color:#E0E0E0">  <th>Title</th>  <th>Qty</th></tr></HeaderTemplate>

<ItemTemplate><tr>  <td><%# Eval("BookTitle") %></td>  <td><%# Eval("BookQty") %></td></tr></ItemTemplate>

<FooterTemplate></table></FooterTemplate>

</asp:Repeater>

</form></body></html>Figure 11-7. Passing control information to a code-behind class.

Now the code-behind file serves a general-purpose data retrieval function that can be called by any page to supply a recordset for any of its controls. Of course, the control must use compatible binding techniques. For instance, a DropDownList has its DataTextField and DataValueField identified for binding. The present code-behind file does not answer this need. Still, it serves the needs of all information display controls.

Returning Recordsets from Functions

A more generally useful technique for supplying database information to a page, one that does not involve providing control information to the code-behind class, is to convert the code-behind subprogram to a function which returns a DataReader. By employing this technique, a code-behind file can become totally independent of any Web pages which inherit it.

In the following example, a GridView is used to display a subset of information from the BooksDB.mdb database. However, the code-behind class needs no knowledge of the page to which it returns its results.

Title Price

Oracle Database $69.99

Databases in Depth $29.95

Database Processing $136.65

Access Database Design $34.95

SQL Server 2005 $29.99

Page 200: Assignment Instructions

Adobe Photoshop CS2 $29.99

Learning Web Design $39.95

Macromedia Flash Professional $44.99

Digital Photographer Handbook $24.95

Creating Motion Graphics $59.95

How Computers Work $29.99

Upgrading and Repairing PCs $59.99

USB System Architecture $49.99

Designing Embedded Hardware $44.95

Contemporary Logic Design $102.95

Java How to Program $98.59

C Programming Language $44.25

Programming C# $44.95

Programming PHP $39.95

Visual Basic.NET Programming $49.99

Operating System Concepts $95.75

The UNIX Operating System $19.95

Windows Server 2003 $29.99

Linux in a Nutshell $44.95

Mastering Active Directory $49.99

Ajax in Action $22.67

Professional ASP.NET 2.0 $32.99

Cascading Style Sheets $39.95

DOM Scripting $23.09

Microsoft ASP.NET 2.0 $29.99<%@ Page Inherits="DBClass"src="DBClass.vb" %>

<SCRIPT Runat="Server">

Sub Page_Load

  DisplayGrid.DataSource=Return_Reader()  DisplayGrid.DataBind()

End Sub

</SCRIPT>

<html><body><form Runat="Server">

<asp:GridView id="DisplayGrid"   Runat="Server"  AutoGenerateColumns="False">

  <Columns>

Page 201: Assignment Instructions

  <asp:BoundField    HeaderText="Title"    DataField="BookTitle"/>

  <asp:BoundField    HeaderText="Price"    DataField="BookPrice"/>

  </Columns>

</asp:GridView></form></body></html>Figure 11-8. Using a code-behind function to access a database.

The On_Load script issues a function call to Return_Reader() in the code-behind file, and binds the returned recordset to the GridView. The DBClass.vb code-behind file is shown below. Function Return_Reader() returns the full set of records drawn from the Books table of the BooksDB.mdb database. That is, it returns a DataReader (the DBReader recordset) to any page that calls the function.

Imports System.Data.OleDb

Public Class DBClass Inherits Page Function Return_Reader() Dim DBConnection As OleDbConnection Dim DBCommand As OleDbCommand Dim DBReader As OleDbDataReader Dim SQLString As String DBConnection = New OleDbConnection( _ "Provider=Microsoft.Jet.OLEDB.4.0;" & _ "Data Source=" & Server.MapPath("../Databases/BooksDB.mdb")) DBConnection.Open() SQLString = "SELECT * FROM Books ORDER BY BookID" DBCommand = New OleDbCommand(SQLString, DBConnection) DBReader = DBCommand.ExecuteReader() Return DBReader DBReader.Close() DBConnection.Close() End Function End ClassListing 11-9. DBClass.vb code-behind file with data access function.

Notice that even though the code-behind class returns a full recordset, the GridView uses BoundFields to display only selected fields from the recordset. Thus, any number of different pages can import this class and call its function to display their own selected fields of information. The class is a general-purpose database access function that can be adapted to numerous uses.

Data Access Layer

The purpose of code-behind development is to encapsulate the business logic behind an application within separate, stand-alone software components. The goal is to divide an

Page 202: Assignment Instructions

application into different layers, or tiers—a business logic layer and a user interface layer. Doing so promotes creation of processing components that can be shared among multiple pages, and it becomes easier to maintain the components separately than if coded multiple times on multiple pages.

A second way to encapsulate page components within different layers is to separate "data access" from the user interface. In previous applications this has been accomplished with code-behind files in which data access is part of the processing logic. ASP.NET 2.0 introduces more direct ways to create a data access layer.

In many of the applications you have seen so far, a Web page combines a data source control (such as an AccessDataSource) with an information display control (such as a GridView).

<asp:AccessDataSource id="BookSource" Runat="Server" DataFile="../Databases/BooksDB.mdb" SelectCommand="SELECT * FROM Books"/>

<asp:GridView id="BookGrid" Runat="Server"/> DataSourceId="BookSource"/>Listing 11-10. Typical use of data source control with information display control.

In this example, a data access method is contained on the page, no different in fact from coding a script on the page to connect to the database, issue an SQL command, and assign the resulting DataReader to the GridView. The only difference is that the script is encapsulated within the AccessDataSource control rather than appearing separately in the script portion of the page.

As you have also seen, this same data access method has been employed on numerous pages throughout these tutorials, each time using the same AccessDataSource to retrieve the same set of database records. Consider what would happen if the design of the database changed. Now, a different set of records may need to be retrieved from the redesigned database, forcing a need to track down and change all instances of the AccessDataSource. Or, consider the same situation arising if the path to the database changes. The point is, the data access method is coded along side the control that displays its returned recordset. A better solution is to uncouple data access from its display. As in the case with code-behind scripts, a separate level, or tier, of data access makes it easier to share data access components and to manage changes wrought by changing database designs and locations.

The <asp:ObjectDataSource> Control

Under ASP.NET 2.0, a new <asp:ObjectDataSource> control permits you to remove the mechanics of data access to a separate software component, and to link to that component through the ObjectDataSource control. The control is, in effect, a "logical" description of data access whose "physical" implementation details are hidden inside a code-behind data-access component. An information display control such as a GridView binds to the ObjectDataSource, which solicits behind-the-scenes data access by the data-access component. A general format for coding an ObjectDataSource is given in Figure 11-9.

<asp:ObjectDataSource id="id" Runat="Server" TypeName="class" SelectMethod="method"/>

Figure 11-9. General format for <asp:ObjectDataSource> control.

Page 203: Assignment Instructions

An id is required for reference by the control that links to the data source. TypeName gives the name of a Visual Basic class containing the data access component; SelectMethodgives the name of the data access method contained in the class, and which returns the recordset bound to the display control.

Creating a Data Access Class

A Visual Basic data access class is a stand-alone component that retrieves and returns a set of records from a database. This work is performed by a data-access method coded as part of the class. In the following listing, a BookInfo class is defined. It contains a Get_Books() method (function) that retrieves a set of records from the Books table of theBooksDB.mdb database and returns the DataReader to any ObjectDataSource that calls it.

Imports System.Data.OleDb

Public Class BookInfo

Function Get_Books() Dim DBConnection As OleDbConnection Dim DBCommand As OleDbCommand Dim DBReader As OleDbDataReader Dim SQLString As String DBConnection = New OleDbConnection( _ "Provider=Microsoft.Jet.OLEDB.4.0;" & _ "Data Source=c:\eCommerce\Databases\BooksDB.mdb") DBConnection.Open() SQLString = "SELECT * FROM Books ORDER BY BookID" DBCommand = New OleDbCommand(SQLString, DBConnection) DBReader = DBCommand.ExecuteReader() Return DBReader DBReader.Close() DBConnection.Close() End Function

End ClassListing 11-11. A BookInfo data access class.

The app_Code Directory

Like a code-behind file, the Visual Basic class is saved with the extension .vb. In this example, the file is saved as BookInfo.vb. In order to make the class accessible to all pages that call it, it must be saved in the app_Code directory located in the root directory of the application. In the current example, the app_Code directory appears inside thec:\eCommerce root directory.

Binding to a Data Access Class

Once a data access class has been placed inside the app_Code directory, it is called by an ObjectDataSource control for binding its output to a display control coded on the page. In the following example, a GridView appears on a page. The GridView is linked to an ObjectDataSource through its DataSourceID. The ObjectDataSource points to the BookInfoclass contained in the BookInfo.vb file through its TypeName attribute, and it calls the class's Get_Books() method through its SelectMethod attribute.

Page 204: Assignment Instructions

<h3>Data Access with an ObjectDataSource</h3>

<asp:ObjectDataSource id="BookSource" Runat="Server" TypeName="BookInfo" SelectMethod="Get_Books"/>

<asp:GridView id="GridView" Runat="Server" AutoGenerateColumns="False" DataSourceID="BookSource"> <Columns> <asp:BoundField HeaderText="Book ID" DataField="BookID"/> <asp:BoundField HeaderText="Title" DataField="BookTitle"/> <asp:BoundField HeaderText="Price" DataField="BookPrice"/> </Columns> </asp:GridView>Listing 11-12. Code to bind an ObjectDataSource to a GridView.

As a result, the Get_Books() method returns the retrieved DataReader (DBReader) to the ObjectDataSource, which binds the recordset to the GridView.

Data Access with an ObjectDataSource

Book ID Title Price

DB111 Oracle Database $69.99

DB222 Databases in Depth $29.95

DB333 Database Processing $136.65

DB444 Access Database Design $34.95

DB555 SQL Server 2005 $29.99

GR111 Adobe Photoshop CS2 $29.99

GR222 Learning Web Design $39.95

GR333 Macromedia Flash Professional $44.99

GR444 Digital Photographer Handbook $24.95

GR555 Creating Motion Graphics $59.95

HW111 How Computers Work $29.99

HW222 Upgrading and Repairing PCs $59.99

HW333 USB System Architecture $49.99

HW444 Designing Embedded Hardware $44.95

HW555 Contemporary Logic Design $102.95

Page 205: Assignment Instructions

SW111 Java How to Program $98.59

SW222 C Programming Language $44.25

SW333 Programming C# $44.95

SW444 Programming PHP $39.95

SW555 Visual Basic.NET Programming $49.99

SY111 Operating System Concepts $95.75

SY222 The UNIX Operating System $19.95

SY333 Windows Server 2003 $29.99

SY444 Linux in a Nutshell $44.95

SY555 Mastering Active Directory $49.99

WB111 Ajax in Action $22.67

WB222 Professional ASP.NET 2.0 $32.99

WB333 Cascading Style Sheets $39.95

WB444 DOM Scripting $23.09

WB555 Microsoft ASP.NET 2.0 $29.99Figure 11-10. Binding an ObjectDataSource to a GridView.

You should notice identical coding between this BookInfo.vb data-access class and the code-behind DBPage.vb class used in the previous tutorial. You should also notice that the page using the data-access class does not require inheriting the class nor a src pointer to the location of the class. All links between the Web page and the data-access class are given in the TypeName and SelectMethod attributes of the ObjectDataSource. An ObjectDataSource is simply easier to use than a code-behind file for supplying database information to a server control.

In the current example, the ObjectDataSource returns all records and all fields from the Books table. Display of selected fields from the returned recordset is governed by the BoundFields defined in the GridView. An alternative design for the BookInfo class is to create different methods for different returned recordsets. Then, different pages can call these different methods to return different recordsets.

Passing Parameters to a Data Access Class

An ObjectDataSource can be supplied with control parameters to restrict access to selected records. In the following example, a DropDownList permits the user to select the types of books to display.

Data Access with ObjectDataSource Parameters

           

Book ID Title Price

DB111 Oracle Database $69.99

Page 206: Assignment Instructions

DB222 Databases in Depth $29.95

DB333 Database Processing $136.65

DB444 Access Database Design $34.95

DB555 SQL Server 2005 $29.99Figure 11-11. Selecting parameters for an ObjectDataSource.

The DropDownList is itself populated by an ObjectDataSource that calls a new method added to the previous BookInfo class. Coding for the DropDownList and for the newGet_Types() method is shown below.

<asp:ObjectDataSource id="TypeSource" Runat="Server" TypeName="BookInfo" SelectMethod="Get_Types"/>

<asp:DropDownList id="TypeList" Runat="Server" AutoPostBack="True" DataSourceID="TypeSource" DataTextField="BookType" DataValueField="BookType"/>Listing 11-13. Code for DropDownList and its ObjectDataSource.Imports System.Data.OleDb

Public Class BookInfo

Function Get_Types() Dim DBConnection As OleDbConnection Dim DBCommand As OleDbCommand Dim DBReader As OleDbDataReader Dim SQLString As String DBConnection = New OleDbConnection( _ "Provider=Microsoft.Jet.OLEDB.4.0;" & _ "Data Source=c:\eCommerce\Databases\BooksDB.mdb") DBConnection.Open() SQLString = "SELECT DISTINCT BookType FROM Books ORDER BY BookType" DBCommand = New OleDbCommand(SQLString, DBConnection) DBReader = DBCommand.ExecuteReader() Return DBReader DBReader.Close() DBConnection.Close() End Function

... End ClassListing 11-14. Revised BookInfo class with Get_Types() method added.

Code for the GridView is identical to the previous example. Its ObjectDataSource, though, now includes a ControlParameter identifying the SelectedValue from the DropDownList as a parameter named BookType that is passed to the Get_Books() method of the BookInfo class.

<asp:ObjectDataSource id="BookSource" Runat="Server" TypeName="BookInfo" SelectMethod="Get_Books"> <SelectParameters> <asp:ControlParameter Name="BookType" ControlID="TypeList" PropertyName="SelectedValue"/>

Page 207: Assignment Instructions

</SelectParameters></asp:ObjectDataSource>

<asp:GridView id="BookGrid" Runat="Server" AutoGenerateColumns="False" DataSourceId="BookSource"> <Columns> <asp:BoundField HeaderText="Book ID" DataField="BookID"/> <asp:BoundField HeaderText="Title" DataField="BookTitle"/> <asp:BoundField HeaderText="Price" DataField="BookPrice"/> </Columns> </asp:GridView>Listing 11-15. Code for GridView and its ObjectDataSource.

The Get_Books() method of the BookInfo class is revised to accept the passed BookType value identified in the ObjectDataSource's ControlParameter. This value is associated with the parameter @BookType in a WHERE clause added to the revised SELECT statement.

Imports System.Data.OleDb

Public Class BookInfo Function Get_Types() Dim DBConnection As OleDbConnection Dim DBCommand As OleDbCommand Dim DBReader As OleDbDataReader Dim SQLString As String DBConnection = New OleDbConnection( _ "Provider=Microsoft.Jet.OLEDB.4.0;" & _ "Data Source=c:\eCommerce\Databases\BooksDB.mdb") DBConnection.Open() SQLString = "SELECT DISTINCT BookType FROM Books ORDER BY BookType" DBCommand = New OleDbCommand(SQLString, DBConnection) DBReader = DBCommand.ExecuteReader() Return DBReader DBReader.Close() DBConnection.Close() End Function Function Get_Books (BookType as String) Dim DBConnection As OleDbConnection Dim DBCommand As OleDbCommand Dim DBReader As OleDbDataReader Dim SQLString As String DBConnection = New OleDbConnection( _ "Provider=Microsoft.Jet.OLEDB.4.0;" & _ "Data Source=c:\eCommerce\Databases\BooksDB.mdb") DBConnection.Open() SQLString = "SELECT * FROM Books WHERE BookType = @BookType ORDER BY BookID" DBCommand = New OleDbCommand(SQLString, DBConnection)

Page 208: Assignment Instructions

DBCommand.Parameters.AddWithValue("@BookType", BookType) DBReader = DBCommand.ExecuteReader() Return DBReader DBReader.Close() DBConnection.Close() End Function End ClassListing 11-16. Code for revised Get_Books() method of BookInfo class.

The revised SELECT statement includes a reference to parameter @BookType, a placeholder for a value that restricts selection of records to those with this value in the BookType field. There is a need, then, to associate this parameter with an actual value.

The Command Object's Parameters Collection

A value is associated with a parameter appearing in a Command object (DBCommand in this example) by adding a parameter name and value to the Command object's Parameterscollection. A general format for this collections' AddWithValue() method is shown in Figure 11-12.

CommandObject.Parameters.AddWithValue("@parameter", value)

Figure 11-12. General format to add a parameter to a Command object.

The name of the parameter, @parameter, appearing in the Command object is associated with a value. In the current example, this value comes from the BookType argument passed to the Get_Books() method when it is called by the ObjectDataSource. Therefore, the following code appears in the script:

DBCommand.Parameters.AddWithValue("@BookType", BookType)

The @BookType parameter appearing in the SELECT statement is replaced by the BookType value passed to the Get_Books() method. The selected records are then returned in the DataReader passed back to the ObjectDataSource and bound to the GridView.

Data Access Updating

The <asp:ObjectDataSource> control contains, besides the SelectMethod to retrieve information from a database, three additional methods to perform the full range of database operations. The UpdateMethod changes information in a database; the InsertMethod adds a new record to a database; and the DeleteMethod deletes a record from a Database. These methods are applied through bindings to server controls that permit these operations.

Master/Detail Editing with an ObjectDataSource

Recall that a master/detail setup with a GridView and associated DetailsView permits additions, changes, and deletions for a database. These operations can be performed through an ObjectDataSource as demonstrated in Figure 11-13.

Page 209: Assignment Instructions

Note that making actual changes to the BooksDB.mdb database is not permitted in these tutorials.

Database Maintenance with an ObjectDataSource

ID

DB111

DB222

DB333

Page 210: Assignment Instructions

DB444

DB555

1 2 3 4 5 6

ID DB111

Type Database

Title Oracle Database

Author K. Loney

Description Get thorough coverage of Oracle Database 10g from the most comprehensive reference available, published by Oracle Press. With in-depth details on all the new features, this powerhouse resource provides an overview of database architecture and Oracle Grid Computing technology, and covers SQL, SQL*Plus, PL/SQL, dynamic PL/SQL, object-

Page 211: Assignment Instructions

oriented features, and Java programming in the Oracle environment. You'll also find valuable database administration and application development techniques, plus an alphabetical reference covering major Oracle commands, keywords, features, and functions, with cross-referencing of topics.

Price $69.99

Qty 10

Sale False

Edit Delete NewFigure 11-13. Performing database maintenance through an ObjectDataSource.

Coding the GridView

The above GridView presents the collection of BookID values for all records in the database. Clicking on a BookID displays the associated record in the accompanying DetailsView. Code for the GridView is shown below.

<asp:AccessDataSource id="BookSource1" Runat="Server" DataFile="~/Databases/BooksDB.mdb" SelectCommand="SELECT BookID FROM Books ORDER BY BookID"/>

<asp:GridView id="BookGrid" DataSourceID="BookSource1" Runat="Server" DataKeyNames="BookID" AutoGenerateColumns="False" AllowPaging="True" PageSize="5" HeaderStyle-BackColor="#E0E0E0" SelectedIndex="0" SelectedRowStyle-BackColor="#F0F0F0" Style="float:left; margin-bottom:200px"> <Columns> <asp:TemplateField HeaderText="ID" HeaderStyle-Font-Size="10pt" ItemStyle-Font-Size="10pt"> <ItemTemplate> <asp:LinkButton CommandName="Select" Runat="Server" Text='<%# Eval("BookID") %>'/> </ItemTemplate> </asp:TemplateField> </Columns> </asp:GridView>Listing 11-17. Code for GridView master listing.

You should recognize this coding from previous examples. The BookID values from the database are formatted as LinkButtons which, when clicked, supply the SelectedValue for retrieving the full record for display in the DetailsView. Note that the GridView is populated through an AccessDataSource. An ObjectDataSource could have been used, however, its paging method is a little too complex to be worth the trouble.

Page 212: Assignment Instructions

Coding the DetailsView

Code for the DetailsView is given in Listing 11-18. Again, its layout should be familiar from previous examples. All fields from the database are rendered in BoundField controls with the BookID field identified as the record key in the DataKeyNames property.

<asp:ObjectDataSource id="BookSource2" Runat="Server" TypeName="BookUpdate" SelectMethod="SelectBook" InsertMethod="InsertBook" UpdateMethod="UpdateBook" DeleteMethod="DeleteBook"> <SelectParameters> <asp:ControlParameter Name="BookID" ControlID="BookGrid" PropertyName="SelectedValue"/> </SelectParameters></asp:ObjectDataSource>

<asp:DetailsView id="BookDetails" Runat="Server" DataSourceID="BookSource2" AutoGenerateRows="False" AutoGenerateEditButton="True" AutoGenerateInsertButton="True" AutoGenerateDeleteButton="True" DataKeyNames="BookID" SelectedIndex="0" ShowHeader="True" RowStyle-Font-Size="10pt" RowStyle-VerticalAlign="Top" OnItemInserted="Rebind_GridView_Add" OnItemDeleted="Rebind_GridView_Delete"> <Fields> <asp:BoundField HeaderText="ID" HeaderStyle-BackColor="#E0E0E0" HeaderStyle-Font-Bold="True" DataField="BookID" ReadOnly="True"/> <asp:BoundField HeaderText="Type" HeaderStyle-BackColor="#F0F0F0" HeaderStyle-Font-Bold="True" DataField="BookType"/> <asp:BoundField HeaderText="Title" HeaderStyle-BackColor="#F0F0F0" HeaderStyle-Font-Bold="True" DataField="BookTitle"/> <asp:BoundField HeaderText="Author" HeaderStyle-BackColor="#F0F0F0" HeaderStyle-Font-Bold="True" DataField="BookAuthor"/>

Page 213: Assignment Instructions

<asp:BoundField HeaderText="Description" HeaderStyle-BackColor="#F0F0F0" HeaderStyle-Font-Bold="True" DataField="BookDescription"/> <asp:BoundField HeaderText="Price" HeaderStyle-BackColor="#F0F0F0" HeaderStyle-Font-Bold="True" DataField="BookPrice"/> <asp:BoundField HeaderText="Qty" HeaderStyle-BackColor="#F0F0F0" HeaderStyle-Font-Bold="True" DataField="BookQty"/> <asp:BoundField HeaderText="Sale" HeaderStyle-BackColor="#F0F0F0" HeaderStyle-Font-Bold="True" DataField="BookSale"/> </Fields> </asp:DetailsView>Listing 11-18. Code for DetailsView detail listing.

The connection between the BookID selected in the GridView and this record's display in the DetailsView is given in the SelectParameters of the ObjectDataSource that populates the DetailsView. As shown in the listing below, the SelectedValue from the GridView supplies the parameter passed to the SelectBook method of the BookUpdate class that selects and returns this record.

<asp:ObjectDataSource id="BookSource2" Runat="Server" TypeName="BookUpdate" SelectMethod="SelectBook" InsertMethod="InsertBook" UpdateMethod="UpdateBook" DeleteMethod="DeleteBook"> <SelectParameters> <asp:ControlParameter Name="BookID" ControlID="BookGrid" PropertyName="SelectedValue"/> </SelectParameters></asp:ObjectDataSource>Listing 11-19. Code for DetailsView detail listing.

A partial listing of the BookUpdate.vb file containing the SelectBook method is shown below. Remember that this file must be placed in the app_Code directory to be accessible.

Imports System.Data.OleDb

Public Class BookUpdate

Function SelectBook (BookID as String) Dim DBConnection As OleDbConnection Dim DBCommand As OleDbCommand

Page 214: Assignment Instructions

Dim DBReader As OleDbDataReader Dim SQLString As String DBConnection = New OleDbConnection( _ "Provider=Microsoft.Jet.OLEDB.4.0;" & _ "Data Source=c:\eCommerce\Databases\BooksDB.mdb") DBConnection.Open() SQLString = "SELECT * FROM Books WHERE BookID = @BookID" DBCommand = New OleDbCommand(SQLString, DBConnection) DBCommand.Parameters.AddWithValue("@BookID", BookID) DBReader = DBCommand.ExecuteReader() Return DBReader DBReader.Close() DBConnection.Close() End Function ... End ClassListing 11-20. Code for SelectBook method of BookUpdate class.

The BookID identified in the ObjectDataSource's ControlParameter is passed to this function and becomes the parameter value for selecting this book for display in the DetailsView.

Inserting Records with an ObjectDataSource

The ObjectDataSource for the DetailsView includes an InsertMethod="InsertBook" property identifying InsertBook as the method called when the DetailView's "Insert" button is clicked. This method appears in the BookUpdate class in the continuing script coded below.

Imports System.Data.OleDb

Public Class BookUpdate

Function SelectBook (BookID as String) ... End Function Function InsertBook (BookID as String, _ BookType As String, _ BookTitle As String, _ BookAuthor As String, _ BookDescription As String, _ BookPrice As Decimal, _ BookQty As Integer, _ BookSale As Boolean) Dim DBConnection As OleDbConnection Dim DBCommand As OleDbCommand Dim SQLString As String DBConnection = New OleDbConnection( _ "Provider=Microsoft.Jet.OLEDB.4.0;" & _ "Data Source=c:\eCommerce\Databases\BooksDB.mdb") DBConnection.Open() SQLString = "INSERT INTO Books " & _ "(BookID, BookType, BookTitle, BookAuthor, " & _ "BookDescription, BookPrice, BookQty, BookSale) " & _ "VALUES " & _ "(@BookID, @BookType, @BookTitle, @BookAuthor, " & _ "@BookDescription, @BookPrice, @BookQty, @BookSale)" DBCommand = New OleDbCommand(SQLString, DBConnection)

Page 215: Assignment Instructions

DBCommand.Parameters.AddWithValue("@BookID", BookID) DBCommand.Parameters.AddWithValue("@BookType", BookType) DBCommand.Parameters.AddWithValue("@BookTitle", BookTitle) DBCommand.Parameters.AddWithValue("@BookAuthor", BookAuthor) DBCommand.Parameters.AddWithValue("@BookDescription", BookDescription) DBCommand.Parameters.AddWithValue("@BookPrice", BookPrice) DBCommand.Parameters.AddWithValue("@BookQty", BookQty) DBCommand.Parameters.AddWithValue("@BookSale", BookSale) DBCommand.ExecuteNonQuery() DBConnection.Close() End Function ... End ClassListing 11-21. Code for InsertBook method of BookUpdate class.

The InsertBook function receives the field names and data types from the DetailsView in its argument list. You must make sure that the names match those in the DetailsView; the order in which the fields are listed is not important. A set of Command Parameters are identified, associating the passed field names with the parameter names in the INSERTstatement. Then the record is inserted with the Command object's ExecuteNonQuery() method.

Updating Records with an ObjectDataSource

The ObjectDataSource also includes an UpdateMethod property naming the "UpdateBook" method to call when the DetailView's "Update" button is clicked. This method appears in the BookUpdate class in the continuing script coded below.

Imports System.Data.OleDb

Public Class BookUpdate

Function SelectBook (BookID as String) ... End Function Function InsertBook (BookID as String, ... ... End Function Function UpdateBook (BookID as String, _ BookType As String, _ BookTitle As String, _ BookAuthor As String, _ BookDescription As String, _ BookPrice As Decimal, _ BookQty As Integer, _ BookSale As Boolean) Dim DBConnection As OleDbConnection Dim DBCommand As OleDbCommand Dim SQLString As String DBConnection = New OleDbConnection( _ "Provider=Microsoft.Jet.OLEDB.4.0;" & _ "Data Source=c:\eCommerce\Databases\BooksDB.mdb") DBConnection.Open() SQLString = "UPDATE Books SET " & _ "BookType=@BookType, " & _ "BookTitle=@BookTitle, " & _

Page 216: Assignment Instructions

"BookAuthor=@BookAuthor, " & _ "BookDescription=@BookDescription, " & _ "BookPrice=@BookPrice, " & _ "BookQty=@BookQty, " & _ "BookSale=@BookSale " & _ "WHERE BookID = @BookID" DBCommand = New OleDbCommand(SQLString, DBConnection) DBCommand.Parameters.AddWithValue("@BookType", BookType) DBCommand.Parameters.AddWithValue("@BookTitle", BookTitle) DBCommand.Parameters.AddWithValue("@BookAuthor", BookAuthor) DBCommand.Parameters.AddWithValue("@BookDescription", BookDescription) DBCommand.Parameters.AddWithValue("@BookPrice", BookPrice) DBCommand.Parameters.AddWithValue("@BookQty", BookQty) DBCommand.Parameters.AddWithValue("@BookSale", BookSale) DBCommand.Parameters.AddWithValue("@BookID", BookID) DBCommand.ExecuteNonQuery() DBConnection.Close() End Function ...

End ClassListing 11-22. Code for UpdateBook method of BookUpdate class.

In a similar fashion to the InsertBook function, the UpdateBook function receives a list of field names and data types passed by the ObjectDataSource. Also, a similar set ofParameters are added to the Command object associating the passed fields with parameter values in the UPDATE statement. Note that the order in which these Parameters are identified match the order in which the appear in the UPDATE statement. The Command object then issues its ExecuteNonQuery() method to update the database with the passed values.

Deleting Records with an ObjectDataSource

The ObjectDataSource includes a DeleteMethod property naming the "DeleteBook" method to call when the DetailView's "Delete" button is clicked. This method appears in theBookUpdate class in the continuing script coded below.

Imports System.Data.OleDb

Public Class BookUpdate

Function SelectBook (BookID as String) ... End Function Function InsertBook (BookID as String,... ... End Function Function UpdateBook (BookID as String, ... ... End Function Function DeleteBook (BookID as String) Dim DBConnection As OleDbConnection Dim DBCommand As OleDbCommand Dim SQLString As String DBConnection = New OleDbConnection( _

Page 217: Assignment Instructions

"Provider=Microsoft.Jet.OLEDB.4.0;" & _ "Data Source=c:\eCommerce\Databases\BooksDB.mdb") DBConnection.Open() SQLString = "DELETE FROM Books WHERE BookID = @BookID" DBCommand = New OleDbCommand(SQLString, DBConnection) DBCommand.Parameters.AddWithValue("@BookID", BookID) DBCommand.ExecuteNonQuery() DBConnection.Close() End Function End ClassListing 11-23. Code for DeleteBook method of BookUpdate class.

In this case the only argument passed to the function is the key of the record to be deleted—the BookID value identified by the DataKeyNames property of the DetailsView.

Rebinding the GridView

One other small housekeeping chore is coded in this application. When the ObjectDataSource calls the InsertBook and DeleteBook methods, the database reflects these additions and deletions. However, The GridView is not automatically bound to the changed database. An added record does not appear in the list of BookIDs, nor does the list reflect a deleted BookID. These changes only appear after another record is selected for display. The application would be a bit more elegant if additions and deletions were immediately reflected in the GridView listing. To this purpose, a pair of subprogram calls are added to the DetailsView.

<asp:DetailsView id="BookDetails" Runat="Server" DataSourceID="BookSource2" AutoGenerateRows="False" AutoGenerateEditButton="True" AutoGenerateInsertButton="True" AutoGenerateDeleteButton="True" DataKeyNames="BookID" SelectedIndex="0" ShowHeader="True" RowStyle-Font-Size="10pt" RowStyle-VerticalAlign="Top" OnItemInserted="Rebind_GridView_Add" OnItemDeleted="Rebind_GridView_Delete"> ...</asp:DetailsView>Listing 11-24. Calling subprograms to rebind the GridView.

After a new record is added and after an existing record is deleted through the DetailsView, subprograms are called to update the GridView to reflect these operations. These two on-page subprograms are shown below.

<SCRIPT Runat="Server">

Sub Rebind_GridView_Add (Src As Object, Args As DetailsViewInsertedEventArgs) BookGrid.DataBind()End Sub

Sub Rebind_GridView_Delete (Src As Object, Args As DetailsViewDeletedEventArgs) BookGrid.DataBind()End Sub

</SCRIPT>

Page 218: Assignment Instructions

Listing 11-25. Subprograms to rebind the GridView.

In both cases, the GridView's DataBind() method is called. This action causes the ObjectDataSource associated with the GridView to repopulate it with a new set of BookID values from the database. The GridView is now up-to-date in its display.

Data Access with DataSets

The <asp:ObjectDataSource> control can be employed along with DataSets to provide database maintenance functions. Recall that DataSets are in-memory tables that are filled with data from database tables. Manipulation of this information takes place in memory, with changes written back to the database through the DataSet's Update() method. When using DataSets, it is not necessary, even, to supply SQL INSERT, UPDATE, or DELETE statements to rewrite changes to the database. These statements are composed and issued for you.

An example of using DataSet methods is shown in Figure 11-14. This is a default DetailsView whose record display is chosen from a DropDownList. Automatic "Edit," "Delete," and "Insert" buttons call class methods to carry out processing through a DataSet.

Note that making actual changes to the BooksDB.mdb database is not permitted in these tutorials.

Update BooksDB.mdb Database

                                                             

BookID DB111

BookType Database

BookTitle Oracle Database

BookAuthor K. Loney

BookDescription Get thorough coverage of Oracle Database 10g from the most comprehensive reference available, published by Oracle Press. With in-depth details on all the new features, this powerhouse resource provides an overview of database architecture and Oracle Grid Computing technology, and covers SQL, SQL*Plus, PL/SQL, dynamic PL/SQL, object-oriented features, and Java programming in the Oracle environment. You'll also find valuable database administration and application development techniques, plus an alphabetical reference covering major Oracle commands, keywords, features, and functions, with cross-referencing of topics.

BookPrice $69.99

BookQty 10

BookSale

Edit Delete New

Figure 11-14. Database updating with a DetailsView and ObjectDataSource using DataSet methods.

Populating the DropDownList

Page 219: Assignment Instructions

A DropDownList permits selection of records from the Books table. This list is filled with all (unique) BookIDs drawn from the table.

<asp:ObjectDataSource id="BookIDSource" Runat="Server" TypeName="DSClass" SelectMethod="Select_BookIDs"/>

<asp:DropDownList id="DropList" DataSourceID="BookIDSource" Runat="Server" DataTextField="BookID" DataValueField="BookID" AutoPostBack="True"/>Listing 11-26. Code to populate DropDownList with BookIDs.

The DropDownList is associated with an ObjectDataSource that supplies BookID values. The data source calls the Select_BookIDs method from DSClass (the data access class) to return these values. Code for this class and method are given below. The class appears in file DSClass.vb in the standard app_Code directory.

Imports System.Data.OleDbImports System.Data

Public Class DSClass

Function Select_BookIDs() Dim DBConnection As OleDbConnection Dim DBCommand As OleDbCommand Dim DBReader As OleDbDataReader Dim SQLString As String DBConnection = New OleDbConnection( _ "Provider=Microsoft.Jet.OLEDB.4.0;" & _ "Data Source=c:\eCommerce\Databases\BooksDB.mdb") DBConnection.Open() SQLString = "SELECT DISTINCT BookID FROM Books ORDER BY BookID" DBCommand = New OleDbCommand(SQLString, DBConnection) DBReader = DBCommand.ExecuteReader() Return DBReader DBReader.Close() DBConnection.Close() End Function ... End ClassListing 11-27. Code for Select_BookIDs method of DSClass class.

Two namespaces are imported to the class: System.Data.OleDb and System.Data. The former is necessary for accessing the BooksDB.mdb database, the later for working with DataSets as described below. The Select_BookIDs method simply returns a DataReader through the ObjectDataSource associated with the DropDownList.

Selecting a Record for Display

The DropDownList has an AutoPostBack="True" property such that selection from the list automatically reloads the page and triggers reloading of the DetailsView with the selected record. The DetailsView is also populated by an ObjectDataSource which, in this case, uses the selected BookID as its selection parameter.

Page 220: Assignment Instructions

<asp:ObjectDataSource id="BookSource" Runat="Server" TypeName="DSClass" SelectMethod="Select_Book" UpdateMethod="Update_Book" InsertMethod="Insert_Book" DeleteMethod="Delete_Book"> <SelectParameters> <asp:ControlParameter Name="BookID" ControlID="DropList" PropertyName="SelectedValue"/> </SelectParameters></asp:ObjectDataSource>

<asp:DetailsView id="BookView" DataSourceID="BookSource" Runat="Server" AutoGenerateRows="True" AutoGenerateEditButton="True" AutoGenerateDeleteButton="True" AutoGenerateInsertButton="True" DataKeyNames="BookID" RowStyle-VerticalAlign="Top" RowStyle-Font-Size="10pt"/>Listing 11-28. Code to populate a DetailsView through an ObjectDataSource.

The ObjectDataSource includes a SelectParameter identifying the selected value from the DropDownList as a selection parameter named BookID. This value is passed to theSelect_Book method of DSClass for choosing which particular book to display in the DetailsView. Code for the Select_Book method is shown below in the continuing DSClassscript.

Imports System.Data.OleDbImports System.Data

Public Class DSClass Function Select_BookIDs() ... End Function Function Select_Book (BookID As String) Dim DBConnection As OleDbConnection Dim DBAdapter As OleDbDataAdapter Dim DBDataSet As DataSet Dim SQLString As String DBConnection = New OleDbConnection( _ "Provider=Microsoft.Jet.OLEDB.4.0;" & _ "Data Source=c:\eCommerce\Databases\BooksDB.mdb") SQLString = "SELECT * FROM Books WHERE BookID = '" & BookID & "'" DBAdapter = New OleDbDataAdapter(SQLString, DBConnection) DBDataSet = New DataSet DBAdapter.Fill(DBDataSet) Return DBDataSet End Function ...

End ClassListing 11-29. Code for Select_Book method of DSClass.

Filling a DataSet

Page 221: Assignment Instructions

Rather than issuing a SELECT statement through a Command object and returning a DataReader to the ObjectDataSource, a DataSet is used. In this case, the value from the DropDownList is received by the Select_Book method as argument BookID, which is plugged into a SELECT statement that is issued through a DataAdapter. The adapter fills a DataSet (DBDataSet in this example) with this table of information. The DataSet is returned to the ObjectDataSource, which populates the DetailsView with this record.

Keep in mind that the DataSet table is a single record from the BooksDB.mdb database. It is the first, or only, table in the DataSet's Tables collection (identified as Tables(0)) and comprises a single row in the table's Rows collection (identified as Tables(0).Rows(0)). These references become important later when updating the database.

Figure 11-15. Visualizing the DSDataSet DataSet.

Editing with a DataSet

When the DetailsView's "Edit" button is clicked, the display is placed in edit mode, supplying textboxes for changing values in all fields except the BookID field, which is the record key. Since the DetailsView is associated with an ObjectDataSource, clicking its "Update" button activates the ObjectDataSource's UpdateMethod which, in turn, calls theUpdate_Book method of the DSClass. This method is shown below in the continuing script.

Imports System.Data.OleDbImports System.Data

Public Class DSClass Function Select_BookIDs() ... End Function Function Select_Book (BookID As String) ... End Function Function Update_Book (BookID As String, _ BookType As String, _ BookTitle As String, _ BookAuthor As String, _ BookDescription As String, _ BookPrice As Decimal, _ BookQty As Integer, _ BookSale As Boolean) Dim DBConnection As OleDbConnection Dim DBAdapter As OleDbDataAdapter Dim DBDataSet As DataSet Dim SQLString As String Dim DBCommandBuilder As OleDbCommandBuilder

Page 222: Assignment Instructions

DBConnection = New OleDbConnection( _ "Provider=Microsoft.Jet.OLEDB.4.0;" & _ "Data Source=c:\eCommerce\Databases\BooksDB.mdb") SQLString = "SELECT * FROM Books WHERE BookID = '" & BookID & "'" DBAdapter = New OleDbDataAdapter(SQLString, DBConnection) DBDataSet = New DataSet DBAdapter.Fill(DBDataSet) DBDataSet.Tables(0).Rows(0).Item("BookType") = BookType DBDataSet.Tables(0).Rows(0).Item("BookTitle") = BookTitle DBDataSet.Tables(0).Rows(0).Item("BookAuthor") = BookAuthor DBDataSet.Tables(0).Rows(0).Item("BookDescription") = BookDescription DBDataSet.Tables(0).Rows(0).Item("BookPrice") = BookPrice DBDataSet.Tables(0).Rows(0).Item("BookQty") = BookQty DBDataSet.Tables(0).Rows(0).Item("BookSale") = BookSale DBCommandBuilder = New OleDbCommandBuilder(DBAdapter) DBAdapter.Update(DBDataSet) End Function ...

End ClassListing 11-30. Code for Update_Book method of DSClass.

Once again a DataSet is filled with a table containing a record from the BooksDB.mdb database. This time the value used to select the record comes from the BookID argument passed from the ObjectDataSource along with the other values from the DetailsView's edit fields.

Now it is a matter of replacing the current values in the fields with the changed values passed through the argument list. Note that a reference to a current field is in the format

DataSet.Tables(0).Rows(0).Item("field") = value

Updating pertains to a particular table in the DataSet's Tables collection (Tables(0) for this first, or only, table), to a particular row in the table's Rows collection (Rows(0) for this first, or only, row), and to a particular cell along this row (Item("field") for a named field). Each field is assigned its corresponding passed value, using the names in the method's argument list.

Using a CommandBuilder

Once these changes are made, the database is updated with the DataSet values. One of the ways to update the database is by composing an SQL UPDATE statement and issuing it through the adapter. An easier way, however, is to make use of a DataSet's CommandBuilder, which composes the statement for you. By creating an OleDbCommandBuilderobject, appropriate SQL commands are built "behind the scenes" to permit changes in a DataSet to be rewritten to a database. The general formats for creating a CommandBuilder are shown below.

Dim CommandBuilder As OleDbCommandBuilderCommandBuilder = New OleDbCommandBuilder(adapter)

or

Dim CommandBuilder = New OleDbCommandBuilder(adapter)

Figure 11-16. General format to create OleDbCommandBuilder object.

Page 223: Assignment Instructions

In the current example, DBCommandBuilder is created for the adapter DBAdapter. Then the adapter executes its Update() method, applying the changed DataSet to the database from which it was orginally filled.

DBCommandBuilder = New OleDbCommandBuilder(DBAdapter)DBAdapter.Update(DBDataSet)Listing 11-31. Code to create an UPDATE statement and update the database with DataSet changes.

Inserting with a DataSet

A similar technique is used when adding a new record to a database. The DetailsView's "Insert" button activates the ObjectDataSource's InsertMethod, calling the Insert_Bookmethod of DSClass and passing along new data values.

Imports System.Data.OleDbImports System.Data

Public Class DSClass Function Select_BookIDs() ... End Function Function Select_Book (BookID As String) ... End Function Function Update_Book (BookID As String, _ ... ... End Function Function Insert_Book (BookID As String, _ BookType As String, _ BookTitle As String, _ BookAuthor As String, _ BookDescription As String, _ BookPrice As Decimal, _ BookQty As Integer, _ BookSale As Boolean) Dim DBConnection As OleDbConnection Dim DBAdapter As OleDbDataAdapter Dim DBDataSet As DataSet Dim SQLString As String Dim DBCommandBuilder As OleDbCommandBuilder DBConnection = New OleDbConnection( _ "Provider=Microsoft.Jet.OLEDB.4.0;" & _ "Data Source=c:\eCommerce\Databases\BooksDB.mdb") SQLString = "SELECT * FROM Books WHERE BookID = ''" DBAdapter = New OleDbDataAdapter(SQLString, DBConnection) DBDataSet = New DataSet DBAdapter.Fill(DBDataSet) Dim AddedRow as DataRow = DBDataSet.Tables(0).NewRow() AddedRow("BookID") = BookID AddedRow("BookType") = BookType AddedRow("BookTitle") = BookTitle AddedRow("BookAuthor") = BookAuthor AddedRow("BookDescription") = BookDescription AddedRow("BookPrice") = BookPrice AddedRow("BookQty") = BookQty

Page 224: Assignment Instructions

AddedRow("BookSale") = BookSale DBDataSet.Tables(0).Rows.Add(AddedRow) DBCommandBuilder = New OleDbCommandBuilder(DBAdapter) DBAdapter.Update(DBDataSet) End Function ...

End ClassListing 11-32. Code for Insert_Book method of DSClass.

Again, a DataSet is created. This time, however, it is not filled with a record from the database; a current record is not being affect. Instead, a null record fills the DataSet to ensure only that the structure of the Books table is represented in the DataSet. A new, empty row (a DataRow object) is created using the Tables Collection's NewRow() method.

Dim AddedRow as DataRow = DBDataSet.Tables(0).NewRow()

Once this empty row is filled with data passed from the DetailsView's insert template, the row is added to the null table and the database is updated with this added information, using the CommandBuilder to create an appropriate INSERT statement.

DBDataSet.Tables(0).Rows.Add(AddedRow)DBCommandBuilder = New OleDbCommandBuilder(DBAdapter)DBAdapter.Update(DBDataSet)

Deleting with a DataSet

The Delete_Book method of DSClass removes an identified record from the database. It receives a BookID key from the ObjectDataSource through its DeleteMethod, the value coming from the displayed record in the DetailsView.

Imports System.Data.OleDbImports System.Data

Public Class DSClass Function Select_BookIDs() ... End Function Function Select_Book (BookID As String) ... End Function Function Update_Book (BookID As String, _ ... ... End Function Function Insert_Book (BookID As String, _ ... ... End Function Function Delete_Book (BookID As String) Dim DBConnection As OleDbConnection Dim DBAdapter As OleDbDataAdapter Dim DBDataSet As DataSet Dim SQLString As String

Page 225: Assignment Instructions

Dim DBCommandBuilder As OleDbCommandBuilder DBConnection = New OleDbConnection( _ "Provider=Microsoft.Jet.OLEDB.4.0;" & _ "Data Source=c:\eCommerce\Databases\BooksDB.mdb") SQLString = "SELECT * FROM Books WHERE BookID = '" & BookID & "'" DBAdapter = New OleDbDataAdapter(SQLString, DBConnection) DBDataSet = New DataSet DBAdapter.Fill(DBDataSet) DBDataSet.Tables(0).Rows(0).Delete() DBCommandBuilder = New OleDbCommandBuilder(DBAdapter) DBAdapter.Update(DBDataSet) End Function End ClassListing 11-33. Code for Delete_Book method of DSClass.

Once again the corresponding record from the database fills a DataSet. The Rows Collection's Delete() method removes the record from the DataSet, and the CommandBuilder creates an appropriate DELETE statement for updating the database to reflect the deleted record.

Web Services

As described so far, the programming of ASP.NET pages is purely a "local" phenomenon. That is, scripts are contained on the same pages that access them, or they are packaged as code-behind files residing on the same local server. The point is, access to your scripts is restricted to the local pages that use them. It is not possible for users to run you scripts except within the confines of your own applications.

Normally, this restricted access to processing routines is not an issue. Your scripts are written for the special purposes of your applications, and there is little reason outsiders need access to them. By the same token, there may be little reason for your applications to access scripts housed on somebody else's server. However, consider a couple of scenarios in which this might not be the case—where remote uses do need access to your scripts or where you need access to remote scripts.

Sharing Server Processes

Imagine, for instance, that you have created a business process, perhaps a code-behind file, that extracts a recordset of your products for display on your e-commerce site. Members of the Web community interested in your products must, of course, come to your site and view these products through your own Web pages. Assume, though, that an enterprising company wishes to serve as a clearinghouse for all such products. Its purpose is to provide a single site where the Web community can window-shop for products from your company and from other companies with like products for sale. This purpose would be served by making your database of products available to the integrator company and accessible through its Web pages. However, you would not want the integrator company to have direct access to your private database. Preferably, you would supply the data in real time, but not compromise access to your server.

A reverse process can also be imagined where you need access to processing routines residing on remote servers. For example, a commercial enterprise often deals with suppliers and distributors of its products. Products are ordered from wholesalers and distributed to retailers through a supply-chain process. Efficiencies are gained by having the various servers manage the transactions automatically, behind the scenes. For instance, your server might run processes housed on a wholesaler's server to place orders when stocks reach certain low levels; or, your

Page 226: Assignment Instructions

server runs processes on a retailer's server to detect inventory levels that automatically trigger sales.

The needs described above are (1) for remote sites to run scripts on your server to provide information to the remote sites or (2) for your site to run scripts on remote sites to provide your site with their information. In either case, the information needs to be shared without either party compromising access to their servers or the information they contain.

This ability to run local processes from remote sites and to run remote processes from the local site is available through Web Services, a technology that is part of ASP.NET. Through Web Services you can make your processing methods available and executable by others on the Web, and you can execute methods on your pages that are made available by others. The general idea is that Web page processing can be handled by your scripts or by scripts housed anywhere on the Web.

The implications of Web Services are enormous. In effect, no longer are you restricted to supplying your own code for your own applications running on your own local server. Web applications can be composed of software components residing anywhere on the Web. In a real sense, the Web itself hosts your applications, with components scattered across multiple servers, your own and potentially many others, all working in concert. In the extreme case, your application may be composed entirely of Web Services supplied by others, all residing on remote servers but used as if they were your own.

Web Services Development Process

In its most simple form, a Web Service is a Visual Basic function that can be called from anywhere on the Web. The function is created and accessed through the following steps.

As the supplier of a Web Service,

Create a Visual Basic class that encapsulates your processing function, much as you would for a local function designed for use from your own Web pages.

Convert the class to a Web Service by including special code to identify it as such, saving the class as a .asmx file in a Web-accessible directory.

Make the URL to your Web Service available to others who wish to use it.

In this way, you can supply access to your information without providing or revealing the private internal processes to access it.

As the consumer of a Web Service,

Create a proxy class to access the remote Web Service. A proxy class is a Visual Basic class file with the .vb extension that makes a call to a Web Service.

Page 227: Assignment Instructions

Place the Visual Basic class file in your app_Code directory for access by any of your pages requiring the service.

Create an ASP.NET page that uses the Web Service proxy class to run the function from the provider site.

In this way, your page runs the remote Web Service as a local function even though it resides at the remote site. That is, you instantiate the remote function just as if it resided on your local server.

Setting up the Development Environment

The development of a Web Service is the easiest part of the process. All that is required is to create a Visual Basic function that returns the information produced by the service. This function is converted into a Web Service and saved as a .asmx file in a Web-accessible directory so that users can get to it. For the examples presented here it is assumed you have created a virtual directory named c:\MyWebServices on your server. Here is where all Web Services that you supply are stored.

The consumer of a Web Service has the most work to do. In order to use the service you need to acquire the following tools and set up the following directory structures.

First, download and intall the .NET Framework Version 2.0 Software Development Kit (SDK) on your development computer. This kit is available athttp://www.microsoft.com/downloads/details.aspx?familyid=fe6f2099-b7b4-4f47-a244-c96d69c35dec&displaylang=en. It includes everything needed to consume Web Services.

Second, set up a folder on your development computer and collect the needed tools for creating a proxy class into this central location for ease of use. Consider creating ac:\WebServices folder on your development computer and copying the following files to it (use Windows Search to locate these files and copy and paste them to the WebServicesfolder):

wsdl.exe - Web Services Description Language program to convert remote .asmx Web Services files into local Visual Basic classes to access the remote Web Service.

cmd.exe - Windows Command Processor needed to run the wsdl.exe converter.

A development c:\WebServices folder should resemble the directory shown below.

Page 228: Assignment Instructions

Figure 11-17. Contents of WebServices folder.

Keep in mind that this discussion of Web Services is from two different viewpoints. As a developer of Web services, you need a Web-accessible folder on your Web server (say,c:\MyWebServices) to place Web Services (.asmx files) that can be accessed by others. As a consumer of remote Web Services supplied by others, you need a folder on your development computer (say, c:\WebServices) to create proxy files to access these remote services. These .vb files are eventually copied to the app_Code folder on your Web server so that your server applications can use these remote services through their local proxy files.

The process of creating and accessing a Web Service is illustrated below.

Page 229: Assignment Instructions

Figure 11-18. Creating and consuming a Web Service.

Creating Web Services

This first example of a Web Service is fairly simple. Suppost you have written a function that reports the number of shopping days until Christmas. This is generally useful knowledge which could be of interest to other sites which might wish to supply this same information to their applications. You decide, then, to package this function as a Web Service and make it available to other sites. They might pay big bucks to access your function rather than having to write their own original code.

Initially, this function is written for local consumption. It is coded for execution from this page to produce the following display.

Number of shopping days until Christmas: 346

Figure 11-19. Using a local function to report number of shopping days until Christmas.<SCRIPT Runat="Server">

Sub Page_Load Page.DataBind()End Sub

Function GetShoppingDays()

Page 230: Assignment Instructions

Dim ShoppingDays As Integer Dim ChristmasDay As String = "12/24/" & DatePart("yyyy", DateString())

ShoppingDays = DateDiff("d", DateString(), ChristmasDay) Return ShoppingDays

End Function

</SCRIPT>

Number of shopping days until Christmas: <asp:Label Text=<%# GetShoppingDays() %> Runat="Server"/>Listing 11-34. Code to call function to report number of shopping days until Christmas.

A Label control includes the call to function GetShoppingDays(). The function binds to the control when the page loads through the Page.DataBind() method. TheGetShoppingDays() function uses the intrinsic Visual Basic DateDiff() function to report the number of day's difference between the current date (DateString()) and the day before Christmas. The current year is extracted from the current date (DatePart()) and appended to "12/24/" so that this date always reflects the current year.

Creating a Web Service File

In order to convert the GetShoppingDays() function into a Web Service it must be recoded as a Visual Basic class and saved as a .asmx file. This file is shown below with the code needed for a Web Service shown in bold.

<%@ WebService Class="ShoppingDays" %>

Imports SystemImports System.WebImports System.Web.ServicesImports System.Xml.Serialization

Public Class ShoppingDays Inherits Web Service

<WebMethod()> Public Function GetShoppingDays() As Integer

Dim NoShoppingDays As Integer Dim ChristmasDay As String = "12/24/" & DatePart("yyyy", DateString())

ShoppingDays = DateDiff("d", DateString(), ChristmasDay) Return NoShoppingDays

End Function

End ClassListing 11-35. Code to convert a function to a Visual Basic class saved as ShoppingDays.asmx.

The <%@ WebService Class="ShoppingDays" %> directive identifies this as a Web Service and supplies the name through which it is accessed. The System, System.Web,System.Web.Services, and System.XML.Serialization namespaces are imported to expose classes that ASP.NET requires to enable Web Services; theMicrosoft.VisualBasic namespace is imported to expose intrinsic Visual Basic functions—DateDiff() and DateString() in the current example.

Page 231: Assignment Instructions

The Public Class named ShoppingDays is created as a wrapper for the function and the function is prefixed with <WebMethod()> to expose the function for access through a Web Service. Without the WebMethod() applied to the function it would be accessible only to local pages.

This file is saved with the .asmx extension inside a folder that is accessible from the Web (c:\MyWebServices).

Testing a Web Service

A Web Service can be tested in the browser by entering its local URL. When the Web Service is accessed from a browser the default Web Service Help page is displayed listing all the methods of the service along with a default form for testing them. Of course, this is not how the Web Service will be ultimately accessed. In the end, it needs to be accessible through scripted function calls. In the meantime, the Web Service can be tested through a simple URL.

Assuming your ShoppingDays.asmx file is saved in your server's c:\MyWebServices folder, a URL is issued to the file and the following page is displayed.

Figure 11-20. Accessing a Web Service through a local URL.

Notice the link at the top of the page to GetShoppingDays. This is a link to the named Web Method contained in the Web Service. All methods that originate as Visual Basic functions and are packaged together as a Web Service are shown as links. There is just one method in this case. A click on this link produces the page shown below.

Page 232: Assignment Instructions

Figure 11-21. Testing a Web Service method.

An "Invoke" button is displayed for testing the chosen Web Method. In this example a click on the button returns the number of shopping days until Christmas. When the button is clicked, a window opens to show the returned value, displayed in XML format.

Page 233: Assignment Instructions

Figure 11-22. Return of method call to Web Service.

At the time this method was invoked there were 232 shopping days remaining until Christmas.

This particular implementation of the Web Service is not particularly useful, although people who need to know this information certainly can use the URL and get back an XML report. For certain Web Services this may be adequate. The principle purpose behind Web Services, however, is application-to-application communications. The goal is to make a Web Service available in a form that can be accessed as a local function by remote Web pages which need access to the service provided.

NOTE: It is assumed that development of a Web Service takes place on the local development computer, http://localhost. Under .NET Framework 2.0 the Web Service's GET, POST, and SOAP protocols necessary to access Web Services remotely are disabled for security reasons. They work only when using a URL to the http://localhostmachine. When accessing a Web Service through a URL to a remote server, or when remote users access your local Web Service through a URL, the following message is produced,"The test form is only available for requests from the local machine."

indicating that permission is not granted to view the service.

However, if you are a provider of Web Services and wish to permit remote URL access to your services, add the following <webServices> section to your application'sweb.config file:

<!-- Web.Config Configuration File -->

<configuration>

<webServices> <protocols>

Page 234: Assignment Instructions

<add name="HttpGet"/> <add name="HttpPost"/> </protocols> </webServices>

</configuration>

Thereafter, remote users can access a local Web Service through a URL to your site and be presented with the same Web Services test page.

Web Service Namespaces

You might notice in Figure 11-20 that a warning is presented that the Web Service is running under the temporary namespace http://tempuri.org/. This is the default namespace when none is given for a service. All Web Services, however, should be assigned unique namespaces so that conflicts do not exist between the same Web Service name appearing at different Web sites. There could be, for example, a Web Service named ShoppingDays provided through another remote server. So that potential like-named services are not in conflict it is advised that a unique namespace be used when creating the service.

To assign a namespace to a Web Service add a <WebService(Namespace:="url")> entry as preface to the class declaration in the .asmx file. The URL can be any address that makes the class name unique among Web sites. The following namespace has been added to the ShoppingDays class.

<WebService(Namespace:="http://www.dradamsweb.com")> Public Class ShoppingDaysListing 11-36. Code to add a namespace to a Web Service.

When creating your own Web Services, it is recommended that the Namespace provided be the URL of your own Web Site and that the class name be unique at your site.

Creating a .asmx file and placing it in a Web-accessible directory is all that is required to supply a Web Service that can be accessed from anywhere on the Web. As a user of someone else's Web Service, however, you need to create a proxy class, a Visual Basic class, to consume the remote service.

Master Pages

One of the main issues in putting together a Web site with multiple pages is coordinating the overall look and feel of the site. Normally, you wish for the site to be visually consistent from page to page and to operate navigationally in a consistent manner. This usually means that you must take great care to duplicate common elements on each page, using identical layout tags, titles, banners, menus, and the like. Although this effort produces the required consistency, it doesn't go unpunished. Any time a change is made to common elements on one page it must be propogated to the remaining pages, a pesky and sometimes error-prone task.

Relief from the tedium of creating and updating the look and behavior of common page elements is at hand with ASP.NET master pages. A master page is a template page that defines the standard visual elements and behaviors that you want for all the pages at your site. Then, during site navigation, individual content pages are merged automatically with this master page to deliver changing content within the framework of the master page. A master page is created one time, and changes to common elements occur in one place.

Page 235: Assignment Instructions

Creating a Master Page

A master page is an ASP.NET page with the extension .master. It is given a layout to be followed on all pages that use this master page as their design template. It includes XHTML elements, static text, and server controls to be shared by all pages. The master page is identified by a special <%@ Master %> directive that replaces the <%@ Page %>directive used for ordinary .aspx pages.

<%@ Master Language="vb" %>Listing 12-1. Directive for master page.

The page itself looks pretty much like an ordinary Web page. It includes <html>, <head>, <body>, and <form> elements, usually along with <table> tags to structure its contents.

A master page also includes one or more <asp:ContentPlaceHolder> controls that define areas on the page where replaceable content appears. These are the areas where individual content pages are displayed when the master page is merged with them to create the final Web page. The general format for a ContentPlaceHolder is shown below.

<asp:ContentPlaceHolder id="id" Runat="Server"/>

Figure 12-1. General format for <asp:ContentPlaceHolder> control.

Once a ContentPlaceHolder is defined for the master page, it becomes the target area for displaying content pages that take on characteristics of the surrounding master page. In the following illustration, a master page named Site.master gives the overall layout of a Web page. Page content is organized inside a table layout with embedded coding for a common title across the top and a common menu of navigation links down the side. These elements appear on all Web pages. There is also a ContentPlaceHolder appearing in one of the layout cells. It is here where content from other pages is displayed when they are merged with the master page.

Page 236: Assignment Instructions

Figure 12-2. Merging content pages with a master page.

Page 237: Assignment Instructions

The above illustration shows a master page with a ContentPlaceHolder (id="CONTENT"). Individual content pages are merged with the master page to take on its common appearance when they are displayed. Although this illustration shows a single ContentPlaceHolder, any number of placeholder controls can be coded on the master page for displaying any number of separate content sections of content pages.

It is important to realize that site navigation, still, is among the content pages. In the above illustration, the common menu on the master page includes links to Page1.aspx,Page2.aspx, and Page3.aspx. When any one of these pages is loaded into the browser, it embeds itself inside the ContentPlaceHolder of the master page, and is surrounded by master page content. The content page—the .aspx page—takes on the trappings of its associated master page.

Creating Content Pages

Content pages are .aspx pages specially coded to work with master pages. A content page is identified with a page directive that links it to a master page.

<%@ Page MasterPageFile="file.master" %>Listing 12-2. Directive for content page.

The name of the master page with which a content page is associated is given by the MasterPageFile specification. Content for the page is enclosed inside <asp:Content>controls whose general format is shown below.

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

...page content

</asp:Content>

Figure 12-3. General format for <asp:Content> control.

ContentPlaceHolderID gives the id of a ContentPlaceHolder control on the master page. It identifies where on the master page this content section should appear. All content—XHTML, text, and server controls—must appear inside the <asp:Content> control. Importantly, <html>, <head>, and <body> tags are not coded on the content page. All that appears inside the Content control is the actual content to be displayed in the associated section of the master page.

Neither is a <form> tag coded on a content page. The <form Runat="Server"> tag is coded on the master page and serves to encompass all content, coded and merged, appearing on the page. This practice is in keeping with the requirement that a Web page can have only one <form Runat="Server"> tag; it is on the master page.

A content page can have any number of <asp:Content> sections. Each section targets a different ContentPlaceHolder control on the master page.

Merging Master and Content Pages

Page 238: Assignment Instructions

The following example demonstates use of the master page and content pages shown in the illustration in Figure 12-2. The visual sense is that the application works like XHTML frames. It is true that it functions like a frameset, but the underlying technology is very different.

Figure 12-4. Example display of master and content pages.

Again, keep in mind that a master page is a "wrapper" surrounding content pages. Therefore, navigation links are always to content pages, not to the master page. Initial loading of the above example is through the Page1.aspx content page, not through the Site.master page. Likewise, menu links are to other content pages. When a content page is accessed, it is merged with the master page to display its content surrounded by master elements.

Using Multiple ContentPlaceHolders

As mentioned, a master page can contain one or more ContentPlaceHolders to be filled with changing content. This does not mean that multiple content pages are needed to fill the multiple placeholders. Rather, all ContentPlaceHolders on the master page require a like number of <asp:Content> sections on all content pages. Below is a simple example to illustrate this point.

Figure 12-5. Example display of master pages with multiple content sections.

The following code is for the above Site.master master page. It includes two <asp:ContentPlaceHolder> controls to be filled with content from each of the content pages that use this master template.

<%@ Master %>

<html><head> <title>Web Site</title></head>

<body><form Runat="Server">

<table border="1"><tr> <td colspan="3"> <div>Site Title</div> </td><tr> <td> <b>Menu</b><br/> <br/> <a href="Page1.aspx">Page 1</a><br/> <a href="Page2.aspx">Page 2</a><br/> <a href="Page3.aspx">Page 3</a><br/> </td> <td> <asp:ContentPlaceHolder id="CONTENT1" Runat="Server"/> </td> <td> <asp:ContentPlaceHolder id="CONTENT2" Runat="Server"/> </td></tr></table>

Page 239: Assignment Instructions

</form></body></html>Listing 12-3. Code for a master page with two content placeholders.

Below is code for one such content page. It contains two <asp:Content> controls to match the two placeholders on the master page.

<%@ Page MasterPageFile="Site.master" %>

<asp:Content ContentPlaceHolderID="CONTENT1" Runat="Server">

<b>Page 1</b><br/><br/>

Content 1 from Page1.aspx

</asp:Content>

<asp:Content ContentPlaceHolderID="CONTENT2" Runat="Server">

<b>Page 1</b><br/><br/>

Content 2 from Page1.aspx

</asp:Content>Listing 12-4. Code for a content page with content for two master placeholders.

The above examples are rather simple illustrations of the idea behind master pages. Remember, though, that the content sections of master pages can contain a full complement of XHTML formatting codes, text, graphics, server controls, and scripts. All of this code is contained inside <asp:Content> sections of the content pages that are integrated with a master page. Master pages are very easy to set up and use, and they reduce the amount of duplicating coding traditionally needed to maintain a consistent look and feel for a Web site.

<asp:Menu> Control

Navigation between pages of a Web site is conventionally set up with standard <a> anchor tags linked to the various pages. Often, this set of tags is enclosed inside a menu section on a page and duplicated across all pages that shares these navigational links. Of course, when using master pages, this set of links can be created one time only on the master page for display on all associated content pages.

It is not unusual that Web site navigation links change over time as new pages are added to a site, old pages are removed, and other pages are shifted around. There is routine need, then, to reconfigure these links. In this sense, navigation links are no different from other content information maintained for the Web site. They represent dynamic data that change over time. As such, there is convenience in being able to maintain this information apart from the pages on which it appears. Maintaining the navigational structure of a Web site in an external data store, rather than hard coded on the page, enhances the ability to keep it up to date.

ASP.NET 2.0 introduces the <asp:Menu> control to help manage the navigational structure of a Web site. It is used to create static links, much like hard coding <a> tags; it also permits

Page 240: Assignment Instructions

maintenance of a site's navigational links in an external file for convenience in changing the links and, effectively, changing the structure of the site.

Creating a Navigation Menu

The navigational structure of a Web site is defined with an <asp:Menu> control in the general format shown in Figure 12-6. Certain of the property settings pertain to static menu items that are always displayed; others pertain to dynamic menu items displayed in "PopOut" submenus. Also, some of the items are pertinent when hard coding menu items, and others when binding the Menu to an external data source.

<asp:Menu id="id" Runat="Server" DataSourceID="id" DisappearAfter="milliseconds" DynamicBottomSeparatorImageUrl="url" DynamicEnableDefaultPopOutImage="False|True" DynamicHorizontalOffset="n" DynamicItemFormatString="{string}" DynamicPopOutImageTextFormatString="{string}" DynamicPopOutImageUrl="url" DynamicTopSeparatorImageUrl="url" DynamicVerticalOffset="n" ItemWrap="False|True" MaximumDynamicDisplayLevels="n" Orientation="Horizontal|Vertical" ScrollDownImageUrl="url" ScrollDownText="string" ScrollUpImageUrl="url" ScrollUpText="string" StaticBottomSeparatorImageUrl="url" StaticDisplayLevels="n" StaticEnableDefaultPopOutImage="False|True" StaticItemFormatString="{string}" StaticPopOutImageTextFormatString="{string}" StaticPopOutImageUrl="url" StaticSubMenuIndent="n" StaticTopSeparatorImageUrl="url" Target="_blank|_parent|_search|_self|_top|framename"

DynamicHoverStyle-property="value"... DynamicMenuItemStyle-property="value"... DynamicSelectedStyle-property="value"... DynamicMenuStyle-property="value"... StaticHoverStyle-property="value"... StaticMenuStyle-property="value"... StaticMenuItemStyle-property="value"... StaticSelectedStyle-property="value"...>

<Items>

<asp:MenuItem

Page 241: Assignment Instructions

DataItem="field" DataPath="path" ImageUrl="url" NavigateUrl="url" PopOutImageUrl="url" Selectable="False|True" Selected="False|True" SeparatorImageUrl="url" Target="_blank|_parent|_search|_self|_top|framename" Text="string" Value="string" ValuePath="path" /> ...

</Items>

<DataBindings>

<asp:MenuItemBinding TextField="field" ValueField="field" NavigateUrlField="field" Target="_blank|_parent|_search|_self|_top|framename" />

</DataBindings>

</asp:Menu>

Figure 12-6. General format for <asp:Menu> control.

Creating a Static Menu

A static menu is created by coding an <asp:Menu> control inside of which is a set of menu items contained inside an <Items> section. Individual menu items are identify by<asp:MenuItem> controls containing link and display information. The following example shows a simple two-level set of menu items appearing on a master page to load content pages.

Figure 12-7. Basic navigation menu.

Code for this set of basic navigation links is shown in Listing 12-5. Each menu item is defined by an <asp:MenuItem> control. To establish submenus, as in this example,<asp:MenuItem> child controls representing submenu items are nested inside their parent <asp:MenuItem> controls.

<%@ Master %>

<html><head> <title>Web Site</title></head>

<body>

Page 242: Assignment Instructions

<form Runat="Server">

<table border="1"><tr> <td style="width:100px; vertical-align:top; background-color:#E0E0E0">

<b>Menu</b><br/>

<asp:Menu id="NavigationMenu" Runat="Server" StaticDisplayLevels="2">

<Items>

<asp:MenuItem Text="Page 1" NavigateUrl="Page1.aspx"> <asp:MenuItem Text="Page 1-1" NavigateUrl="Page1-1.aspx"/> <asp:MenuItem Text="Page 1-2" NavigateUrl="Page1-2.aspx"/> </asp:MenuItem> <asp:MenuItem Text="Page 2" NavigateUrl="Page2.aspx"> <asp:MenuItem Text="Page 2-1" NavigateUrl="Page2-1.aspx"/> <asp:MenuItem Text="Page 2-2" NavigateUrl="Page2-2.aspx"/> </asp:MenuItem> <asp:MenuItem Text="Page 3" NavigateUrl="Page3.aspx"> <asp:MenuItem Text="Page 3-1" NavigateUrl="Page3-1.aspx"/> <asp:MenuItem Text="Page 3-2" NavigateUrl="Page3-2.aspx"/> </asp:MenuItem>

</Items>

</asp:Menu>

</td> <td style="width:400px; vertical-align:top"> <asp:ContentPlaceHolder id="CONTENT" Runat="Server"/> </td></tr></table>

</form></body></html>Listing 12-5. Code for a Menu control appearing on a master page.

Each MenuItem must contain a Text property to produce a clickable link; it contains a NavigateUrl property to give the absolute or relative URL of the page. By default, only the first level (level 0) of menu items is displayed. If preferred, as in this example, the number of levels to display can be set with the StaticDisplayLevels property of the Menu control. If this property is not set, PopOut menus appear as described below.

This menu appears on the master page for display on all content pages. For this reason, the NavigateURl links probably should be full URLs beginning with "http://". If, for example, the content pages appear in different folders or subfolders, then relative links would be different as various of these pages are loaded; on certain pages the links would not be relative to the particular page. If, however, all content pages are in the same directory, then relative links, as in this example, remain constant.

By default, linked pages are loaded into the same window. An alternate is to supply a Target attribute for the Menu using any of the standard values: "_blank" (new window),"_top" (main browser window), "_search" (the windows search pane), "_self" (current window), "_parent" (parent frame of the current frame in a frameset), or a frame name (in a frameset).

Page 243: Assignment Instructions

Using Graphic Links

You can use graphic images in place of text links. The following example employs a set of graphic buttons to replace text labels by coding the ImageUrl property rather than theText property of <asp:MenuItem> controls.

Figure 12-8. Navigation menu using graphic images.

Coding difference between the Menu control for this application and the previous example are shown below. Additional indention of submenus takes place with theStaticSubMenuIndent property and additional vertical spacing between buttons with the VerticalPadding style for menu items.

<asp:Menu id="NavigationMenu" Runat="Server" StaticDisplayLevels="2" StaticSubMenuIndent="20" StaticMenuStyle-VerticalPadding="1">

<Items>

<asp:MenuItem ImageUrl="Page1.gif" NavigateUrl="Page1.aspx"> <asp:MenuItem ImageUrl="Page1-1.gif" NavigateUrl="Page1-1.aspx"/> <asp:MenuItem ImageUrl="Page1-2.gif" NavigateUrl="Page1-2.aspx"/> </asp:MenuItem> <asp:MenuItem ImageUrl="Page2.gif" NavigateUrl="Page2.aspx"> <asp:MenuItem ImageUrl="Page2-1.gif" NavigateUrl="Page2-1.aspx"/> <asp:MenuItem ImageUrl="Page2-2.gif" NavigateUrl="Page2-2.aspx"/> </asp:MenuItem> <asp:MenuItem ImageUrl="Page3.gif" NavigateUrl="Page3.aspx"> <asp:MenuItem ImageUrl="Page3-1.gif" NavigateUrl="Page3-1.aspx"/> <asp:MenuItem ImageUrl="Page3-2.gif" NavigateUrl="Page3-2.aspx"/> </asp:MenuItem>

</Items>

</asp:Menu>Listing 12-6. Code for a Menu control using graphic images as links.

Styling a Static Menu

You can apply various stylings to menu items. In the following example, StaticSubMenuIndent gives the amount of indention of submenus, vertical padding is added between menu items with a StaticMenuStyle-VerticalPadding, and various font and color styles are applied through StaticMenuItemStyle (normal), StaticHoverStyle (mouse over), andStaticSelectedStyle (selected item) settings.

Figure 12-9. Styled navigation menu.

Code for this application is given below. Note that when any of the "hover" styles (StaticHoverStyle) are applied to visually change a menu item on a mouse-over, the <head> tag for the page must include Runat="Server".

<%@ Master %>

<html>

Page 244: Assignment Instructions

<head Runat="Server"> <title>Web Site</title></head>...

<asp:Menu id="NavigationMenu" Runat="Server" StaticDisplayLevels="2" Width="100" StaticSubMenuIndent="10" StaticMenuStyle-VerticalPadding="2" StaticMenuItemStyle-Font-Name="Verdana" StaticMenuItemStyle-Font-Size="9pt" StaticMenuItemStyle-ForeColor="#990000" StaticHoverStyle-BackColor="#707070" StaticHoverStyle-ForeColor="#FFFFFF" StaticSelectedStyle-Font-Bold="True" StaticSelectedStyle-BackColor="#FFFFFF" StaticSelectedStyle-ForeColor="#990000">

<Items>

<asp:MenuItem Text="Page 1" NavigateUrl="Page1.aspx"> <asp:MenuItem Text="Page 1-1" NavigateUrl="Page1-1.aspx"/> <asp:MenuItem Text="Page 1-2" NavigateUrl="Page1-2.aspx"/> </asp:MenuItem> <asp:MenuItem Text="Page 2" NavigateUrl="Page2.aspx"> <asp:MenuItem Text="Page 2-1" NavigateUrl="Page2-1.aspx"/> <asp:MenuItem Text="Page 2-2" NavigateUrl="Page2-2.aspx"/> </asp:MenuItem> <asp:MenuItem Text="Page 3" NavigateUrl="Page3.aspx"> <asp:MenuItem Text="Page 3-1" NavigateUrl="Page3-1.aspx"/> <asp:MenuItem Text="Page 3-2" NavigateUrl="Page3-2.aspx"/> </asp:MenuItem>

</Items>

</asp:Menu>

...</html>Listing 12-7. Code to style a Menu control.

Selected Styles and Master Pages

When an <asp:Menu> control appears on a master page, the "selected" styles (StaticSelectedStyle) do not work as expected. That is, menu styling to indicate a clicked link does not appear when the new content page is loaded. In the above example, selected items are displayed with a white background and bold characters. However, special scripting is needed to produce this effect. The need for this scripting is because selected styles are applied after the page loads. Since the new content page also causes reloading of the master page, prior reference to the selected menu item is no longer available. The new page "doesn't remember" which menu item was clicked on the previous page.

A script to style a selected menu item to correspond with a loaded page is shown below. This Page_Load subprogram appears on the example master page. It gets the path of the current page, extracts the .aspx file name from the path, and programmatically selects the menu item with a matching NavigateUrl property. Once this item is selected, allStaticSelectedStyle properties are automatically applied to the item.

Page 245: Assignment Instructions

Sub Page_Load

'-- Get page name from relative path Dim ThisPage As String = Page.AppRelativeVirtualPath Dim SlashPos As Integer = InStrRev(ThisPage,"/") Dim PageName As String = Right(ThisPage, Len(ThisPage) - SlashPos)

'-- Select menu item with matching NavigateUrl property Dim ParentMenu As MenuItem Dim ChildMenu As MenuItem For Each ParentMenu in NavigationMenu.Items If ParentMenu.NavigateUrl = PageName Then ParentMenu.Selected = True Else For Each ChildMenu in ParentMenu.ChildItems If ChildMenu.NavigateUrl = PageName Then ChildMenu.Selected = True End If Next End If Next

End SubListing 12-8. Programmatically selecting a Menu item.

The relative path to a Web page is contained in that page's AppRelativeVirtualPath property. For instance, when content page Page1-1.aspx is loaded in the above example, thePage.AppRelativeVirtualPath property of this page resembles the following string,

"~/Tutorials/ASPNET2/ASPNET11/Site4/Page1-1.aspx"

giving the relative directory path to the current page. Of interest here is just the name of the page since this is the value of the NavigateUrl property of the menu item that was clicked to load this page.

This page name is extracted from the string by searching the string backwards with the Visual Basic InStrRev() method to locate the final "/" character, beyond which is the page name. Then the Visual Basic Right() method extracts this page name.

Dim ThisPage As String = Page.AppRelativeVirtualPathDim SlashPos As Integer = InStrRev(ThisPage,"/")Dim PageName As String = Right(ThisPage, Len(ThisPage) - SlashPos)Listing 12-9. Extracting a page name from a directory path.

Now it is a matter of iterating the items in the menu to locate the one which has a NavigateUrl property that matches this page name.

Dim ParentMenu As MenuItemDim ChildMenu As MenuItemFor Each ParentMenu in NavigationMenu.Items If ParentMenu.NavigateUrl = PageName Then ParentMenu.Selected = True Else For Each ChildMenu in ParentMenu.ChildItems If ChildMenu.NavigateUrl = PageName Then ChildMenu.Selected = True End If Next End If

Page 246: Assignment Instructions

NextListing 12-10. Iterating items in a Menu list.

A menu control has an Items collection of all root-level (first-level) menu items. If a menu item also has a submeum of items, these are contained in its ChildItems collection. A For Each...Next loop is used to iterate these collections. In the above example, the NavigationMenu.Items collection of root-level Items (ParentMenu) is iterated, within which these Items' collections of ChildItems (ParentMenu.ChildItems) are iterated. A test is made of each menu item to determine if has a NavigateUrl property value matching that of the page name extracted from the page path. If so, then this menu item is selected by setting its Selected="True" property. By doing so, StaticSelectedStyle settings are applied to this menu item, and the item is highlighted to match the displayed page.

PopOut Menus

When the number of StaticDisplayLevels is set to less than the number of menu levels, a PopOut submenu is displayed when the mouse is positioned over a static menu item. In the following example, StaticDisplayLevels="1" displays only the first (level-0) static menu items. Also, an arrowhead ( ) appears next to the menu item to indicate the presence of a PopOut submenu.

<asp:SiteMapDataSource> Control

Coding navigation menu items inside the <asp:Menu> control works fine for small Web sites. Once a site grows to a large hierarchy of pages, though, it becomes more difficult to maintain the menu since it means editing the Web page itself. A better solution is to move the menu items to an external file where the menu can be maintained apart from the Web pages which use it.

A common way to maintain a site menu is as an external XML file. Since XML data has a natural hierarchical structure, it is compatible for representing the structure of a hierarchical menu. An XML menu file can be linked to an <asp:Menu> control and used in the same fashion as if the menu had been coded internally as part of the control.

An external site menu is known as a site map. It is made available to an <asp:Menu> control through an <asp:SiteMapDataSource> control, much in the same way as an AccessDataSource feeds data to a GridView, DetailsView, FormView, or other type of bound information display control. Its general format is shown below.

<asp:SiteMapDataSource id="id" Runat="Server" ShowStartingNode="False|True" SiteMapProvider="provider"/>

Figure 12-13. General format for <asp:SiteMapDataSource> control.

The SiteMapDataSource looks for a special file named web.sitemap as containing an XML-encoded site menu. Then, the SiteMapDataSource control is associated with a Menu control through the latter's DataSourceID property. Outline coding for these two controls is shown below.

<asp:SiteMapDataSource id="SiteMap" Runat="Server/>

Page 247: Assignment Instructions

<asp:Menu id="NavigationMenu" Runat="Server" DataSourceID="SiteMap" ... />Listing 12-12. Linking a SiteMapDataSource to a Menu control.

The Menu control is linked to the SiteMapDataSource control which is linked to the web.sitemap file. The result is that the file's XML menu becomes the Menu control's operational menu. Furthermore, any changes to the menu structure takes place in the web.sitemap file and is automatically reflected in the Menu control.

The web.sitemap File

The web.sitemap file represents a hierarchical menu coded in special XML syntax to produce a Web site map. The following code shows the site map for the menu structure used in previous examples.

<?xml version="1.0" ?><siteMap>

<siteMapNode>

<siteMapNode title="Page 1" url="Page1.aspx"> <siteMapNode title="Page 1-1" url="Page1-1.aspx"/> <siteMapNode title="Page 1-2" url="Page1-2.aspx"/> </siteMapNode>

<siteMapNode title="Page 2" url="Page2.aspx"> <siteMapNode title="Page 2-1" url="Page2-1.aspx"/> <siteMapNode title="Page 2-2" url="Page2-2.aspx"/> </siteMapNode>

<siteMapNode title="Page 3" url="Page3.aspx"> <siteMapNode title="Page 3-1" url="Page3-1.aspx"/> <siteMapNode title="Page 3-2" url="Page3-2.aspx"> <siteMapNode title="Page 3-2-1" url="Page3-2-1.aspx"/> <siteMapNode title="Page 3-2-2" url="Page3-2-2.aspx"/> </siteMapNode> </siteMapNode>

</siteMapNode>

</siteMap>Figure 12-14. Code for example web.sitemap file.

The XML file must contain a <siteMap> tag surrounding all content. Inside this tag is a single <siteMapNode> root-node tag within which additional <siteMapNode> tags represent the hierarchical structure of menu items. These items are given title attributes for the text labels that appear in the menu, along with url attributes giving the relative URL to the page. These URLs must be relative to the root directory (the virtual Web directory) of the Web site because the web.sitemap file must appear in the root Web directory.

Once this web.sitemap file is placed into the root Web directory, it is automatically located by the SiteMapDataSource control to provide a site menu for any <asp:Menu> control that links to it. In

Page 248: Assignment Instructions

the following example, this technique is used to provide menu items for a Menu control that works identically to the previous hard-coded menus.

Figure 12-15. Imported navigation menu from web.sitemap file.

Code for the SiteMapDataSource and Menu controls is shown below. Since the root node of the web.sitemap file is a single enclosing <siteMapNode> tag without menu content, aShowStartingNode="False" property is added to the SiteMapDataSource to skip this empty node when displaying the menu. Menu styles are applied in the same way as if the menu were coded as part of the Menu control.

<asp:SiteMapDataSource id="SiteMap" Runat="Server" ShowStartingNode="False"/>

<asp:Menu id="NavigationMenu" Runat="Server" DataSourceID="SiteMap"

StaticDisplayLevels="1" StaticMenuItemStyle-VerticalPadding="2" StaticMenuItemStyle-Font-Name="Verdana" StaticMenuItemStyle-Font-Size="9pt" StaticMenuItemStyle-ForeColor="#990000" StaticHoverStyle-BackColor="#707070" StaticHoverStyle-ForeColor="#FFFFFF"

DynamicMenuStyle-HorizontalPadding="5" DynamicMenuStyle-VerticalPadding="2" DynamicMenuStyle-BackColor="#E0E0E0" DynamicMenuStyle-ForeColor="#990000" DynamicMenuStyle-BorderWidth="1" DynamicMenuStyle-BorderColor="#C0C0C0" DynamicMenuItemStyle-VerticalPadding="2" DynamicMenuItemStyle-Font-Name="Verdana" DynamicMenuItemStyle-Font-Size="9pt" DynamicMenuItemStyle-ForeColor="#990000" DynamicHoverStyle-BackColor="#707070" DynamicHoverStyle-ForeColor="#FFFFFF"/>Listing 12-13. Setting styles for a site map built from web.sitemap file.

Site Map Providers

There can be only a single web.sitemap file in the root Web directory. This restriction is reasonable since normally there is only one menu for a Web site. However, you may wish to store your XML menu file under a different file name or provide alternative site maps for the same site. When creating sites maps with different file names, they still must be stored in the root Web directory.

Assume, for instance, that instead of using web.sitemap for the above XML menu file you decide to save it as Menu.sitemap. You need to document this change in the web.configfile that also is stored in the Web root directory. Recall that this file is used in companion with script debugging and has the following initial setup.

<!-- Web.Config Configuration File -->

<configuration>

Page 249: Assignment Instructions

<system.web> <customErrors mode="Off"/> </system.web></configuration>Listing 12-14. Code for web.config file.

The need is to define a new site map Provider in addition to the web.sitemap file. The following <sitemap> section is added to the <system.web> section of the web.config file.

<!-- Web.Config Configuration File -->

<configuration> <system.web> <customErrors mode="Off"/>

<siteMap enabled="true"> <providers>

<add name="MenuProvider" type="System.Web.XmlSiteMapProvider, System.Web, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" siteMapFile="Menu.sitemap"/>

</providers> </siteMap>

</system.web></configuration>Listing 12-15. Code for web.config file to define a new Provider for a site map.

A name is assigned to this added provider ("MenuProvider" in this example), and the name of the site map file is given in the siteMapFile attribute ("Menu.sitemap" in this example). The type attribute is coded as shown.

Once this change is made to the web.config file, a SiteMapDataSource control points to this newly defined provider through its SiteMapProvider property, giving its assigned name. The coding change for the above example site to use this provider is shown below.

<asp:SiteMapDataSource id="SiteMap" Runat="Server" SiteMapProvider="MenuProvider" ShowStartingNode="False"/>

<asp:Menu id="NavigationMenu" Runat="Server" DataSourceID="SiteMap" ... />Listing 12-16. Code to use a site map Provider defined in the web.config file.

As long as there is only one site map for a Web site it is not necessary to change from the default web.sitemap file. However, different site map providers are needed if different menu systems are used for different areas of your Web site.

<asp:SiteMapPath> Control

Page 250: Assignment Instructions

One of the great advantages of using an external site map—coded in the default web.sitemap file or in a special site map provider—is that the external file also can be the source of a breadcrumbs navigation trail. You probably have encountered these navigation trails on many sites. They list the trail of pages that you have navigated to reach the current page, and you can backtrack through the list by clicking on the listed links.

A breadcrumbs navigation trail is shown in the top row of following example. Select the path "Page 3   Page 3-2   Page 3-2-1" to view the full extent of the trail. Then click the breadcrumb links to backtrack through the pages of the menu hierarchy.

Figure 12-16. Site navigation menu with breadcrumbs trail.

Coding an <asp:SiteMapPath> Control

A trail of menu links is created by coding an <asp:SiteMapPath> control wherever on the page the breadcrumbs trail is to appear. By default, this control uses the site map contained in the web.sitemap file. The general format for a SiteMapPath control is shown in Figure 12-17.

<asp:SiteMapPath id="id" Runat="Server" ParentLevelsDisplayed="n" PathSeparator="string" RenderCurrentNodeAsLink="False|True" SiteMapProvider="provider" CurrentNodeStyle-property="value"... NodeStyle-property="value"... PathSeparatorStyle-property="value".../>

Figure 12-17. General format for <asp:SiteMapPath> control.

Normally, the full path to the current page is displayed in the breadcrumbs list. If preferred, the list can be shortened by giving the number of page links to display prior to the current page in the ParentLevelsDisplayed property. By default, the greater-than symbol ">" is displayed as the path separator character. A different character string can be specified in the PathSeparator property. By default, the current page reference in the list is not a clickable link, but can be effected as such with the RenderCurrentNodeAsLink="True"setting. Display styles can be set with CurrentNodeStyle, NodeStyle, and PathSeparatorStyle properties.

As noted, the SiteMapPath control references the site map contained in the web.sitemap file. If this provider has been changed to a different file, this fact needs to be indicated by specifying the provider name in the SiteMapProvider property (see the previous tutorial about site map providers).

Example SiteMapPath Code

The following code listing is for the previous example application. This code is for the master page containing Menu, SiteMapDataSource, and SiteMapPath controls. Both the

Page 251: Assignment Instructions

SiteMapDataSource and SiteMapPath controls use a site map coded in the default web.sitemap file appearing in the root Web directory.

<%@ Master %>

<html><head Runat="Server"> <title>Web Site</title></head>

<body><form Runat="Server">

<table border="1"><tr> <td colspan="2">

<asp:SiteMapPath id="SitePath" Runat="Server" PathSeparator=" &#8250; " PathSeparatorStyle-Font-Size="14pt" NodeStyle-Font-Name="Verdana" NodeStyle-Font-Size="8pt" NodeStyle-ForeColor="#990000" CurrentNodeStyle-Font-Underline="False" CurrentNodeStyle-Font-Bold="True" CurrentNodeStyle-ForeColor="#FF0000"/>

</td></tr><tr> <td style="width:100px; vertical-align:top; background-color:#E0E0E0">

<b>Menu</b><br/>

<asp:SiteMapDataSource id="SiteMap" Runat="Server" ShowStartingNode="False"/>

<asp:Menu id="NavigationMenu" Runat="Server" DataSourceID="SiteMap"

StaticDisplayLevels="1" StaticMenuItemStyle-VerticalPadding="2" StaticMenuItemStyle-Font-Name="Verdana" StaticMenuItemStyle-Font-Size="9pt" StaticMenuItemStyle-ForeColor="#990000" StaticHoverStyle-BackColor="#707070" StaticHoverStyle-ForeColor="#FFFFFF"

DynamicMenuStyle-HorizontalPadding="5" DynamicMenuStyle-VerticalPadding="2" DynamicMenuStyle-BackColor="#E0E0E0" DynamicMenuStyle-ForeColor="#990000" DynamicMenuStyle-BorderWidth="1" DynamicMenuStyle-BorderColor="#C0C0C0" DynamicMenuItemStyle-VerticalPadding="2" DynamicMenuItemStyle-Font-Name="Verdana" DynamicMenuItemStyle-Font-Size="9pt" DynamicMenuItemStyle-ForeColor="#990000" DynamicHoverStyle-BackColor="#707070" DynamicHoverStyle-ForeColor="#FFFFFF"/>

</td>

Page 252: Assignment Instructions

<td style="vertical-align:top"> <asp:ContentPlaceHolder id="CONTENT" Runat="Server"/> </td></tr></table>

</form></body></html>Listing 12-17. Code to add a SiteMapPath to a page.

<asp:TreeView> Control

One of the ways to provide a Web site menu is by using an <asp:Menu> control, either hard coded with page links or in connection with an <asp:SiteMapDataSource> control to import an external file of links. This type of menu displays static and PopOut menus for navigating the site. A second way to populate a site menu is with an <asp:TreeView> control that provides the same functionality as a Menu control but in the form of expanding and contracting menu items. An example of a site with a TreeView menu is shown in Figure 12-18. As in previous examples, this menu is coded on the master page for the site.

Figure 12-18. Site navigation menu with <asp:TreeView> control.

Coding an <asp:TreeView> Control

A TreeView is a hierarical arrangement of items that can be expanded and contracted to reveal or hide nodes of the tree. It can be used as a Web site menu or for any other page displays of hierarchical information. The general format for creating an <asp:TreeView> control is shown below.

<asp:TreeView id="id" Runat="Server" CollapseImageToolTip="string" CollapseImageUrl="url" DataSourceID="id" ExpandDepth="n" ExpandImageToolTip="string" ExpandImageUrl="url" ImageSet="Arrows|BulletedList|BulletedList2|BulletedList3| BulletedList4|Contacts|Events|Faq|Inbox|News|Simple| Simple2|Msdn|WindowsHelp|XPFileExplorer" MaxDataBindDepth="n" NodeIndent="n" NodeWrap="False|True" NoExpandImageUrl="url" ShowExpandCollapse="False|True" ShowLines="False|True" Target="_blank|_parent|_search|_self|_top|framename" HoverNodeStyle-property="value"... LeafNodeStyle-property="value"... NodeStyle-property="value"... ParentNodeStyle-property="value"...

Page 253: Assignment Instructions

RootNodeStyle-property="value"... SelectedNodeStyle-property="value"... property="value"...> <Nodes>

<asp:TreeNode ImageToolTip="string" ImageUrl="url" NavigateUrl="url" Target="_blank|_parent|_search|_self|_top|framename" Text="string" /> ... </Nodes>

<DataBindings>

<asp:TreeNodeBinding DataMember="nodeName" NavigateUrlField="field" TextField="field" Target="_blank|_parent|_search|_self|_top|framename" /> </DataBindings>

</asp:TreeView>

Figure 12-19. General format for <asp:TreeView> control.

In its simplest format, a TreeView control contains a <Nodes> section inside of which <asp:TreeNode> controls describe the hierarchical structure of the tree. These items are structured in the same way as <asp:MenuItem> controls are arranged in an <asp:Menu> control. When used as a menu system, TreeNodes have NavigateUrl properties to link to pages and Text properties for the linking text.

Code for the previous example is shown below. The menu is coded on the master page for the site. Because HoverNodeStyle is applied to the links, Runat="Server" must be coded in the <head> tag.

<%@ Master %>

<html><head Runat="Server"> <title>Web Site</title></head><body><form Runat="Server">

<table border="1"><tr> <td style="width:140px; vertical-align:top; background-color:#E0E0E0">

Page 254: Assignment Instructions

<b>Menu</b><br/>

<asp:TreeView id="NavigationTree" Runat="Server" HoverNodeStyle-Font-Bold="True" RootNodeStyle-ForeColor="Blue">

<Nodes>

<asp:TreeNode Text="Page 1" NavigateUrl="Page1.aspx"> <asp:TreeNode Text="Page 1-1" NavigateUrl="Page1-1.aspx"/> <asp:TreeNode Text="Page 1-2" NavigateUrl="Page1-2.aspx"/> </asp:TreeNode>

<asp:TreeNode Text="Page 2" NavigateUrl="Page2.aspx"> <asp:TreeNode Text="Page 2-1" NavigateUrl="Page2-1.aspx"/> <asp:TreeNode Text="Page 2-2" NavigateUrl="Page2-2.aspx"/> </asp:TreeNode>

<asp:TreeNode Text="Page 3" NavigateUrl="Page3.aspx"> <asp:TreeNode Text="Page 3-1" NavigateUrl="Page3-1.aspx"/> <asp:TreeNode Text="Page 3-2" NavigateUrl="Page3-2.aspx"> <asp:TreeNode Text="Page 3-2-1" NavigateUrl="Page3-2-1.aspx"/> <asp:TreeNode Text="Page 3-2-2" NavigateUrl="Page3-2-2.aspx"/> </asp:TreeNode> </asp:TreeNode>

</Nodes>

</asp:TreeView>

</td> <td style="width:350px; vertical-align:top"> <asp:ContentPlaceHolder id="CONTENT" Runat="Server"/> </td></tr></table>

</form></body></html>Listing 12-18. Code to add a TreeView control to a page.

Other style properties can be applied to the nodes. Root nodes are the main nodes, Parent nodes have lower-level Child nodes, and Leaf nodes have no lower-level nodes.

Binding to a SiteMapDataSource

It is not necessary to hard code menu nodes inside a TreeView. Recall from previous tutorials how an <asp:Menu> control can bind to an external SiteMapDataSource to present a site menu without having to code menu items inside the Menu control. In a like manner, a TreeView can bind to a SiteMapDataSource. This data source is the special XML file named web.sitemap stored in the root Web directory. Its coding in service to the previous Menu control is repeated below.

<?xml version="1.0" ?><siteMap>

<siteMapNode>

<siteMapNode title="Page 1" url="Page1.aspx">

Page 255: Assignment Instructions

<siteMapNode title="Page 1-1" url="Page1-1.aspx"/> <siteMapNode title="Page 1-2" url="Page1-2.aspx"/> </siteMapNode>

<siteMapNode title="Page 2" url="Page2.aspx"> <siteMapNode title="Page 2-1" url="Page2-1.aspx"/> <siteMapNode title="Page 2-2" url="Page2-2.aspx"/> </siteMapNode>

<siteMapNode title="Page 3" url="Page3.aspx"> <siteMapNode title="Page 3-1" url="Page3-1.aspx"/> <siteMapNode title="Page 3-2" url="Page3-2.aspx"> <siteMapNode title="Page 3-2-1" url="Page3-2-1.aspx"/> <siteMapNode title="Page 3-2-2" url="Page3-2-2.aspx"/> </siteMapNode> </siteMapNode>

</siteMapNode>

</siteMap>Figure 12-20. Code for web.sitemap file.

This web.sitemap file can provide input to a TreeView to produce a site menu, and to an accompanying SiteMapPath control for a breadcrumbs trail of visited links. All of these controls are put together in the following example.

Figure 12-21. Binding a SiteMapDataSource to a TreeView.

Code for the full master Web page is shown below. The TreeView links to the SiteMapDataSource control through its DataSourceID property. Both the SiteMapDataSource and SiteMapPath controls assume input of the web.sitemap file in the root Web directory.

<%@ Master %>

<html><head Runat="Server"> <title>Web Site</title></head><body><form Runat="Server">

<table border="1"><tr> <td colspan="2">

<asp:SiteMapPath id="SitePath" Runat="Server" PathSeparator=" › " PathSeparatorStyle-Font-Size="14pt" NodeStyle-Font-Name="Verdana" NodeStyle-Font-Size="8pt" NodeStyle-ForeColor="#990000" CurrentNodeStyle-Font-Underline="False" CurrentNodeStyle-Font-Bold="True" CurrentNodeStyle-ForeColor="#FF0000"/>

</td></tr><tr> <td style="width:140px; vertical-align:top; background-color:#E0E0E0">

<b>Menu</b><br/>

Page 256: Assignment Instructions

<asp:SiteMapDataSource id="SiteMap" Runat="Server" ShowStartingNode="False"/>

<asp:TreeView id="NavigationTree" Runat="Server" DataSourceID="SiteMap" ImageSet="Arrows" ExpandDepth="0" ShowLines="True" HoverNodeStyle-Font-Bold="True" RootNodeStyle-ForeColor="Blue"/>

</td> <td style="width:350px; vertical-align:top"> <asp:ContentPlaceHolder id="CONTENT" Runat="Server"/> </td></tr></table>

</form></body></html>Listing 12-19. Code to link a TreeView to SiteMapDataSource and SiteMapPath controls that use the web.sitemap file.

This TreeView has its ImageSet property set to "Arrows" for its menu expansion and contraction graphics; plus, ShowLines="True" is set to display connecting lines between menu items. Also, its ExpandDepth property gives the default level at which menu items are automatically expanded. The setting of "0" means that only the currently selected level of menu items is showing without having to manually expand lower levels.

As explained previously, a different file from the default web.sitemap file can be used as the data Provider for the SiteMapDataSource and SiteMapPath controls. Refer to the discussion on adding a Provider to the web.config file in the SiteMapDataSource tutorial.

Binding to an XML File

A second external source of a site menu is a standard XML file. This file does not have to be in the exacting format of a web.sitemap file. Neither does it have to appear in the root Web directory. Still, it must follow XML coding conventions. The following example site uses an XML file to produce a menu display similar to the above examples. A SiteMapPath control cannot be used to produce a breadcrumbs trail because an XML file is not linked through a SiteMapDataSource control.

Figure 12-22. Binding an XML file to a TreeView.

XML File Format

An XML file for a site menu is in the same general format as the set of Nodes in a TreeView, as shown in the following SiteMap.xml file used in the current example. A single root node (<SiteMap> in this example, although any other node name can be used) encompases the site map. Individual menu items are identified by XML tags (<MenuItem> in this example, although other names can be used) structured according to the parent and child menu structure desired.

Page 257: Assignment Instructions

<?xml version="1.0" ?>

<SiteMenu>

<MenuItem Title="Page 1" URL="Page1.aspx"> <MenuItem Title="Page 1-1" URL="Page1-1.aspx"/> <MenuItem Title="Page 1-2" URL="Page1-2.aspx"/></MenuItem>

<MenuItem Title="Page 2" URL="Page2.aspx"> <MenuItem Title="Page 2-1" URL="Page2-1.aspx"/> <MenuItem Title="Page 2-2" URL="Page2-2.aspx"/></MenuItem>

<MenuItem Title="Page 3" URL="Page3.aspx"> <MenuItem Title="Page 3-1" URL="Page3-1.aspx"/> <MenuItem Title="Page 3-2" URL="Page3-2.aspx"> <MenuItem Title="Page 3-2-1" URL="Page3-2-1.aspx"/> <MenuItem Title="Page 3-2-2" URL="Page3-2-2.aspx"/> </MenuItem></MenuItem>

</SiteMenu>Figure 12-23. XML file used as a site map.

Each menu item tag requires two attributes in order to map properly to the TreeView that displays it. In this example, Title identifies the text label to appear in the menu, and URLgives the link to the page. Any attribute names can be used since these are XML tags with no predefined names or attributes. The URL value is the relative path to the page. In this example, the pages are assumed to be in the same directory as the Web page that displays them; therefore, the path is simply the file name.

XML Data Source

Once the XML menu file is created, it needs to be linked to the TreeView which displays it. This linkage is made through an <asp:XmlDataSource> control tied to the TreeView. The general format for an XmlDataSource control is shown below.

<asp:XmlDataSource id="id" Runat="Server" DataFile="file" XPath="X-Path expression"/>

Figure 12-24. General format for <asp:XmlDataSource> control.

This control has a DataFile property giving the path to an XML file. For the current example, this setting is DataFile="SiteMenu.xml" since the XML file is in the same directory as the master page containing the <asp:TreeView> control to which it relates. The XPath property gives the node path to the first XML node to be displayed in the menu. Since the root node <SiteMenu> is not displayed in the current example, the path to the first <MenuItem> node is XPath="SiteMenu/MenuItem".

Shown below is how the XmlDataSource and TreeView controls are linked together so that the XML file represented by the former control supples data for the menu displayed by the latter control.

Page 258: Assignment Instructions

<asp:XmlDataSource id="XMLMenuSource" Runat="Server" DataFile="SiteMenu.xml" XPath="SiteMenu/MenuItem"/>

<asp:TreeView id="NavigationTree" Runat="Server" DataSourceID="XMLMenuSource" ...</asp:TreeView>Listing 12-20. Linking a TreeView to an XML file used as a site map.

Data Bindings

Since the node names and attribute names appearing in the XML file are not predefined, there is the need to translate between these names and expected Text and NavigateUrlproperty names used by the TreeView to produce a menu. This translation process is governed by the <DataBindings> section of the TreeView. This section is shown below for the current example TreeView. Notice that the DataBinding section along with the XmlDataSource replace the Nodes section of the TreeView in the previous example.

<asp:XmlDataSource id="XMLMenuSource" Runat="Server" DataFile="SiteMenu.xml" XPath="SiteMenu/MenuItem"/>

<asp:TreeView id="NavigationTree" Runat="Server" DataSourceID="XMLMenuSource" ImageSet="Arrows" HoverNodeStyle-Font-Bold="True" RootNodeStyle-ForeColor="Blue">

<DataBindings> <asp:TreeNodeBinding DataMember="MenuItem" TextField="Title" NavigateUrlField="URL"/> </DataBindings>

</asp:TreeView>Listing 12-21. Data bindings to link a TreeView to an XML file used as a site map.

The DataBindings section encloses one or more <asp:TreeNodeBinding> controls to translate between node and attribute names in the XML file and expected Text andNavigateUrl values for a TreeView. The DataMember property gives the XML node name of a menu item (MenuItem in this example); TextField identifies the XML node attribute used as the Text property of a menu item (Title in this example); and NavigateUrlField identifies the XML node attribute used as the NavigateUrl property of a menu item (URLin this example).

<asp:MultiView> Control

The <asp:MultiView> control is a container for a group of <asp:View> controls, where each View control is a visible portion of a page to display. Each View represents a series of steps in an application that otherwise would require coding of separate pages. The Views are revealed according to user preferences or in a scripted sequence. The MultiView is particularly applicable to mobile devices with small screens that cannot render a page in full but in incremental steps revealing a portion of the page at a time.

Page 259: Assignment Instructions

<asp:MultiView id="id" Runat="Server" ActiveViewIndex="viewindex">

<asp:View id="id" Runat="Server"

...Controls, text, and XHTML

</asp:View> ...

</asp:MultiView>

Figure 12-25. General format for <asp:MultiView> control.

The MultiView control encloses one or more View controls, where each View is a portion of the page that is visible. Views are indexed in their coded order of appearance beginning with 0. The MultiView's ActiveViewIndex property can be set to indicate the starting View when the page opens. Also, Views can be revealed in script by assigning an index value to this property or by calling the MultiView's SetActiveView(viewID) method giving the id of a View to display.

Normally, Views are rendered in their index sequence by supplying Button or LinkButton controls to navigate forwards and backwards from one View to the next. These buttons are coded inside a View control and are configured as command buttons. They are given the special CommandNames "PrevView" and "NextView" by which the MultiView control automatically navigates the Views. The following pair of buttons, for example, automatically reveals the previous and next Views based on their index numbers. No scripting is required.

<asp:View id="View3" Runat="Server">

...displayed content

<asp:Button Runat="Server" Text="Prev" CommandName="PrevView"/>

<asp:Button Runat="Server" Text="Next" CommandName="NextView"/>

</asp:View>Listing 12-22. Code to navigate page views.

If script processing is to take place when a View is revealed, a button's CommandName can be replaced by an OnClick event handler to call a subprogram, overriding the normal sequencing of Views. The subprogram then returns to the sequence by calling the MultiView's SetActiveView() method to reveal the next View.

Displaying a MultiView Page

A MultiView can be used for any application that reveals page content in a sequence. That content could be a series of instructions, a sequence of steps, a pattern of discussion, a slide show, or about any other content revealed a portion at a time. The following page, for example, contains a MultiView control enclosing seven View controls that reveals a set of questions in

Page 260: Assignment Instructions

sequence. The visible sense is that seven different pages are displayed; however, there is only a single page whose Views are hidden and revealed as buttons are clicked.

Figure 12-26. Displaying a MultiView.

Code for the above application is shown below. Apart from the heading, the entire page is enclosed in an <asp:MultiView> control with separate <asp:View> controls describing the content displays. The MultiView has its ActiveViewIndex initialized to 0 to display the first of the Views when the page opens.

<html><body><form Runat="Server">

<h3>Account Application</h3>

<asp:MultiView id="Questionnaire" ActiveViewIndex="0" Runat="Server">

<!-- VIEW 1 --><asp:View id="View1" Runat="Server">

<p>You can apply for an account to receive special access to information at this site by filling out the following form. </p>

<asp:Panel Style="position:absolute; top:210px" Runat="Server"> <asp:Button Runat="Server" Text="Begin" CommandName="NextView" Width="50"/> </asp:Panel>

</asp:View>

<!-- VIEW 2 --><asp:View id="View2" Runat="Server">

<p>Please enter your email address:</p> Email: <asp:TextBox id="Email" Width="200" Runat="Server"/><br/>

<asp:Panel Style="position:absolute; top:210px" Runat="Server"> <asp:Button Runat="Server" Text="Prev" CommandName="PrevView" Width="50"/> <asp:Button Runat="Server" Text="Next" CommandName="NextView" Width="50"/> </asp:Panel>

</asp:View>

<!-- VIEW 3 --><asp:View id="View3" Runat="Server">

<p>What is your occupation?</p> <asp:DropDownList id="Occupation" Runat="Server"> <asp:ListItem Text="IT Professional"/> <asp:ListItem Text="Student"/> <asp:ListItem Text="Educator"/> <asp:ListItem Text="Other"/>

Page 261: Assignment Instructions

</asp:DropDownList><br/>

<asp:Panel Style="position:absolute; top:210px" Runat="Server"> <asp:Button Runat="Server" Text="Prev" CommandName="PrevView" Width="50"/> <asp:Button Runat="Server" Text="Next" CommandName="NextView" Width="50"/> </asp:Panel>

</asp:View>

<!-- VIEW 4 --><asp:View id="View4" Runat="Server">

What type of applications do you primarily develop?<br/> <asp:RadioButtonList id="Applications" Runat="Server"> <asp:ListItem Text="ASP.NET 2.0"/> <asp:ListItem Text="ASP.NET 1.1"/> <asp:ListItem Text="Windows"/> <asp:ListItem Text="Mobile"/> </asp:RadioButtonList><br/>

<asp:Panel Style="position:absolute; top:210px" Runat="Server"> <asp:Button Runat="Server" Text="Prev" CommandName="PrevView" Width="50"/> <asp:Button Runat="Server" Text="Next" CommandName="NextView" Width="50"/> </asp:Panel>

</asp:View>

<!-- VIEW 5 --><asp:View id="View5" Runat="Server">

Which programming languages do you use (check all that apply)?<br/> <asp:CheckBoxList id="Languages" Runat="Server"> <asp:ListItem Text="ASP"/> <asp:ListItem Text="Visual Basic"/> <asp:ListItem Text="C#"/> <asp:ListItem Text="C++"/> </asp:CheckBoxList><br/>

<asp:Panel Style="position:absolute; top:210px" Runat="Server"> <asp:Button Runat="Server" Text="Prev" CommandName="PrevView" Width="50"/> <asp:Button Runat="Server" Text="Next" OnClick="Verify_Form" Width="50"/> </asp:Panel>

</asp:View>

Page 262: Assignment Instructions

<!-- VIEW 6 --><asp:View id="View6" Runat="Server">

<p>Verify the following information and click the "Submit" button to submit your application.</p>

<table border="0" cellpadding="0"> <tr> <td><b>Email:</b></td> <td><asp:Label id="EmailOut" Runat="Server"/></td> </tr> <tr> <td><b>Occupation:</b></td> <td><asp:Label id="OccupationOut" Runat="Server"/></td> </tr> <tr> <td><b>Applications:</b></td> <td><asp:Label id="ApplicationsOut" Runat="Server"/></td> </tr> <tr> <td><b>Languages:</b></td> <td><asp:Label id="LanguagesOut" Runat="Server"/></td> </tr> </table>

<asp:Panel Style="position:absolute; top:210px" Runat="Server"> <asp:Button Runat="Server" Text="Prev" CommandName="PrevView" Width="50"/> <asp:Button Runat="Server" Text="Submit" OnClick="Submit_Form" Width="50"/> </asp:Panel>

</asp:View>

<!-- VIEW 7 --><asp:View id="View7" Runat="Server">

<p><asp:Label id="ExitMsg" Runat="Server"/></p>

<asp:Panel Style="position:absolute; top:210px" Runat="Server"> <asp:Button Runat="Server" Text="Restart" CommandName="SwitchViewByID" CommandArgument="View1" Width="50"/> </asp:Panel>

</asp:View>

</asp:MultiView>

</form></body></html>Listing 12-23. Code for MultiView.

Each View includes Button controls for navigating the Views. The CommandNames "PrevView" and "NextView" automatically reveal the previous and next

Page 263: Assignment Instructions

Views in sequence. Notice that these buttons are enclosed inside a Panel control with CSS style settings to position them at an exact location on the page.

Navigation is in sequence until the fifth View (id="View5"). Here, the "Next" button includes an OnClick event handler to call subprogram Verify_Form to summarize entered information prior to the next View (id="View6") where the summary is displayed. Code for this subprogram is shown below.

Sub Verify_Form (Src As Object, Args As EventArgs)

If Email.Text = "" Then EmailOut.Text = "Missing" Else EmailOut.Text = Email.Text End If

OccupationOut.Text = Occupation.SelectedValue

If Applications.SelectedValue = "" Then ApplicationsOut.Text = "Missing" Else ApplicationsOut.Text = Applications.SelectedValue End If

LanguagesOut.Text = "" Dim Item As ListItem For Each Item In Languages.Items If Item.Selected Then LanguagesOut.Text &= Item.Text & ", " End If Next If LanguagesOut.Text = "" Then LanguagesOut.Text = "Missing" Else LanguagesOut.Text = Left(LanguagesOut.Text, Len(LanguagesOut.Text) - 2) End If

Questionnaire.SetActiveView(View6)

End SubListing 12-24. Code to verify and summarized entered information.

All controls on the page are accessible by scripts even though they may be hidden by their enclosing Views. For example, the Email TextBox displayed in View1 can have its Textproperty tested even though View1 is no longer visible. Also, a message can be assigned to the Text property of the EmailOut Label even though it is currenly hidden in View6.

The subprogram simply checks for entered values in the different Views and displays entered data or a "Missing" message in the summary table in View6. Once this output is formatted, View6 is revealed by applying the MultiView's SetActiveView() method: Questionnaire.SetActiveView(View6). Note that the id of the set View is not enclosed in quotes. The method's parameter must be of a View data type, not a string.

Similar button techniques are used in View6 to call subprogram Submit_Form to perform processing and reestablish the navigation sequence of Views.

Sub Submit_Form (Src As Object, Args As EventArgs)

Page 264: Assignment Instructions

'-- Insert code to save form information

ExitMsg.Text = "Thank you for your application." Questionnaire.SetActiveView(View7)

End SubListing 12-25. Subprogram called from a View.

Link buttons with the special command names "PrevView" and "NextView" automatically navigate to previous and next Views through the MultiView's built-in navigation mechanism. At times, though, you may need to alter this sequence to reveal a View that is not previous or next in sequence. In View7 of the above example (the final View), a button is provided to return to View1 (the first View). Neither a previous nor next navigation is appropriate. The need is to jump immediately to View1.

<asp:Button Runat="Server" Text="Restart" CommandName="SwitchViewByID" CommandArgument="View1" Width="50"/>Listing 12-26. Switching views with View names.

By coding the special CommandName="SwitchViewByID" property and by naming a View in the accompanying CommandArgument, navigation is directly to the named View. No scripting is required. An alternative, of course, is to call a subprogram that changes Views with the MultiView's SetActiveView() method; however, coding the command properties of the button avoids this extra scripting.

External Buttons for View Navigation

In the previous example, buttons are coded inside the individual Views in order to take advantage of built-in MultiView navigation. This technique works fine except that it requires duplication of the same buttons in most, if not all, Views. A slightly different technique can be used when identical buttons are shared by all Views—when creating a "menu system" for navigation rather than a linked sequence of Views. In the following example, a common set of buttons link to separate views.

Figure 12-27. Displaying a MultiView with a common menu of external buttons.

Code for the MultiView and the first of the six Views is shown below. Notice that no button links are coded inside any of the Views. Rather, all buttons appear outside the MultiView in a positioned Panel that is common to all Views.

<asp:MultiView id="ValidationViews" ActiveViewIndex="0" Runat="Server">

<!-- VIEW 1 --><asp:View id="View1" Runat="Server">

<h3>Validation Controls</h3>

<p>For common types of data validation ASP.NET provides a set of validation controls. These validators can test for missing values, comparison values, values within a range, and other forms of data to ensure that proper data is supplied to processing scripts. These controls are associated with TextBox controls and perform their tests automatically

Page 265: Assignment Instructions

when Button, LinkButton, or ImageButton controls are clicked to call subprograms for processing. If a validation test is not met, the validator displays an error message to call attention to this fact, and the user is given the chance to reenter valid data in the associated TextBox.</p>

</asp:View>

<!-- VIEW 2 --> ...<!-- VIEW 3 --> ...<!-- VIEW 4 --> ...<!-- VIEW 5 --> ...<!-- VIEW 6 --> ...

</asp:MultiView>

<!-- Navigation Buttons -><asp:Panel Width="100%" BackColor="#C0C0C0" Runat="Server" Style="text-align:center; position:absolute; top:325px; padding:5px">

<asp:Button Runat="Server" Text="Validation Controls" CommandName="View1" OnCommand="Change_View" Width="150"/><asp:Button Runat="Server" Text="RequiredFieldValidator" CommandName="View2" OnCommand="Change_View" Width="150"/><asp:Button Runat="Server" Text="RangeValidator" CommandName="View3" OnCommand="Change_View" Width="150"/><br/><asp:Button Runat="Server" Text="CompareValidator" CommandName="View4" OnCommand="Change_View" Width="150"/><asp:Button Runat="Server" Text="CustomValidator" CommandName="View5" OnCommand="Change_View" Width="150"/><asp:Button Runat="Server" Text="ValidationSummary" CommandName="View6" OnCommand="Change_View" Width="150"/>

</asp:Panel>Listing 12-27. Code for a MultiView with a common set of external buttons.

Since the navigation buttons are not coded inside the Views, automatic navigation is not available to reveal Views with "PrevView," "NextView," or "SwitchViewByID" command names. Instead, these command buttons call the common subprogram Change_View to handle navigation. Each button passes a CommandName string giving the View to reveal.

Page 266: Assignment Instructions

Sub Change_View (Src As Object, Args As CommandEventArgs)

Select Case (Args.CommandName) Case "View1" ValidationViews.SetActiveView(View1) Case "View2" ValidationViews.SetActiveView(View2) Case "View3" ValidationViews.SetActiveView(View3) Case "View4" ValidationViews.SetActiveView(View4) Case "View5" ValidationViews.SetActiveView(View5) Case "View6" ValidationViews.SetActiveView(View6) End Select

End SubListing 12-28. Subprogram to switch views through external command buttons.

A Visual Basic Select Case structure determines the CommandName string that is passed to the subprogram and sets the active view to the corresponding View name through the MultiView's SetActiveView() method.

Incidentally, as might seem possible, you cannot set the active view directly from the CommandName with a statement such as

ValidationViews.SetActiveView(Args.CommandName)

The reason is that the CommandName is a string value rather than a View data type expected as the SetActiveView() parameter. However, you can use the CommandName string to locate the <asp:View> control with a matching id and assign this found control as the view.

Dim FoundView As View = ValidationViews.FindControl(Args.CommandName)ValidationViews.SetActiveView(FoundView)

This code can replace the Select Case statement in the above example.

<asp:Wizard> Control

The <asp:Wizard> control is a container for a group of <asp:WizardStep> controls, where each WizardStep is a visible portion of a page to display. Like the MultiView control, the Wizard control reveals individual screens coded on a single page. Whereas the MultiView is especially designed for use on mobile devices with restricted screen sizes, the Wizard is designed to manage a set of data entry screens. Various input controls such as textboxes, radio buttons, checkboxes, and drop-down lists can be packaged as separate screens in a sequence of data entry steps. At the completion of this process, entered data can be saved to an external data source as captured information.

The Wizard control has a more elaborate set of properties than a MultiView, and it supports several events for scripting the data entry process. The general format for a Wizard and its WizardSteps is shown in Figure 12-28.

Page 267: Assignment Instructions

<asp:Wizard id="id" Runat="Server" ActiveStepIndex="stepindex" CancelButtonImageUrl="url" CancelButtonText="string" CancelButtonType="Button|LinkButton" CancelDestinationPageUrl="url" CellPadding="n" CellSpacing="n" DisplayCancelButton="False|True" DisplaySideBar="False|True" FinishDestinationPageUrl="url" FinishCompleteButtonImageUrl="url" FinishCompleteButtonText="string" FinishCompleteButtonType="Button|LinkButton" FinishPreviousButtonImageUrl="url" FinishPreviousButtonText="string" FinishPreviousButtonType="Button|LinkButton" HeaderText="string" StepNextButtonImageUrl="url" StepNextButtonText="string" StepNextButtonType="Button|LinkButton" StepPreviousButtonImageUrl="url" StepPreviousButtonText="string" StepPreviousButtonType="Button|LinkButton" StartNextButtonImageUrl="url" StartNextButtonText="string" StartNextButtonType="Button|LinkButton" CancelButtonStyle-property="value" FinishCompleteButtonStyle-property="value" FinishPreviousButtonStyle-property="value" NavigationButtonStyle-property="value" NavigationStyle-property="value" StepNextButtonStyle-property="value" StepPreviousButtonStyle-property="value" SideBarStyle-property="value" StartNextButtonStyle-property="value" StepStyle-property="value" property="value"...>

<HeaderTemplate> ...controls, XHTML, and text </HeaderTemplate> <StartNavigationTemplate> ...controls, XHTML, and text </StartNavigationTemplate> <StepNavigationTemplate> ...controls, XHTML, and text </StepNavigationTemplate> <FinishNavigationTemplate> ...controls, XHTML, and text

Page 268: Assignment Instructions

</FinishNavigationTemplate> <SideBarTemplate> <asp:DataList id="SideBarList" Runat="Server"> <ItemTemplate> <asp:Button id="SideBarButton" Runat="Server"/> or <asp:LinkButton id="SideBarButton" Runat="Server"/> </ItemTemplate> </asp:DataList> </SideBarTemplate> <WizardSteps> <asp:WizardStep id="id" Runat="Server" AllowReturn="False|True" StepType="Start|Step|Finish|Complete" Title="string" > ...controls, XHTML, and text </asp:WizardStep> ...

</WizardSteps>

</asp:Wizard>

Figure 12-28. General format for <asp:Wizard> control.

Wizard Layout

It is helpful to keep in mind the general layout of a Wizard and its WizardSteps. The figure below shows the four sections that can be displayed by coding an <asp:Wizard> control enclosing five <asp:WizardStep> controls. No special layout or styling is applied except for the dotted borders indicating the Wizard and its four control sections.

Figure 12-29. Displaying a default Wizard.

A SideBar menu appears along the left side of the display. It presents a menu of links to the WizardSteps that comprise the sequence of page displays. A SideBar is optional and can be suppressed by coding the Wizard's DisplaySideBar="False" property. The SideBar is centered vertically within the total height of the Wizard.

A Header appears at the top of each WizardStep display. The easiest way to display a common heading is by coding the Wizard's HeaderText property as is done above. If no header is provided, none is displayed.

A WizardStep is a content display area coded inside an <asp:WizardStep> control. One or more WizardStep controls are coded for individual step displays. Four types of steps are recognized and coded as the control's StepType property. The single "Start" step is the first screen displayed; it contains a single button to navigate to the next step. One or more "Step" types are intermediate screens displayed in the order in which their <asp:WizardStep> controls are coded; they contain a pair of buttons to navigate to previous and next steps. A single "Finish" step is the last of the intermediate steps; it contains a button to navigate to the previous step and a

Page 269: Assignment Instructions

button to navigate to the last step. The"Complete" step is the last screen displayed; it does not produce navigation buttons.

An id for a step is optional but must be included if the step is referenced in script. The Title property gives a text string that labels the step and automatically appears in the SideBar menu; all steps are displayed in the menu except for the "Complete" step. In the absence of a Title, the id is used to label the step in the menu. TheAllowReturn="False" property is coded for a WizardStep in order to disallow return to this step following its initial display.

Below the WizardSteps is a Navigation area containing buttons to navigate to previous and next WizardSteps. The Navigation area recognizes three types of button sets. AStartNavigation button set presents a single button linking to the next step; this button is displayed along with the StepType="Start" WizardStep. A pair of "Previous" and "Next"StepNavigation buttons are displayed along with all StepType="Step" WizardSteps. A FinishNavigation button displays a "Previous" and "Finish" button; these are displayed along with the StepType="Finish" WizardStep. The Finish button automatically links to the StepType="Complete" step unless overridden by the FinishDestinationPageUrlproperty giving a different final page. The StepType="Complete" step does not display navigation buttons.

A "Cancel" button can be added to all steps by coding the Wizard's DisplayCancelButton="True" property. No change is made in the step being displayed when this button is clicked. It must be scripted to perform whatever processing is desired when the button is clicked.

Shown below is code for the Wizard display shown in Figure 12-29. As you can see, very little code and no scripting is needed to produce a functional Wizard. This framework can be used for any number of sequenced displays simply by changing the content appearing in the WizardStep controls.

<h3>Default Wizard Layout</h3>

<asp:Wizard id="Wizard1" Runat="Server" HeaderText="Header">

<WizardSteps>

<asp:WizardStep Title="Start" StepType="Start" Runat="Server"> This is the <b>Start</b> WizardStep. <br/><br/><br/> </asp:WizardStep>

<asp:WizardStep Title="Step 1" StepType="Step" Runat="Server"> This is <b>Step</b> 1 WizardStep. <br/><br/> </asp:WizardStep>

<asp:WizardStep Title="Step 2" StepType="Step" Runat="Server"> This is <b>Step</b> 2 WizardStep. <br/> </asp:WizardStep>

<asp:WizardStep Title="Step 3" StepType="Step" Runat="Server"> This is <b>Step</b> 3 WizardStep. <br/><br/> </asp:WizardStep>

Page 270: Assignment Instructions

<asp:WizardStep Title="Finish" StepType="Finish" Runat="Server"> This is the <b>Finish</b> WizardStep. <br/><br/><br/> </asp:WizardStep>

<asp:WizardStep StepType="Complete" Runat="Server"> This is the <b>Complete</b> WizardStep. <br/><br/> </asp:WizardStep>

</WizardSteps>

</asp:Wizard>Listing 12-29. Code for a default Wizard.

Basic Layout Formatting

As mentioned, the SideBar menu is aligned on the vertical center of the height of the entire Wizard. Since the height of the WizardStep area varies depending on the amount of content it contains, the menu often slides up and down the page to keep itself center-aligned on the Wizard as you navigate from step to step. The navigation area, as well, changes its position to immediately follow the content area. Also, default buttons are aligned to the right of the Navigation area and change their sizes depending on their text labels. These movements and size changes are apparent in the previous example where the amount of displayed content changes from step to step.

To avoid this annoyance of moving controls and changing sizes, a few simple style settings can be applied to the Wizard. The following example sets specific sizes and positions of controls to keep the Wizard stable while the content changes.

Figure 12-30. Displaying a Wizard with fixed-sized header, menu, and content areas.

The following code additions are made to the Wizard control to effect the above formatting.

<asp:Wizard id="Wizard2" Runat="Server" HeaderText="Header"

HeaderStyle-Width="300"

SideBarStyle-Width="70" SideBarStyle-VerticalAlign="Top"

StepStyle-Width="300" StepStyle-Height="100" StepStyle-VerticalAlign="Top"

NavigationStyle-Width="300" NavigationStyle-HorizontalAlign="Left" NavigationButtonStyle-Width="70">

...</asp:Wizard>Listing 12-30. Code for a Wizard with fixed-sized header, menu, and content areas.

The SideBar area is given a fixed width and is vertically aligned at the top of the Wizard. The Header, WizardStep, and Navigation areas are given fixed widths, and the WizardStep area is

Page 271: Assignment Instructions

given a fixed height, with content aligned at the top of the area. The Navigation section sizes all buttons the same and aligns them to the left of the area.

Using Templates

Much of the formatting and styling of a Wizard can be accomplish with server style properties. For additional layout specifications, you can code templates to describe in exact detail how the various sections should appear, as is done in the following example output and code. WizardStep controls are coded the same as in previous examples with one exception. They are surrounded by a Panel that applies 5 pixels of padding surrounding their content.

Figure 12-31. Displaying a Wizard with templates.<asp:Wizard id="Wizard3" Runat="Server" SideBarStyle-Width="70" SideBarStyle-VerticalAlign="Top" SideBarStyle-BackColor="#E0E0E0" StepStyle-Width="300" StepStyle-Height="130" StepStyle-VerticalAlign="Top" HeaderStyle-Height="27" HeaderStyle-BackColor="#E0E0E0" NavigationStyle-Height="27" NavigationStyle-BackColor="#E0E0E0" NavigationStyle-HorizontalAlign="Left"> <SideBarTemplate><br/> <asp:DataList id="SideBarList" Runat="Server" HeaderStyle-Font-Bold="True" HeaderStyle-HorizontalAlign="Center"> <HeaderTemplate> Steps </HeaderTemplate> <ItemTemplate> <asp:Button id="SideBarButton" CommandName="MoveTo" Font-Size="8pt" Width="60" Runat="Server"/> </ItemTemplate> </asp:DataList> </SideBarTemplate> <HeaderTemplate> <asp:Image ImageUrl="Wizard.gif" ImageAlign="AbsMiddle" Runat="Server"/> <asp:Label Text="Wizard Header" Runat="Server" Font-Size="12pt" Font-Bold="True"/> </HeaderTemplate> <StartNavigationTemplate> <asp:Button id="StartNextButton" CommandName="MoveNext" Runat="Server" Text="Next" Font-Size="8pt" Width="60"/> </StartNavigationTemplate> <StepNavigationTemplate> <asp:Button id="StepPreviousButton" CommandName="MovePrevious" Text="Prev" Font-Size="8pt" Width="60" Runat="Server"/> <asp:Button id="StepNextButton" CommandName="MoveNext" Text="Next" Font-Size="8pt" Width="60" Runat="Server"/> </StepNavigationTemplate> <FinishNavigationTemplate> <asp:Button CommandName="MovePrevious" Text="Prev" Runat="Server"

Page 272: Assignment Instructions

Font-Size="8pt" Width="60"/> <asp:Button CommandName="MoveComplete" Text="Finish" Runat="Server" Font-Size="8pt" Width="60"/> </FinishNavigationTemplate> ...</asp:Wizard>Listing 12-31. Code for a Wizard with templates.

The SideBarMenu is formatted by coding a <SideBarTemplate>. The template must contain an <asp:DataList> control enclosing a single Button or LinkButton so the Wizard can properly format the links. The DataList must be assigned id="SideBarList", and the enclosed button must be assigned id="SideBarButton" and includeCommandName="MoveTo". In this example, a <HeaderTemplate> is included in the DataList to supply a heading for the column of links. When Buttons are used as links, the final completion step is represented by a button "placeholder," since a link is never provided to this step. When LinkButtons are used, no such "ghost" link appears.

A Header is formatted with a <HeaderTemplate>. It can contain fixed text, XHTML, images, and server controls that are common to all steps.

The Navigation area is described by three templates. A <StartNavigationTemplate> describes and formats the button displayed when the StepType="Start" WizardStep is displayed. Either a Button or LinkButton is coded in the template along with other desired text, XHMTL, and controls. The button must contain CommandName="MoveNext" to start the paging sequence.

A <StepNavigationTemplate> formats buttons linking to the previous and next steps. This pair of Buttons or LinkButtons is displayed for all StepType="Step" WizardSteps. The buttons must include CommandName="MovePrevious" and CommandName="MoveNext" in order to function properly.

A <FinishNavigationTemplate> formats buttons for the final step in the sequence, when the StepType="Finish" WizardStep is displayed. Button command names must beCommandName="MovePrevious" and CommandName="MoveComplete".

Templates may supply part of the layout and styling for their Wizard sections. This styling can be supplemented with server style properties. When both a server style property and a template is provided for the same property, the template style takes precedence.

In the following example, Wizard properties are substituted for templates to duplicate as closely as possible previous layout and styling. Notice that SideBar buttons cannot be changed to Buttons using style properties. Also, the graphic image cannot be displayed in the header without a HeaderTemplate. Otherwise, the same general layout can be achieved without templates.

Figure 12-32. Displaying a Wizard using server properties.<asp:Wizard id="Wizard4" Runat="Server" HeaderText="WizardHeader" HeaderStyle-Font-Bold="True" HeaderStyle-Width="300" HeaderStyle-Height="30" HeaderStyle-BackColor="#E0E0E0" StepStyle-Width="300" StepStyle-Height="130" StepStyle-VerticalAlign="Top"

Page 273: Assignment Instructions

NavigationStyle-BackColor="#E0E0E0" NavigationStyle-HorizontalAlign="Left" NavigationButtonStyle-Width="60" NavigationButtonStyle-Font-Size="8pt" SideBarStyle-Width="70" SideBarStyle-VerticalAlign="Top" SideBarStyle-BackColor="#E0E0E0">

...</asp:Wizard>Listing 12-32. Code for a Wizard with style properties.

Using a Wizard for Data Entry

The Wizard control is designed primarily to collect information from users. Its screen displays typically include textboxes, radio buttons, checkboxes, and drop-down lists to solicit input data. The final step in the process often writes this data to an external data store to retain it for future processing. The following example presents a data entry form spanning five screens to collect data and a final screen to summarize input.

Figure 12-33. Data entry with a Wizard.

This Wizard setup does not use templates. All layout and styling is given in property settings for the control and in individual WizardSteps. Complete coding for the Wizard along with example WizardSteps are shown below.

<h3 style="text-align:center">ASP.NET 2.0 Exam</h3>

<asp:Wizard id="Exam" Runat="Server" StepStyle-Width="400" StepStyle-Height="150" StepStyle-VerticalAlign="Top" NavigationStyle-BackColor="#E0E0E0" NavigationStyle-HorizontalAlign="Left" NavigationButtonStyle-Width="120" StartNextButtonText="Start Exam" StepPreviousButtonText="Previous Question" StepNextButtonText="Next Question" FinishPreviousButtonText="Previous Question" FinishCompleteButtonText="Submit Exam" SideBarStyle-Width="90" SideBarStyle-VerticalAlign="Top" SideBarStyle-BackColor="#E0E0E0" OnFinishButtonClick="Score_Exam">

<WizardSteps>

<asp:WizardStep id="Step0" Title="Start" StepType="Start" Runat="Server"><asp:Panel Style="padding:5px" Runat="Server"> <b>Instructions</b><br/> <br/>

Page 274: Assignment Instructions

Please answer all questions. If you do not know the answer to a question, peek at the answer of the person sitting next to you. Also, make sure you sit next to a smart person.</asp:Panel></asp:WizardStep>

<asp:WizardStep id="Step1" Title="Question 1" StepType="Step" Runat="Server"><asp:Panel Style="padding:5px" Runat="Server"> <table> <tr style="vertical-align:top"> <td rowspan="2">1.</td> <td>Which version of ASP.NET 2.0 is this?</td> </tr> <tr> <td> <asp:RadioButtonList id="Q1" Runat="Server"> <asp:ListItem>1.0</asp:ListItem> <asp:ListItem>1.1</asp:ListItem> <asp:ListItem>2.0</asp:ListItem> <asp:ListItem>8.0</asp:ListItem> </asp:RadioButtonList> </td> </tr> </table></asp:Panel></asp:WizardStep>

...

<asp:WizardStep StepType="Complete" Runat="Server"><asp:Panel Style="padding:5px" Runat="Server"> <b>Exam Completed</b><br/> <br/> Score = <asp:Label id="Score" Runat="Server"/></asp:Panel></asp:WizardStep>

</WizardSteps>

</asp:Wizard>Listing 12-33. Code for a Wizard with style properties.

Wizard Events and Handlers

Recall that when the "Finish" button (the "Submit Exam" button in this example) is clicked at the StepType="Finish" step, the WizardStep with StepType="Complete" is displayed. This final step does not produce navigation buttons; it represents the last step in the process where input is completed and no opportunity exists to revisit previous steps. This completion step is where some action takes place following data entry. Therefore, it is often the step where a subprogram is called to process the input data. In the current example, the completion step reports the results of answering exam questions. Thus, prior to this final screen display, a subprogram call is needed to evaluate exam answers.

Button clicks in a Wizard trigger several events for which event handlers are provided. These events and handlers are described in the following table.

Event Handler Signature Argument Description

Page 275: Assignment Instructions

OnActiveStepChanged EventArgs A button is clicked which displays a different WizardStep.

OnCancelButtonClick EventArgs The CancelButton is clicked.

OnFinishButtonClick WizardNavigationEventArgs The FinishStepButton is clicked.

OnNextButtonClick WizardNavigationEventArgs A NextStepButton is clicked.

OnPreviousButtonClick WizardNavigationEventArgs A PreviousStepButton is clicked.

OnSideBarButtonClick WizardNavigationEventArgs A SideBarButton is clicked.

Figure 12-34. Wizard events, event handlers, and subprogram event arguments.

When a subprogram with the signature EventArgs is called on a button click, the argument ActiveStep is available for inspection. This argument is a reference to the WizardStep currently being displayed. You can determine, then, the Title of the current step by a reference to wizardId.ActiveStep.Title, or you can determine the id of the current step with wizardId.ActiveStep.Id, using the id of the Wizard as the object referent.

When a subprogram with the signature WizardNavigationEventArgs is called, the index of the step revealed by the click is given by the argument NextStepIndex. Thus, a script can determine the index of the currently displayed step.

In the example, at the StepType="Finish" step, when button "Submit Exam" is clicked, the StepType="Complete" step is automatically displayed. This final step displays the exam score. Before this screen is displayed, however, it is necessary to score the exam by calling subprogram Score_Exam in response to the button click. AnOnFinishButtonClick="Score_Exam" event handler is coded in the Wizard to respond to this button click.

Sub Score_Exam (Src As Object, Args As WizardNavigationEventArgs)

Dim RightAnswers As Integer = 0

If Q1.SelectedIndex = 2 Then RightAnswers += 1 End If

If Q2.SelectedIndex = 0 Then RightAnswers += 1 End If

If Q3.SelectedIndex = 0 Then RightAnswers += 1 End If

If Q4.Text = "<asp:TextBox Runat=""Server""/>" Then RightAnswers += 1 End If

If Q5.Text <> "" Then RightAnswers += 1 End If

Score.Text = String.Format("{0:P0}", (RightAnswers / 5))

End SubListing 12-34. Subprogram to score exam when Finish button is clicked.

Page 276: Assignment Instructions

The percentage of correct responses is displayed in a Label control on the StepType="Complete" screen. It is not necessary to display this step in script; it is automatically displayed when the Finish button is clicked.

External Data Sources and Destinations

In the above example, data entry responses are not written to an external data store. However, saving responses is easily accomplished in the OnFinishButtonClick subprogram in the same manner as other scripts open and write to external files and databases.

A Wizard cannot be associated with a data source control. Therefore, an external data source cannot automatically format steps and supply their data during displays. Still, scripts can be coded on the page to retrieve information from external data sources for binding to controls coded in any of the hard-coded steps.

Themes and Skins

A theme is a collection of property settings used to define the visual appearance of pages and their controls for consistency across the pages of a Web site. A theme can be applied to an entire Web site, to a single page and its controls, or to an individual control. There is similarity to applying Cascading Style Sheets, with the addition of control properties for which there are no CSS equivalents. Figure 12-35 shows selected controls to which theming is applied to those in the left column.

Themed Control No Themed Control

Themed Label control No Themed Label control

Themed HyperLink No Themed HyperLink

Themed LinkButton No Themed LinkButton

Themed Item 1

Themed Item 2

Themed Item 3

No Themed Item 1

No Themed Item 2

No Themed Item 3

           

Themed GridView

BookTitle BookPrice

Oracle Database $69.99

No Themed GridView

BookTitle BookPrice

Oracle Database $69.99

Page 277: Assignment Instructions

Databases in Depth $29.95

Database Processing $136.65

Access Database Design $34.95

SQL Server 2005 $29.99

Databases in Depth $29.95

Database Processing $136.65

Access Database Design $34.95

SQL Server 2005 $29.99

Themed DetailsView

Title: Oracle Database

Price: $69.99

1 2 3 4 5

No Themed DetailsView

Title: Oracle Database

Price: $69.99

1 2 3 4 5

Figure 12-35. Applying Themes to server controls.

The Themes Directory

Themes are collected in a special app_Themes directory that appears in the root directory (the virtual directory) of a Web site. You must create this directory with this name irrespective of whether a theme is applied to the entire site or to one particular control on one particular page of the site.

Inside the app_Themes directory are one or more subdirectories containing different styling themes that can be applied to the site. Only one subdirectory needs to be created if only one theme is applied. This subdirectory can have any name. For example, the theme applied in the above example is contained inside the app_Themes\ASPTheme1 directory of this tutorial site.

Skin Files

A theme is a collection of styled controls packaged as text files with the special extension .skin. Any control to be styled can be described in its own skin file. The file contains coding for a control with property settings describing a model to follow in styling this type of control. For example, the following Button.skin file appears in the ASPTheme1subdirectory to define styling for an <asp:Button> control. This styling is applied to the themed Button appearing in Figure 12-35.

<asp:Button SkinID="ASPButton" Runat="Server" Font-Name="Comic Sans MS" Font-Size="10pt" Font-Bold="True" BackColor="#990000" ForeColor="#FFFFFF" BorderStyle="Outset" BorderWidth="3"/>Figure 12-36. A Button.skin file for a Button control.

The themed control includes only those property settings that are shared among all such controls that adopt this skin. Individual controls may have properties that are unique to themselves and are not included in the skin. Incidentally, the file name Button.skin is not significant. Any valid file name can be used as long as it has the .skin extension.

Page 278: Assignment Instructions

Named Skins

There are two categories of skins that can be created. A named skin is assigned a SkinID, as in the above example. A named skin is adopted by individual controls by refering to this SkinID. For instance, a Button control takes on the appearance of the button described in the Button.skin file by including the following SkinID reference.

<asp:Button Text="Themed Button" SkinID="ASPButton" Runat="Server"/>Listing 12-35. Adopting a Button skin through a SkinID reference.

Other buttons on the page do not take on this appearance if they do not include this SkinID reference.

Default Skins

A default skin does not have a SkinID. It becomes the skin for all such controls on any page that applies the theme of which this skin is a part.

<asp:Button Runat="Server" Font-Name="Comic Sans MS" Font-Size="10pt" Font-Bold="True" BackColor="#990000" ForeColor="#FFFFFF" BorderStyle="Outset" BorderWidth="3"/>Figure 12-37. A default Button.skin file for a Button control.

In this rewrite of the Button.skin file, the control is not labeled with a SkinID. Therefore, this styling applies to all <asp:Button> controls on a page that adopts theming.

A Collective Skins File

As mentioned, it is common to code a skin for a control type in its own separate .skin file. However, all skins can be packaged in a single .skin file. In the following illustration, all skins that comprise a theme are coded in a single file rather than as separate skin files.

<asp:Button SkinID="ASPButton" Runat="Server" Font-Name="Comic Sans MS" Font-Size="10pt" Font-Bold="True" BackColor="#990000" ForeColor="#FFFFFF" BorderStyle="Outset" BorderWidth="3"/>

<asp:Label SkinID="ASPLabel" Runat="Server" BackColor="#F0F0F0" ForeColor="#990000" BorderStyle="Ridge" BorderWidth="3" Style="padding:3px"/>

<asp:HyperLink SkinID="ASPHyperLink" Runat="Server"

Page 279: Assignment Instructions

Font-Name="Comic Sans Ms" Font-Size="12pt" BackColor="#FFFFFF" ForeColor="#990000"/>

<asp:LinkButton SkinID="ASPLinkButton" Runat="Server" Font-Name="Comic Sans Ms" Font-Size="12pt" BackColor="#FFFFFF" ForeColor="#990000"/>

<asp:TextBox SkinID="ASPTextBox" Runat="Server" Width="200" Height="27" BackColor="#F0F0F0" ForeColor="#990000" BorderStyle="Inset" BorderWidth="3" Style="padding:3px"/>

<asp:RadioButtonList SkinID="ASPRadioButtonList" Runat="Server" Font-Bold="True" BackColor="#E0E0E0" ForeColor="#990000" BorderStyle="Solid" BorderWidth="1" BorderColor="#990000"/>

<asp:DropDownList SkinID="ASPDropDownList" Runat="Server" Font-Name="Comic Sans Ms" BackColor="#E0E0E0" ForeColor="#990000"/>

<asp:GridView SkinID="ASPGridView" Runat="Server" CellPadding="2" BorderStyle="Ridge" BorderWidth="5" BackColor="#F0F0F0" ForeColor="#990000" HeaderStyle-BackColor="#990000" HeaderStyle-ForeColor="#FFFFFF" AlternatingRowStyle-BackColor="#C07777" AlternatingRowStyle-ForeColor="#FFFFFF"/>

<asp:DetailsView SkidID="ASPDetailsView" Runat="Server" AllowPaging="True" CaptionAlign="Left" CellPadding="2" BorderStyle="Ridge" BorderWidth="5" BackColor="#F0F0F0" ForeColor="#990000" HeaderStyle-BackColor="#990000" HeaderStyle-ForeColor="#FFFFFF" AlternatingRowStyle-BackColor="#C07777" AlternatingRowStyle-ForeColor="#FFFFFF" PagerStyle-BackColor="#E0E0E0"/>Figure 12-38. Contents of Controls.skin file.

Again, the name of the .skin file is not important. Skin definitions are applied individually through their SkinID references, or they are applied to all such controls by not coding aSkinID in the skin file.

Page 280: Assignment Instructions

Applying Themes

Themes and their skins can be applied to an entire Web site (application level) or to selected pages (page level). To apply a theme at the application level, the web.config file in the site's root directory is modified to include a <pages> element giving the name of the theme to apply, that is, the name of the subdirectory of the app_Themes directory containing applicable .skin files. For example, the theme folder named ASPTheme1 is applied to an entire site with the following web.config setup.

<configuration> <system.web> <pages theme="ASPTheme1"/> </system.web></configuration>Figure 12-39. Contents of web.config file to apply a theme to a site.

In this case, any control on any page can take part in this theme by including the SkinID of a control described in a .skin file in the ASPTheme1 directory. If a control's skin does not have a SkinID assigned, then the skin is applied to all such controls on all pages of the site.

An alternative to applying a theme site-wide is to apply it to individual pages. A page takes part in a theme by setting the Theme attribute in the page's <@ Page> directive.

<@ Page Language="vb" Debug="True" Theme="ASPTheme1" %>Listing 12-36. Page directive to apply a theme to a page.

Again, any control on the page can adopt a theme by referencing the SkinID of a control described in one of the named directory's .skin files. If a control's skin does not have aSkinID assigned, then the skin is applied to all such controls on that page.

Coding a Theme attribute in the page directive on all pages of a site is the equivalent of including a <pages> element in the web.config file. Either technique can be used, although changing themes is easier if coded once in the web.config file.

Themes and Style Sheets

A standard CSS stylesheet can be part of a theme. This is necessary where common styling beyond those properties defined in control skins is applied. To include a style sheet as part of a theme, create a text file with a .css extension and code style settings in the same manner as you would for any linked style sheet. Make sure the file is saved to your themes folder along with its companion .skin files. Now, the style sheet is applied to all pages to which the theme is applied.

Themes are equivalent to CSS style sheets, at least for those theme properties that have CSS equivalents. You may, in fact, have skin properties and CSS styles that both apply to the same control. Where there is an equivalent property and style, the theme takes precedence. For example, the following skin and style sheet entries all pertain to the same Button control and all specify conflicting styles.

Button Skin Button Style Sheet

<asp:Button SkinID="MyButtonSkin" <style type="text/css">

Page 281: Assignment Instructions

  BackColor="Red" ForeColor="White"  .../>

#MyButton {background-color:blue;           color:yellow}</style>

Styled Button

<asp:Button id="MyButton" SkinID="MyButtonSkin" Runat="Server"  style="background-color:green; color:black"/>

Figure 12-40. Application of multiple styles to a button.

If a Theme="theme" attribute is applied to a page in a <@ Page> directive, or if a <pages theme="theme"/> entry appears in a web.config file, then the skin setting is applied exclusively. Unlike working with standard style sheets, theme-related style sheets do not cascade. Only the theme is applied.

If, however, the desire is to share control styling with style sheets, then the Theme attribute must be replaced by a StyleSheetTheme="theme" attribute in the <@ Page> directive, or<pages stylesheettheme="theme"/> is coded in the web.config file. In this case, theme styling is overridden by style sheet styles, with CSS styles taking precedence.

Setting Themes Programmatically

Rather than setting a theme declaratively—in a <@ Page> directive or in the web.config file—you can apply it dynamically through a script. So that the theme is applied prior to a page being loaded, it must take place during the page.preinit event, that is, before the page-load process is "initiated." This means that setting the page's theme must take place in a Page_PreInit subprogram.

Sub Page_PreInit

Page.Theme = "ASPTheme1"

End SubListing 12-37. Setting a page theme during page-load initiation.

The name of the theme to apply to the page is, of course, the name of a theme directory containing the skins and style sheets to apply. When this technique is used, it is not necessary to include a theme setting in the <@ Page> directive or in the web.config file unless it represents a default scheme that is overridden by the scripted theme.

The following buttons alternate between two different themes that can be applied to this page. The "ASPTheme1" button applies the theme from directory ASPTheme1 when this page opens. The "ASPTheme2" button applies a different theme from directory ASPTheme2. Click the buttons and scroll to Figure 12-35 to view the two themes, which the buttons themselves take on as well.

 

Code for these two buttons along with the Page_PreInit script to set this page's theme is shown below. Notice that the buttons are assigned SkinIDs so that they take on the alternate themes they represent.

Sub Page_PreInit

Page 282: Assignment Instructions

If Request.QueryString("Theme") <> "" Then Page.Theme = Request.QueryString("Theme") Else Page.Theme = "ASPTheme1" End If

End Sub

<asp:Button Text="ASPTheme1" SkinID="ASPButton" Runat="Server" PostBackUrl="aspnet12-08.aspx?Theme=ASPTheme1"/><asp:Button Text="ASPTheme2" SkinID="ASPButton" Runat="Server" PostBackUrl="aspnet12-08.aspx?Theme=ASPTheme2"/>Listing 12-38. Code to alternate page themes.

Shown below are the two skins for <asp:Button> controls appearing in the two themes. Comparable styling differences appear for the other controls in Figure 12-35.

ASPTheme1 Button Skin ASPTheme2 Button Skin

<asp:Button SkinID="ASPButton" Runat="Server" Font-Name="Comic Sans MS" Font-Size="10pt" Font-Bold="True" BackColor="#990000" ForeColor="#FFFFFF" BorderStyle="Outset" BorderWidth="3"/>

<asp:Button SkinID="ASPButton" Runat="Server" Font-Name="Verdana" Font-Size="8pt" Font-Bold="True" BackColor="LightSkyBlue" ForeColor="Navy" BorderStyle="Outset" BorderWidth="3" Height="27"/>

Figure 12-41. ASPTheme1 and ASPTheme2 button skins.

Posting to Pages

The previous buttons cannot use standard OnClick event handlers to call the Page_PreInit subprogram because these controls do not exist until after the page is loaded. By that time it is too late to set a theme for the page. For theme choices to be available prior to page loading, they are packaged as query strings appended to the URL for this page and issued through the button's PostBackUrl property.

A button's normal purpose is to call a subprogram. However, it can be configured for general post-back to a page without identifying a subprogram. The PostBackUrl property supplies a relative or absolute URL for a page to which post-back is made. Then, one of the special page subprograms—Page_Load, Page_Init, or Page_PreInit—can handle post-back processing. In this example, the Page_PreInit subprogram handles processing of the posted-back query string.

A test is made for a query string value since, other than arriving by the button click, no other navigations to this page are accompanied by a query string. When arriving at this page through navigation links with no query string is attached, the ASPTheme1 theme is applied. When a button is clicked, however, a query string is available to this page and its named theme is applied to the page's Page.Theme property.

Page 283: Assignment Instructions

Incidentally, besides a Button control, ImageButtons and LinkButtons also support the PostBackUrl property. They can be used not only to call subprograms through their OnClickand OnCommand event handlers, but can be configured to post information to the same or to a different page, optionally including a query string to identify passed values.

eCommerce Site Design

At this point a number of previous learnings can be pulled together to illustrate the integration of Web pages and workflows that tie them together. The following set of tutorials lead you through the steps for developing an online commercial Web site for selling products. This example uses the BooksDB.mdb database of books from previous tutorials. There is occasion in these examples to explore most of the considerations in developing an online store. You can view this Web site in operation by clicking the eCommerce Site link in the tutorial menu. The following illustration outlines the pages and links that comprise the site. General descriptions of these pages are given below.

Page 284: Assignment Instructions
Page 285: Assignment Instructions

Figure 13-1. Pages and workflow of eCommerce site.

Default.aspx Page

The site is built around the use of master pages, all which share a common title, menu, and search features. A book search is the basic navigation technique. A couple of ways of looking for books is provided: viewing a list of books within particular book-type categories and keyword searches for book titles, descriptions, or author names. A visitor arriving at this page is assigned a unique order number to identify this shopper.

Search.aspx Page

When the visitor clicks on a category or submits a search word, transfer is made to the Search.aspx page. The search routines produce a listing all of the books in a chosen category or all the books that match a search criterion. Links are provided for additional details about a book and its purchase on the Details.aspx page.

Details.aspx Page

Full information about a book is displayed on this page. It receives a book identification from the Search.aspx page to call up the information from the database. A button is provided for adding this book to the shopping cart by writing a record to the ShopCart table of the BooksDB.mdb database.

ShopCart.aspx Page

The shopping cart page is accessible at any time from the common menu. It lists a customer's shopping cart items stored in the ShopCart table. Opportunity is provided to change item quantities or to delete items. When the visitor is ready to purchase the books, a link is made to the CreditCheck.aspx page for order checkout.

CreditCheck.asps Page

This page simulates a credit card processing service. It is provided with a merchant ID, a customer ID, the total amount of the order, and a return URL as the destination for returning credit processing information. The page returns an order approval and customer information that is collected.

OrderCapture.aspx Page

This page receives the return information from the credit card processing service and issues an email confirmation to the customer. Transfer is then made to the SalesOrder.aspx page.

Page 286: Assignment Instructions

SalesOrder.aspx Page

This page displays the final sales order for the customer to confirm the sale and print a copy. The shopping cart is emptied and a new order number is assigned for additional shopping under this new number.

There are two other pages that contain scripting but do not have a visible presence. SubmitForm.aspx and ReturnOrder.aspx are intermediate script pages to transmit forms between pages.

All pages for the site are stored in the eCommerce directory, which is set up as a virtual Web directory. The site is supported by the BooksDB.mdb database, which appears inside the Databases subdirectory of the main eCommerce directory. Book pictures are in a BookPictures subdirectory.

On subsequent tutorial pages the design and coding details for this site are discussed. This application is not a fully functional eCommerce site, but it does include most of the common features that can be fleshed out to make it functional.

Master and Content Pages

Below is shown the general layout for all of the pages of the eCommerce Web site. It is based on the use of a master page to present the common elements, which include the banner across the top of the page and the site menus down the left. Different content appears in the right-hand section of the page when menu links are clicked or when performing book searches.

Figure 13-2. Layout of master page.

Code to set up the eCommerce.master page is shown in Listing 13-1. The entire page is formatted inside a table with cells containing the site title, the site menu and book search sections, and an <asp:ContentPlaceHolder> control defining a content section for display of site pages. The script section is described below.

Page 287: Assignment Instructions

<%@ Master %>

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

<html><head Runat="Server"> <title>webWarehouse.com</title> <link href="stylesheetEC.css" rel="stylesheet"></head>

<body><form Runat="Server">

<table id="MasterTable" border="0" cellspacing="0" width="100%"><tr> <td id="HeaderCell" colspan="2"> webWarehouse.com </td></tr>

<tr> <td id="CounterCell" colspan="2">

<!-- Visitor Counter --><asp:AccessDataSource id="CounterSource" Runat="Server" DataFile="../Databases/BooksDB.mdb" SelectCommand="SELECT VisitorCounter FROM Counters"/>

<asp:FormView id="VisitorCount" DataSourceID="CounterSource" Runat="Server"> <ItemTemplate> Visitors: <asp:Label Font-Bold="True" Runat="Server" Text='<%# String.Format("{0:N0}", Eval("VisitorCounter")) %>'/> </ItemTemplate></asp:FormView>

</td></tr><tr> <td id="MenuCell" nowrap>

<!-- Menu --><asp:Label Text="Menu" Runat="Server" BackColor="#990000" ForeColor="#FFFFFF" Font-Bold="True" Width="130px" Style="padding:2px"/><br/>

<asp:SiteMapDataSource id="SiteMapSource" Runat="Server" SiteMapProvider="eCommerceProvider" ShowStartingNode="False"/>

<asp:Menu Runat="Server" DataSourceID="SiteMapSource" Width="130" StaticMenuItemStyle-ForeColor="#990000" StaticHoverStyle-BackColor="#FFFFFF"/><br/>

<!-- Category Search --><asp:Label Text="Category" Runat="Server" BackColor="#990000" ForeColor="#FFFFFF" Font-Bold="True" Width="130px" Style="padding:2px"/><br/>

Page 288: Assignment Instructions

<asp:AccessDataSource id="BookSource" Runat="Server" DataFile="../Databases/BooksDB.mdb" SelectCommand="SELECT DISTINCT BookType FROM Books ORDER BY BookType"/>

<asp:GridView DataSourceID="BookSource" Runat="Server" AutoGenerateColumns="False" ShowHeader="False" GridLines="Both" Width="130"> <Columns> <asp:TemplateField> <ItemTemplate> <asp:Panel Width="125" Runat="Server" onMouseOver="this.style.backgroundColor='#FFFFFF'" onMouseOut="this.style.backgroundColor='#E0E0E0'"> <asp:LinkButton Text='<%# Eval("BookType") %>' Runat="Server" OnCommand="Get_Category" CommandName='<%# Eval("BookType") %>' ForeColor="#990000" Font-Underline="False"/> </asp:Panel> </ItemTemplate> </asp:TemplateField>

</Columns>

</asp:GridView><br/>

<!-- Criterion Search --><asp:Label Text="Search" Runat="Server" BackColor="#990000" ForeColor="#FFFFFF" Font-Bold="True" Width="130px" Style="padding:2px"/><br/>

<asp:TextBox id="SearchCriterion" Runat="Server" Font-Size="8pt" Width="105px"/><asp:Button Text="Go" OnClick="Get_Criterion" Runat="Server" Font-Size="8pt"/>

</td>

<td id="ContentCell"> <asp:ContentPlaceholder id="CONTENT" Runat="Server"/> </td></tr></table>

</form></body></html>Listing 13-1. Code for eCommerce.master page.

A short external style sheet is linked to the master page to set overall styling for the page and its structuring table. Code for this stylesheetEC.css file is shown in Listing 13-2. All other styling is applied with server style properties assigned to individual server controls.

body {margin:0px; background-image:url(Backdrop.gif); background-repeat:repeat-y}

Page 289: Assignment Instructions

table#MasterTable td, th {font-family:arial; font-size:10pt; vertical-align:top}table#MasterTable td#TitleCell {background-color:#990000; color:white; padding:5px; font-size:32pt; font-weight:bold}table#MasterTable td#CounterCell {background-color:#E0E0E0}table#MasterTable td#MenuCell {width:150px; padding:5px}table#MasterTable td#ContentCell {width:100%; padding:10px}Listing 13-2. Code for stylesheetEC.css linked style sheet.

Coding the Site Menu

The site menu has direct links to three pages: the home page (Default.aspx), the shopping cart page (ShopCart.aspx), and a page of special-offer books (Specials.aspx). The menu is produced from a web.sitemap file in the eCommerce root directory. Code for this file is shown in Listing 13-3. Links are relative to the eCommerce directory where all site pages are stored.

<?xml version="1.0" ?><siteMap> <siteMapNode> <siteMapNode title="Home" url="Default.aspx"/> <siteMapNode title="Shopping Cart" url="ShopCart.aspx"/> <siteMapNode title="Specials" url="Specials.aspx"/> </siteMapNode></siteMap>Listing 13-3. Code for web.sitemap file.

Placement of the site menu on the master page uses a SiteMapDataSource control to point to the default web.sitemap file as the source of menu items. The starting, root node (<siteMap>) is not part of the menu display. A Menu control displays these items by linking to the SiteMapDataSource through its DataSourceID property.

<asp:SiteMapDataSource id="SiteMapSource" Runat="Server" ShowStartingNode="False"/>

<asp:Menu id="SiteMenu" Runat="Server" DataSourceID="SiteMapSource" Width="130" StaticMenuItemStyle-ForeColor="#990000" StaticHoverStyle-BackColor="#FFFFFF"/><br/>Listing 13-4. Code for display of site menu.

Coding the Category Search

One of the site search options is to view all books by category, that is, by their BookType values in the database. These categories are drawn from the database and displayed in a series of LinkButton controls arranged inside a GridView as shown in Listing 13-5.

<asp:AccessDataSource id="BookSource" Runat="Server" DataFile="../Databases/BooksDB.mdb" SelectCommand="SELECT DISTINCT BookType FROM Books ORDER BY BookType"/>

<asp:GridView DataSourceID="BookSource" Runat="Server" AutoGenerateColumns="False"

Page 290: Assignment Instructions

ShowHeader="False" GridLines="Both" Width="130"> <Columns> <asp:TemplateField> <ItemTemplate> <asp:Panel Width="125" Runat="Server" onMouseOver="this.style.backgroundColor='#FFFFFF'" onMouseOut="this.style.backgroundColor='#E0E0E0'"> <asp:LinkButton Text='<%# Eval("BookType") %>' Runat="Server" OnCommand="Get_Category" CommandName='<%# Eval("BookType") %>' ForeColor="#990000" Font-Underline="False"/> </asp:Panel> </ItemTemplate> </asp:TemplateField>

</Columns>

</asp:GridView>Listing 13-5. Code for display of book category search menu.

An AccessDataSource retrieves all distinct BookTypes from the database for use as text labels and command names for the LinkButtons. Clicking a button calls subprogramGet_Category to transfer to the Search.aspx page, appending the chosen category type passed to the subprogram through the button's command name.

<SCRIPT Runat="Server">

Sub Get_Category (Src As Object, Args As CommandEventArgs) Response.Redirect("Search.aspx?Category=" & Args.CommandName)End Sub

...</SCRIPT>Listing 13-6. Code for the Get_Category subprogram to redirect to the Search.aspx page with a query string.

The Get_Category subprogram adds the passed command name (Args.CommandName)—which is a BookType value—to a query string and redirects to the Search.aspx page. This query string provides notification to the page of the category-type listing of books to produce.

Notice that each LinkButton is surrounded by a Panel control with two JavaScript event handlers: onMouseOver and onMouseOut. These events trigger the enclosed JavaScripts to change the background color of the Panels to white and back to gray as the mouse moves on and off the Panels. This code causes the LinkButtons to behave visually like the site menu which uses similar hover styles.

Coding the Criterion Search

A second form of book search is provided. In this case, the visitor can enter a word or phrase (full or partial) into a textbox and perform a search of the database for a matching value. As in the case with a category search, the entered search criterion value is passed to

Page 291: Assignment Instructions

the Search.aspx page to perform the lookup. Code for the Get_Criterion subprogram to redirect to the search page is shown in Listing 13-7.

Sub Get_Criterion (Src As Object, Args As EventArgs) If SearchCriterion.Text <> "" Then Response.Redirect("Search.aspx?Criterion=" & SearchCriterion.Text) End IfEnd SubListing 13-7. Code for the Get_Criterion subprogram to redirect to the Search.aspx page with a query string.

Here, the search value is appended as a query string to the URL for the Search.aspx page. It is taken directly from the SearchCriterion TextBox control.

Keeping Track of Customers

There needs to be a way of keeping track of visitors to the site. In addition to being window shoppers, visitors can become customers and select books for purchase. Therefore, different purchases made by different customers need to be distinguished. One way of doing this is to assign a unique identification number to each person who arrives at the site. A random number for this purpose is assigned in the Page_Load subprogram coded at the top of the eCommerce.master page.

Sub Page_Load

If Session("OrderNumber") = "" Then RANDOMIZE Session("OrderNumber") = (INT((9999999 - 1111111 + 1) _ * RND + 1111111)).toString() End If

End SubListing 13-8. Code for the Page_Load subprogram to generate a random order number.

This random number formula produces a seven-digit number between 1111111 and 9999999. This number is assigned to a Session variable—Session("OrderNumber")—to remain with the visitor while navigating the site. If the visitor leaves the site or halts navigation for longer than 20 minutes, or the visitor closes the browser window before returning, a new number is assigned.

It is important to keep in mind that for each visitor to a Web site ASP.NET automatically creates a Session for that individual. A special Session ID is assigned so that the activities of concurrent visitors can be individually managed by ASP.NET. Session variable OrderNumber plays a similar role for the eCommerce site. It is a way of keeping track of concurrent customers and, as discussed later for the shopping cart, of keeping track of which purchases go with which customers.

Since the order number generator routine is on the eCommerce.master page, it can produce a new number each time a page is loaded. However, this number needs to be created only one time, the first time the visitor arrives at the site. Therefore, a condition test permits creation of this number only if it does not yet exist.

Even with generation of a seven-digit random number, there is the possibility, however slight, that the same number can be produced for different individuals. Although this possibility is not checked in the example site, the safest bet is to also write this number to a database. Then, the

Page 292: Assignment Instructions

number-generator routine can check against this database for duplicate numbers being produced, and generate another number if this is the case. This precautionary step is left for your discretion and programming.

Counting Visitors - The global.asax File

A visitor counter is a popular device for Web sites and is included on the eCommerce site. A visitor counter keeps track of the number of different visitors to a site. It does not add to the tally for each page visited; it tracks the number of visitors who newly arrive at the site irrespective of how long they stay, how many pages they view, or whether they temporarily leave and return.

The concept of a Session fits in well with this definition of a visitor. An ASP.NET Session is automatically created when a visitor first arrives at any page of a site; it remains in effect as long as the visitor is active within a 20-minute time interval or until the visitor closes the browser window. It remains active even if visitors leave the site, as long as they return within the 20-minute time period.

Given that a Session is a practical definition of a visit, a counter can be incremented each time ASP.NET creates a new Session as a means of counting visitors. It happens that ASP.NET provides the perfect place to increment this counter—in the global.asax file. Note that the file extension is .asax, not .aspx.

A global.asax file is located in the root Web directory and is a scripted page that runs each time a new Session is created. It does not contain any pre-scripted routines and, in fact, the file does not exist until you create it. You create the file only if you have scripts to run during Session creation. The following global.asax file appears in the rooteCommerce directory. It contains a script to increment a visitor counter for the site.

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

Sub Session_Start

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

End SubListing 13-9. Code for global.asax file for eCommerce site.

The Session_Start subprogram is recognized by ASP.NET and is run automatically whenever a new Session is created for a newly arrived visitor at a site. In this case, the subprogram updates the VisitorCounter field in the Counters table of the BooksDB.mdb database. This table is added to the database along side the Books table that has been used in previous examples in these tutorials. Its layout is shown below.

Page 293: Assignment Instructions

Figure 13-3. Layout of Counters table of BooksDB.mdb database.

The single VisitorCounter field contains a single record defined as a double integer type. You need to initialize the counter to 0 at the beginning of the counting process. Of course, if you wish to impress visitors, set the counter to a much larger number.

A report of the VisitorCounter appears at the top of the master page. A link to the field is made through an AccessDataSource bound to a FormView that displays the visitor count. Code for these controls is repeated in Listing 13-10.

<asp:AccessDataSource id="CounterSource" Runat="Server" DataFile="../Databases/BooksDB.mdb" SelectCommand="SELECT VisitorCounter FROM Counters"/>

<asp:FormView id="VisitorCount" DataSourceID="CounterSource" Runat="Server"> <ItemTemplate> Visitors: <asp:Label Font-Bold="True" Runat="Server" Text='<%# String.Format("{0:N0}", Eval("VisitorCounter")) %>'/> </ItemTemplate></asp:FormView>Listing 13-10. Code to display visitor counter.

Although only a single data value is displayed, it must be rendered through a FormView or other type of control that permits data binding. A stand-alone Label control, for instance, cannot be bound to an AccessDataSource. A FormView is used because it permits selective display of data values in its <ItemTemplate>.

Content Pages

The initial content page for the site—the "home" page— is Default.aspx; it is retrieved when linking to the site itself. This page includes little information in the example, and other information should be added to make it more informative and inviting. Its layout as a content page is shown in Listing 13-11.

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

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

<p>Welcome to the <asp:Label Text="webWarehouse" Runat="Server"Font-Size="14pt" ForeColor="#990000"/>, your source for computer- and Web-related books.</p>

<p>Click any of the Categories to view available books of that type, or enter a value into the Search box to locate book titles, descriptions, or

Page 294: Assignment Instructions

author names containing that entered text.</p>

</asp:Content>Listing 13-11. Code for Default.aspx content page.

The ContentPlaceHolderID="CONTENT" property of the Content control points to the like-named ContentPlaceHolder control on the master page where this page's content appears. As you can see, all that needs to be coded on a content page is whatever information appears inside the master page placeholder.

Performing Product Searches

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

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

The Search.aspx Content Page

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

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

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

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

Page 295: Assignment Instructions

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

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

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

<Columns>

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

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

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

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

<asp:TemplateField HeaderText="View Details" ItemStyle-HorizontalAlign="Center"> <ItemTemplate> <asp:LinkButton Text="Details" Runat="Server" OnCommand="View_Details" CommandName='<%# Eval("BookID") %>'/> </ItemTemplate> </asp:TemplateField>

</Columns>

</asp:GridView>

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

Page 296: Assignment Instructions

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

Displaying Matching Records

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

?Category=value?Criterion=value

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

<SCRIPT Runat="Server">

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

Page 297: Assignment Instructions

End Sub

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

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

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

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

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

Function Get_Sale_Price (Price As Decimal, Sale As Boolean)

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

End FunctionListing 13-14. Code for Get_Sale_Price function.

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

Displaying Product Details

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

Page 298: Assignment Instructions

and redirect to the Details.aspx page. This subprogram, along with a reminder of its LinkButton caller, is shown below.

Sub View_Details (Src As Object, Args As CommandEventArgs)

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

End Sub

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

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

The Specials.aspx Content Page

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

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

<SCRIPT Runat="Server">

Sub View_Details (Src As Object, Args As CommandEventArgs)

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

End Sub

</SCRIPT>

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

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

<asp:AccessDataSource id="SpecialsSource" Runat="Server" DataFile="../Databases/BooksDB.mdb" SelectCommand="SELECT * FROM Books WHERE BookSale = True ORDER BY BookID"/>

<asp:GridView id="BookGrid" Runat="Server" DataSourceID="SpecialsSource" AutoGenerateColumns="False" HeaderStyle-BackColor="#990000" HeaderStyle-ForeColor="#FFFFFF" Cellpadding="5" Style="margin-top:10px"> <Columns>

Page 299: Assignment Instructions

<asp:BoundField HeaderText="ID" DataField="BookID" ItemStyle-Width="60"/> <asp:BoundField HeaderText="Title" DataField="BookTitle" ItemStyle-Width="200"/> <asp:TemplateField HeaderText="Price" ItemStyle-Width="70" ItemStyle-HorizontalAlign="Right"> <ItemTemplate> <asp:Label Runat="Server" Text='<%# String.Format("{0:C}", Eval("BookPrice")) %>'/> </ItemTemplate> </asp:TemplateField> <asp:TemplateField HeaderText="Special" ItemStyle-Width="70" ItemStyle-HorizontalAlign="Right" ItemStyle-Font-Bold="True"> <ItemTemplate> <asp:Label Runat="Server" Text='<%# String.Format("{0:C}", _ Eval("BookPrice") * Application("Discount")) %>'/> </ItemTemplate> </asp:TemplateField> <asp:TemplateField HeaderText="View Details" ItemStyle-HorizontalAlign="Center"> <ItemTemplate> <asp:LinkButton Text="Details" Runat="Server" OnCommand="View_Details" CommandName='<%# Eval("BookID") %>'/> </ItemTemplate> </asp:TemplateField> </Columns>

</asp:GridView>

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

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

LinkButtons on this page work identically to those on the Search.aspx page. They call the View_Details subprogram which passes a BookID query string to the Details.aspxpage. That page, therefore, receives a query string from two different

Page 300: Assignment Instructions

pages, Search.aspx and Specials.aspx, both identifying a book about which details are displayed.

The Application Object

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

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

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

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

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

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

End Sub

Sub Session_Start

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

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

Page 301: Assignment Instructions

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

Shopping for Products

The Details.aspx page gives full information about a book chosen on the Search.aspx or Specials.aspx page. While viewing this information, visitors can add this book to their shopping carts.

Figure 13-5. Layout of Details.aspx page.

The Details.aspx Content Page

Coding for this page is shown below. As a content page, it occupies the same ContentPlaceHolder control on the master page as do other pages of the site. The Import directives are needed for scripts described below.

<%@ Page MasterPageFile="eCommerce.master" Language="vb" Debug="True"%><%@ Import Namespace="System.Data.OleDb" %><%@ Import Namespace="System.Drawing" %>

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

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

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

Page 302: Assignment Instructions

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

<asp:FormView id="FormViewDisplay" DataSourceID="BookSource" Runat="Server" Style="margin-top:10px"/>

<ItemTemplate> <table border="0"> <tr> <td style="width:60px"> <asp:Image Runat="Server" ImageUrl='<%# "BookPictures/" & Eval("BookID") & ".jpg" %>' Style="float:left; margin-right:10px"/> </td> <td style="width:440px"> <asp:Label id="BookID" Runat="Server" Text='<%# Eval("BookID") %>' Font-Bold="True"/><br/> <asp:Label id="BookTitle" Runat="Server" Text='<%# Eval("BookTitle") %>' Font-Bold="True" Font-Size="14pt"/><br/> <asp:Label id="BookAuthor" Runat="Server" Text='<%# Eval("BookAuthor") %>'/><br/> <asp:Label id="BookPrice" Runat="Server" Text='<%# String.Format("{0:C}", Eval("BookPrice"))) %>'/> <asp:Label id="BookSalePrice" Runat="Server" Text='<%# Get_Sale_Price(Eval("BookPrice"), Eval("BookSale")) %>' Font-Bold="True" ForeColor="#990000" /><br/><br/> <asp:Button Text="Add to Cart" OnClick="Add_To_Cart" Runat="Server"/> </td> </tr> <tr> <td colspan="2"> <br/> <asp:Label Width="500" Runat="Server" Text='<%# Eval("BookDescription") %>'/> </td> <tr> </table>

</ItemTemplate>

</asp:FormView>

</asp:Content>Listing 13-18. Code for Details.aspx page.

Selecting a Book for Display

A FormView control is used to display book information since it offers flexibility in arranging the display. It is associated with an AccessDataSource to retrieve book information. However, since the book to be displayed is identified in a query string passed to this page, the Page_Load subprogram must capture this query string value and use it to compose aSelectCommand for the AccessDataSource. This portion of the page script along with the function to calculate special pricing are shown below.

<SCRIPT Runat="Server">

Sub Page_Load()

Page 303: Assignment Instructions

If Not Page.IsPostBack Dim SQLString As String SQLString = "SELECT * FROM Books " & _ "WHERE BookID = '" & Request.QueryString("BookID") & "'" BookSource.SelectCommand = SQLString End If

End Sub

Function Get_Sale_Price (Price As Decimal, Sale As Boolean)

If Sale = True Then Price = Price * Application("Discount") Return String.Format("Special Price: {0:C}", Price) End If

End Function

...</SCRIPT>Listing 13-19. Code to select and display product details.

Here, the query string value (a BookID) is appended to a SELECT statament and assigned to the SelectCommand of the AccessDataSource for the FormView. The Get_Sale_Pricesubprogram is similar to the one on the Search.aspx page. It returns a calculated discounted price, formatted inside a character string, or it returns nothing.

Creating a Shopping Cart

A button on the page permits customers to add this book to their shopping carts. There are a number of ways to implement a shopping cart, the easiest probably being as a database table. Records are added to or removed from the table as customers go about their shopping. At the close of shopping, customer purchases are extracted from the table to produce sales orders summarizing purchases.

A ShopCart table is added to the BooksDB.mdb database to capture sales information for all customers visiting the site. The format of this table is shown below.

Figure 13-6. Format of ShoppingCart table.

Page 304: Assignment Instructions

The OrderNumber field is used to store a customer identification to distinguish one customer's books from another's. The special Session("OrderNumber") value generated when a visitor arrives at the site is used for this unique identifier. Other fields identify the book selected for purchase along with book information needed to create a final sales order for this customer. Customers and books, then, are uniquely identified by the combination OrderNumber and BookID fields.

Adding an Item to the Shopping Cart

When the "Add to Cart" button is clicked, the Add_To_ShopCart subprogram is called to write a new record to the ShopCart table. Four items of information are needed from the FormView to create this record: the BookID, title, regular price, and sale price. This information is in four Label controls inside the FormView.

Sub Add_To_Cart (Src As Object, Args As EventArgs)

Dim FVBookID As Label = FormViewDisplay.FindControl("BookID") Dim FVBookTitle As Label = FormViewDisplay.FindControl("BookTitle") Dim FVBookPrice As Label = FormViewDisplay.FindControl("BookPrice") Dim FVBookSalePrice As Label = FormViewDisplay.FindControl("BookSalePrice") Dim BookPrice As Decimal = FVBookPrice.Text If FVBookSalePrice.Text <> "" Then BookPrice = Replace(FVBookSalePrice.Text, "Special Price: ", "") End If Dim DBConnection As OleDbConnection Dim DBCommand As OleDbCommand Dim SQLString As String Dim SQLAddString As String DBConnection = New OleDbConnection( _ "Provider=Microsoft.Jet.OLEDB.4.0;" & _ "Data Source=" & Server.MapPath("../Databases/BooksDB.mdb")) DBConnection.Open() SQLString = "SELECT Count(*) FROM ShopCart " & _ "WHERE OrderNumber = '" & Session("OrderNumber") & "' " & _ "AND BookID = '" & FVBookID.Text & "'" DBCommand = New OleDbCommand(SQLString, DBConnection) If DBCommand.ExecuteScalar() = 0 Then SQLAddString = "INSERT INTO ShopCart (OrderNumber, OrderDate, " & _ "BookID, BookTitle, BookPrice, BookQty) VALUES (" & _ "'" & Session("OrderNumber") & "', " & _ "'" & Today & "', " & _ "'" & FVBookID.Text & "', " & _ "'" & FVBookTitle.Text & "', " & _ BookPrice & ", 1)" DBCommand = New OleDbCommand(SQLAddString, DBConnection) DBCommand.ExecuteNonQuery() End If DBConnection.Close() Src.Text = "Item Added" Src.ForeColor = Color.FromName("#990000") Src.BackColor = Color.FromName("#E0E0E0") Src.Font.Bold = True End SubListing 13-20. Code to add a product selection to the ShopCart table.

Page 305: Assignment Instructions

Recall that with bound controls which themselves contain multiple controls you cannot directly reference these embedded controls. That is, with a FormView you must "find" its internal controls containing the information you need by using the its FindControl() method. As has been illustrated previously, finding the four Label controls containing needed book information is accomplished with the following statements.

Dim FVBookID As Label = FormViewDisplay.FindControl("BookID")Dim FVBookTitle As Label = FormViewDisplay.FindControl("BookTitle")Dim FVBookPrice As Label = FormViewDisplay.FindControl("BookPrice")Dim FVBookSalePrice As Label = FormViewDisplay.FindControl("BookSalePrice")Listing 13-21. Code to find book information in FormView control.

The four controls are found through their id values and assigned to script-generated objects of the same type. These objects, then, serve as surrogate controls to access their associated values in the FormView.

The FVBookSalePrice value is not in correct format for writing to the ShopCart table. It includes the string "Special Price: " that needs to be removed leaving only a decimal number.

Dim BookPrice As Decimal = FVBookPrice.TextIf FVBookSalePrice.Text <> "" Then BookPrice = Replace(FVBookSalePrice.Text, "Special Price: ", "")End IfListing 13-22. Code to reformat book prices for writing to the ShopCart Table.

Either the FVBookPrice or FVBookSalePrice value is assigned to variable BookPrice. It is necessary to assign these values to a separate variable, otherwise the book sale price showing in the FormView also takes on the revised format of FVBookSalePrice when the "Special Price: " string is removed. If there is an FVBookSalePrice value, it becomes the final BookPrice, replacing the normal price of the item.

Once the three items of information (FVBookID, FVBookTitle, and BookPrice) have been found and formatted, they can be written as a record to the ShopCart table along with theSession("OrderNumber") to identify this customer and the current date produced by the Visual Basic Today property. First, though, one other determination needs to be made.

It is conventional to create a shopping cart record only the first time a product is selected for purchase. The record indicates the purchase of a single item. This practice avoids having to deal with multiple clicks on a "Buy" button as indicating a desire for more than one item. The clicks could, instead, indicate incompetence with a mouse, and how would the script know? It is better to deal with the quantity desired as a separate issue on a separate shopping cart page.

At present, then, the need is to ensure that only a single book record for any one book is written to the database. Therefore, the script checks for an existing record before writing a new one. This portion of the script is repeated below.

Dim DBConnection As OleDbConnectionDim DBCommand As OleDbCommandDim SQLString As StringDim SQLAddString As String DBConnection = New OleDbConnection( _ "Provider=Microsoft.Jet.OLEDB.4.0;" & _ "Data Source=" & Server.MapPath("../Databases/BooksDB.mdb"))DBConnection.Open()SQLString = "SELECT Count(*) FROM ShopCart " & _

Page 306: Assignment Instructions

"WHERE OrderNumber = '" & Session("OrderNumber") & "' " & _ "AND BookID = '" & FVBookID.Text & "'"DBCommand = New OleDbCommand(SQLString, DBConnection)If DBCommand.ExecuteScalar() = 0 Then SQLAddString = "INSERT INTO ShopCart (OrderNumber, OrderDate, " & _ "BookID, BookTitle, BookPrice, BookQty) VALUES (" & _ "'" & Session("OrderNumber") & "', " & _ "'" & Today & "', " & _ "'" & FVBookID.Text & "', " & _ "'" & FVBookTitle.Text & "', " & _ BookPrice & ", 1)" DBCommand = New OleDbCommand(SQLAddString, DBConnection) DBCommand.ExecuteNonQuery() End IfDBConnection.Close()Listing 13-23. Code to write a single book record to the ShopCart Table.

An initial SELECT statement is issued to get a count of the number of records in the ShopCart table where the OrderNumber field matches the Session("OrderNumber") and theBookID field matches the FVBookID value from the FormView. If this count is not 0, then a record already exists for this purchase and no record is written to the table. If the returned count is equal to 0, then a new record is written to the table.

A new record is written to the ShopCart table by issuing an SQL INSERT statement. This statement writes six fields of information: the Session("OrderNumber") for the customer, the FVBookID from the FormView, today's date, the FVBookTitle from the FormView, the BookPrice variable, and the quantity 1. After writing this record, the script changes the text of the "Add to Cart" button to read "Item Added" to visually indicate this addition to the shopping cart.

Src.Text = "Item Added"Src.ForeColor = Color.FromName("#990000")Src.BackColor = Color.FromName("#E0E0E0")Src.Font.Bold = TrueListing 13-24. Code to style "Add to Cart" button to indicate addition of item to ShopCart table.

At this point, the customer can return to shopping or can go to the shopping cart page to review purchases, change quantities ordered, delete items from the shopping cart, or proceed to checkout.

Maintaining a Shopping Cart

The ShopCart.aspx page is visited by clicking on the menu link. It displays the current items in the ShopCart table for this customer. Options are provided to change the quantities ordered for any book or to delete the book from the shopping cart. A button is displayed to continue checkout processing.

Page 307: Assignment Instructions

Figure 13-7. Layout of ShopCart.aspx page.

The ShopCart.aspx Content Page

Shown below is the code outline for the ShopCart.aspx page including the GridView and its AccessDataSource to present items from the ShopCart table for a customer.

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

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

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

<asp:Label Text="Shopping Cart" Font-Size="14pt" ForeColor="#990000" Runat="Server"/><br/><br/><table border="0"><tr> <td><b>Order No: </b></td> <td><asp:Label id="OrderNumberLabel" Runat="Server"/></td></tr></table>

<asp:Label id="NoItemsMessage" Runat="Server" Text="No items in shopping cart" EnableViewState="False" Visible="False" Width="180" Height="40" BackColor="#E0E0E0" ForeColor="#990000" BorderStyle="Solid" BorderWidth="1" BorderColor="#C0C0C0" Style="margin-top:10px; padding:15px"/>

<asp:Panel Width="550" Runat="Server"ForeColor="#990000" HorizontalAlign="Right">

Page 308: Assignment Instructions

<asp:Label id="ErrMessage" Text=" " EnableViewState="False" Runat="Server"/></asp:Panel>

<asp:AccessDataSource id="ShopCartSource" Runat="Server" DataFile="../Databases/BooksDB.mdb" OnSelected="Get_Rows"

SelectCommand="SELECT * FROM ShopCart WHERE OrderNumber=@OrderNumber"

UpdateCommand="UPDATE ShopCart SET BookQty=@BookQty WHERE OrderNumber=@OrderNumber AND BookID=@BookID"

DeleteCommand="DELETE FROM ShopCart WHERE OrderNumber=@OrderNumber AND BookID=@BookID"> <SelectParameters> <asp:ControlParameter Name="OrderNumber" ControlId="OrderNumberLabel" PropertyName="Text"/> </SelectParameters> <UpdateParameters> <asp:ControlParameter Name="OrderNumber" ControlId="OrderNumberLabel" PropertyName="Text"/> </UpdateParameters> <DeleteParameters> <asp:ControlParameter Name="OrderNumber" ControlId="OrderNumberLabel" PropertyName="Text"/> </DeleteParameters>

</asp:AccessDataSource>

<asp:GridView id="ShopCartGrid" DataSourceID="ShopCartSource" Runat="Server" AutoGenerateColumns="False" DataKeyNames="OrderNumber, BookID" OnRowUpdating="Validate_Quantity" Cellpadding="3" ShowFooter="True" HeaderStyle-BackColor="#990000" HeaderStyle-ForeColor="#FFFFFF" EditRowStyle-BackColor="#E0E0E0"> <Columns> <asp:TemplateField HeaderText="ID" ItemStyle-Width="50"> <ItemTemplate> <asp:Label Runat="Server" Text='<%# Eval("BookID") %>'/> </ItemTemplate> <EditItemTemplate> <asp:Label Runat="Server" Text='<%# Eval("BookID") %>'/> </EditItemTemplate> </asp:TemplateField> <asp:TemplateField HeaderText="Title" ItemStyle-Width="200"> <ItemTemplate> <asp:Label Runat="Server" Text='<%# Eval("BookTitle") %>'/> </ItemTemplate> <EditItemTemplate>

Page 309: Assignment Instructions

<asp:Label Runat="Server" Text='<%# Eval("BookTitle") %>'/> </EditItemTemplate> </asp:TemplateField> <asp:TemplateField HeaderText="Price" ItemStyle-HorizontalAlign="Right" ItemStyle-Width="60"> <ItemTemplate> <asp:Label Runat="Server" Text='<%# String.Format("{0:N}", Eval("BookPrice")) %>'/> </ItemTemplate> <EditItemTemplate> <asp:Label Runat="Server" Text='<%# String.Format("{0:N}", Eval("BookPrice")) %>'/> </EditItemTemplate> </asp:TemplateField> <asp:TemplateField HeaderText="Qty" ItemStyle-Width="40" FooterStyle-HorizontalAlign="Right" ItemStyle-HorizontalAlign="Right"> <ItemTemplate> <asp:Label Runat="Server" Text='<%# String.Format("{0:D}", Eval("BookQty")) %>' Width="25" Font-Size="9pt" Style="text-align:right"/> </ItemTemplate> <EditItemTemplate> <asp:TextBox id="BookQty" Runat="Server" Text='<%# Bind("BookQty") %>' MaxLength="2" Width="25" Height="17" Font-Size="9pt" Style="padding:0px; text-align:right"/> </EditItemTemplate> <FooterTemplate> <asp:Label Text="Shipping" Runat="Server"/><br/> <asp:Label Text="Total" Runat="Server" Font-Bold="True" Width="50" BorderStyle="Solid" BorderWidth="0" Style="padding:2px; margin-top:5px"/> </FooterTemplate> </asp:TemplateField> <asp:TemplateField HeaderText="Amount" ItemStyle-Width="80" ItemStyle-HorizontalAlign="Right" FooterStyle-HorizontalAlign="Right"> <ItemTemplate> <asp:Label Runat="Server" Text='<%# String.Format("{0:N}", _ Get_Amount(Eval("BookPrice"), Eval("BookQty"))) %>'/> </ItemTemplate> <EditItemTemplate> <asp:Label Runat="Server" Text='<%# String.Format("{0:N}", _ Get_Amount(Eval("BookPrice"), Eval("BookQty"))) %>'/> </EditItemTemplate> <FooterTemplate> <asp:Label Runat="Server" Text='<%# String.Format("{0:N}", Get_Shipping()) %>'/><br/> <asp:Label id="OrderTotal" Runat="Server" Text='<%# String.Format("{0:C}", Get_Order_Total()) %>' Width="80" Font-Bold="True" BorderStyle="Solid" BorderWidth="1"

Page 310: Assignment Instructions

BorderColor="#C0C0C0" Style="padding:2px; margin-top:5px"/> </FooterTemplate> </asp:TemplateField> <asp:TemplateField HeaderText="Edit" ItemStyle-Width="75" FooterStyle-BackColor="#E0E0E0"> <ItemTemplate> <asp:Button Text="Edit" CommandName="Edit" Runat="Server" Font-Size="7pt" Width="35"/> <asp:Button Text="Delete" CommandName="Delete" Runat="Server" Font-Size="7pt" Width="35"/> </ItemTemplate> <EditItemTemplate> <asp:Button Text="Update" CommandName="Update" Runat="Server" Font-Size="7pt" Width="35"/> <asp:Button Text="Cancel" CommandName="Cancel" Runat="Server" Font-Size="7pt" Width="35"/> </EditItemTemplate> <FooterTemplate> <asp:Button Text="Checkout »" OnClick="Submit_Form" Runat="Server" Font-Size="8pt" Width="75" Style="margin-top:22px"/> </FooterTemplate> </asp:TemplateField> </Columns> </asp:GridView>

</asp:Content>Listing 13-25. Code for ShopCart.aspx page.

The "Checkout »" button in the FooterTemplate calls a subprogram named Submit_Form which is explained in the following tutorial. In order to test the current page without receiving an error on this missing subprogram, you may wish to temporarily delete this event handler from the button. It can be replaced after coding the Submit_Form subprogram.

Retrieving Customer Products

An AccessDataSource populates the GridView with all database records with an OrderNumber field matching the Session("OrderNumber") being maintained for this customer. This AccessDataSource, the Label displaying the order number, and the Page_Load script to assigned the number to the Label are shown below. Interaction between these page elements retrieves the appropriate records for display.

<SCRIPT Runat="Server">

Sub Page_Load

If Not Page.IsPostBack Then OrderNumberLabel.Text = Session("OrderNumber") End If

End Sub

...</SCRIPT>

Page 311: Assignment Instructions

<asp:Label Text="Shopping Cart" Font-Size="14pt" ForeColor="#990000" Runat="Server"/><br/><br/><table border="0"><tr> <td><b>Order No: </b></td> <td><asp:Label id="OrderNumberLabel" Runat="Server"/></td></tr></table>

<asp:AccessDataSource id="ShopCartSource" Runat="Server" DataFile="../Databases/BooksDB.mdb" OnSelected="Get_Rows"

SelectCommand="SELECT * FROM ShopCart WHERE OrderNumber=@OrderNumber"

UpdateCommand="UPDATE ShopCart SET BookQty=@BookQty WHERE OrderNumber=@OrderNumber AND BookID=@BookID"

DeleteCommand="DELETE FROM ShopCart WHERE OrderNumber=@OrderNumber AND BookID=@BookID"> <SelectParameters> <asp:ControlParameter Name="OrderNumber" ControlId="OrderNumberLabel" PropertyName="Text"/> </SelectParameters> <UpdateParameters> <asp:ControlParameter Name="OrderNumber" ControlId="OrderNumberLabel" PropertyName="Text"/> </UpdateParameters> <DeleteParameters> <asp:ControlParameter Name="OrderNumber" ControlId="OrderNumberLabel" PropertyName="Text"/> </DeleteParameters>

</asp:AccessDataSource>Listing 13-26. Code to link an order number to AccessDataSource parameters.

The first time the page loads, Session("OrderNumber") is assigned to the OrderNumberLabel for display on the page. This Label also serves as the source for the AccessDataSource's SelectCommand parameter to retrieve the matching records (WHERE OrderNumber=@OrderNumber).

Often, a command parameter is located inside the control that is bound to the data source. Its value is given in a binding expression (<%# Eval(field) %>) where the field name matches the parameter name (@field). In a previous example of master/detail editing with a DetailsView it is described how to indicate command parameters that are outside the control to which the data source is bound. This takes place in the <SelectParameters> section of the AccessDataSource, where an <asp:ControlParameter> identifies the outside control where the parameter value can be found. In the current example, the parameter value is identified as the Text property of the OrderNumberLabel whose value is assigned in the Page_Load subprogram. This Label, then, supplies the @OrderNumber value for selecting records from the ShopCart table. Notice also that this same Label supplies@OrderNumber values for the AccessDataSource's UPDATE and DELETE statments.

Missing Shopping Cart Items

Page 312: Assignment Instructions

It is possible, of course, that the customer arrives at the shopping cart page without having yet chosen items for purchase. In this case, an empty GridView is not displayed; rather, a message box is displayed indicating that no items appear in the shopping cart.

A determination needs to be made whether there are records in the ShopCart table for this customer. This information is given by the number of rows (records) returned by the AccessDataSource when it issues its SelectCommand. The data source's Selected event can be trapped by an OnSelected event handler to call a subprogram that checks the data source's AffectedRows property. If the number of rows returned by the AccessDataSource is 0, then the message box is displayed. Code for this subprogram and the affected Label are shown below.

Sub Get_Rows (Src As Object, Args As SqlDataSourceStatusEventArgs)

If Args.AffectedRows = 0 Then NoItemsMessage.Visible = True End If

End Sub

...<asp:Label id="NoItemsMessage" Runat="Server" Text="No items in shopping cart" EnableViewState="False" Visible="False" Width="180" Height="40" BackColor="#E0E0E0" ForeColor="#990000" BorderStyle="Solid" BorderWidth="1" BorderColor="#C0C0C0" Style="margin-top:10px; padding:15px"/>Listing 13-27. Get_Rows subprogram.

The subprogram argument for a data source's event handler is SqlDataSourceStatusEventArgs. This argument's AffectedRows property gives the number of rows returned when aSelectCommand is issued. In this example, if no rows are returned—if this customer does not have any records in the ShopCart table—the NoItemsMessage Label is made visible. Note that the Label is pre-configured as a boxed display with an enclosed message. The subprogram simply makes it visible in place of the GridView, which is not visible since is has no rows to display.

GridView Display

The GridView displays all records for the customer from the ShopCart table. It also defines an EditItemTemplate for each column. Only for the BookQty column, however, is a TextBox displayed rather than a Label displaying the field value. This is the only value for which editing is permitted. Its binding to the database uses a Bind() method rather than anEval() method to permit updating its value.

<asp:TemplateField HeaderText="Qty" ItemStyle-Width="40" FooterStyle-HorizontalAlign="Right" ItemStyle-HorizontalAlign="Right"> <ItemTemplate> <asp:Label Runat="Server" Text='<%# String.Format("{0:D}", Eval("BookQty")) %>' Width="25" Font-Size="9pt" Style="text-align:right"/> </ItemTemplate> <EditItemTemplate> <asp:TextBox id="BookQty" Runat="Server"

Page 313: Assignment Instructions

Text='<%# Bind("BookQty") %>' MaxLength="2" Width="25" Height="17" Font-Size="9pt" Style="padding:0px; text-align:right"/> </EditItemTemplate> <FooterTemplate> <asp:Label Text="Shipping" Runat="Server"/><br/> <asp:Label Text="Total" Runat="Server" Font-Bold="True" Width="50" BorderStyle="Solid" BorderWidth="0" Style="padding:2px; margin-top:5px"/> </FooterTemplate></asp:TemplateField>Listing 13-28. Code for an updatable book quantity control.

The "Amount" column displays calculated values by calling functions to produce line-item amounts. In this column's FooterTemplate, shipping and order total amounts are given by function calls. Code for this column is repeated below along with the script functions producing its values. Notice that a global TotalAmount variable is declared for summarizing individual line-item amounts. Operation of these functions should be familiar from past examples.

Dim TotalAmount As Decimal = 0.00

Function Get_Amount (Price As Decimal, Quantity As Integer) Dim Amount As Decimal Amount = Price * Quantity TotalAmount += Amount Return AmountEnd Function

Function Get_Shipping() Dim Shipping As Decimal = TotalAmount * Application("Shipping") TotalAmount += Shipping Return ShippingEnd Function

Function Get_Order_Total() Return TotalAmountEnd Function

...<asp:TemplateField HeaderText="Amount" ItemStyle-Width="80" ItemStyle-HorizontalAlign="Right" FooterStyle-HorizontalAlign="Right"> <ItemTemplate> <asp:Label Runat="Server" Text='<%# String.Format("{0:N}", _ Get_Amount(Eval("BookPrice"), Eval("BookQty"))) %>'/> </ItemTemplate> <EditItemTemplate> <asp:Label Runat="Server" Text='<%# String.Format("{0:N}", _ Get_Amount(Eval("BookPrice"), Eval("BookQty"))) %>'/> </EditItemTemplate> <FooterTemplate> <asp:Label Runat="Server" Text='<%# String.Format("{0:N}", Get_Shipping()) %>'/><br/> <asp:Label id="OrderTotal" Runat="Server" Text='<%# String.Format("{0:C}", Get_Order_Total()) %>' Width="80" Font-Bold="True" BorderStyle="Solid" BorderWidth="1" BorderColor="#C0C0C0" Style="padding:2px; margin-top:5px"/> </FooterTemplate></asp:TemplateField>

Page 314: Assignment Instructions

Listing 13-29. Code for producing and displaying calculated amounts.

Updating the Quantity Ordered

When a row's "Edit" button is clicked, the row is put in edit mode for changing the quantity ordered for a book. When the "Update" button is clicked, the quantity is updated. First, though, the GridView's OnRowUpdating="Validate_Quantity" event handler calls a subprogram to validated the changed amount.

Sub Validate_Quantity (Src As Object, Args As GridViewUpdateEventArgs)

If Not IsNumeric(Args.NewValues("BookQty")) Then Args.Cancel = True ErrMessage.Text = "Book quantity must be numeric" ElseIf Args.NewValues("BookQty") < 1 Then Args.Cancel = True ErrMessage.Text = "Book quantity is out of range" End If

End Sub

...<asp:GridView id="ShopCartGrid" DataSourceID="ShopCartSource" Runat="Server" AutoGenerateColumns="False" DataKeyNames="OrderNumber, BookID" OnRowUpdating="Validate_Quantity" ...Listing 13-30. Code for validating changed item quantities.

The quantity entered is checked for numeric characters and a positive value. If these tests are failed, then updating is cancelled and an error message is written to a message Label. Otherwise, the AccessDataSource's UpdateCommand is issued.

UpdateCommand="UPDATE ShopCart SET BookQty=@BookQty WHERE OrderNumber=@OrderNumber AND BookID=@BookID"Listing 13-31. AccessDataSource update command.

Here is a case where it requires two fields to uniquely identify a record in the ShopCart table. When a customer purchases more than one book, there are multiple records with the same OrderNumber. This value needs to be combined with a BookID value to produce a unique key. Recall that when performing updates to a record a GridView needs to identify in its DataKeyNames property the names of the values which serve as record keys. In this case, then, both the OrderNumber and BookID names are identified.

Of course, there is no OrderNumber value inside the GridView; it appears in the external OrderNumberLabel. However, this Label's value is associated with the UPDATE statement's@OrderNumber parameter through the AccessDataSource's <UpdateParameters> entry. In a round-about way, OrderNumber is a data key name even though its value comes from the external OrderNumberLabel. Also, as a key field, BookID must be bound to the GridView with the Eval() method rather than the Bind() method since its value is not permitted to be updated.

After updating takes place, the AccessDataSource is automatically re-bound to the GridView to display the changed quantity and its effect on shopping cart calculations.

Page 315: Assignment Instructions

Deleting a Shopping Cart Record

When a "Delete" button is clicked, the book on that row of the shopping cart display is deleted from the shopping cart. No intermediate processing is performed; the item is immediately deleted by issuance of the AccessDataSource's DeleteCommand.

DeleteCommand="DELETE FROM ShopCart WHERE OrderNumber=@OrderNumber AND BookID=@BookID"Listing 13-32. AccessDataSource delete command.

As in the case for row updating, the @BookID parameter comes from the BookID field of the GridView and the @OrderNumber parameter comes from the OrderNumberLabel as given by the <DeleteParameters> for the AccessDataSource.

After deletion of the record takes place, the AccessDataSource is automatically re-bound to the GridView to display the changed shopping cart contents.

Checkout Processing

A click on the "Checkout" button on the shopping cart page begins a series of steps to finalize purchase of items listed in the shopping cart. Normally, the eCommerce merchant has established an account with an external credit card processing service which handles all the financial and banking transactions surrounding the order. Involvement of the eCommerce site is minimal. Although this example site does not have formal arrangements with a commercial credit card processing service, it does illustrate the typical manner of interacting with such a service.

Credit Card Processing

The typical process includes submission of order information to the processing service and awaiting return notification of the success or failure of the transaction. Thus, when a "Checkout" button is clicked, redirection takes place to the processing service's Web site. Here, through a secure connection, the customer submits credit card information and, if approved, transactions are completed to charge the customer's account and debit the merchant's account. At completion, the processing service site returns confirmation to a designated page at the merchant site where local processing continues.

Although techniques may differ slightly, the usual manner of connecting to a credit card processing service is through a form submission. Typically, four pieces of information are required to be submitted, with options to include more information depending on merchant preferences. The four minimal fields of information are,

Merchant ID - the merchant's account number with the credit card processing service Customer ID - the customer's identification number—the local site's customer number or

order number Order Amount - the total amount of the order Return URL - the address of the local page to which a transaction confirmation is

returned (this URL can be prearranged with the processing service and not have to be transmitted with each order.)

When the credit card transaction is completed, the processing service returns confirmation information to the merchant site, specifically to the page designated in the return URL address

Page 316: Assignment Instructions

sent to or prearranged with the service. Again, the varieties of returned information differ depending on merchant arrangements with the service, but at minimum a single data value signifying success or failure of the transaction is returned. This can be a simple "yes" or "no" response depending on the practice. More likely, the processing service collects additional information about the customer which is returned to the merchant site. This information can include shipping and billing addresses, email addresses, and other personal information that is collected through the secure connection. If this information is not collected by the processing service, then the merchant site needs to collect it in order to ship the order and otherwise correspond with the customer. For the current example, customer information is assumed to be collected by the credit card processing service and returned to the eCommerce site. This saves from having to establish a secure local connection to collect private information.

This interaction between a merchant site and a credit card processing service is simulated by the CreditCheck.aspx page. It takes on the role of the processing service, collecting customer credit card and shipping information, approving or rejecting the order, and returning confirmation to the OrderCapture.aspx page. Information returned from theCreditCheck.aspx page is used to produce an email confirmation to the customer and to save the order information to the BooksDB.mdb database. Although not illustrated here, this saved information would become input to the eCommerce site's inventory and distribution systems for picking and shipping the order, and to other local systems that manage customer sales and services.

HTML Forms

The need exists, then, to transmit order information from the ShopCart.aspx page to the CreditCheck.aspx page, and then to transmit confirmation information from theCreditCheck.aspx page to the OrderCapture.aspx page. Recall that there are various ways to pass information from one page to another. Query strings, for instance, have been used to pass data and control information between pages of the eCommerce site. In the current case, though, forms are used to pass order information between pages since form submission is a popular method of transmitting order information to and from a credit card processing services.

Under ASP.NET, the <form Runat="Server"> tag that surrounds page content causes same-page form submission. A submitted form posts back to the same page that contains the form, normally by calling subprograms coded on the page. For credit card processing, however, a form is needed that does not post back to the same eCommerce page. The form needs to be submitted to a different page, specifically to the URL of the credit card processing service (locally, the CreditCheck.aspx page).

The need, then, is to create a page containing a standard "HTML form" that can be submitted to an external site rather than to the page itself. HTML forms represent the standard, non-ASP.NET, way of submitting form information. They are contained also within <form> tags and require use of two attributes to transmit their contents.

<form name="name" action="url" method="get|post"> ...

</form>

Figure 13-8. General format for HTML <form> tag.

Page 317: Assignment Instructions

A form may require a name depending on the processing steps surrounding the form. The action attribute supplies the URL of the page to which form information is to be sent. This can be a relative URL to a local page or an absolute URL to an external site. The method attribute indicates the manner in which form information is transmitted. The post method transmits form contents in a separate data stream accompanying the URL request and is the preferred method. The get (default) method appends the contents of the form to the URL as a query string. Unless otherwise required, the post method always should be used so as not to reveal form information in a Web-visible query string.

HTML forms include standard XHTML tags as containers for data. Normally, the container is an <input> tag with attributes shown in the following general format.

<input type="text|hidden" name="name" value="value"/>

Figure 13-9. General format for HTML <input> tag.

An <input> tag is the HTML form equivalent of an <asp:TextBox> control. It holds a text string value given by its value attribute. Its name attribute identifies the value since, as in using query strings, a submitted form transmits a string of name=value pairs to the destination site. As many <input> tags are coded as are needed to transmit separate data values. When setting up an HTML form to pass information between Web sites, the tag's type attribute normally is set to "hidden" rather than "text". It is common practice not to display textbox values since their purpose is only to hold values for form submission.

Normally included on an HTML form is a special <input> tag to cause form submission. This tag has a type="submit" attribute that displays the tag as a button and transmits the form to its action URL when the button is clicked. Its general format is shown below.

<input type="submit" name="name" value="value"/>

Figure 13-10. General format for HTML <input type=submit> tag.

It is usually not necessary to assign a name to the button. Its value attribute, however, can override the default label "Submit Query" appearing on the face of the button.

eCommerce Submit Form

When working with a credit card processing service, the layout of the HTML form is dictated by the service provider. Their documentation normally gives the URL for the form'saction attribute and specifies the exact names to use for <input> tags. It is often the case that you can simply copy and paste their required form directly onto your page.

Assume that the credit card processing service used by the eCommerce site requires receipt of the four common information items described above, and that they are expected to be received under the form field names ReturnURL, MerchantID, CustomerID, and OrderTotal. The

Page 318: Assignment Instructions

transmitting page, then, needs an HTML form in the format shown below. In this case, local URLs are shown, although these would be full http addresses when performing commercial processing.

<form action="CreditCheck.aspx" method="post"> <input type="hidden" name="ReturnURL" value="OrderCapture.aspx/> <input type="hidden" name="MerchantID" value="webWarehouse.com"/> <input type="hidden" name="CustomerID" value="11111111"/> <input type="hidden" name="OrderTotal" value="123.45"/> <input type="submit" value="Submit Form"/></form>Listing 13-33. Code to define an HTML form to pass data values to a credit card processing service.

A click on the "Submit Form" button sends the form to the action URL where the information is extracted from the form and used as part of the credit checking and transaction processing procedure. On completion, confirmation notification is returned to the ReturnURL page.

Accessing HTML Form Information

Pages receiving HTML form information access the transmitted information in a way very similar to accessing query strings. The received information arrives as a set of name=valuepairs accessible through the Request.Form collection. The names are the name attributes that are assigned to the <input> tags; the values are the data strings associated with theirvalue attributes. Thus, the data values sent through the previous example form are accessible in a receiving script through the following references.

Request.Form("ReturnURL")Request.Form("MerchantID")Request.Form("CustomerID")Request.Form("OrderTotal")Listing 13-34. Script references to data values passes by an HTML form.

The receiving page captures this form information and uses the submitted values to begin the credit card checkout process.

The SubmitForm.aspx Page

With this background, return attention to the ShopCart.aspx page and consider the need to react to its "Checkout" button by submitting a form to the local CreditCheck.aspxpage, which simulates processing by an external credit card processing service. Immediately you see a problem. There already is a <form Runat="Server"> tag on this page, and ASP.NET pages cannot have more than one form tag. (This <form Runat="Server"> tag is coded on the eCommerce.master page that encompasses all content pages.) Obviously, the credit check submission form cannot be coded on this page. It must appear on a page that does not contain a <form Runat="Server"> tag.

The solution is relatively simple. Transfer order information to a different page—one without a <form Runat="Server"> tag but with a standard HTML form tag—and transmit order information from there. A SubmitForm.aspx page is created for just this purpose. It needs to have available four pieces of order information: a MerchantID (which can be hard-coded on the form itself), a ReturnURL (which can be hard-coded on the form), a CustomerID (the current Session("OrderNumber") can be used for this purpose), and an OrderTotal (not yet

Page 319: Assignment Instructions

accessible by the SubmitForm.aspx page. Therefore, the ShopCart.aspx page needs to ensure that the total amount of the order is available when transfer is made to theSubmitForm.aspx page. It can do so by assigning the order amount to a Session variable.

When the "Checkout" button on the ShopCart.aspx page is clicked, subprogram Submit_Form is called. It saves the total order amount as a Session variable, and then redirects to the SubmitForm.aspx page.

Sub Submit_Form (Src As Object, Args As EventArgs)

Dim OrderTotal As Label = ShopCartGrid.FooterRow.FindControl("OrderTotal") Session("OrderTotal") = OrderTotal.Text Response.Redirect("SubmitForm.aspx")

End SubListing 13-35. Submit_Form subprogram of ShopCart.aspx page.

The GridView's FindControl() method locates the OrderTotal Label in the footer row as the source of the order's total amount. This value is saved as Session("OrderTotal") in order to be accessible when redirection takes place to the SubmitForm.aspx page.

Code for the SubmitForm.aspx page includes a standard HTML form with fields representing the four items of information needed for submission to the credit card processing service.

<html><head> <title>SubmitForm</title></head>

<body onLoad="SubmitForm.submit()">

<form name="SubmitForm" action="CreditCheck.aspx" method="post">

<input type="hidden" name="ReturnURL" value="OrderCapture.aspx"/><input type="hidden" name="MerchantID" value="webWarehouse.com"/><input type="hidden" name="CustomerID" value="<%=Session("OrderNumber")%>"/><input type="hidden" name="OrderTotal" value="<%=Session("OrderTotal")%>"/>

</form>

</body></html>Listing 13-36. Code for SubmitForm.aspx page.

Importantly, this is a stand-alone ASP.NET page that is not associated with the eCommerce.master page. It does not include a <form Runat="Server"> tag. Instead, it contains a standard HTML form with <input> tags named according to required field names for the credit card processing service.

As noted, the ReturnURL and MerchantID values can be hard-coded on the form since they do not change. The CustomerID and OrderTotal values are given by like-named Session variables. In-line expressions automatically supply these Session values; therefore, when the page loads the form is ready to be submitted.

Form submission needs to be automated rather than defining a submit button for manual submission. There is no reason to present the customer with this page and require another button click to submit the form. This is only a pass-through page for form submission. So, no submit

Page 320: Assignment Instructions

button is included on the form. Rather, a JavaScript submit() method is applied to the form to immediately submit it to the CreditCheck.aspx page when the page loads. Note that the form must include a name attribute that can be referenced by the submit()method.

<body onLoad="SubmitForm.submit()">Listing 13-37. Code for automatic submission of form with JavaScript submit() method.

Since loading of the form and its submission are script directed, the customer has no visual awareness that this is happening. After clicking the "Checkout" button on the shopping cart page, the very next screen displayed is the CreditCheck.aspx page. The SubmitForm.aspx page is run behind the scenes with no customer awareness.

Credit Card Processing

The CreditCheck.aspx page is linked from the SubmitForm.aspx page, having been passed form information concerning the customer's order. The page shown below is displayed to collect credit card information and to pass approval and processing results back to the eCommerce site. This page is assumed to be external to the eCommerce site. Often times the credit card processing service permits use of logos and styling schemes to format the page to resemble a page from the merchant site.

Figure 13-11. Layout of CreditCheck.aspx page.

Credit Processing Form

The layout of the CreditCheck.aspx page is shown below. No special form display or editing controls are used. Standard server controls are formatted in a table for layout.

<html><head> <title>Credit Check</title> <link href="stylesheetEC.css" rel="stylesheet">

Page 321: Assignment Instructions

</head><body><form Runat="Server">

<table id="MasterTable" border="0" cellspacing="0" width="100%"><tr> <td id="HeaderCell"> webWarehouse.com </td></tr></table><br/>

<asp:Label id="ReturnURL" Visible="False" Runat="Server"/>

<asp:Label id="Instructions" Runat="Server" Text="Enter the requested information to complete your order with "/><asp:Label id="MerchantID" Font-Bold="True" Runat="Server"/>.<br/><br/>

<table border="0"><tr> <th> Customer ID: </th> <td colspan="5"> <asp:Label id="CustomerID" Runat="Server" Font-Bold="True"/> <asp:Label id="Approved" Runat="Server" ForeColor="#FF0000" Font-Bold="True"/> </td></tr><tr> <th> Order Amount: </th> <td colspan="5"> <asp:Label id="OrderTotal" Runat="Server" Font-Bold="True"/></td></tr><tr> <td colspan="6"> </td></tr><tr> <th> Credit Card: </th> <td colspan="5"> <asp:DropDownList id="CreditCard" Runat="Server"> <asp:ListItem>Discover</asp:ListItem> <asp:ListItem>MasterCard</asp:ListItem> <asp:ListItem>Visa</asp:ListItem> <asp:ListItem>Discover</asp:ListItem> </asp:DropDownList> </td></tr><tr> <th> Account: </th> <td colspan="5"> <asp:TextBox id="Account" Runat="Server" Text="1111 1111 1111 1111" Size="16" MaxLength="19"/> </td></tr><tr> <th> Expiration Date: </th> <td colspan="5"> Month: <asp:DropDownList id="ExpMonth" Runat="Server"> <asp:ListItem>01</asp:ListItem> <asp:ListItem>02</asp:ListItem>

Page 322: Assignment Instructions

<asp:ListItem>03</asp:ListItem> <asp:ListItem>04</asp:ListItem> <asp:ListItem>05</asp:ListItem> <asp:ListItem>06</asp:ListItem> <asp:ListItem>07</asp:ListItem> <asp:ListItem>08</asp:ListItem> <asp:ListItem>09</asp:ListItem> <asp:ListItem>10</asp:ListItem> <asp:ListItem>11</asp:ListItem> <asp:ListItem>12</asp:ListItem> </asp:DropDownList> Year: <asp:DropDownList id="ExpYear" Runat="Server"> <asp:ListItem>2003</asp:ListItem> <asp:ListItem>2004</asp:ListItem> <asp:ListItem>2005</asp:ListItem> <asp:ListItem>2006</asp:ListItem> <asp:ListItem>2007</asp:ListItem> </asp:DropDownList> </td></tr><tr> <td colspan="6"> </td></tr><tr> <th> Name: </th> <td colspan="5"> <asp:TextBox id="Name" Runat="Server" Text="Your Name" Size="30" MaxLength="50"/></td></tr><tr> <th> Address: </th> <td colspan="5"> <asp:TextBox id="Address" Runat="Server" Text="Your Address" Size="30" MaxLength="50"/></td></tr><tr> <th> City: </th> <td><asp:TextBox id="City" Runat="Server" Text="Your City" Size="30" MaxLength="30"/></td> <th> State: </th> <td><asp:TextBox id="State" Runat="Server" Text="ST" Size="2" MaxLength="2"/></td> <th> Zip: </th> <td><asp:TextBox id="Zip" Runat="Server" Text="55555" Size="10" MaxLength="10"/></td></tr><tr> <th> Email: </th> <td colspan="5"> <asp:TextBox id="Email" Runat="Server" Text="[email protected]" Size="30" MaxLength="50"/></td></tr></table><br/>

<asp:Panel id="ContinuePanel" Runat="Server"> <asp:Button id="ContinueButton" Text="Continue Purchase" Runat="Server" OnClick="CheckCredit"/> <asp:Button id="CancelButton" Text="Return to Shopping" Runat="Server" OnClick="Cancel_Order"/> <asp:Label id="Message" EnableViewState="False" Runat="Server" style="color:#FF0000"/></asp:Panel>

Page 323: Assignment Instructions

<asp:Panel id="CompletePanel" Visible="False" Runat="Server"> Click to <asp:Button Text="Complete Order" Runat="Server" OnClick="Submit_Order"/> or <asp:Button Text="Return to Shopping" Runat="Server" OnClick="Cancel_Order"/></asp:Panel>

</form></body></html>Listing 13-38. Code for CreditCheck.aspx page.

Capturing Form Information

This page is linked from the SubmitForm.aspx page which submits a form to this page containing order information. The Page_Load script for this page captures the submitted information from the Request.Form collection, using the specified field names.

<%@ Page Language="vb" Debug="True"%>

<SCRIPT Runat="Server">

Sub Page_Load

If Not Page.IsPostBack Then ReturnURL.Text = Request.Form("ReturnURL") MerchantID.Text = Request.Form("MerchantID") CustomerID.Text = Request.Form("CustomerID") OrderTotal.Text = String.Format("{0:C}", Request.Form("OrderTotal")) End If

End Sub

...</SCRIPT>Listing 13-39. Code for Page_Load subprogram to capture received form information.

Request.Form values are available to the page only the first time the page loads. If the page is posted back to the server, as is done when confirming and submitting order information, the Request.Form collection is lost. It does not take part in the page's View State. Therefore, the four received fields are saved to Label controls when the page loads. These Label values are display on the form except for the passed ReturnURL value. It is not intended to be displayed, but is needed later to redirect back to the eCommerce site. Its Label control is made invisible.

Normally, TextBox controls on the form would be blank for customers to enter their personal information. These controls have been prefilled with example data. You will need, however, to enter your real email address if you wish to receive an email confirmation for your example order. No other use is made of this entered address.

Checking Credit

Page 324: Assignment Instructions

After the form is filled out by the customer, it is submitted for approval by clicking the "Continue Purchase" button. This button calls the Check_Credit subprogram. In this example, the customer's credit isn't actually checked. The order is simply approved and confirmation is sent back to the eCommerce site. Changes that the subprogram causes in the page display are shown in the following illustration.

Figure 13-12. Layout of CreditCheck.aspx page after order confirmation.

The Credit_Check subprogram that processes the form and produces this output is shown below. A modest check is made for a valid email address. Then all form controls are disabled and a different set of buttons are provided to finalize the order. The Approved Label is set to "Approved."

Sub Check_Credit (Src As Object, Args As EventArgs)

Dim ValidEmail As Boolean = True If Email.Text <> "" Then If InStr(Email.Text, "@") = 0 Then ValidEmail = False Message.Text = "Invalid email address" ElseIf Mid(Email.Text, Len(Email.Text) - 3, 1) <> "." Then ValidEmail = False Message.Text = "Invalid email address" End If Else ValidEmail = False Message.Text = "Missing email address" End If If ValidEmail = True Then CreditCard.Enabled = False Account.Enabled = False ExpMonth.Enabled = False ExpYear.Enabled = False Name.Enabled = False Address.Enabled = False City.Enabled = False

Page 325: Assignment Instructions

State.Enabled = False Zip.Enabled = False Email.Enabled = False

Instructions.Text = "Click the ""Complete Order"" button to " & _ "complete your order with " Approved.Text = "Approved" ContinuePanel.Visible = False CompletePanel.Visible = True End If

End SubListing 13-40. Code for Check_Credit subprogram to validate email address and disable text controls.

Returning Credit Approval Information

Clicking the "Complete Order" button returns order approval and customer information back to the merchant site—to the return URL address provided by the received form. In this example, return is to the OrderCapture.aspx page.

As is typical, the credit card processing service returns information as a form submission to the return URL. In this case, though, it is not necessary to use a separate form page to collect and submit form information as was done previously when submitting a form to this page. Since the CreditCheck.aspx page is not embedded inside a master page, a standard HTML form can appear on this page and submit directly back to the return URL.

It is not entirely true that an ASP.NET page cannot contain more than one form. Certainly, it can contain only a single <form Runat="Server"> form. However, it can contain a second form if (1) the form is a standard HTML form, and (2) this HTML form appears outside the server form.

The following listing shows the code added to the CreditCheck.aspx page in order to create and submit an HTML form for return of order confirmation and customer information to the merchant site.

Dim SubmitForm As Boolean = False

Sub Submit_Order (Src As Object, Args As EventArgs) SubmitForm = TrueEnd Sub

Sub Cancel_Order (Src As Object, Args As EventArgs) Approved.Text = "Cancel" Submit_Order(Nothing, Nothing)End Sub

...<body><form Runat="Server">

...

</form>

<form name="ReturnOrder" action='<%= ReturnURL.Text %>' method="post">

<input type="hidden" name="ReturnApproved" value='<%= Approved.Text %>'/><input type="hidden" name="ReturnMerchantID" value='<%= MerchantID.Text %>'/>

Page 326: Assignment Instructions

<input type="hidden" name="ReturnCustomerID" value='<%= CustomerID.Text %>'/><input type="hidden" name="ReturnOrderTotal" value='<%= OrderTotal.Text %>'/><input type="hidden" name="ReturnName" value='<%= Name.Text %>'/><input type="hidden" name="ReturnAddress" value='<%= Address.Text %>'/><input type="hidden" name="ReturnCity" value='<%= City.Text %>'/><input type="hidden" name="ReturnState" value='<%= State.Text %>'/><input type="hidden" name="ReturnZip" value='<%= Zip.Text %>'/><input type="hidden" name="ReturnEmail" value='<%= Email.Text %>'/>

</form>

<% If SubmitForm = True Then %><script type="text/javascript"> ReturnOrder.submit()</script><% End If %>

</body></html>Listing 13-41. Code to add an HTML form and processing routines to the CreditCheck.aspx page.

Pay particular attention to placement of the HTML form. It physically follows the server form, appearing after its closing </form> tag, and it appears before the closing </body> and</html> tags for the page.

All form <input> tags that hold values to be returned to the merchant site are defined as "hidden" fields (textboxes). There is no reason these fields need to be visible on the page. Form submission takes places behind the scenes without user involvement; there is no "submit" button. Also, embedded scripts supply the values for these fields. These values, along with the return URL for the form's action attribute, come from corresponding server controls on the page. When the page loads, these values automatically appear in the form ready for submission.

HTML form submission cannot be triggered by a server script. It must come either through a click on a "submit" button (missing in this case) or through a browser script written in JavaScript. Recall in the previous form submission that it came through a script that was run when the page loaded: <body onLoad="SubmitForm.submit()">. Here, though, this technique cannot be used; i.e., the form cannot be submitted immediately when the CreditCheck.aspx page loads. It is submitted after the customer enters personal information onto the page and clicks the "Complete Order" button to finalize the purchase.

The trigger for submitting the form, then, is the "Complete Order" button click. This event calls subprogram "Submit_Order". As shown in the above listing, this subprogram includes a single statement, SubmitForm = True, which sets the value of a previously declared global variable. Setting this variable sets the condition for submitting the form.

Notice that the HTML form is accompanied by a JavaScript that is enclosed inside an in-line server script. This code is repeated below.

<% If SubmitForm = True Then %><script type="text/javascript"> ReturnOrder.submit()</script><% End If %>Listing 13-42. A JavaScript enclosed inside an in-line server script to submit an HTML form.

The JavaScript contains a single statement to submit the HTML form named ReturnOrder. This script runs only if server variable SubmitForm = True, which happens when the "Complete

Page 327: Assignment Instructions

Order" button is clicked and the Submit_Form subprogram is run. Thus, through a combination of server and browser scripts the HTML form is submitted to the return URL, in this case to the OrderCapture.aspx page.

Cancelling an Order

At any time during the credit approval process the customer can click the "Return to Shopping" button to exit this process. This button calls the Cancel_Order subprogram (repeated below). Here, the Approved Label is given the value "Cancel" prior to calling the Submit_Order subprogram and submitting the form.

Sub Cancel_Order (Src As Object, Args As EventArgs) Approved.Text = "Cancel" Submit_Order(Nothing, Nothing)End SubListing 13-43. Code to cancel order processing.

The Submit_Order subprogram is normally called on a click of the "Complete Order" button. Therefore, it has the normal signature and arguments of a subprogram called in this fashion. When calling this subprogram from the Cancel_Order subprogram, however, there are no button arguments involved. Still, the subprogram expects passed arguments. Here, null arguments (Nothing, Nothing) are passed to satisfy signature expectations.

At this point, with HTML form submission, control returns to the local OrderCapture.aspx page in order to receive and process order confirmation and customer information returned from the credit card processing service through its submitted form.

Order Capture and Email

The OrderCapture.aspx page is linked from the credit card processing site (locally, the OrderForm.aspx page). The purpose of this page is to capture returned order approval and customer information sent through a submitted form.

An eCommerce site requires information about customer purchase transactions in order to keep its own books in order. For instance, it needs to know if the purchase was approved; it needs to know the shipping address of the customer to send the merchandise; and it needs to know the dollar amount of the approved purchase to update its accounting system. Ancilliary processing that can take place includes updating the inventory system to reduce the quantities of items on hand by the quantities purchased. Information to initiate this processing is produced or gathered by the credit card processing service and packaged into a return form sent to the eCommerce site.

There are a whole host of business processing events initiated by a single purchase. The focus here, though, is not on all possible transaction events, only on some common technical tasks surrounding on-line commerce. One of these common tasks is generation of email confirmations to customers, shown below in the format produced by theOrderCapture.aspx page.

Page 328: Assignment Instructions

Figure 13-13. Order confirmation email message.

One thing to note about the OrderCapture.aspx page—it produces no page display; it is comprised entirely of script with no XHTML tags or server controls. It is a non-visual transition step to producing a final sales order page that does visually summarize the completed order.

Capturing Order Information

The first part of the script on this page captures order information from the HTML form sent from the CreditCheck.aspx page. The System.Data.OleDb namespace is required for access to the ShopCart table; the System.Net.Mail namespace is required to produce an email message.

<%@ Page Language="vb" Debug="True" %><%@ Import Namespace="System.Data.OleDb" %><%@ Import Namespace="System.Net.Mail" %>

<SCRIPT Runat="Server">

Sub Page_Load

'-- Capture information from credit check return Dim Approved As String = Request.Form("ReturnApproved") Dim MerchantID As String = Request.Form("ReturnMerchantID") Dim CustomerID As String = Request.Form("ReturnCustomerID") Dim OrderAmount As String = Request.Form("ReturnOrderAmount") Dim Name As String = Request.Form("ReturnName") Dim Address As String = Request.Form("ReturnAddress")

Page 329: Assignment Instructions

Dim City As String = Request.Form("ReturnCity") Dim State As String = Request.Form("ReturnState") Dim Zip As String = Request.Form("ReturnZip") Dim EmailAddress As String = Request.Form("ReturnEmail")

If Approved = "Cancel" Then Response.Redirect("ShopCart.aspx") End If

...End Sub

</SCRIPT>Listing 13-44. Script of OrderCapture.aspx page to capture form information from CreditCheck.aspx page.

When the page loads, all form values are assigned to variables which are used in the continuing script to produce an email message. If, however, the customer had decided to abandon order processing, then the Request.Form("Approved") field contains the string "Cancel." In this case, no email is sent and the customer is redirected to the shopping cart page.

Composing an Order Confirmation Email

Email is sent from a Web server through its SMTP (Simple Mail Transfer Protocol) service. As described under "Sending Email," the ASP.NET MailMessage class is used to compose and send emails through an SmtpClient.

The following code is a continuation of the Page_Load subprogram, and begins the email production process by created a new MailMessage object (Email) and setting its To, From, and Subject properties. Notice that the values supplied for the To and Subject properties are the EmailAddress and CustomerID variables that are captured from the returned form. The From property is a fictitious email address.

'-- Send email confirmation of orderIf EmailAddress <> "" Then Dim Email = New MailMessage Dim Client = New SmtpClient("localhost") Dim FromAddress = New MailAddress("[email protected]") Email.From = FromAddress Dim ToAddress = New MailAddress(EmailAddress) Email.To.Add(ToAddress) Email.Subject = "Order " & CustomerID & " Confirmation" ...Listing 13-45. Portion of OrderCapture.aspx script to create an email confirmation.

The major effort is in composing the body of the message. This is done by creating an EmailBody variable and concatenating lines of text to it. The IsBodyHtml property of this message will be set to True, so XHTML code can be part of the message body. The following lines prepare the beginning lines of the message, including the header for the table within which shopping cart items are displayed.

...

Page 330: Assignment Instructions

Dim EmailBody As String = "" EmailBody &= "<html>" EmailBody &= "<body>" EmailBody &= "<span style=""font-size:14pt; font-weight:bold"">" EmailBody &= " webWarehouse.com" EmailBody &= "</span><br/>" EmailBody &= "<span style=""font-size:12pt; font-weight:bold"">" EmailBody &= " Order Confirmation" EmailBody &= "</span><br/><br/>" EmailBody &= "Date: " & Today & "<br/>" EmailBody &= "Order No.: " & CustomerID & "<br/>" EmailBody &= "<br>" EmailBody &= Name & "<br/>" EmailBody &= Address & "<br/>" EmailBody &= City & ", " & State & " " & Zip & "<br/>" EmailBody &= "<p>Thank you for your order. We appreciate your shopping at " EmailBody &= "webWarehouse.com. If you have any questions about your order, " EmailBody &= "please email us at <a href=""mailto:[email protected]"">" EmailBody &= "[email protected]</a> and reference the order number " EmailBody &= "listed above.</p>" EmailBody &= "<table border=""1"" cellpadding=""3""" Emailbody &= "style=""border-collapse:collapse"">" EmailBody &= "<tr style=""background-color:#F0F0F0"">" EmailBody &= " <th>ID</th>" EmailBody &= " <th>Title</th>" EmailBody &= " <th>Qty</th>" EmailBody &= " <th>Price</th>" EmailBody &= " <th>Amount</th>" EmailBody &= "</tr>" ...Listing 13-46. Portion of OrderCapture.aspx script to compose the body of an email confirmation.

Note how variables are interspersed with text strings. Also, be aware that quotation marks that are part of the message must be entered as double quotes ("") since the enclosing text string is itself quoted.

Next, items from the shopping cart for this customer are included as rows within the display table. This retrieval requires a connection to the database and selection from theShopCart table of all records for this customer. The returned recordset is iterated and a table row is formatted for each record. During this iteration, BookAmounts are calculated and the OrderAmount is summed.

... Dim BookAmount As Decimal Dim OrderAmount As Decimal = 0.00 Dim DBConnection As OleDbConnection Dim DBCommand As OleDbCommand Dim DBReader As OleDbDataReader Dim SQLString As String DBConnection = New OleDbConnection( _ "Provider=Microsoft.Jet.OLEDB.4.0;" & _ "Data Source=" & Server.MapPath("../Databases/BooksDB.mdb")) DBConnection.Open() SQLString = "SELECT * FROM ShopCart " & _ "WHERE OrderNumber = '" & Session("OrderNumber") & "' " & _ "ORDER BY BookID" DBCommand = New OleDbCommand(SQLString, DBConnection) DBReader = DBCommand.ExecuteReader() While DBReader.Read() BookAmount = DBReader("BookPrice") * DBReader("BookQty")

Page 331: Assignment Instructions

OrderAmount += BookAmount EmailBody &= "<tr>" EmailBody &= " <td>" & DBReader("BookID") & "</td>" EmailBody &= " <td>" & DBReader("BookTitle") & "</td>" EmailBody &= " <td style=""text-align:right"">" & _ String.Format("{0:D}", DBReader("BookQty")) & _ " </td>" EmailBody &= " <td style=""text-align:right"">" & _ String.Format("{0:N}", DBReader("BookPrice")) & _ " </td>" EmailBody &= " <td style=""text-align:right"">" & _ String.Format("{0:N}", BookAmount) & "</td>" EmailBody &= " </td>" EmailBody &= "</tr>" End While DBReader.Close() DBConnection.Close() ...Listing 13-47. Portion of OrderCapture.aspx script to display purchases in email confirmation.

Finally, the shipping cost is calculated and the total lines are added to the table.

... Dim OrderShipping As Decimal = OrderAmount * Application("Shipping") OrderAmount += OrderShipping EmailBody &= "<tr>" EmailBody &= " <td colspan=""4"" style=""align:right"">" & _ String.Format("{0:N}", Shipping) & "</td>" EmailBody &= " <td style=""align:right"">" & _ String.Format("{0:N}", OrderShipping) & "</td>" EmailBody &= "</tr>" EmailBody &= "<tr>" EmailBody &= " <td style=""align:right"">" & _ String.Format("{0:C}", OrderAmount) & "</td>" EmailBody &= "</tr>" EmailBody &= "</table>" EmailBody &= "" EmailBody &= "</body>" EmailBody &= "</html>" Email.Body = EmailBody Email.IsBodyHtml = True Client.Send(Email) End If

Response.Redirect("SalesOrder.aspx")Listing 13-48. Portion of OrderCapture.aspx script to summarize order and send email confirmation.

After the EmailBody variable accumulates all the appropriate text, database values, and surrounding XHTML code, it is assigned to the Body property of the Email object. The object's format is set for HTML content by setting its IsBodyHtml="True" property. Then the email is sent through SMTP services. Finally, redirection takes place to theSalesOrder.aspx page for the customer to view a summary of this order.

This Page_Load code is all that appears on the OrderCapture.aspx page. It does not produce a page display nor is the customer even aware that the page is loaded. Visually, the customer returns from the credit card processing page directly to the SalesOrder.aspx page. As mentioned above, the OrderCapture.aspx page would be the appropriate place to also take care

Page 332: Assignment Instructions

of other business processing tasks associated with the order, but these other tasks are not part of this example site.

Sales Order Display

In returning the customer to the eCommerce site following credit processing, a short detour is made through the OrderCapture.aspx page to generate an email confirmation. From the OrderCapture.aspx page the customer arrives at the SalesOrder.aspx page where a summary of the order is displayed. Customers are encouraged to print the sales order for their records.

Figure 13-14. Layout of SalesOrder.aspx page.

The SalesOrder.aspx Page

There is similarity in the code for the SalesOrder.aspx page and the ShopCart.aspx page. Both present the same information in the same format except that the sales order form does not permit editing. This code is presented in the listing below along with the script to populate the GridView. You should be familiar with GridView coding and function calls from the shopping cart. Note that this page integrates with the eCommerce.master page. The System.Data.OleDb namespace is required for additional scripting described below.

<%@ Page MasterPageFile="eCommerce.master" Language="vb" Debug="True"%><%@ Import Namespace="System.Data.OleDb" %>

<SCRIPT Runat="Server">

Dim OrderTotal As Decimal = 0.00

Sub Page_Load

If Not Page.IsPostBack Then OrderNumberLabel.Text = Session("OrderNumber")

Page 333: Assignment Instructions

TodayDate.Text = Today End If

End Sub

Function Get_Amount (Price As Decimal, Quantity As Integer)

Dim Amount As Decimal Amount = Price * Quantity OrderTotal += Amount Return Amount

End Function

Function Get_Shipping()

Dim Shipping As Decimal = OrderTotal * Application("Shipping") OrderTotal += Shipping Return Shipping

End Function

Function Get_Order_Total()

Return OrderTotal

End Function

...</SCRIPT>

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

<asp:Label Text="Sales Order" Font-Size="14pt" ForeColor="#990000" Runat="Server"/><br/><br/><table border="0"><tr> <td><b>Date: </b></td> <td><asp:Label id="TodayDate" Runat="Server"/></td></tr><tr> <td><b>Order No: </b></td> <td><asp:Label id="OrderNumberLabel" Runat="Server"/></td></tr></table>

<asp:AccessDataSource id="ShopCartSource" Runat="Server" DataFile="../Databases/BooksDB.mdb" SelectCommand="SELECT * FROM ShopCart WHERE OrderNumber=@OrderNumber"> <SelectParameters> <asp:ControlParameter Name="OrderNumber" ControlId="OrderNumberLabel" PropertyName="Text"/> </SelectParameters>

</asp:AccessDataSource>

<asp:GridView id="ShopCartGrid" DataSourceID="ShopCartSource" Runat="Server" AutoGenerateColumns="False" ShowFooter="True" Cellpadding="3" HeaderStyle-BackColor="#990000"

Page 334: Assignment Instructions

HeaderStyle-ForeColor="#FFFFFF" EditRowStyle-BackColor="#E0E0E0"> <Columns> <asp:BoundField HeaderText="ID" DataField="BookID" ItemStyle-Width="50"/> <asp:BoundField HeaderText="Title" DataField="BookTitle" ItemStyle-Width="200"/>

<asp:BoundField HeaderText="Price" DataField="BookPrice" HtmlEncode="False" DataFormatString="{0:N}" ItemStyle-Width="60" ItemStyle-HorizontalAlign="Right"/> <asp:TemplateField HeaderText="Qty" ItemStyle-Width="40" FooterStyle-HorizontalAlign="Right" ItemStyle-HorizontalAlign="Right"> <ItemTemplate> <asp:Label id="BookQty" Runat="Server" Text='<%# String.Format("{0:D}", Eval("BookQty")) %>' Width="25" Font-Size="9pt"/> </ItemTemplate> <FooterTemplate> <asp:Label Text="Shipping" Runat="Server"/><br/> <asp:Label Text="Total" Runat="Server" Font-Bold="True" Width="50" BorderStyle="Solid" BorderWidth="0" Style="padding:2px; margin-top:5px"/> </FooterTemplate> </asp:TemplateField> <asp:TemplateField HeaderText="Amount" ItemStyle-Width="80" ItemStyle-HorizontalAlign="Right" FooterStyle-HorizontalAlign="Right"> <ItemTemplate> <asp:Label Runat="Server" Text='<%# String.Format("{0:N}", _ Get_Amount(Eval("BookPrice"), Eval("BookQty"))) %>'/> </ItemTemplate> <FooterTemplate> <asp:Label Runat="Server" Text='<%# String.Format("{0:N}", Get_Shipping()) %>'/><br/> <asp:Label id="OrderTotal" Runat="Server" Text='<%# String.Format("{0:C}", Get_Order_Total()) %>' Width="80" Font-Bold="True" BorderStyle="Solid" BorderWidth="1" BorderColor="#C0C0C0" Style="padding:2px; margin-top:5px"/> </FooterTemplate> </asp:TemplateField>

Page 335: Assignment Instructions

</Columns> </asp:GridView>

<p>Thank you for your order. We appreciate your shopping at webWarehouse.com. If you have any questions about your order, email usat <a href=""mailto:[email protected]"">[email protected]</a>and reference the order number listed above.</p>

</asp:Content>Listing 13-49. Code for SalesOrder.aspx page.

When the page loads, the Session("OrderNumber") and current date are written to two Label controls. The OrderNumberLabel is for display purposes; it also is needed as a parameter reference for the AccessDataSource to issue its SELECT statement to populate the GridView. This technique has been used previously when SelectCommand parameters are not part of the control being populated.

Clearing the Shopping Cart

Once information about the order has been captured and saved for future reference (this latter step is not part of this example), ordered items can be cleared from the shopping cart. This action rids the ShopCart table of build-up of old records over time. Some online sites maintain shopping cart information for extended periods of time so that customers can return later to continue shopping or to complete the checkout process. In the current example, shopping records are deleted when order payment is completed.

Deletion of shopping cart items needs to take place after the sales order display, that is, after the page loads. If performed during the Page_Load process, the items are deleted prior to binding them to the GridView and no order gets displayed.

A counterpart to the Page_Load event is the Page_Unload event. Page_Unload occurs after all other page events occur. This event signals the proper time—after the page is finished loading and the sales order GridView is displayed—to delete items from the shopping cart. The Page_Unload subprogram of the SalesOrder.aspx page to delete shopping cart items is shown below.

Sub Page_UnLoad

Dim DBConnection As OleDbConnection Dim DBCommand As OleDbCommand Dim SQLString As String DBConnection = New OleDbConnection( _ "Provider=Microsoft.Jet.OLEDB.4.0;" & _ "Data Source=" & Server.MapPath("../Databases/BooksDB.mdb")) DBConnection.Open() SQLString = "DELETE FROM ShopCart " & _ "WHERE OrderNumber = '" & Session("OrderNumber") & "'" DBCommand = New OleDbCommand(SQLString, DBConnection) DBCommand.ExecuteNonQuery() DBConnection.Close()

'-- Assign a new order number RANDOMIZE

Page 336: Assignment Instructions

Session("OrderNumber") = (INT((9999999 - 1111111 + 1) _ * RND + 1111111)).toString()

End SubListing 13-50. Code for Page_Unload subprogram.

After deleting all shopping cart records for this customer, a new Session("OrderNumber") is assigned. The current shopping session is over and any further shopping takes place under a different order number. The same random number generator that produces the initial order number is used to create the new order number.

Shopping Cart Maintenance

Although shopping cart records for customers who complete the shopping process are deleted, there still may be build-up of old records of customers who abandon the site. A customer, for instance, may leave the site and never return, or fail to return prior to a Session's 20-minute time-out period. This customer receives a different order number, and previous shopping cart items do not get deleted. Long-term maintenance of the shopping cart, then, requires it to be purged periodically of old records of long-lost customers.

One way to do this is by periodically running a script to check the OrderDate field written to each ShopCart record. If the date is of a certain age, then the record is deleted. This is the reason, in fact, for including this date in a shopping cart record. Below is a script to remove shopping cart records that are older than five days.

'-- Remove old shopping cart records Dim NumberOfDays as Integer Dim DBConnection As OleDbConnection Dim DBConnection1 As OleDbConnection Dim DBCommand As OleDbCommand Dim DBReader As OleDbDataReader Dim SQLString As String DBConnection = New OleDbConnection( _ "Provider=Microsoft.Jet.OLEDB.4.0;" & _ "Data Source=" & Server.MapPath("../Databases/BooksDB.mdb")) DBConnection1 = New OleDbConnection( _ "Provider=Microsoft.Jet.OLEDB.4.0;" & _ "Data Source=" & Server.MapPath("../Databases/BooksDB.mdb")) DBConnection.Open() SQLString = "SELECT * FROM ShopCart" DBCommand = New OleDbCommand(SQLString, DBConnection) DBReader = DBCommand.ExecuteReader() While DBReader.Read() If DateDiff(DateInterval.Day, Today, DBReader("OrderDate")) < -5 Then DBConnection1.Open() SQLString = "DELETE FROM ShopCart " & _ "WHERE OrderNumber = '" & DBReader("OrderNumber") & "'" DBCommand = New OleDbCommand(SQLString, DBConnection1) DBCommand.ExecuteNonQuery() DBConnection1.Close() End If End While DBReader.Close() DBConnection.Close()Listing 13-51. Script to remove outdated shopping cart items.

Page 337: Assignment Instructions

This script uses the Visual Basic DateDiff() function to return the number of days between the current date and the OrderDate of a record. If the difference is greater than 5 (actually -5 because of comparing an older date to the current date), the record is deleted.

This script can be run periodically as a separate database maintenance page, or it can be coded on one of the pages of the eCommerce site to take place routinely, say, whenever shopping cart records are normally deleted following order completion. For example, this routine can be placed in the Page_Unload subprogram described above. Then, after a known customer's records are deleted, this routine checks for any other records that are older than five days and deletes them also. Thus, shopping cart maintenance is built into the normal routines of the eCommerce site.

Integration with PayPal

The final success of an e-commerce site is getting the money into the bank. For this purpose you must integrate your site with a credit card processing service. This service handles all the details of interacting with your customers over a secure connection, collecting credit card information, interfacing with the Federal banking system to debit and credit bank accounts, and notifying your site of the success of the transactions.

There are numerous commercial services available at various setup costs, transaction fees, and volume limits. For instance, a popular online payment service has entry-level costs including a $179 set-up fee and a $20 per month transaction fee for up to 500 transactions per month. Other fees are applicable for additional customer and merchant support services. In contrast, a payment service such as PayPal can be joined at no set-up cost and very modest transaction fees of around 3.5% of sales. Since this latter service is widely popular, the steps involved in integrating your e-commerce site with PayPal are summarized below. They are typical of integration with most commercial services.

Handling HTML Forms

Most online payment services work by placing a short HTML form on your Web page. This form links to the payment service through the action attribute of the <form> tag. Hidden textbox fields on the form are populated with transaction information—at minimum with your membership identification with the service, a customer identification, the total amount of the order, and the URL of your site page to which work flow will return when credit processing is completed.

The problem is that ASP.NET pages are forms themselves, surrounded by a single <form Runat="Server"> tag that automatically posts back to itself. Therefore, you cannot embed an HTML form inside this server form.

If you are not using master pages, you can get around this limitation by placing the HTML form outside the server form. The setup looks something like that shown below, similar to the example CreditCheck.aspx page explained in the "Credit Card Processing" section of this tutorial.

<form Runat="Server">

...page content

</form>

<form action="credit card processing URL" method="post">

Page 338: Assignment Instructions

...transaction data fields

</form>Listing 13-52. Web page layout with two separate forms.

Posting of the HTML form takes place outside the context of the server form. Transfer is made to the action URL without involving post-back of the separate server form.

If, however, you are using master pages then it is impossible to place an HTML form outside the server form. The master page containing the <form Runat="Server"> control always encompasses the entire content page. An HTML form coded on a content page is always inside the server form. In this situation you will need to place the HTML form on a separate page from a master/content page and transmit it behind the scenes, using a technique like that explained in the "Checkout Processing" section of this tutorial. An alternative is to configure a stand-alone Web page that resembles the layout of the master/content pages. In this latter case the page layout can appear like the two forms coded above.

Setting Up an Account with PayPal

You must have an account with PayPal before conducting online business. This is a relatively simple process.

1. Go to the PayPal site (https://www.paypal.com) and click on the Sign Up Now link at the top of the page.

2. Choose an account type. A Premier account is sufficient for conducting personal business and handling most credit cards. A Business account permits you to conduct sales under a business name and to handle all payment types. You can decide which account best fits your purpose.

3. You are asked to enter personal information (and business information for a Business account) including an email address as your account identification and a password.

4. After submitting this information, you will receive an email from PayPal confirming your account.

Additional set-up steps are completed after you received the email account confirmation. At that time you return to PayPal and log in under your private member account—your confirmed email address and password.

1. Add a Bank Account. You must add a bank account through which customer payments are received and refunds are returned. This is your personal or business bank account indicated by bank name, account type, routing number, and account number, the latter two taken from the numbers appearing at the bottom of your checks.

2. Confirm a Bank Account. After you add a bank account, PayPal makes two small deposits into the account. After getting the deposit amounts from your bank, return to PayPal and enter these amounts to confirm your bank account. You will not be able to conduct business until after the account is confirmed.

3. Add a Credit Card. You can add a personal or business credit card to your account. This is optional but necessary if you intend to use the card for you own purchases. Incidentally, you cannot use this card to test your system by purchasing your own products. You cannot purchase and receive payment under the same card.

Page 339: Assignment Instructions

You may find that it takes several days to get your account set up and functioning. However, this gives you time to explore the PayPal site to become familiar with personal and merchant services. You should probably download relevant documentation and guides at https://www.paypal.com/us/cgi-bin/webscr?cmd=_resource-center. Make sure you spend time reading the documentation. You will discover useful features and service descriptions beyond the summary coverage provided here.

Developer Site

As a site developer you may wish to join as a member of PayPal Developer Central. As a member you have free access to a "Sandbox" development environment in which you can test your pages prior to their full integration with online PayPal. It works exactly like normal PayPal without risk of introducing buggy code into the real world. Check out this site at https://developer.paypal.com/.

PayPal "Buy Now" Buttons

The most basic way to interact with PayPal is through a "Buy Now" button. A click on this button links from your site to PayPal where the purchase transaction for a single product is completed. This method requires no scripting; plus, the button code can be generated automatically by filling in forms at the PayPal site.

Your setup work with PayPal is through their "Merchant Services" site section. The first step in creating a "Buy Now" button is to enter details about the item for sale.

Figure 13-15. Entering product information for a PayPal "Buy Now" button.

Next, you choose a button style for display on your site. As an option, you can provide a link to your own graphic image for use as the button.

Page 340: Assignment Instructions

Figure 13-16. Choosing a button style for a PayPal "Buy Now" button.

Then, you provide return URLs for linking back to your site following the PayPal transaction. Different pages can be specified for a successful purchase versus a cancelled purchase.

Figure 13-17. Configuring return URLs for a PayPal "Buy Now" button.

Other optional steps include indicating whether multiple items can be purchased and whether to collect shipping information.

Figure 13-18. Selecting multiple quantities and shipping information options for a PayPal "Buy Now" button.

Finally, code for the form needed to connect to and transmit this information to PayPay is generated. This code is copied and pasted onto your page at the display location of the "Buy Now" button, usually next to a picture and description of the item for sale. When this button is clicked, transfer is made immediately to the PayPal site where the customer is walked through the purchase transaction. After completion of the purchase, or its cancel, transfer returns to your site. Both you and the customer receive email confirmations of the purchase.

Page 341: Assignment Instructions

Figure 13-19. Generated code for a PayPal "Buy Now" button.

Shown below is typical code for a PayPal "BuyNow" button. Once you have produced this first button, you can modify it for use in purchasing other products at your site.

<form action="https://www.paypal.com/cgi-bin/webscr" method="post"><input type="hidden" name="cmd" value="_xclick"><input type="hidden" name="business" value="[email protected]"><input type="hidden" name="item_name" value="ASP.NET 2.0 Tutorial"><input type="hidden" name="item_number" value="WDS03"><input type="hidden" name="amount" value="52.00"><input type="hidden" name="return" value="http://www.dradamsweb.com/default.aspx"><input type="hidden" name="cancel_return" value="http://www.dradamsweb.com/default.aspx"><input type="hidden" name="no_note" value="1"><input type="hidden" name="currency_code" value="USD"><input type="hidden" name="bn" value="PP-BuyNowBF"><input type="image" border="0" name="submit" src="https://www.paypal.com/en_US/i/btn/x-click-but01.gif" alt="Make payments with PayPal - it's fast, free and secure!"></form>Listing 13-53. Form code for a PayPal "Buy Now" button.

Customer Interaction with PayPal

When your site visitor clicks the "Buy Now" button to purchase an item, the enclosing form transfers to the PayPal site, carrying with it the information coded on the form. The initial page is shown in Figure 13-19. From here, several screens lead the purchaser through the steps necessary to indicate payment method, enter credit card information, and confirm and finalize the transaction. At the end, transfer returns to the merchant site at one of the pages designated by the return URLs associated with the "Buy Now" button.

Page 342: Assignment Instructions

Figure 13-20. Customer interaction with PayPal.

With a merchant account, you can always log on to PayPal to review sales transactions. Summaries are provided along with options to transfer funds from your PayPal account to your bank account, to initiate shipping, or to return refunds to customers.

PayPal Shopping Cart

Normally, in running a commercial site, you will not wish to have a checkout procedure associated with each individual item for sale. The usual approach is to maintain a shopping cart of items and, at the end, perform checkout processing for the total amount of the order. PayPal permits you to integrate your own or a third-party shopping cart, or to use it's built-in shopping cart feature.

To use PayPal's shopping cart, and in the same manner for creating "Buy Now" buttons, you create "Add to Cart" and "View Cart" buttons. The former are associated with each product for sale, the latter for viewing the current contents of the shopping cart and initiating the checkout process.

Page 343: Assignment Instructions

Figure 13-21. Viewing the PayPal shopping cart.

Using a Merchant Shopping Cart

It is a little more problematic to integrate your own site's shopping cart with PayPal. It involves utilizing the Instant Payment Notification (IPN) service. You can log on to PayPal and select this option. In this case, the Auto Return option—the default option in which you provide a return URL for buy-now and shopping-cart purchases—must be turned off for replacement by a different method of returning to your site.

The IPN feature permits you to know immediately the success or failure of the sale and to take action with your customer such as producing a sales order or otherwise corresponding online with the customer. Still, there are follow-up emails generated by PayPal confirming and summarizing the transaction.

This option also requires you to produce a special page to interact with IPN notification. This page contains only script (it has no visual presence) and performs the following tasks.

1. Receives information from PayPal with data fields summarizing the transaction.

2. Echoes the received information back to PayPay as security confirmation that the information was actually and accurately sent by PayPal.

3. Receives verification back from PayPal on the success of the transaction.4. On the basis of the returned "VERIFIED" or "NOT VERIFIED" flag, redirects to a local

page for follow-up interaction with the customer.

With IPN as the chosen return option, the URL designated in the form's "return" field is a script page to receive, echo back, and receive processing verification from PayPal. The script is a bit unusual in that it receives a "form" from PayPal, posts a "form" back to PayPal, and receives another "form" back from PayPal, all without using HTML forms. The technique involves posting form information through the HttpWebRequest class, effectively posting forms through script rather than through HTML forms. An example page to perform this processing is shown below.

<%@ Page Language="vb" Debug="True" %><%@ Import Namespace="System.IO" %><%@ Import Namespace="System.Text" %><%@ Import Namespace="System.Net" %>

<SCRIPT Runat="Server">

Sub Page_Load

'-- Capture posted form values from PayPal Dim FormValues As String = Request.Form.ToString()

Page 344: Assignment Instructions

' Create the postback to PayPal to verify sent information Dim PostBackRequest As HttpWebRequest PostBackRequest = WebRequest.Create("https://www.paypal.com/cgi-bin/webscr") PostBackRequest.Method = "POST" PostBackRequest.ContentType = "application/x-www-form-urlencoded" Dim PostBackString As String = FormValues + "&cmd=_notify-validate" PostBackRequest.ContentLength = PostBackString.Length

' Send the postback reply to PayPal Dim PostBackWriter As StreamWriter PostBackWriter = New StreamWriter(PostBackRequest.GetRequestStream(), Encoding.ASCII) PostBackWriter.Write(PostBackString) PostBackWriter.Close()

' Receive final verification from PayPal Dim ResponseReader As StreamReader ResponseReader = New StreamReader(PostBackRequest.GetResponse().GetResponseStream()) Dim ResponseString As String = ResponseReader.ReadToEnd() ResponseReader.Close()

If ResponseString = "VERIFIED" Then 'Set a flag and transfer to sales success page Session("Verified") = "OK" Response.Redirect("SalesConfirmation.aspx") Else 'Set flag and transfer to sales failure page Session("Verified") = "" Response.Redirect("NoSalesConfirmation.aspx") End If

End Sub

</SCRIPT>Listing 13-54. Example receive and post-back page.

When this page is called by PayPal as your return URL page, it receives transaction information collected at the PayPal site. This information includes billing and shipping information and numerous other items of information pertaining to the sale. Your script receives this information just as it would a standard HTML form, through the Request.Formcollection. Notice the first line in the script.

Dim FormValues As String = Request.Form.ToString()Listing 13-55. Receiving a form as a string.

Here, the entire form is assigned to a string variable, FormValues, for post-back to PayPal to validate the transmission. If you need to capture this information for your own purposes, such as saving any shipping and billing information collected by PayPal, you can parse the Request.Form collection into its individual fields.

Dim Items As StringFor Each Item in Request.Form Session(Item) = ItemNextListing 13-56. Capturing form fields returned by PayPal IPN notification.

The main purpose for the returned form string, however, is for echo back to PayPay for verification. The next section of script composes this Web request and sends it to PayPal.

Page 345: Assignment Instructions

' Create the postback to PayPal to verify sent informationDim PostBackRequest As HttpWebRequest PostBackRequest = WebRequest.Create("https://www.paypal.com/cgi-bin/webscr")PostBackRequest.Method = "POST"PostBackRequest.ContentType = "application/x-www-form-urlencoded"Dim PostBackString As String = FormValues + "&cmd=_notify-validate"PostBackRequest.ContentLength = PostBackString.Length

' Send the postback reply to PayPalDim PostBackWriter As StreamWriter PostBackWriter = New StreamWriter(PostBackRequest.GetRequestStream(), Encoding.ASCII)PostBackWriter.Write(PostBackString)PostBackWriter.Close()Listing 13-57. Posting received order information back to PayPal.

Discussion of the coding details is beyond the purpose of these tutorials. Suffice it to say that the post-back returns to the PayPal site accompanied by the information previously received. PayPal verifies that this is, indeed, the information it sent, using this echo as a means to verify that the transmission was not intercepted and changed nor was the original transmission a "spoof" of PayPal.

The script then waits for a second transmission from PayPal, this time a final verification of the order. A single data item is received, the string value "VERIFIED" if payment is verified. In the above script, this string value is received in variable ResponseString.

' Receive final verification from PayPalDim ResponseReader As StreamReader ResponseReader = New StreamReader(PostBackRequest.GetResponse().GetResponseStream())Dim ResponseString As String = ResponseReader.ReadToEnd()ResponseReader.Close()Listing 13-58. Receiving final post from PayPal.

At this point, your script does whatever it needs to do depending on whether "VERIFIED" was received or not. Normally, on a verified order, you will want to redirect to a page at your site to handle final order processing.

Other PayPal Options

The above discussion only scratches the surface of payment options available through PalPal. If wanted, you can produce invoices, handle subscriptions, recurring payments or donations, and initiate shipments through UPS or the postal service; plus, there is a wealth of merchant services for managing your account. The best way to learn about these features is to visit the site and, at no cost, establish an account. Then you can nose around before making a commitment, and even cancel your account if you decide not to proceed.

SELECT Statement

Database management systems provide SQL, the Structured Query Language, for working with information in a database. When using SQL, you are relying on the database management system to perform the work. Rather than coding a server script to access tables or to maintain the data in the databases, this work is farmed out to the DBMS. The script simply issues an SQL request to the DBMS, which independently carries out the task. This method promotes the notion

Page 346: Assignment Instructions

of a three-tier, client/server processing system where data access and database processing is localized to the database server.

The SELECT Statement

The most commonly used of the SQL statements is the SELECT statement. As the name implies, this statement is used to select records from a database table. The selection can encompass the entire table with all of its fields, or it can be restricted to certain fields in certain records that match given search criteria. Optionally, the selected records can be ordered, or sorted, on particular fields. The group of selected records itself becomes a recordset that can be processed in the same fashion as used for an entire table.

The general format for the SELECT statement is shown below:

SELECT [TOP n [PERCENT]] * | [DISTINCT] field1 [,field2]... FROM TableName WHERE criteria ORDER BY FieldName1 [ASC|DESC] [,FieldName2 [ASC|DESC] ]...

Figure B-1. General format for SELECT statement.

The keyword SELECT is followed by one of two specifications identifying the fields of data to be selected from a table. An asterisk (*) denotes that all fields are to be selected for each record. Otherwise, you can provide a list of field names, separated by commas, and only those data fields will be selected. The FROM clause identifies the table from which these records and fields are to be selected.

For example, the statement,

SELECT * FROM MyTableListing B-1. Selecting all records and all fields from a table.

selects all records from MyTable and includes all (*) of the fields that make up a record. In short, it retrieves the entire table. In contrast, the statement,

SELECT LastName, FirstName FROM MyTableListing B-2. Returning selected fields from all records in a table.

selects all records from the table, but returns only the fields named LastName and FirstName. In this case, the resulting recordset contains as many rows as there are records in the table, but only two columns.

The DISTINCT Keyword

Some table fields are likely to contain non-unique data values. That is, the same value may appear in more than one record. In order to retrieve only the unique values from these fields, precede the field name with the keyword DISTINCT. For instance, the following statement retrieves a single column of data containing only the unique values in field ItemType.

Page 347: Assignment Instructions

SELECT DISTINCT ItemType FROM MyTableListing B-3. Selecting only records with unique values in a field of a table.

The TOP Predicate

In order to limit the number of records returned you can preface the selection with the keyword TOP, specifying the first n number of records or the first n PERCENT of records in the table.

SELECT TOP 10 * FROM MyTableListing B-4. Selecting all fields from the first 10 records of a table.

The WHERE Clause

A common way to restrict the number of records returned from a selection is to supply a criterion against which records can be matched. Only the matching records are returned. The SELECT statement has an optional WHERE clause to supply the condition for selecting.

The keyword WHERE is followed by one or more selection criteria. A typical way to use this feature is to check for equality, that is, to look for a matching value in one of the record's fields. For example, if you are processing a set of customer records based on the state in which they are located, you might wish to select only those records where the State field contains the value "GA".

SELECT * FROM Customers WHERE State='GA'Listing B-5. Selecting only those records with a matching field value.

Here, the database management system delivers only those records that have a matching state code.

Note in this example that the criterion value is enclosed in single quotes (apostrophes). Any time you are matching against a database text field, the criterion value must be enclosed in single quotes (WHERE State = 'GA'). If you are testing against a numeric field, the data value is not enclosed in quotes (WHERE Number > 10). If you are testing against adate/time field, the criterion value is surrounded by # symbols (WHERE TheDate > #1/1/01#).

You can use any of the common conditional operators to formulate your selection criterion.Operator Meaning

= equal to

<> not equal to

< less than

> greater than

<= less than or equal to

=> equal to or greater than

LIKE is contained in

Figure B-2. Conditional operators for composing WHERE clauses.

Also, you can combine tests using the logical operators AND, OR, and NOT to expand or contract your selection.

Page 348: Assignment Instructions

SELECT * FROM Customers WHERE State='GA' OR State='KY'Listing B-6. Using conditional and logical operators to select records.

The LIKE operator locates records where a field contains a given string value. In its most general form, this comparison tests for a string anywhere in a field.

SELECT * FROM Products WHERE Description LIKE '%micro%'Listing B-7. Selecting for records with a string value anywhere in a field.

In this example, the string "micro" is located anywhere in the Description field. This criterion retrieves records where the field contains the actual word "micro", as well as records containing "Microsoft", "microcomputer", "microphone", "micrometer", "micron", or any other text combination containing this string. The search string must be enclosed in "%" characters inside single quotes.

Variations on the LIKE comparison check for the string at the beginning of a field (WHERE Description LIKE 'micro%') or at the end of a field (WHERE Description LIKE '%micro'). The "%" sign is a wildcard character meaning any following or preceding text.

The ORDER BY Clause

A SELECT statement can also include the ORDER BY clause in order to arrange, or sort, the set of records retrieved from a table.

The ORDER BY clause identifies the names of fields on which to sort the records. If more than one field name is supplied, then sorting takes place in the order in which the names appear, separated by commas. The first field becomes the major sort field, the second field becomes the intermediate sort field, and the third field becomes the minor sort field. Thus, you can arrange a set of names in order by last name, first name, and middle initial by using a SELECT statement that resembles the following:

SELECT * FROM Customers ORDER By LastName, FirstName, MIListing B-8. Sorting a group of returned records on three fields.

You can also specify whether ordering is to take place in ascending or descending sequence by coding ASC or DESC following the field name. The default order is ascending (ASC), which does not need to be coded.

SELECT * FROM Customers ORDER By LastName DESC, FirstName ASC, MIListing B-9. Sepcifying descending and ascending sort order for a returned recordset.

Including TOP n along with ORDER BY returns records that fall at the top or the bottom of a range of values. When ordered with DESC, the top of the range is retrieved; when ordered with ASC, the bottom of the range is retrieved.

SELECT TOP 10 ItemName, ItemPrice FROM Products ORDER BY ItemPrice ASCListing B-10. Selecting records at the bottom of a range.

The preceding statement selects the 10 lowest-priced products from the table. You can also use the PERCENT designation to return a certain percentage of records that fall at the top or the bottom of a range specified by an ORDER BY clause.

Page 349: Assignment Instructions

The WHERE and ORDER BY clauses are optional in a SELECT statement and either can appear. If both are included, however, the WHERE clause must precede the ORDER BY clause.

Composing SQL Strings

Sometimes SELECT statements can become quite complex with selected fields, selection criteria, and ordering clauses. Therefore, when scripting these statements, it is often convenient to first compose the statement within a script variable, and then issue the statement through the variable name.

SQLString = "SELECT * FROM Customers WHERE State='GA' ORDER BY LastName DESC"CommandObject = New OleDbCommand(SQLString, Connection)Listing B-11. Assigning a SELECT statement to a script variable.

Here, the string of characters comprising the SELECT statement are assigned to variable SQLString. Then, this variable is used in issuing the SQL statement through a Command object.

If the SELECT statement is particular long or complex, you can piece it together a bit at a time by concatenating to the string.

SQLString = "SELECT * FROM Customers "SQLString &= "WHERE State='GA' OR State='KY' "SQLString &= "ORDER BY LastName DESC, FirstName, MI"Listing B-12. Composing a SELECT statement by concatenation to a script variable.

An alternative is to concatenate portions of the SELECT statement by using line continuations.

SQLString = "SELECT * FROM Customers " _ & "WHERE State='GA' OR State='KY' " _ & "ORDER BY LastName DESC, FirstName, MiddleInitial"Listing B-13. Alternate form of composing a SELECT statement by concatenation to a script variable.

Here, subsequent clauses are concatenated to create the SQL string (making sure that necessary spaces are included to separate the clauses).

Apostrophes in Text Fields

It is often the case that text values contain apostrophes, e.g., names (O'Reilly), possessives (Bill's), contractions (it's), and the like. However, an SQL statement such as the following,

SELECT * FROM Customers WHERE LastName = 'O'Reilly'Listing B-14. Invalid coding of apostrophes within a text string.

causes an error because it is invalid to code an apostrophe within a value which itself is enclosed in apostrophes. The way around the problem is to code double apostrophes ('') in place of any single apostrophe within the value.

SELECT * FROM Customers WHERE LastName = 'O''Reilly'Listing B-15. Valid coding of apostrophes within a text string.

Page 350: Assignment Instructions

Integrating Variable Data

It is normally the case that SQL statements are composed by integrating script-generated data values rather than by using string or numeric constants. For example, a script might define a variable, StateCode, to hold a state code, and use whatever value is stored in the variable as the search criterion. In this case, a script must compose an appropriateSELECT statement by concatenating fixed portions of the SELECT statement with the variable value.

Dim StateCode As String...SQLString = "SELECT * FROM Customers WHERE State = '" & StateCode & "'"Listing B-16. Integrating a script-generated text string withing a SELECT string.

Notice that apostrophes are included in the literal text strings surrounding the variable so that the StateCode value is treated as a string. When retrieving numeric data, apostrophes are not required.

Dim TheAge As Integer...SQLString = "SELECT * FROM Customers WHERE Age = " & TheAgeListing B-17. Integrating a script-generated numeric value within a SELECT string.

When composing statements that include a date, the special symbol "#" must surround criteria for a Date/Time field.

Dim TheDate As Date...SQLString = "SELECT * FROM Orders WHERE OrderDate = #" & TheDate & "#"Listing B-18. Integrating a script-generated Date/Time value within a SELECT string.

Sometimes it can get a bit tedious in composing SQL statements when there are several variables to integrate. In these cases, you should consider breaking the statement into line-continued portions so you can better visualize the statement composition.

Dim TheState As StringDim TheAge As IntegerDim TheDate As Date...SQLString = "SELECT * FROM Customers WHERE " & _ "State = '" & TheState & "' AND " & _ "Age > " & TheAge & " AND " & _ "ResDate = #" & TheDate & "# " & _ "ORDER BY Age ASC"Listing B-19. Composing a complex SELECT statement.

Notice that spaces are used to align fields and values for ease of reading since spaces are generally ignored in SQL unless they have meaning as a data value. Focus on the variables and the literal portions inside the double quotes to see what values are strung together to produce the statement.

In summary, then, SQL SELECT statements for the three data types have the following general constructions.

Page 351: Assignment Instructions

SQLString =

"SELECT * FROM Table WHERE StringField = '" & StringVariable & "'""SELECT * FROM Table WHERE NumericField = " & NumericVariable"SELECT * FROM Table WHERE DateField = #" & DateVariable & "#"

Figure B-3. General formats for WHERE clauses and data types.

When composing statements that include strings with possible apostrophes, you should use the Visual Basic Replace() statement to convert single apostophes to double apostrophes.

Dim TheName As String...SQLString = "SELECT * FROM Customers WHERE " & _ "Name = '" & Replace(TheName, "'", "''") & "'"Listing B-20. Replacing single apostrophes with double apostrophes in string fields.

If variable TheName contains the string "O'Reilly", then the resulting SQL statement is

SELECT * FROM Customers WHERE Name = 'O''Reilly'Listing B-21. A SELECT statement with apostrophes replaced by double apostrophes.

INSERT Statement

The INSERT statement is used to add records to a database table. Its general format for adding a single record is shown below.

INSERT INTO TableName (FieldName1 [,FieldName2]...) VALUES (Value1 [,Value2]...)

Figure B-4. General format for INSERT statement.

The keywords INSERT INTO are followed by the name of the table into which a new record is to be added. The table name is followed by a comma-separated list of field names, enclosed in parentheses, for which values are supplied. The VALUES clause gives, in parentheses, a comma-separated list of corresponding data values for the added fields. No set of records is returned by an INSERT statement.

Typically, all fields are named and supplied with values, but this need not be the case. Those fields which are named and which have corresponding values are entered into the added record; unspecified fields are added but have no values assigned.

The most common and clearest way to specify a new record is to name all fields and to supply a value for each.

INSERT INTO MyTable (Field1, Field2, Field3, Field4, Field5) VALUES('text field value', 'memo field value', 100, 3000, #01/01/04#)Listing B-22. Inserting a new record into a database table.

In this example, a new record containing five fields is added to table MyTable. The names of the fields are provided along with the data values to be assigned, respectively, to each field. Values

Page 352: Assignment Instructions

for fields that are defined as text fields in the table must be enclosed in single quotes (apostrophes); values for numeric fields are not enclosed in single quotes; values for Date/Time fields are enclosed in # symbols. It is perfectly acceptable, though, to insert a new record with only some of the fields having values, as long as the values match the field names and data types.

INSERT INTO MyTable (Field1, Field5) VALUES('text field value', #01/01/04#)Listing B-23. Inserting a new record with only selected field values into a database table.

In this case, those fields that are not assigned values take on whatever default values are defined for those fields in the database.

If an inserted text field contains apostrophes, they must be replaced with double apostrophes. See the discussion about apostrophes in data values on the SELECT page.

Inserting Variables into INSERT Statements

As in the case of the SELECT statement, SQL INSERT statements normally are composed by a script which inserts variable data into the statements. Again, it can be helpful to construct the statement as continued lines of code in order to better visualize it.

Dim ItemNo As StringDim ItemName As StringDim ItemPrice As DoubleDim ItemQuantity As IntegerDim PurchaseDate As Date...SQLString = "INSERT INTO Products " & _ "(ItemNo, ItemName, ItemPrice, ItemQty, ItemDate) " & _ "VALUES (" & _ "'" & ItemNo & "', " & _ "'" & ItemName & "', " & _ ItemPrice & ", " & _ ItemQuantity & ", " & _ "#" & PurchaseDate & "#)"Listing B-24. Composing an INSERT statement in script.

This construction produces an INSERT statement resembling the following.

INSERT INTO Products (ItemNo, ItemName, ItemPrice, ItemQty, ItemDate) VALUES ('AAA111', 'Software', 100.00, 15, #07/15/04#)Listing B-25. An INSERT statement composed in script.

UPDATE Statement

The UPDATE statement is used to change records in a database table. Its general format for updating a single record is shown below.

UPDATE TableName SET (FieldName1=value1 [,FieldName2=value2]...) WHERE criteria

Page 353: Assignment Instructions

Figure B-5. General format for UPDATE statement.

The keyword UPDATE is followed by the name of the table being updated. The keyword SET is followed by a comma-separated list of field names and associated data values that change the current values of the specified record in the table. The WHERE clause gives the criteria for locating the particular record to update. It is not necessary to change the values of all fields in the record; specify only those fields and values to be changed.

UPDATE MyTable SET Field2 = 'new text value', Field3 = 200, Field5 = #02/02/04# WHERE Field1 = 'KEY001'Listing B-26. Updating a record in a database table.

In this example, three fields are changed in the record identified by the value 'KEY001' in Field1 in table MyTable. Values for fields that are defined as text fields in the table must be enclosed in single quotes (apostrophes); values for numeric fields are not enclosed in single quotes; values for Date/Time fields are enclosed in # symbols.

The WHERE Clause

It is nearly always necessary to include a WHERE clause in an UPDATE statement in order to change a particular record. If no WHERE clause appears, then all records in the table are updated with the same values.

A common way of specifying a record to change is by matching its unique "key" field.

UPDATE Products SET ItemQuantity = 0 WHERE ProductID = 'AA111'Listing B-27. Updating a record based on the value of a key field.

You can, though, use any of the common conditional operators given previously in Figure B-2.

UPDATE Products SET ItemQuantity = 0 WHERE ItemQuantity < 10 AND ItemType = 'Software'Listing B-28. Updating a record based on multiple criteria.

If a text field contains apostrophes, they must be replaced with double apostrophes. See the discussion about apostrophes in data values relative to the SELECT statement.

As in the case of SELECT and INSERT statements, the UPDATE statement normally is composed in a script using combinations of literal strings and variables.

Dim TheQuantity As IntegerDim TheType as String...SQLString = "UPDATE Products " & _ "SET ItemQty = 0 " & _ "WHERE ItemQuantity < " & TheQuantity & _ " AND NOT ItemType = '" & TheType & "'"Listing B-29. A script-composed UPDATE statement to update a record based on multiple criteria.

This code resolves into a UPDATE statement resembling the following.

Page 354: Assignment Instructions

UPDATE Products SET ItemQty = 0 WHERE ItemQuantity < 10 AND NOT ItemType = 'Software'"Listing B-30. An UPDATE statement composed in script.

DELETE Statement

The DELETE statement is used to delete records from a database table. Its general format for deleting a single record is shown below.

DELETE FROM TableName WHERE criteria

Figure B-6. General format for DELETE statement.

The keywords DELETE FROM are followed by the name of the table from which a record is to be deleted. The WHERE clause supplies the identification of the record. No set of records is returned by a DELETE statement.

The WHERE Clause

It is almost always necessary to include a WHERE clause identifying the record to delete. If no WHERE clause is supplied, then all records are deleted. The keyword WHERE is followed by one or more selection criteria. A common way of specifying a record to delete is by a match to its unique "key" field.

DELETE FROM Products WHERE ItemNo = '99999'Listing B-31. Specifying a record to delete from a database table.

You can, though, use any of the common conditional operators given previously in Figure B-2.

DELETE FROM Products WHERE ItemQuantity = 0 AND ItemType = 'Software'Listing B-32. Identifying a record to delete using multiple criteria.

When matching against a text field, the criterion value must be enclosed in single quotes; in testing against a numeric field, the data value is not enclosed in quotes; in testing against a Date/Time field, the criterion value is surrounded by # symbols.

If a comparison text field contains apostrophes, they must be replaced with double apostrophes. See the discussion about apostrophes in data values on the SELECT page.

As in the case of SELECT, INSERT, and UPDATE statements, the DELETE statement normally is composed in a script using combinations of literal strings and variables.

Dim TheDate As DateDim TheQuantity As IntegerDim TheType as String...SQLString = "DELETE FROM Products" & _ " WHERE PurchaseDate < #" & TheDate & "#" & _ " OR (ItemQty < " & TheQuantity & _ " AND ItemType = '" & TheType & "')"Listing B-33. Composing a script-generated DELETE statement.

Page 355: Assignment Instructions

This code resolves into a DELETE statement resembling the following.

DELETE FROM Products WHERE PurchaseDate < #07/15/02# OR (ItemQty < 10 AND ItemType = 'Software')Listing B-34. A DELETE statement composed in script.

ArrayLists

ASP.NET Collections are data structures that are similar to Visual Basic arrays, functioning as script storage areas for collections of data values. Collections can supply arrays of values for use in script processing; they can be used as intermediate data stores for imported values that are bound to controls. There are three types of Collections under ASP.NET:ArrayLists, HashTables, and SortedLists. This tutorial discusses ArrayLists; follow-up tutorials cover HashTables and SortedLists.

Using ArrayLists

An ArrayList is an sequence of storage locations, each containing a single data value. After an ArrayList is created in a script, its data values are available for script processing or they can be bound to a list control to automatically generate the items in the list. When an ArrayList is bound to an <asp:RadioButtonList>, for example, the individual buttons with their labels and values are produced automatically in the same manner in which an external data source is bound to the control.

Figure C-1. Using an ArrayList for data binding.

The following code creates an ArrayList of color names when the page is loaded. The ArrayList is then bound to a RadioButtonList to supply its values, producing a set of buttons like those shown in the above illustration. Any of the list controls—RadioButtonList, CheckBoxList, DropDownList, or ListBox—can be bound to an ArrayList.

Red

Green

BlueFigure C-2. Binding an ArrayList to a RadioButtonList.<SCRIPT Runat="Server">

Sub Page_Load

Dim ColorList = New ArrayList ColorList.Add("Red") ColorList.Add("Green")

Page 356: Assignment Instructions

ColorList.Add("Blue")

RadioList.DataSource = ColorList RadioList.DataBind()

End Sub

</SCRIPT>

<form Runat="Server">

<asp:RadioButtonList id="RadioList" Runat="Server"/>

</form>Listing C-1. Code to bind an ArrayList to a RadioButtonList.

Creating ArrayLists

An ArrayList is created by assigning a New ArrayList() object to a reference variable, optionally specifying the number of entries in the list within the parentheses. Then, data values are added to the list with the list's Add() method. Once the ArrayList is filled with data, its values can be used to support script processing, or the list can be bound to one of the list controls. An ArrayList is bound to the DataSource property of the control, and the control's DataBind() method is called to bind the values. For controls that require them,DataTextField and DataValueField properties can be assigned from the array. The general formats for the statements involved in creating and binding an ArrayList are shown below.

Dim ArrayList = New ArrayList()

ArrayList.Add(value)...

Control.DataSource = ArrayListControl.DataTextField = "value"Control.DataValueField = "value"Control.DataBind()

Figure C-3. General formats for creating and binding an ArrayList.

An ArrayList can be defined without specifying the exact number of entries; e.g., New ArrayList() or New ArrayList without parentheses. An "undimensioned" array is necessary when creating it from an external data source containing an unknown number of data values. If a size is not specified, an ArrayList is initially configured for 16 entries; however, it expands as needed as data values are added to the list. Each time it expands, an additional 16 slots are provided. It can be compacted to its final size by using theArrayList.TrimToSize() method to set the final size of the list to its final number of data values. It is not necessary to compact the list for it to work properly.

An ArrayList can be sorted with the ArrayList.Sort() method. Sorting in reverse order is accomplished by applying the ArrayList.Sort() method followed by theArrayList.Reverse() method.

Items can be removed programmatically from the list by supplying the value of the item to delete: ArrayList.Remove("value"). Entries in the list are indexed starting with 0. So, an item

Page 357: Assignment Instructions

also can be deleted by supplying its index number: ArrayList.RemoveAt(index); or a range of items can be deleted by supplying the first and last index numbers of the range: ArrayList.RemoveRange(index1,index2). The ArrayList can be cleared of all entries with ArrayList.Clear().The current capacity of the list (including empty elements) can be determined from its ArrayList.Capacity property, and the current number of data values in the list is given by itsArrayList.Count property.

Individual items in an ArrayList can be accessed by their indexes, beginning with 0. So, for example, the reference ArrayList(3) accesses the contents of the fourth element in the list. The entire contents of the list can be accessed through a For...Next or For Each...Next loop which iterates the elements of the array. The following scripts show two ways to access, in turn, all values stored in the previous ColorList array.

Dim i As IntegerFor i = 0 to ColorList.Count - 1 ...process ColorList(i) '-- process array valueNext

Dim Element As StringFor Each Element in ColorList ...process Element '-- process array valueNextListing C-2. Code to iterate items in an ArrayList.

The iteration variable Element used in the second loop is declared as a string variable. This is because the values in the ArrayList are strings (color names). The iteration variable must be declared as the same type as the values in the ArrayList.

Editing an ArrayList

The contents of an ArrayList can be changed by a script after it has been initially populated with values. The following button, for example, adds three new colors to the ColorListarray that is loaded with this page. In addition, the script reverse sorts the array and re-binds its values to the RadioButtonList in Figure C-2. After clicking the button, scroll up to look at the newly populated RadioButtonList.

Figure C-4. Editing an ArrayList.<SCRIPT Runat="Server">

Sub EditArray (Src As Object, Args As EventArgs)

ColorList.Add("Purple") ColorList.Add("Yellow") ColorList.Add("Brown") ColorList.Sort() ColorList.Reverse() ColorList.TrimToSize()

RadioList.DataSource = ColorList RadioList.DataBind()

End Sub

Page 358: Assignment Instructions

</SCRIPT>

<form Runat="Server">

<asp:Button Text="Edit Array" OnClick="EditArray" Runat="Server"/>

</form>Listing C-3. Code to edit an ArrayList.

Maintaining the View State of ArrayLists

If an ArrayList is bound to a list control and no further access to the array is needed, then it can be created one time only, the first time the page loads. The array values that are bound to the list control are available on subsequent page postings because the control itself takes part in the page's View State. An ArrayList, however, does not participate in the page’s View State. Therefore, if access to the ArrayList is needed on subsequent page postings, then it needs to be recreated each time the page is loaded. This is the case in the previous example where ColorList is created the first time the page loads and its values are bound to the RadioButtonList; it is recreated when the button is clicked and a post-back operation takes place to edit the array. Thus, the script to create ColorList appears in the Page_Load subprogram so that it is run each time the page opens.

Rather than recreating an ArrayList from scratch each time the page is loaded, the original array can be assigned to a View State variable from which it is reconstituted each time the page is loaded. That is, even though an ArrayList itself does not take part in the page's View State, it can be made to participate in the View State with assignment to this variable. The following script illustrates this idea.

<SCRIPT Runat="Server">

Sub Page_Load

If Not Page.IsPostBack Then

'-- Define and populate an ArrayList Dim ColorList = New ArrayList ColorList.Add("Red") ColorList.Add("Green") ColorList.Add("Blue")

'-- Bind ArrayList to radio buttons RadioList.DataSource = ColorList RadioList.DataBind

'-- Save ArrayList as View State variable ViewState("ColorList") = ColorList

End If

'-- Define new ArrayList and reconstitute from View State variable Dim ColorList = New ArrayList ColorList = ViewState("ColorList")

End Sub

</SCRIPT>Listing C-4. Code to save an ArrayList in View State.

Page 359: Assignment Instructions

The first time the page is loaded, ColorList is created and populated as a new ArrayList, and it is bound to a RadioButtonList. The RadioButtonList maintains its bound values on page post-backs because all server controls take part in the page's View State. Assuming, though, that access to the ArrayList is needed on subsequent page postings, it is saved as ViewState("ColorList") to also participate in the page's View State. Subsequently, on each page post-back, a new ArrayList is created and repopulated from the View State variable.

Lookup Tables

One of the popular uses of an ArrayList is as a lookup table that can be searched by a script to find information. In the following example, a color name is chosen from a DropDownList. The index value of the selected item is used as the index location in an ArrayList of the hexadecimal value of the color.

         

Figure C-5. Using an ArrayList for table lookup.

For this example, two ArrayLists are built. The first is populated with color names and bound to a DropDownList. The second is populated with matching hexadecimal values to serve as a memory-resident lookup table. The hexadecimal list is declared as a global variable because it is accessed in two separate subprograms. It is saved in View State since it is needed on page post-backs.

<SCRIPT Runat="Server">

Dim HexList As ArrayList

Sub Page_Load

If Not Page.IsPostBack Then

HexList = New ArrayList HexList.Add("#FF0000") HexList.Add("#008800") HexList.Add("#0000FF") ViewState("HexList") = HexList

Dim ColorList = New ArrayList ColorList.Add("Red") ColorList.Add("Green") ColorList.Add("Blue") Colors.DataSource = ColorList Colors.DataBind()

End If

HexList = New ArrayList HexList = ViewState("HexList")

End Sub

Sub Get_Hex (Src As Object, Args As EventArgs)

HexColor.Text = HexList(Colors.SelectedIndex)

Page 360: Assignment Instructions

HexColor.Style("color") = HexList(Colors.SelectedIndex)

End Sub

</SCRIPT>

<form Runat="Server">

<asp:DropDownList id="Colors" Runat="Server"/><asp:Button Text="Find Hex" OnClick="Get_Hex" Runat="Server"/><asp:Label id="HexColor" Runat="Server"/>

</form>Listing C-5. Code to use an ArrayList for table lookup.

The ColorList ArrayList is built the first time the page loads and is bound to the Colors DropDownList. It is not necessary to build the ArrayList on every page post-back since the DropDownList is maintained in the page's View State on subsequent page loads. ColorList is needed only one time, as the original data source for the DropDownList.

The HexList ArrayList, however, must be built every time the page loads, initially and on post-back when the "Find Hex" button is clicked. Therefore, HexList is initially saved as a View State variable and is recreated from this variable on each page load.

When the button is clicked, the Get_Hex subprogram is called. The purpose of this subprogram is to find the hexadecimal value that equates to the color name selected in the DropDownList. The values in an ArrayList are indexed starting with 0; so are the values in a DropDownList. Therefore, the index value of the selected color (Colors.SelectedIndex) can be used as the index of the matching hexadecimal value in the ArrayList: HexList(Colors.SelectedIndex). This matching value is displayed in the HexColor Label control. The color of the text is set to this same hexadecimal value.

Loading ArrayLists from External Data Sources

Previous examples show ArrayLists created through script, with individual values added as hard coded text. More likely, ArrayLists will be loaded from external data sources where changing values can be maintained without editing scripts. In the following example, an XML file supplies state names and state codes. The names are loaded into a DropDownList; the codes are loaded into an ArrayList. A matching code is displayed for a state name chosen from the DropDownList.

                       

Figure C-6. Using an ArrayList created from an XML file for table lookup.

This following listing shows the first portion of content from file StateCodes.xml. Element <name> is used to populate the DropDownList; element <code> is used to build the ArrayList.

<?xml version="1.0" ?><statecodes> <state> <name>Alabama</name>

Page 361: Assignment Instructions

<code>AL</code> </state> <state> <name>Alaska</name> <code>AK</code> </state> <state> <name>Arizona</name> <code>AZ</code> </state> ...</statecodes>Figure C-6. XML file used to load an ArrayList

Script and HTML code for this application is shown below.

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

<SCRIPT Runat="Server">

Dim CodesArray As ArrayList

Sub Page_Load

If Not Page.IsPostBack Then

'-- Import XML file into data set Dim StateCodes = New DataSet StateCodes.ReadXml("StateCodes.xml")

'-- Bind state names to drop-down list StatesList.DataSource = StateCodes StatesList.DataTextField = "name" StatesList.DataValueField = "name" StatesList.DataBind()

'-- Create array of state codes CodesArray = New ArrayList Dim Row As DataRow For Each Row in StateCodes.Tables("state").Rows CodesArray.Add(Row("code")) Next ViewState("CodesArray") = CodesArray

End If

'-- Rebuild array of state codes CodesArray = New ArrayList CodesArray = ViewState("CodesArray")

End Sub

Sub Get_Code (Src As Object, Args As EventArgs) TheCode.Text = CodesArray(StatesList.SelectedIndex)End Sub

</SCRIPT>

<form Runat="Server">

<asp:DropDownList id="StatesList" Runat="Server"/><asp:Button Text="Find Code" OnClick="Get_Code" Runat="Server"/>

Page 362: Assignment Instructions

<asp:Label id="TheCode" Runat="Server"/>

</form>Listing C-7. Code to load and use an XML file for table lookup.

Recall from earlier discussions that an XML file must be imported to the page as a DataSet in order to be in proper tabular (row and column) format for binding to controls and for iterating the items in this recordset. Therefore, the System.Data namespace must be imported to the page, and as repeated below, the file must be read into a DataSet. This importing into a DataSet takes place only the first time the page loads since names from the DataSet are bound to the DropDownList and codes from the DataSet are used to build the ArrayList. The DropDownList and ArrayList can be independently maintained on subsequent post-backs of the page.

'-- Import XML file into data setDim StateCodes = New DataSetStateCodes.ReadXml(StateCodes.xml")Listing C-8. Importing an XML file into a DataSet.

Next, the DataSet is bound to the DropDownList using state names (the name field imported from the <name> element) as the Text and Value properties of the list. Recall that if only one of these properties is assigned the other property takes on the same value by default.

'-- Bind state names to drop-down listStatesList.DataSource = StateCodesStatesList.DataTextField = "name"StatesList.DataValueField = "name"StatesList.DataBind()Listing C-9. Binding a DataSet to a DropDownList.

Finally, the rows of the DataSet are iterated to build the CodesArray. The name of the DataSet table, "state," is taken from the tags surrounding each group of state names and codes. An iteration variable, Row, is declared as a DataRow type and a For Each...Next loop is set up to iterates these DataRows in the DataSet table's Rows collection. At each row, the value in the "code" column (taken from the XML tag name) is added as an element in the ArrayList. After the ArrayList is built, it is saved as a View State variable since it needs to be rebuilt on every page post-back, every time a table lookup is performed.

'-- Create array of state codesCodesArray = New ArrayListDim Row As DataRowFor Each Row in StateCodes.Tables("state").Rows CodesArray.Add(Row("code"))NextViewState("CodesArray") = CodesArrayListing C-10. Script to iterate a DataSet to build an ArrayList.

Remember that items in the DropDownList and elements the ArrayList are indexed the same, beginning with 0. State names in the DropDownList have the same index value as their associated state codes in the ArrayList. Therefore, subprogram Get_Code uses the SelectedIndex of the DropDownList as the index for the matching code in the ArrayList.

Sub Get_Code (Src As Object, Args As EventArgs) TheCode.Text = CodesArray(StatesList.SelectedIndex)End SubListing C-11. Locating a ArrayList value matching a DropDownList selection.

Page 363: Assignment Instructions

The above examples show a few of the ways in which ArrayLists can be used to support page processing. They can be used in all of the variety of ways you use standard Visual Basic arrays, and then some. One of the issues surrounding ArrayLists, however, is that they are single dimensional. They store a list of single data values. This issue is partially resolved by HashTables and SortedLists, topics taken up in the following tutorials.

HashTables

A HashTable is a storage array of items, each representing a key/value pair. The keys are the indexes to their related values, and very quick searches can be made for values by supplying their lookup keys. When a Hashtable is bound to a control, its keys and/or values become the Text and/or Value properties of the control.

Figure C-7. Using a HashTable for data binding.

Creating a Hashtable

A HashTable is created by assigning a New HashTable() object to a reference variable, optionally specifying the number of entries in the table within the parentheses. Then, data values are added to the table with the table's Add() method, supplying a key and associated value. Once the HashTable is filled with data, it can be used for table lookup or bound to a list control. A HashTable is bound to the DataSource property of the control. Assignment of the HashTable's keys and/or values are made to the control's Text property (DataTextField) and Value property (DataValueField). The control's DataBind() method is called to bind the values. General formats for the statements involved in binding a HashTable to a list control are shown below.

Dim HashTable = New HashTable()

HashTable.Add(key,value)...

Control.DataSource = HashTableControl.DataTextField = "Key|Value"Control.DataValueField = "Key|Value"Control.DataBind()

Figure C-8. General formats for creating and binding a HashTable.

The following code creates a HashTable named URLs to which six entries are added. The keys are string names of Web search sites; the values are URL addresses of the associated sites.

<SCRIPT Runat="Server">

Page 364: Assignment Instructions

Sub Page_Load

If Not Page.IsPostBack Then

Dim URLs = New Hashtable() URLs.Add("Google", "http://www.google.com") URLs.Add("MSN", "http://search.msn.com") URLs.Add("Yahoo", "http://www.yahoo.com") URLs.Add("Lycos", "http://www.lycos.com") URLs.Add("AltaVista", "http://www.altavista.com") URLs.Add("Excite", "http://www.excite.com")

End If

End Sub

</SCRIPT>Listing C-12. Script to create a HashTable.

A HashTable expands automatically as key/value pairs are added to the table. The final size of a HashTable is given by its Count property. Note that the order in which items are added to the HashTable does not matter. A HashTable maintains its own internal order based on its keys and cannot be sorted outside of this order.

Binding a Hashtable to Controls

A HashTable can be used to provide the text labels and values that are automatically assigned to list controls. In the following example, four list controls are defined on the page. Buttons are provided for scripting page transfers to the selected URLs.

asp:RadioButtonList asp:CheckBoxList asp:DropDownList asp:ListBox

Lycos

MSN

Yahoo

AltaVista

Excite

Google

Lycos

MSN

Yahoo

AltaVista

Excite

Google

                       

Figure C-9. Using a HashTable for data binding.

Below is the Page_Load script that creates a HashTable and binds its Key and Value properties to the Text and Value properties to the target controls. The controls are assigned preselected values so that the subprograms to open search sites do not have to check for nonselections.

Page 365: Assignment Instructions

<SCRIPT Runat="Server">

Sub Page_Load

If Not Page.IsPostBack Then

Dim URLs = New HashTable() URLs.Add("Google", "http://www.google.com") URLs.Add("MSN", "http://search.msn.com") URLs.Add("Yahoo", "http://www.yahoo.com") URLs.Add("Lycos", "http://www.lycos.com") URLs.Add("AltaVista", "http://www.altavista.com") URLs.Add("Excite", "http://www.excite.com")

'-- Bind Hashtable to controls

RadioButtons.DataSource = URLs RadioButtons.DataTextField = "Key" RadioButtons.DataValueField = "Value" RadioButtons.DataBind() RadioButtons.Items(0).Selected = True

CheckBoxes.DataSource = URLs CheckBoxes.DataTextField = "Key" CheckBoxes.DataValueField = "Value" CheckBoxes.DataBind() CheckBoxes.Items(0).Selected = True

DropDownList.DataSource = URLs DropDownList.DataTextField = "Key" DropDownList.DataValueField = "Value" DropDownList.DataBind() DropDownList.Items(0).Selected = True

ListBox.DataSource = URLs ListBox.DataTextField = "Key" ListBox.DataValueField = "Value" ListBox.DataBind() ListBox.Items(0).Selected = True

End If

End Sub

Sub GetRadioButtonURL(Src as Object, Args As EventArgs) Response.Redirect(RadioButtons.SelectedValue)End Sub

Sub GetCheckBoxURL(Src as Object, Args As EventArgs) Response.Redirect(CheckBoxes.SelectedValue)End Sub

Sub GetDropDownListURL(Src as Object, Args As EventArgs) Response.Redirect(DropDownList.SelectedValue)End Sub

Sub GetListBoxURL(Src as Object, Args As EventArgs) Response.Redirect(ListBox.SelectedValue)End Sub

</SCRIPT>

<form Runat="Server">

Page 366: Assignment Instructions

<asp:RadioButtonList id="RadioButtons" Runat="Server"/><asp:Button Text="Go to Site" OnClick="GetRadioButtonURL" Runat="Server"/>

<asp:CheckBoxList id="CheckBoxes" Runat="Server"/><asp:Button Text="Go to Site" OnClick="GetCheckBoxURL" Runat="Server"/>

<asp:DropDownList id="DropDownList" Runat="Server"/><asp:Button Text="Go to Site" OnClick="GetDropDownListURL" Runat="Server"/>

<asp:ListBox id="ListBox" Runat="Server"/><asp:Button Text="Go to Site" OnClick="GetListBoxURL" Runat="Server"/>

</form>Listing C-13. Code to bind a HashTable to server controls.

The controls' DataSource properties identify the HashTable as the source for data binding. Since a HashTable contains two data values, it is necessary to indicate which is to be used as the Text property and which is to be used as the Value property of the control. In this case, DataTextField specifies the first of the pair of HashTable items ("Key") as the text label for the control; DataValueField specifies the second of the pair ("Value") as the value for the control.

It is not necessary in this example to save the HashTable in View State. This list is created only the first time the page loads. It is used to populate the four list controls but is not recreated and will not be available on subsequent postbacks. Its keys and values populate the list controls which do take part in View State and will be available on subsequent page loads.

The Response.Redirect("url") method redirects to the site given by the SelectedValue property of the control. The SelectedValue is the control's Value property assigned as its DataValueField from the Value property of the HashTable.

Accessing HashTables

A Hashtable can be used for purposes other than binding to controls. It can serve as an internal list of data values indexed by keys, a structure called an "associative array" in some programming languages. For instance, the following drop-down list supplies the name of the site to which a link is to be made. This name (the Text property of the list) is used as the key to the HashTable, returning the associated URL value. The URL is formatted in an <asp:HyperLink> control for linking.

                

Figure C-10. Using a HashTable for table lookup.<SCRIPT Runat="Server">

Dim URLs As HashTable

Sub Page_Load

If Not Page.IsPostBack Then

URLs = New Hashtable() URLs.Add("Google", "http://www.google.com")

Page 367: Assignment Instructions

URLs.Add("MSN", "http://search.msn.com") URLs.Add("Yahoo", "http://www.yahoo.com") URLs.Add("Lycos", "http://www.lycos.com") URLs.Add("AltaVista", "http://www.altavista.com") URLs.Add("Excite", "http://www.excite.com") ViewState("URLs") = URLs

KeyList.DataSource = URLs KeyList.DataValueField = "Key" KeyList.DataTextField = "Key" KeyList.DataBind

End If

URLs = New HashTable URLs = ViewState("URLs")

End Sub

Sub Make_URL (Src As Object, Args As EventArgs)

URLLink.Text = URLs(KeyList.SelectedValue) URLLink.NavigateURL = URLs(KeyList.SelectedValue)

End Sub

</SCRIPT>

<form Runat="Server">

<asp:DropDownList id="KeyList" Runat="Server"/><asp:Button Text="Make URL" OnClick="Make_URL" Runat="Server"/><asp:HyperLink id="URLLink" Target="_blank" Runat="Server"/>

</form>Listing C-14. Code to use a HashTable for table lookup.

The HashTable is declared as globally accessible so it can be referenced in both the Page_Load subprogram and the Make_URL subprogram. The HashTable is built in the Page_Loadsubprogram the first time the page is loaded. It is also assigned to ViewState("URLs"), like was done with ArrayLists, so it can be rebuilt on post-back when the Make_URLsubprogram is called.

The DropDownList contains the HashTable's keys (the site names) as both its Text labels and Values. When the button is clicked, the script uses the DropDownList'sSelectedValue property as the search key to the HashTable. The associated Hashtable value (the URL) is retrieved and assigned as the NavigateURL property of the HyperLink control. The page is opened in a new browser with the Target="_blank" attribute of the control. An illustration of this look-up process is shown below.

Page 368: Assignment Instructions

Figure C-11. Accessing HashTable keys and values.

SortedLists

A SortedList is a storage array of items combining features of both the HashTable and sorted ArrayList. It contains items representing key/value pairs. The keys are the indexes to their related values. Like a HashTable used to bind values to a control, the keys and/or values in a SortedList become the Text and/or Value properties for the control. A SortedList automatically maintains its order of items by the alphabetic or numeric order of its keys.

Figure C-12. Using a SortedList for data binding.

Creating a SortedList

A SortedList is created by assigning a New SortedList() object to a reference variable, optionally specifying the number of entries in the list within the parentheses. Then, data values are added to the list with the list's Add() method, supplying a key and associated value. Once the SortedList is filled with data it can be bound to a list control. A SortedList is bound to the DataSource property of the control. Assignment of the SortedList's keys and/or values are made to the control's Text property (DataTextField) and Value property (DataValueField). The control's DataBind() method is called to bind the values. General formats for the statements involved in binding a SortedList to a list control are shown below.

Page 369: Assignment Instructions

Dim SortedList = New SortedList()

SortedList.Add(key,value)...

Control.DataSource = SortedListControl.DataTextField = "Key|Value"Control.DataValueField = "Key|Value"Control.DataBind()

Figure C-13. General formats for creating and binding a SortedList.

The following code creates a SortedList named URLs to which six entries are added. The keys are string names of Web search sites; the values are the URL addresses of the associated sites.

<SCRIPT Runat="Server">

Sub Page_Load

If Not Page.IsPostBack Then

Dim URLs = New SortedList() URLs.Add("Google", "http://www.google.com") URLs.Add("MSN", "http://search.msn.com") URLs.Add("Yahoo", "http://www.yahoo.com") URLs.Add("Lycos", "http://www.lycos.com") URLs.Add("AltaVista", "http://www.altavista.com") URLs.Add("Excite", "http://www.excite.com")

End If

End Sub

</SCRIPT>

Listing C-15. Script to load a SortedList.

By default, a new SortedList contains 16 elements; however, it expands automatically as items are added to the list. This value becomes the muliplier for expanding the list when adding an item beyond the last available slot. A SortedList can be compacted to its final required size. The TrimToSize() method is applied to set the capacity of the list to its current number of items, which is give by its Count property. A SortedList does not require sorting since items are automatically maintained in alphabetic or numeric order by key values.

Binding a SortedList to Controls

A SortedList can be used to provide the text labels and values that are automatically assigned to list controls. In the following example, these controls are populated from a SortedList, and buttons are provided for scripting page transfers to selected URLs.

asp:RadioButtonList asp:CheckBoxList asp:DropDownList asp:ListBox

Page 370: Assignment Instructions

AltaVista

Excite

Google

Lycos

MSN

Yahoo

AltaVista

Excite

Google

Lycos

MSN

Yahoo

                       

Figure C-14. Binding a SortedList to server controls.

Below is the Page_Load script that creates a SortedList and binds its Key and Value properties to the Text and Value properties to the target controls. The controls are assigned preselected values so that the subprograms to open search sites do not have to check for nonselections. This code is virtually identical to that used previously for a HashTable.

<SCRIPT Runat="Server">

Sub Page_Load

If Not Page.IsPostBack Then

Dim URLs = New SortedList() URLs.Add("Google", "http://www.google.com") URLs.Add("MSN", "http://search.msn.com") URLs.Add("Yahoo", "http://www.yahoo.com") URLs.Add("Lycos", "http://www.lycos.com") URLs.Add("AltaVista", "http://www.altavista.com") URLs.Add("Excite", "http://www.excite.com")

'-- Bind SortedList to controls

RadioButtons.DataSource = URLs RadioButtons.DataTextField = "Key" RadioButtons.DataValueField = "Value" RadioButtons.DataBind() RadioButtons.Items(0).Selected = True

CheckBoxes.DataSource = URLs CheckBoxes.DataTextField = "Key" CheckBoxes.DataValueField = "Value" CheckBoxes.DataBind() CheckBoxes.Items(0).Selected = True

DropDownList.DataSource = URLs DropDownList.DataTextField = "Key" DropDownList.DataValueField = "Value" DropDownList.DataBind() DropDownList.Items(0).Selected = True

ListBox.DataSource = URLs ListBox.DataTextField = "Key" ListBox.DataValueField = "Value"

Page 371: Assignment Instructions

ListBox.DataBind() ListBox.Items(0).Selected = True

End If

End Sub

Sub GetRadioButtonURL(Src as Object, Args As EventArgs) Response.Redirect(RadioButtons.SelectedValue)End Sub

Sub GetCheckBoxURL(Src as Object, Args As EventArgs) Response.Redirect(CheckBoxes.SelectedValue)End Sub

Sub GetDropDownListURL(Src as Object, Args As EventArgs) Response.Redirect(DropDownList.SelectedValue)End Sub

Sub GetListBoxURL(Src as Object, Args As EventArgs) Response.Redirect(ListBox.SelectedValue)End Sub

</SCRIPT>

<form Runat="Server">

<asp:RadioButtonList id="RadioButtons" Runat="Server"/><asp:Button Text="Go to Site" OnClick="GetRadioButtonURL" Runat="Server"/>

<asp:CheckBoxList id="CheckBoxes" Runat="Server"/><asp:Button Text="Go to Site" OnClick="GetCheckBoxURL" Runat="Server"/>

<asp:DropDownList id="DropDownList" Runat="Server"/><asp:Button Text="Go to Site" OnClick="GetDropDownListURL" Runat="Server"/>

<asp:ListBox id="ListBox" Runat="Server"/><asp:Button Text="Go to Site" OnClick="GetListBoxURL" Runat="Server"/>

</form>Listing C-16. Code to bind a SortedList to server controls.

The script which builts the SortedList includes code to bind the keys and values in the list to the Text and Value properties of the controls. The control's DataSource property identifies the SortedList as the source for data binding. DataTextField specifies the first of the pair of SortedList items ("Key") as the text label for the control; DataValueFieldspecifies the second of the pair ("Value") as the value for the control. The order of the list is the sorted order of keys of the SortedList.

It is not necessary to save this SortedList in View State. The list is created only the first time the page loads. It is used to populate the four list controls but is not recreated and will not be needed on subsequent post-backs.Its keys and values populate the list controls, which do take part in View State and will be available on subsequent page loads.

As in the case of HashTable binding, the Response.Redirect("url") method redirects to the site specified in the url parameter which, in turn, is supplied by the associatedSelectedValue of the control.

Page 372: Assignment Instructions

Accessing SortedLists

Like a HashTable, a SortedList can be used as a look-up table for extracting values for supplied keys. Previous lookup code for a HashTable can be used for a SortedList with only the need to change references from HashTable to SortedList.


Recommended