paging in asp

86
Paging in ASP.NET Page 1 of 1 Paging in ASP.NET Page 1 of 1 Paging In ASP.NET Introduction When making the transition from ASP to ASP.NET, you will discover that paging through database records has become both remarkably simple and more difficult at the same time. The DataGrid control has made it a breeze to create a Web page that allows the user to page through the records of a database query. However, the simplicity offered by the DataGrid’s default paging mechanism comes at a cost of performance. If you are not familiar with DataGrid paging, consider first reading Scott Mitchell ’s article An Extensive Examination of the DataGrid Web Control: Part 15 , which discusses the topic at more length. Essentially, the DataGrid offers two modes of paging support: default paging and custom paging. Default paging is the easier of the two to implement, and can be done with just a few lines of code. However, realize that the default paging method retrieves all of the data, but then displays only a small subset of data to be displayed on the current page. That is, every time a user navigates to a different page of the data, the DataGrid re- retrieves all of the data. When dealing with a small amount of data, this behavior is not a major concern. For small datasets, the simplicity of default paging outweighs its inefficiency, but for large amounts of data, the unnecessary overhead of the default paging can be detrimental to the performance of the Web application and database. As a solution for the default paging’s inefficiency, the DataGrid also provides custom paging, which avoids the default paging’s inefficiency by retrieving only that data that belongs that is to be displayed on the current page of data. As its name implies, the custom paging method requires you, the developer, to devise some way to select only those records that need to be displayed on a specific page. There are a number of techniques that can be used to employ custom paging, from stored procedures not unlike the one used in Paging Through Records Using a Stored Procedure , to more complicated SQL expressions. For a good read on custom paging, be sure to read the sample chapter from Scott Mitchell’s book, ASP.NET Data Web Controls Kick Start, Providing DataGrid Pagination . While custom paging is efficient, it requires more complicated programming from the developer. As you can see, the ease-of-use offered by the DataGrid’s paging functionality comes at the expense of either efficiency or development time. There’s a larger problem lingering as well. What if you’re not using the DataGrid control at all? Neither the Repeater nor the DataList has built-in paging. Obviously, depending on the particular needs of your ASP.NET page, you may not choose to use any of these controls. You may iterate through the results of a query programmatically, as in classic ASP. Simply put, if you don’t use the DataGrid, you’ll find that paging has actually become more difficult than in ASP. ADO.NET does not support the built-in paging properties and methods of ADO. You are forced to write a paging solution from the ground up. In the following article, I’ll explain how to do just this. The paging solution I’ll present will avoid the problems inherent with the DataGrid’s paging methods and will be control-independent. That is, the paging solution will work with any of the data controls or with no control at all. It will allow the user to sort records by any column, will not make any unrealistic/limiting assumptions about a table’s primary key, and will efficiently retrieve the data required for each page. A screenshot of the paging system in action is shown below.

Upload: api-3716254

Post on 11-Apr-2015

1.093 views

Category:

Documents


1 download

TRANSCRIPT

Page 1: Paging in ASP

Paging in ASP.NET Page 1 of 1

Paging in ASP.NET Page 1 of 1

Paging In ASP.NET

IntroductionWhen making the transition from ASP to ASP.NET, you will discover that paging through database records has become both remarkably simple and more difficult at the same time. The DataGrid control has made it a breeze to create a Web page that allows the user to page through the records of a database query. However, the simplicity offered by the DataGrid's default paging mechanism comes at a cost of performance.

If you are not familiar with DataGrid paging, consider first reading Scott Mitchell's article An Extensive Examination of the DataGrid Web Control: Part 15, which discusses the topic at more length. Essentially, the DataGrid offers two modes of paging support: default paging and custom paging. Default paging is the easier of the two to implement, and can be done with just a few lines of code. However, realize that the default paging method retrieves all of the data, but then displays only a small subset of data to be displayed on the current page. That is, every time a user navigates to a different page of the data, the DataGrid re-retrieves all of the data.

When dealing with a small amount of data, this behavior is not a major concern. For small datasets, the simplicity of default paging outweighs its inefficiency, but for large amounts of data, the unnecessary overhead of the default paging can be detrimental to the performance of the Web application and database.

As a solution for the default paging's inefficiency, the DataGrid also provides custom paging, which avoids the default paging's inefficiency by retrieving only that data that belongs that is to be displayed on the current page of data. As its name implies, the custom paging method requires you, the developer, to devise some way to select only those records that need to be displayed on a specific page. There are a number of techniques that can be used to employ custom paging, from stored procedures not unlike the one used in Paging Through Records Using a Stored Procedure, to more complicated SQL expressions. For a good read on custom paging, be sure to read the sample chapter from Scott Mitchell's book, ASP.NET Data Web Controls Kick Start, Providing DataGrid Pagination. While custom paging is efficient, it requires more complicated programming from the developer.

As you can see, the ease-of-use offered by the DataGrid's paging functionality comes at the expense of either efficiency or development time. There's a larger problem lingering as well. What if you're not using the DataGrid control at all? Neither the Repeater nor the DataList has built-in paging. Obviously, depending on the particular needs of your ASP.NET page, you may not choose to use any of these controls. You may iterate through the results of a query programmatically, as in classic ASP. Simply put, if you don't use the DataGrid, you'll find that paging has actually become more difficult than in ASP. ADO.NET does not support the built-in paging properties and methods of ADO. You are forced to write a paging solution from the ground up.

In the following article, I'll explain how to do just this. The paging solution I'll present will avoid the problems inherent with the DataGrid's paging methods and will be control-independent. That is, the paging solution will work with any of the data controls or with no control at all. It will allow the user to sort records by any column, will not make any unrealistic/limiting assumptions about a table's primary key, and will efficiently retrieve the data required for each page. A screenshot of the paging system in action is shown below.

Page 2: Paging in ASP

Paging in ASP.NET Page 2 of 2

Paging in ASP.NET Page 2 of 2

To Code Behind Or Not To Code-BehindI personally find that using code-behind classes leads to more readable, more easily maintained code. For these reasons, the code examples that follow will use code-behind classes. To demonstrate the paging solution, I'll be showing the code for a single ASP.NET Web page, Paging.aspx. Since the HTML portion of the ASP.NET Web page is very straightforward, I'll focus mainly on the methods that comprise the code-behind class. (NOTE: You can download the complete, commented code for both files at the end of this article.)

Database ConfigurationAt the top of code-behind class, I've stored the database parameters we'll be using throughout the code in variables:

Protected ConnString As String = "server=local;database=Test;Trusted_Connection=true"Protected TableName As String = "Contacts"Protected PrimaryKeyColumn As String = "ID"Protected DefaultSortColumn As String = "DateAdded"Protected ColumnsToRetrieve As String = "DateAdded,Email,LastName"

These variables will allow you to easily configure the code-behind class to work with different tables/columns. If you are using SQL Server, these are the only lines of code in the code-behind class that must be changed. If you are not using SQL Server, you will need to alter the class to use the namespace(s) and associated classes that pertain to your database.

With regard to the HTML portion, you will simply need to change the column headers and databound columns in the Repeater templates to account for the column names you wish to retrieve. For example, in Paging.aspx you will find the Repeater to have the following HeaderTemplate and ItemTemplate:

<HeaderTemplate><table cellpadding="4" cellspacing="0" border="1" bordercolor="#000000">

Page 3: Paging in ASP

Paging in ASP.NET Page 3 of 3

Paging in ASP.NET Page 3 of 3

<tr bgcolor="#eeeeee" valign="top"><td nowrap ID="DateAdded" runat="Server"><b>Date Added</b></td><td nowrap ID="Email" runat="Server"><b>Email</b></td><td nowrap ID="LastName" runat="Server"><b>Last Name</b></td>

</tr></HeaderTemplate>

<ItemTemplate><tr valign="top" ID="BodyRow" runat="server">

<td><%# Container.DataItem("DateAdded") %></td><td><%# Container.DataItem("Email") %></td><td><%# Container.DataItem("LastName") %></td>

</tr></ItemTemplate>

You will need to alter the HeaderTemplate and ItemTemplate so that it has column names from the data you are binding to the Repeater. (Of course, you can replace the Repeater with the DataList or DataGrid; the point of using the Repeater in Paging.aspx was to illustrate that this paging solution was not limited only to the DataGrid Web control.)

Examining the Page_Load() Event HandlerThe remainder of this article examines the various event handlers and methods in the code-behind class. Let's start by examining the Page_Load event handler, which has three main tasks:

First, it retrieves all the primary key values from the table in question. This operation ensures the accuracy of the paging, as it provides an up-to-date count of the records available for paging:

Dim Conn As SqlConnectionDim Query As StringDim SqlComm As SqlCommandDim myDataReader As SqlDataReader

Conn = New SqlConnection(ConnString)Conn.Open()

Query = "SELECT " & PrimaryKeyColumn & " FROM " & _TableName & " ORDER BY " & SetSorting()

SqlComm = New SqlCommand(Query,Conn)myDataReader = SqlComm.ExecuteReader()

At this point, you may be wondering, "Isn't this what the DataGrid's default paging does as well?" Actually, it's not. Remember that the DataGrid's default paging grabs all the data available, regardless of what page of data you are viewing. That is, it retrieves all the columns for every row. We're just retrieving one column from all the rows (a column that usually is just integers). Retrieving a single column's worth of data as opposed to every column's data is obviously far more efficient, particularly when dealing with tables with many columns of data. Furthermore, databases automatically index a table by its primary key, so selecting all of the values of just the primary key field(s) is very quick as a simple scan of the index can be performed without any table data accesses (if this makes no sense to you, don't worry! Just realize reading the values of a primary key is not an expensive operation).

After retrieving the primary key values, the Page_Load event handler uses a SqlDataReader to read the values into an ArrayList and then closes the database connection. This saves these values in a disconnected, easily manipulated format:

While myDataReader.Read()IDList.Add( myDataReader(PrimaryKeyColumn) )

End While

Page 4: Paging in ASP

Paging in ASP.NET Page 4 of 4

Paging in ASP.NET Page 4 of 4

myDataReader.Close()myDataReader = NothingConn.Close()Conn = Nothing

(For those of you unfamiliar with ArrayLists, they are a type of collection found in the .NET Framework that allows for array-style handling of data. Unlike traditional arrays, however, ArrayLists behave in a far more intuitive fashion and do not need to be "re-dimmed" as they grow. There is also a myriad of helpful ArrayList methods that allow for fast sorting and manipulation of the data contained within the collection. For more information on the ArrayList check out this tutorial.)

Why not just use a DataSet, as opposed to reading the SqlDataReader's data into an ArrayList? I've found the latter method to be a bit faster and user friendly. The DataSet involves a lot of complexity that we don't need for the code in question and a great deal of overhead that we don't want. In a way, storing the SqlDataReader's data in an ArrayList provides us with a stripped-down, efficient "dataset" that is perfect for our needs.

Finally, after creating the ArrayList, if the page has not been posted back, the Page_Load event handler calls the Paging()method, displaying the first page of records when Paging.aspx first loads:

If Not Page.IsPostBack ThenPaging()

End If

In Part 2 we'll continue our exploration of the code-behind class.

The SetSorting() MethodThe next method in the code-behind class we'll examine is the SetSorting() method. You may have noticed this function was called from within the query in the Page_Load event handler. This function simply returns the column to order the results by. The first part of the function simply determines if the user has selected a column by which to sort the records. If not, it uses the specified default column by which to sort the records:

If Request.Form("SortBy") Is Nothing ThenReturn DefaultSortColumn & " DESC"

If the user has chosen a column, the function returns the column selected by the user. SetSorting() uses a regular expression to strip any non-alphanumeric characters from the column name and sort direction passed by the user. This is a crucial security step, since the strings in question are being used in a SQL query. Failing to perform this check can permit a devastating SQL Injection attack (see Protecting Yourself From SQL Injection for more information on this topic):

Dim myRegExp As RegExDim EditedSortBy As String Dim EditedSortDir As String

myRegExp = New RegEx("[^A-Za-z0-9_]")

EditedSortBy = myRegExp.Replace(Request.Form("SortBy"),"")EditedSortDir = myRegExp.Replace(Request.Form("SortDir"),"")

Return EditedSortBy & " " & EditedSortDirEnd If

The SetChangeFlag Event HandlerThe SetChangeFlag event handler handles OnSelectedIndexChanged events. These events are triggered whenever the user changes the various display options in Paging.aspx (which are listed in various DropDownList Web controls). For example,

Page 5: Paging in ASP

Paging in ASP.NET Page 5 of 5

Paging in ASP.NET Page 5 of 5

if the user changes the number of records that should be displayed per page or changes how the records should be sorted, the SetChangeFlag routine is called and sets the DisplayChanged variable to a value of True. If the user then attempts to navigate to a different page, this flag tells the ChangePage routine (which we will discuss next) to display the first page of records again with the newly selected options (number of records per page, sorting, etc.). This flag is important because if any of the options change, then the pagination of the records is changed as well. Returning to the first page of records guarantees that the user is seeing a consistent, accurate representation of the records at all times.

Examining the ChangePage Event HandlerThe ChangePage event handler handles OnClick and OnSelectedIndexChanged events triggered whenever the user has clicked a page navigation link (first, previous, next, last), has selected a specific page to which to jump, or has clicked the "Update Display" button after changing display options. The routine is able to handle multiple events (while distinguishing between them) by taking advantage of the sender argument that all ASP.NET control events pass. By converting the senderargument to a string, we are able to determine which server control in Paging.aspx triggered the ChangePage routine:

Sub ChangePage(sender As Object, e As EventArgs)Dim WhichSender As String = sender.ToString()

If the DisplayChanged flag is True or if the "Update Display" button has been clicked, the Paging() method is called. Two arguments are passed to the routine: the first tells the routine to display the first page of records again (since the display options from Paging.aspx have been changed); the second passes the number of records to display on each page:

If DisplayChanged Or WhichSender = "System.Web.UI.WebControls.Button" ThenPaging(1, CType(Request.Form("SelectPageSize"),Integer))

If the display options have not changed, then the ChangePage event handler determines if one of the page navigation links was clicked or if a specific page was selected from the drop down list of pages. Again, two arguments are passed to the Paging() method. The first passes the page number that needs to be displayed. The page number is retrieved from the CommandArgument property of the LinkButton that was clicked or from the selected index property of the drop down list. (Notice that the selected index is increased by one, to account for the fact that the selected index is numbered from 0, while the pages of records are numbered from 1.) The second argument passes the number of records that must be displayed on each page:

ElseIf WhichSender = "System.Web.UI.WebControls.LinkButton" Then

Paging(CType(sender.CommandArgument,Integer), _CType(Request.Form("SelectPageSize"),Integer))

ElsePaging(CType(sender.SelectedIndex + 1,Integer), _

CType(Request.Form("SelectPageSize"),Integer))End If

End IfEnd Sub

Paging the Records with the Paging() MethodThe Paging() method performs the heavy-lifting for the ASP.NET Web page - it retrieves precisely the records that are to be displayed on the selected page. It is called both on the initial load of Paging.aspx and every time the user navigates to a new page or changes the display options. The routine accepts two arguments, both optional. (These arguments were discussed above.) By default, the Paging() method displays the first page of records and includes 10 records per page.

The first block of code in the routine initializes some essential variables. First, the total number of records is determined by using the Count property of the ArrayList we created in the Page_Load event handler:

Sub Paging(Optional WhichPage As Integer = 1,Optional RecordsPerPage As Integer = 10)Dim NumItems As Integer = IDList.Count

Page 6: Paging in ASP

Paging in ASP.NET Page 6 of 6

Paging in ASP.NET Page 6 of 6

Next, we determine the number of complete pages by dividing the number of records by the number of records per page. Note that the \ operator is used for integer division, as opposed to the /:

Dim PageSize As Integer = RecordsPerPageDim Pages As Long = NumItems \ PageSize

Finally, we save the number of complete pages in a separate variable (we'll need this number later) and determine if there are leftover records using the Mod operator. If there are leftover records, we increase the page count by one.

Dim WholePages As Long = NumItems \ PageSize

Dim Leftover As Integer = NumItems Mod PageSize

If Leftover > 0 ThenPages += 1

End If

The next block of code determines which page is currently being displayed and makes sure the selected page is within the valid range of pages. (Invalid pages can only be selected if the user alters the HTML in Paging.aspx and intentionally posts invalid page numbers to the server. From a security standpoint, it is always best to be a little paranoid.) If the selected page is not within the valid range, the Paging() method is called again and reset to the first page:

Dim i As IntegerDim CurrentSelection As StringDim StartOfPage As IntegerDim EndOfPage As Integer

Dim CurrentPage As Integer = WhichPage

If CurrentPage > Pages Or CurrentPage < 0 ThenPaging(1, RecordsPerPage)

If the selected page is within the valid range, various properties of the page navigation LinkButtons are set. If the current page is the last page, the "next" and "last" links are hidden. Otherwise, the "next" and "last" links are displayed and the CommandArgument property of each is set to pass the correct page to the ChangePage event handler. (The CommandArgument is passed to the ChangePage event handler that handles the Click events of the LinkButtons.) If the current page is the first page, the "previous" and "first" links are hidden. Otherwise, they are displayed and the CommandArgument property of each is set to the pass the correct page:

ElseIf CurrentPage = Pages Then

NextLink.Visible = falseLastLink.Visible = false

ElseNextLink.Visible = trueLastLink.Visible = trueNextLink.CommandArgument = CurrentPage + 1LastLink.CommandArgument = Pages

End If

If CurrentPage = 1 ThenPreviousLink.Visible = falseFirstLink.Visible = false

Else

Page 7: Paging in ASP

Paging in ASP.NET Page 7 of 7

Paging in ASP.NET Page 7 of 7

PreviousLink.Visible = trueFirstLink.Visible = truePreviousLink.CommandArgument = CurrentPage - 1FirstLink.CommandArgument = 1

End If

Now that we have our page navigation links in place, we need to populate the page selection drop down list with the proper range of pages. We do this by filling a new ArrayList with the sequence of available pages. We then use databinding to bind this list of pages to the drop down list control. Finally, we set the SelectedIndex property of the control so that the current page is selected:

Dim JumpPageList = new ArrayList

Dim x As Integer

For x = 1 To PagesJumpPageList.Add(x)

Next

JumpPage.DataSource = JumpPageListJumpPage.Databind()

JumpPage.SelectedIndex = CurrentPage - 1

The next block of code simply sets the Text property of two Label controls to display the total number of records and number of pages to the user:

RecordCountLabel.Text = NumItemsPageCountLabel.Text = Pages

With the navigation and information above being displayed, the routine can now determine which records should be retrieved from the database for the current page. Remember that the IDList ArrayList contains all the primary key values available. We need to determine which subset of these values belongs to the current page. We do this using the following calculations:

StartOfPage = PageSize * (CurrentPage - 1)EndOfPage = Min( (PageSize * (CurrentPage - 1)) + (PageSize - 1), _

((WholePages * PageSize) + LeftOver - 1) )

The StartOfPage and EndOfPage are actually the starting and ending indices of the ArrayList that delimit the selected page. We subtract 1 in the calculations to account for the fact that the ArrayList is numbered starting at 0, while the pages are numbered from 1. Calculating the StartOfPage is relatively easy. The EndOfPage calculation is a bit more complex. Because the actual number of records available to display on the last page of data may be less than the number of records the user has selected to view per page, we use the Min function to retrieve whichever figure is less: the theoretical ending index of the page or the total number of records available minus 1 (to account for the zero-based nature of the ArrayList).

To retrieve the subset of values delimited by the indices above, we use the GetRange() method to select the correct subset, convert this subset to an array, and then join the values into a comma-delimited string:

Dim CurrentSubset As String = Join( IDList.GetRange( StartOfPage , (EndOfPage - StartOfPage + 1) ).ToArray , "," )

Page 8: Paging in ASP

Paging in ASP.NET Page 8 of 8

Paging in ASP.NET Page 8 of 8

(For those of you unfamiliar with the GetRange() method, it accepts two arguments: the starting index and the number of items, starting from this index, to retrieve. Thanks to Scott Mitchell for suggesting the ToArray()/Join combination to easily convert the subset to a string.)

At this point, we have the string of primary key values that comprise the selected page. All we need to do is retrieve the records that correspond to these values from the table and bind them to the Repeater control in Paging.aspx. This is easily accomplished using an IN clause in our query and another SqlDataReader:

Dim Conn As SqlConnectionDim Query As String Dim SqlComm As SqlCommand

Conn = New SqlConnection(ConnString)

Query = "SELECT " & ColumnsToRetrieve & " FROM " & TableName & _" WHERE " & PrimaryKeyColumn & " IN ('" & _

CurrentSubSet.Replace(",","','") & _"') ORDER BY " & SetSorting()

SqlComm = New SqlCommand(Query,Conn)

Conn.Open()

myRepeater.DataSource = SqlComm.ExecuteReader()myRepeater.Databind()

Conn.Close()Conn = Nothing

End IfEnd Sub

There are two important points to note in the code above. First, we use the Replace() method to add single quotes around the primary key values in CurrentSubSet. This prevents the query from failing if the primary key values are not integers. (Remember we are trying to avoid any assumptions about the primary key column that will limit the usefulness of our paging technique.) Second, the query is sorted using the SetSorting routine discussed earlier. By sorting the primary key values and the actual records we're retrieving by the same criteria, we allow these records to be sorted by any column in the table. Our paging technique does not require that the records be sorted by the primary key.

ConclusionUsing the code above, you can implement a paging technique that will work with any of the data controls or with no control at all. (All you would have to do is bind the records retrieved in the Paging() method to a different control or store them in a DataReader/DataSet and loop through and display them yourself.) The code is a bit more complex then the DataGrid's paging methods, but infinitely more flexible, accurate, efficient, and extensible.

For more information on paging in ASP.NET, be sure to check out Creating a Generic Page Control.

Happy Programming! http://aspnet.4guysfromrolla.com/code/Paging.zip

Page 9: Paging in ASP

Paging in ASP.NET Page 9 of 9

Paging in ASP.NET Page 9 of 9

Creating a Generic Pager Control

Effectively showing data so it doesn't confuse the end user is a main objective in developing almost all Web data presentation applications. Showing 20 records on one page is bearable but showing 10,000 certainly can be confusing. Splitting the data across several pages, or paging the data, is a commonly employed solution to this problem.

ASP.NET provides only one control that supports paging, the DataGrid. The DataGrid pager control is fine for intranet applications but for public applications, the DataGrid pager doesn't provide much of the functionality needed to make flexible Web applications. For one thing, the DataGrid control limits the Web designer in where he can place the pager or what it should look like. For example, the DataGrid certainly doesn't allow the designer the option of placing the pager vertically. Another control that can benefit from paging is the repeater control. The repeater control allows the Web developer to quickly configure how data is shown, but the paging behavior must be implemented by the Web developer. Implementing a custom pager for different controls that change depending on the data source or the presentation can be time consuming. A generic pager control that's not bound to a specific presentation control is a great time saver. A good generic pager control is more than just a data pager; it should also provide the following functionality:

1. Provide First, Previous, Next, Last and paging buttons 2. Be sensitive to the data. If the pager is set to show 10 records per page and only nine are shown, then the

pager shouldn't be visible. On the first page the Previous and First buttons shouldn't be shown. On the last page the Next and the Last methods shouldn't be shown.

3. Independent of the control that's in charge of the presentation 4. The ability to handle a variety of current and future data sources 5. Easily configurable presentation to integrate with custom applications 6. Notify other controls when paging is taking place 7. Easy to use even by inexperienced Web designers 8. Provide properties to relevant paging data

dowload source code (C#) dowload source code (VB) view demo

There are a few commercial pagers available that provide such functionality, but they don't come cheap. For cash-strapped Web companies creating a custom pager control becomes a necessity.

ASP.NET provides three ways to create custom Web controls: user controls, composite controls, and custom controls. The third type of control, custom control, is a bit misleading. All of the mentioned controls are actually custom controls but what makes the composite control different from the custom control is the dependence on the CreateChildControls() method, which allows the control to re-render itself based on raised events. For the generic pager, the composite control model is chosen.

The following sequence diagram outlines the general mechanism of the pager control.

Page 10: Paging in ASP

Paging in ASP.NET Page 10 of 10

Paging in ASP.NET Page 10 of 10

Even thought the pager control aims to be independent of the presentation control, it must have some way to access the data. Every class that derives from the Control class provides a DataBinding event. By registering itself as a listener to the DataBinding event, the pager can listen-in and make changes to the data. Since all controls that derive from the Control class posses this event, the pager control achieves independence from the presentation control. In other words, any control that derives form the Control class, almost all Web controls, can be bound to. Once the presentation control raises the DataBinding event, the pager control can intercept the DataSourceproperty. Unfortunately MS does not provide an interface that all data bound classes implement, such as an IdataSourceProvider, and not all controls derived from the Control or WebControl class provide a DataSourceproperty, so upcasting to the Control class isn't an option. The only alternative is to use reflection to manipulate the DataSource property directly. Before discussing the event handler method, it should be pointed out that in order to register as an event listener, a reference must be established to the presentation control. The pager control exposes a simple string property, BindToControl, which the Web developer can set through code or through the aspx page to bind the DataSource to the presentation control.

publicstring BindToControl{

get{

if (_bindcontrol == null) thrownew NullReferenceException("You must bind to a control through the BindToControl property

before you use the pager");return _bindcontrol;

}set{_bindcontrol=value;}

}

This method is important enough that it's a good idea to throw a more meaningful message than a standard NullReferenceException. In the pager's OnInit event handler the call to resolve the reference to the presentation

Page 11: Paging in ASP

Paging in ASP.NET Page 11 of 11

Paging in ASP.NET Page 11 of 11

control is made. The OnInit event handler must be used (instead of the constructor) to make sure that the JIT compiled aspx page has set the BindToControl method.

protectedoverridevoid OnInit(EventArgs e){

_boundcontrol = Parent.FindControl(BindToControl);BoundControl.DataBinding += new EventHandler(BoundControl_DataBound);base.OnInit(e);

}

The search for the presentation control is made by searching the pager's Parent control, which in the case of this article is the main page template. There is much danger in using the Parent property in this manner. If, for example, the pager were to be imbedded into another control, such as a Table control, the call to the Parent property would return a reference to the Table control. Since the FindControl method only searches the current control collection, the presentation control won't be found unless it's in that collection. A safer method is to recursively search through each control's control collection until the control is found.

Once the BoundControl is found, the pager is registered as listener to the DataBinding event. Since the pager control manipulates the data source, it's important that this event handler be the last in the calling chain. As long as the presentation control registers its event handlers for the DataBinding event in the OnInit event handler (the default), there won't be a problem when the pager manipulates the data source.

The DataBound event handler takes care of the acquisition of the DataSource property of the presentation control.

privatevoid BoundControl_DataBound(object sender,System.EventArgs e){

Type type = sender.GetType();_datasource = type.GetProperty("DataSource");if (_datasource == null)

thrownew NotSupportedException("The Pager control doesn't support controls that don't contain a datasource");

object data = _datasource.GetGetMethod().Invoke(sender,null);BindParent();

}

Through reflection, the call is made to invoke the Get part of the DataSource property and return a reference to the actual data source. The data source is now known but the pager still needs to know how to manipulate it. A lot of effort went into making the pager presentation independent. Making it data source dependent would defeat the purpose of constructing a flexible control. A pluggable architecture ensures that the pager control will be able to handle all sorts of different data sources, .NET provided or custom.

The perfect solution to provide a robust, scalable and pluggable architecture can be achieved by using the [GoF] builder pattern.

Page 12: Paging in ASP

Paging in ASP.NET Page 12 of 12

Paging in ASP.NET Page 12 of 12

The IDataSourceAdapter interface defines the most basic element, or plug, the pager needs to manipulate the data.

publicinterface IDataSourceAdapter{

int TotalCount{get;}object GetPagedData(int start,int end);

}

The TotalCount property returns the total number of elements in the data source before manipulating the data while the GetPagedData method manipulates the data source by returning a subset of the original data. For example: if the data source is a simple array containing 20 elements and the pager shows 10 elements per page, then a subset of this data would be elements 0-9 for page one and 10-19 for page two. A plug for a DataView type is provided by the DataViewAdapter.

internalclass DataViewAdapter:IDataSourceAdapter{

private DataView _view;internal DataViewAdapter(DataView view){

_view = view;}publicint TotalCount{

get{return (_view == null) ? 0 : _view.Table.Rows.Count;} }publicobject GetPagedData(int start, int end){

DataTable table = _view.Table.Clone();for (int i = start;i<=end && i<= TotalCount;i++){

table.ImportRow(_view[i-1].Row);}return table;

Page 13: Paging in ASP

Paging in ASP.NET Page 13 of 13

Paging in ASP.NET Page 13 of 13

}}

The DataViewAdapter implements the IDataSourceAdapter's GetPagedData method by cloning the original DataTable and then importing rows from the original DataTable to the cloned table. The class's visibility is intentionally set to internal in order to hide the implementation from the Web developer and provide an easier interface through the Builder classes.

publicabstractclass AdapterBuilder{

privateobject _source;privatevoid CheckForNull(){

if (_source == null) thrownew NullReferenceException("You must provide a valid source");}publicvirtualobject Source{

get{

CheckForNull(); return _source;}

set{

_source = value;CheckForNull();

}}publicabstract IDataSourceAdapter Adapter{get;}

}

The abstract AdapterBuilder class provides a more manageable interface to the IdataSourceAdapter type. By employing an extra level of abstraction, instead of using the IDataSourceAdapter directly, it provides an extra layer where pre processing instructions, before paging the data, can take place. The builder also allows the actual implementation, such as the DataViewAdapter to be hidden away from the pager user.

publicclass DataTableAdapterBuilder:AdapterBuilder{

private DataViewAdapter _adapter;private DataViewAdapter ViewAdapter{

get{

if (_adapter == null){

DataTable table = (DataTable)Source;_adapter = new DataViewAdapter(table.DefaultView);

}return _adapter;

}}publicoverride IDataSourceAdapter Adapter{

get{return ViewAdapter;}}

}

Page 14: Paging in ASP

Paging in ASP.NET Page 14 of 14

Paging in ASP.NET Page 14 of 14

publicclass DataViewAdapterBuilder:AdapterBuilder{

private DataViewAdapter _adapter;private DataViewAdapter ViewAdapter{

get{ //lazy instantiate

if (_adapter == null){

_adapter = new DataViewAdapter((DataView)Source);}return _adapter;

}}publicoverride IDataSourceAdapter Adapter{

get{return ViewAdapter;}}

}

The DataView type and the DataTable type are so closely related that it might make sense to make a general DataAdapter. It would be enough to add another constructor that handles a DataTable. Alas when the user needs different functionality for a DataTable, the entire class would have to be replaced or inherited. By constructing a new builder that uses the same IdataSourceAdapter, the user has more freedom on how they choose to implement the adapter.

In the pager control, the look-up of the proper builder is handled by a type safe collection.

publicclass AdapterCollection:DictionaryBase{

privatestring GetKey(Type key){

return key.FullName;}public AdapterCollection(){}publicvoid Add(Type key,AdapterBuilder value){

Dictionary.Add(GetKey(key),value);}publicbool Contains(Type key){

return Dictionary.Contains(GetKey(key));}publicvoid Remove(Type key){

Dictionary.Remove(GetKey(key));}public AdapterBuilder this[Type key]{

get{return (AdapterBuilder)Dictionary[GetKey(key)];}set{Dictionary[GetKey(key)]=value;}

}}

Page 15: Paging in ASP

Paging in ASP.NET Page 15 of 15

Paging in ASP.NET Page 15 of 15

The AdapterCollection relies on the type of the DataSource, which fits in perfectly with the BoundControl_DataBound method. The index key used is the Type.FullName method, ensuring the index key is unique for each type. This puts the responsibility on the AdapterCollection to contain only one builder for a given type. Adding the builder look-up to the BoundControl_DataBound method results in the following:

public AdapterCollection Adapters{

get{return _adapters;}}privatebool HasParentControlCalledDataBinding{

get{return _builder != null;}}

privatevoid BoundControl_DataBound(object sender,System.EventArgs e){

if (HasParentControlCalledDataBinding) return;Type type = sender.GetType();_datasource = type.GetProperty("DataSource");if (_datasource == null)

thrownew NotSupportedException("The Pager control doesn't support controls that don't contain a datasource");

object data = _datasource.GetGetMethod().Invoke(sender,null);

_builder = Adapters[data.GetType()];if (_builder == null)

thrownew NullReferenceException("There is no adapter installed to handle a datasource of type "+data.GetType());

_builder.Source = data;

BindParent();}

The BoundControl_DataBound method also checks to see if the builder is already created with the HasParentControlCalledDataBinding. If it has, then it doesn't go through the exercise of finding the proper builder again. This of course assumes that the user doesn't call DataBinding with different DataSources, which of course they shouldn't. The Adapters table is initialized in the constructor.

public Pager(){

_adapters = new AdapterCollection();_adapters.Add(typeof(DataTable),new DataTableAdapterBuilder());_adapters.Add(typeof(DataView),new DataViewAdapterBuilder());

}

The last method to implement is to call the BindParent to manipulate and return the data.

privatevoid BindParent(){

_datasource.GetSetMethod().Invoke(BoundControl,new object[] { _builder.Adapter.GetPagedData( StartRow,ResultsToShow*CurrentPage)});

}

The method is pretty simple since the actual manipulation of the data is done by the Adapter. Once finished, reflection is again used but this time to set the DataSource property of the presentation control. The behavior of the pager control is now nearly finished, but without proper presentation, it is not very useful.

Page 16: Paging in ASP

Paging in ASP.NET Page 16 of 16

Paging in ASP.NET Page 16 of 16

As stated earlier the best way to achieve a solid separation of presentation from logic is to use templates, or more specifically the Itemplate interface. Indeed, MS realizes the power of templates and employs them almost everywhere, even in the page parser itself. Templates unfortunately aren't the easiest mechanism, and they take a while to learn, but there are many tutorials available that should ease the learning curve. Getting back to the pager control, the pager control contains the following buttons, First, Previous, Next, Last plus the individual pagers. The four navigation buttons are chosen from the ImageButton class instead of the LinkButton class. From a professional Web design point of view an image is more often useful than just a link.

public ImageButton FirstButton{get {return First;}}public ImageButton LastButton{get {return Last;}}public ImageButton PreviousButton{get {return Previous;}}public ImageButton NextButton{get {return Next;}}

The individual pagers are created dynamically since they depend on the datasource and how many records and pagers should be shown per page. The pagers will be added to a Panel which allows the Web designer to specify where he would like to show the pagers. More on the creation of the pagers later, for now the pager control needs to provide a template to allow the user to customize the look and feel of the pager.

[TemplateContainer(typeof(LayoutContainer))]public ITemplate Layout{

get{return (_layout;}set{_layout =value;}

}

publicclass LayoutContainer:Control,INamingContainer{

public LayoutContainer(){this.ID = "Page";}

}

The LayoutContainer class provides a holder for the template. The addition of the custom ID in a template container is always a good idea since it prevents problems that arise with events and how they're called by the page. The following UML diagram defines the presentation of the pager control.

Page 17: Paging in ASP

Paging in ASP.NET Page 17 of 17

Paging in ASP.NET Page 17 of 17

The first step in creating a template is to define a simple layout in the aspx page:

<Layout><asp:ImageButtonid="First"Runat="server" AlternateText="first"/><asp:ImageButtonid="Previous"Runat="server"AlternateText="previous"/><asp:ImageButtonid="Next"Runat="server"AlternateText="next"/><asp:ImageButtonid="Last"Runat="server"AlternateText="last"/><asp:PanelID="Pager"Runat="server"/>

</Layout>

For the purpose of this example, the layout doesn't contain any formatting, such as tables etc ... But they can and should be included as shown later.

The Itemplate interface provides only one method, InstantiateIn, which parses the template and binds it with the holder.

privatevoid InstantiateTemplate(){

_container = new LayoutContainer();Layout.InstantiateIn(_container);First = (ImageButton)_container.FindControl("First");Previous = (ImageButton)_container.FindControl("Previous");Next = (ImageButton)_container.FindControl("Next");Last = (ImageButton)_container.FindControl("Last");Holder = (Panel)_container.FindControl("Pager");this.First.Click += new System.Web.UI.ImageClickEventHandler(this.First_Click);this.Last.Click += new System.Web.UI.ImageClickEventHandler(this.Last_Click);this.Next.Click += new System.Web.UI.ImageClickEventHandler(this.Next_Click);this.Previous.Click += new System.Web.UI.ImageClickEventHandler(this.Previous_Click);

}

Page 18: Paging in ASP

Paging in ASP.NET Page 18 of 18

Paging in ASP.NET Page 18 of 18

The first thing the page control's InstatiateTemplate method does is instantiate the template, Layout.InstantiateIn(_container). The container is just another control that is used like any other control. Making use of this fact, the InstantiateTemplate method finds the four navigational buttons and the panel needed to hold the individual pagers. The buttons are found by their IDs. This is a small limitation placed on the pager control. The navigational buttons MUST have the pre-defined IDs, "First","Previous","Next","Last", "Pager", or they won't be found. Unfortunately this is the only alternative with the presentation structure chosen. The other alternative is for each of the buttons to inherit from the ImageButton class thereby defining a new type. Since each button will be of a different type then a recursive search through the container could be implemented to find a particular type forgoing the need to name the buttons properly. But with proper documentation such a small requirement shouldn't pose problems.

Once the four buttons are found, then the proper event handlers are bound to them. A very important decision must be made as to when the InstantiateTemplate should be called. Normally such a method would be called in the CreateChildControls method, since it basically does just that, creates child controls. Since the pager control doesn't ever change its child controls, then it doesn't need the functionality provided by the CreateChildControls method to change its rendering state based on some event. The quicker the child controls get rendered the better. A good place then to call the instantiate method is in the OnInit event.

protectedoverridevoid OnInit(EventArgs e){

_boundcontrol = Parent.FindControl(BindToControl);BoundControl.DataBinding += new EventHandler(BoundControl_DataBound);InstantiateTemplate();Controls.Add(_container);base.OnInit(e);

}

The OnInit method also performs a very important act, it adds the container to the pager control. Without adding the container to the pager's control collection then the template wouldn't be shown since the Render method would never be called. A template can also be defined programmatically by implementing the Itemplate interface. In step with providing a good flexible control, this feature can be used to provide a default template in case the user doesn't provide one in the aspx page.

publicclass DefaultPagerLayout:ITemplate{

private ImageButton Next;private ImageButton First;private ImageButton Last;private ImageButton Previous;private Panel Pager;

public DefaultPagerLayout(){

Next = new ImageButton();First = new ImageButton();Last = new ImageButton();Previous = new ImageButton();Pager = new Panel();

Next.ID="Next"; Next.AlternateText="Next";First.ID="First"; First.AlternateText="First";Last.ID = "Last"; Last.AlternateText ="Last";Previous.ID="Previous"; Previous.AlternateText="Previous";Pager.ID="Pager";

}publicvoid InstantiateIn(Control control)

Page 19: Paging in ASP

Paging in ASP.NET Page 19 of 19

Paging in ASP.NET Page 19 of 19

{control.Controls.Clear();Table table = new Table();table.BorderWidth = Unit.Pixel(0);table.CellSpacing= 1;table.CellPadding =0;TableRow row = new TableRow();row.VerticalAlign = VerticalAlign.Top;table.Rows.Add(row);TableCell cell = new TableCell();cell.HorizontalAlign = HorizontalAlign.Right;cell.VerticalAlign = VerticalAlign.Middle;cell.Controls.Add(First);cell.Controls.Add(Previous);row.Cells.Add(cell);cell = new TableCell();cell.HorizontalAlign= HorizontalAlign.Center;cell.Controls.Add(Pager);row.Cells.Add(cell);cell = new TableCell();cell.VerticalAlign = VerticalAlign.Middle;cell.Controls.Add(Next);cell.Controls.Add(Last);row.Cells.Add(cell);control.Controls.Add(table);

}}

The DefaultPagerLayout implements all the navigation elements programmatically that were added in the aspx page but this time it formats the elements with a normal HTML table. Now if the user forgets to implement their presentation template, then a default template will be provided for them.

[TemplateContainer(typeof(LayoutContainer))]public ITemplate Layout{

get{return (_layout == null)? new DefaultPagerLayout():_layout;}set{_layout =value;}

}

Getting back to generating the individual pagers, the pager control needs to first establish some useful properties that will tell the control how many individual pagers to generate.

publicint CurrentPage{

get{

string cur = (string)ViewState["CurrentPage"];return (cur == string.Empty || cur ==null)? 1 : int.Parse(cur);

}set{

ViewState["CurrentPage"] = value.ToString();}}

}publicint PagersToShow{

Page 20: Paging in ASP

Paging in ASP.NET Page 20 of 20

Paging in ASP.NET Page 20 of 20

get{return _results;}set{_results = value;}

}publicint ResultsToShow{

get{return _resultsperpage;}set{_resultsperpage = value;}

}

The CurrentPage property stores actually that, the current page in the ViewState of the pager, while the PagersToShow method and the ResultsToShow method define properties that allow the user to define how many pagers to show and how many results to show per page. The default is set to 10.

privateint PagerSequence{

get{

return Convert.ToInt32(Math.Ceiling((double)CurrentPage / (double)PagersToShow));}

}

The NumberofPagersToGenerate returns the current number of pagers that should be generated.

privateint NumberOfPagersToGenerate{

get{return PagerSequence*PagersToShow;}}

The NumberofPagersToGenerate returns the current number of pagers that should be generated.

privateint TotalPagesToShow{

get{return Convert.ToInt32(Math.Ceiling((double)TotalResults/ (double)ResultsToShow));}

}publicint TotalResults{

get{return _builder.Adapter.TotalCount;}}

The TotalPagesToShow method returns the total number of pages that should be shown adjusted by the users predefined ResultsToShow property.

ASP.NET defines default styles, which might not be very useful to the user employing the pager control. Defining custom styles allows the user to customize the pagers look.

public Style UnSelectedPagerStyle {get {return UnselectedPager;}}public Style SelectedPagerStyle {get {return SelectedPager;}}

The UnSelectedPagerStyle provides a style that will be used by the unselected individual pagers, while the SelectedPagerStyle provides a style that will be used by the selected individual pagers.

privatevoid GeneratePagers(WebControl control)

Page 21: Paging in ASP

Paging in ASP.NET Page 21 of 21

Paging in ASP.NET Page 21 of 21

{control.Controls.Clear();int pager = (PagerSequence-1)* PagersToShow +1;

for (;pager<=NumberOfPagersToGenerate && pager<=TotalPagesToShow;pager++){

LinkButton link = new LinkButton();link.Text = pager.ToString();link.ID = pager.ToString();link.Click += new EventHandler(this.Pager_Click);if (link.ID.Equals(CurrentPage.ToString()))

link.MergeStyle(SelectedPagerStyle);else

link.MergeStyle(UnSelectedPagerStyle);control.Controls.Add(link);control.Controls.Add(new LiteralControl("&nbsp;"));

}}

privatevoid GeneratePagers(){

GeneratePagers(Holder);}

The GeneratePagers method dynamically creates all the needed individual pagers as buttons of type LinkButton. The text and the ID property of the individual pagers are assigned the current pager number controlled by the loop; the click event is bound to the proper event handler and the proper styles are set. In the end the pagers are added to the proper housing control which in this case is the Panel object. The button ID serves as a way of identifying which button fired the click event. Defining the event handlers:

privatevoid Pager_Click(object sender, System.EventArgs e){

LinkButton button = (LinkButton) sender;CurrentPage = int.Parse(button.ID);Update();

}

privatevoid Next_Click(object sender, System.Web.UI.ImageClickEventArgs e){

if (CurrentPage<TotalPagesToShow)CurrentPage++;

Update();}privatevoid Previous_Click(object sender, System.Web.UI.ImageClickEventArgs e){

if (CurrentPage > 1)CurrentPage--;

Update();}privatevoid First_Click(object sender, System.Web.UI.ImageClickEventArgs e){

CurrentPage = 1;Update();

}privatevoid Last_Click(object sender, System.Web.UI.ImageClickEventArgs e){

Page 22: Paging in ASP

Paging in ASP.NET Page 22 of 22

Paging in ASP.NET Page 22 of 22

CurrentPage = TotalPagesToShow;Update();

}

Each of the event handlers takes care of setting the current page of the pager control and then update the bound control.

privatevoid Update(){

if (!HasParentControlCalledDataBinding) return;ApplyDataSensitivityRules();BindParent();BoundControl.DataBind();

}

First off, the pager control checks that the control has gone through the necessary steps to initialize the proper adapters by calling the HasParentControlCalledDataBinding method. If it has, then it applies something which was outlined in the specifications as data sensitivity rules. Data sensitivity rules allow the pager control to behave differently based on the data in the BoundControl. The pager control controls data sensitivity internally, but they could easily be moved outside by using the [GoF] State pattern.

publicbool IsDataSensitive{

get{return _isdatasensitive;}set{_isdatasensitive = value;}

}

privatebool IsPagerVisible{

get{return (TotalPagesToShow != 1) && IsDataSensitive;}}

privatebool IsPreviousVisible{

get{

return (!IsDataSensitive)? true:(CurrentPage != 1);

}}

privatebool IsNextVisible{

get{

return (!IsDataSensitive)? true:(CurrentPage != TotalPagesToShow);

}}

privatevoid ApplyDataSensitivityRules(){

FirstButton.Visible = IsPreviousVisible;PreviousButton.Visible = IsPreviousVisible;LastButton.Visible = IsNextVisible;NextButton.Visible = IsNextVisible;

Page 23: Paging in ASP

Paging in ASP.NET Page 23 of 23

Paging in ASP.NET Page 23 of 23

if (IsPagerVisible) GeneratePagers();}

The ApplyDataSensitivityRules applies the predefined rules such as IsPagerVisible, IsPreviousVisible and IsNextVisible. By default data sensitivity is turned on. Users can turn it off by setting the IsDataSensitive property. The presentation of the pager control is now finished. The last finishing touch is to provide events so that the user can make all the necessary adjustments on different pager events.

publicdelegatevoid PageDelegate(object sender,PageChangedEventArgs e);

publicenum PagedEventInvoker{Next,Previous,First,Last,Pager}

publicclass PageChangedEventArgs:EventArgs{

privateint newpage;private Enum invoker;

public PageChangedEventArgs(int newpage):base(){

this.newpage = newpage;}public PageChangedEventArgs(int newpage,PagedEventInvoker invoker){

this.newpage = newpage;this.invoker = invoker;

}publicint NewPage {get{return newpage;}}public Enum EventInvoker{get{return invoker;}}

}

Since the pager control needs to return custom event arguments, a special class PageChangedEventArgs is built. The PageChangedEventArgs class returns the type of PagedEventInvoker, which is a simple enumeration of the possible controls that could raise the event, and the new page number. To handle the custom event arguments a new delegate is defined, PageDelegate. The different events are defined in the following manor:

publicevent PageDelegate PageChanged;publicevent EventHandler DataUpdate;

When an event doesn't have a listener attached to it, ASP.NET raises an exception to counteract this behavior; the pager control defines the following RaiseEvent methods.

privatevoid RaiseEvent(EventHandler e,object sender){

this.RaiseEvent(e,this,null);}

privatevoid RaiseEvent(EventHandler e,object sender, PageChangedEventArgs args){

if(e!=null) {

e(sender,args);}

}privatevoid RaiseEvent(PageDelegate e,object sender)

Page 24: Paging in ASP

Paging in ASP.NET Page 24 of 24

Paging in ASP.NET Page 24 of 24

{this.RaiseEvent(e,this,null);

}

privatevoid RaiseEvent(PageDelegate e,object sender, PageChangedEventArgs args){

if(e!=null) {

e(sender,args);}

}

The event handlers can now raise events (or bubble) by making calls to the RaiseEvents methods.

privatevoid Pager_Click(object sender, System.EventArgs e){

LinkButton button = (LinkButton) sender;CurrentPage = int.Parse(button.ID);RaiseEvent(PageChanged, this,new

PageChangedEventArgs(CurrentPage,PagedEventInvoker.Pager));Update();

}

privatevoid Update(){

if (!HasParentControlCalledDataBinding) return;ApplyDataSensitivityRules();BindParent();_boundcontrol.DataBind();RaiseEvent(DataUpdate,this);

}

For simplicity sake the other event handlers aren't shown, but they raise the PageChanged event in a similar way.The pager control is now ready to use. To use it the Web designer needs only to bind it to a presentation control.

<asp:RepeaterID="repeater"Runat="server"><ItemTemplate>

Column 1: <%# Convert.ToString(DataBinder.Eval( Container.DataItem,"Column1"))%><br>Column 2: <%# Convert.ToString(DataBinder.Eval (Container.DataItem,"Column2"))%><br>Column 3: <%# Convert.ToString(DataBinder.Eval( Container.DataItem,"Column3"))%><br><hr>

</ItemTemplate></asp:Repeater>

<cc1:Pagerid="pager"ResultsToShow="2"runat="server"BindToControl="repeater"><SelectedPagerStyleBackColor="Yellow"/>

</cc1:Pager>

The above aspx page binds the pager to a Repeater control, sets the number of results to show per page to 2, and sets the selected pager color to yellow. The default layout is used. The following test application binds the pager control to a DataGrid.

<asp:DataGridID="Grid"Runat="server"/>

<cc1:Pagerid="PagerGrid"ResultsToShow="2"runat="server"BindToControl="Grid">

Page 25: Paging in ASP

Paging in ASP.NET Page 25 of 25

Paging in ASP.NET Page 25 of 25

<SelectedPagerStyleBackColor="Red"/><Layout>

<asp:ImageButtonid="First"Runat="server"AlternateText="first"/><asp:ImageButtonid="Previous"Runat="server"AlternateText="previous"/><asp:ImageButtonid="Next"Runat="server"AlternateText="next"/><asp:ImageButtonid="Last"Runat="server"AlternateText="last"/><asp:PanelID="Pager"Runat="server"/>

</Layout></cc1:Pager>

The code behind tests the pluggable architecture.

protected Pager pager;protected Repeater repeater;protected Pager PagerGrid;protected DataGrid Grid;

privatevoid Page_Load(object sender, System.EventArgs e){

pager.Adapters.Remove(typeof(DataTable));pager.Adapters.Add(typeof(DataTable),new DataTableAdapterBuilder());DataTable table = GenerateDataTable();repeater.DataSource = table;repeater.DataBind();Grid.DataSource = table;Grid.DataBind();

}private DataTable GenerateDataTable(){

DataTable table = new DataTable();table.Columns.Add("Column1");table.Columns.Add("Column2");table.Columns.Add("Column3");for (int i=0;i<20;i++){

DataRow row = table.NewRow();row[0] = "Row"+(i+1);table.Rows.Add(row);

}return table;

}

The test applications show that the pager control is independent from the presentation controls, can easily handle different data sources, and is very easy to use. For other tests of the pagers functionality please download the zip file which contains all the tests and all the relevant source code.

Even though developing custom Web controls can have a significant learning curve, taking the time to learn it has untold benefits. By providing reusable components, the developer, with a bit of extra work, can turn a normal web control into a multi-use generic control, increasing output ten fold. The pager control is just one example of how to create a control that is independent of current and future presentation needs. Source http://www.15seconds.com/files/030813.zip

Page 26: Paging in ASP

Paging in ASP.NET Page 26 of 26

Paging in ASP.NET Page 26 of 26

Custom ASP.NET Datagrid Paging With Exact Count

IntroductionAnybody in the DB world knows what paging database results is and its effect. From the time I had started getting into good old classic ASP, I was intrigued with the ability to divide large sets of data into sections of x records per page. One thing that I didn't like about paging is that it seemed sites incorporated just a < Prev and Next > link on the search results page. I wasn't satisfied with such a lackluster paging technique, and from there I searched high and low on every ASP Web site I could find to see if there was code to show more advanced paging options, such as how many pages were remaining to be paged through, or, if the next page was the last page of results, how many records were on that last page. Unfortunately, I couldn't find any such code, so I had set out to do it myself. (To see the proposed paging enhancements I like, check out the live demo for this article...)

I have since coded a number of techniques for advanced paging in classic ASP, but my latest challenge has been to incorporate the same paging techniques in ASP.NET! (For more information on ASP.NET, be sure to visit the ASP.NET Article Index.) Pssst, don't ask me to talk about redoing my app in Beta 1, and upon upgrading to Beta 2 was horrified that my code needed to be redone.... by the way, the code in this article is all Beta 2 compliant.

Now, if anyone has looked into the Microsoft .NET SDK and Quickstart samples you will find custom paging samples, but it's the usual next and prev stuff. Now let's see how we can kick this paging up a notch and tell us more detail about our data output.

Accessing our DataThe first step is of course to query our database, and send our data into our datagrid. The first thing we should concern ourselves with in any .Net page is that we import the necessary namespaces for our app. In this case, as with most data access apps, I'm importing System.Data and System.Data.SqlClient for SQL Server. If you use MS Access or another database then System.Data.OleDb namespace and associated classes will work just fine, providing you modify the connection variables and data adapters.

This importing of namespaces is all done before our script tags like so:

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

Now, within our server-side script tags we include our object-oriented stuff – our Subroutine to access our database and bind our result set into our ASP.NET Datagrid. This subroutine, BindSQL() needs to first create all of our variables that we'll use:

Sub BindSQL()Dim MyConnection As SqlConnectionDim DS as DataSetDim MyCommand As SqlDataAdapterDim RcdCount As Integer

'Our SQL stringDim sqlStr As String = "SELECT titles.title, authors.au_lname, " & _

"authors.au_fname, titles.price " & _"FROM authors INNER JOIN titleauthor ON " & _"authors.au_id = titleauthor.au_id " & _"INNER JOIN titles ON " & _

"titleauthor.title_id = titles.title_id"

'The connection to our databaseDim strConn As String = "server=(local);uid=sa;pwd=;" & _

"database=pubs;Trusted_Connection=yes;"

Next we need to instantiate our connection and command object, and the fill our DataSet object with the results of the SQL query:

Page 27: Paging in ASP

Paging in ASP.NET Page 27 of 27

Paging in ASP.NET Page 27 of 27

...

'Open up our connection with our connection objectMyConnection = New SQLConnection(strConn)

'To execute our Sql Statement and provide our active connectionMyCommand = NewSqlDataAdapter(sqlStr, MyConnection)

'Create instance of DataSet object and fill our predetermined 'datagrid with it and we name itDS = new DataSet()MyCommand.Fill(DS, "pubs")

...

Now comes the one part that we'll used for our custom paging – the record count, and you'll see it quite different than our classic ASP way.

RcdCount = DS.Tables("pubs").Rows.Count.ToString()

Now that we have this total count of the records in the DataSet, we'll save it to a global variable, since we'll want to access it from other subroutines. The variable ResultCount should be defined in global-scope, as an Integer. (See the complete source later on in this article to note how to create global-scoped variables)

ResultCount = RcdCount

Next, we display the number of records found in a label control:

RecordCount.Text = "<b><font color=red>" & RcdCount & "</font> records found"

Finally, at this point, we can bind our DataSet to the DataGrid and display a label illustrating what page of results we're currently viewing: which will display :

Pubs.DataSource = DSPubs.Databind()

lblPageCount.Text = "Page " & Pubs.CurrentPageIndex + 1 & " of " & Pubs.PageCount

At this point, we need to determine if we need to show the Next/Prev links, as well as the First Page/Last Page links:

'Do we want to show the prev/First Page buttons?If Pubs.CurrentPageIndex <> 0 Then

Call Prev_Buttons()Firstbutton.Visible = truePrevbutton.Visible = true

ElseFirstbutton.Visible = falsePrevbutton.Visible = false

End If

'Do we want to show the Next/Last Page buttons?If Pubs.CurrentPageIndex <> (Pubs.PageCount-1) then

Page 28: Paging in ASP

Paging in ASP.NET Page 28 of 28

Paging in ASP.NET Page 28 of 28

Call Next_Buttons()NextButton.Visible = trueLastbutton.Visible = true

ElseNextButton.Visible = falseLastbutton.Visible = False

End IfEnd Sub

That concludes our BindSQL() subroutine, which is a bit lengthy. Don't worry, that's, by far, the most complex piece of our ASP.NET Web page! We still have three more short server-side subroutines to examine, but let's first look at the HTML portion of our ASP.NET Web page, which we'll do in Part 2.

In Part 1 we looked at creating a custom paging solution using ASP.NET's DataGrid Web control. In Part 1 we focused in on the first (and most detailed) server-side subroutine: BindSQL(). In this part we'll look at the HTML content of the custom paging Web page and examine the other server-side subroutines and event handlers.

The HTML PortionIn our HTML section we need a couple of label controls to display various bits of information, such as what page we are currently viewing, how many total records there are, etc. We also need a DataGrid, to which we are binding the database results to. Don't forget to put the DataGrid in a WebForm (a <form> tag with the runat="server" attribute specified). Finally, we need a series of LinkButton Web controls, to display our Prev/First Page and Next/Last Page links.

The content for the HTML section is as follows:

<html><body>

'For our recordcount and pagecount<asp:Label id="lblPageCount" runat="server" /><br><asp:label id="RecordCount" runat="server" />

<form runat="server"><ASP:Datagrid id="pubs" runat="server"

AllowPaging="True" AllowCustomPaging="False" Pagesize="10" PagerStyle-Visible = "False"

/>

<%-- Display the First Page/Previous Page buttons --%><asp:linkbutton id="Firstbutton" Text="<< 1st Page"

CommandArgument="0" runat="server" onClick="PagerButtonClick"/>

<asp:linkbutton id="Prevbutton" Text= "" CommandArgument="prev" runat="server" onClick="PagerButtonClick"/>

<%-- Display the Next Page/Last Page buttons --%><asp:linkbutton id="Nextbutton" Text= ""

CommandArgument="next" runat="server" onClick="PagerButtonClick"/>

<asp:linkbutton id="Lastbutton" Text="Last Page >>" CommandArgument="last" runat="server" onClick="PagerButtonClick"/>

Page 29: Paging in ASP

Paging in ASP.NET Page 29 of 29

Paging in ASP.NET Page 29 of 29

<br><br><br><br>

Change Pagesize<asp:DropDownList id="ps" runat="server">

<asp:ListItem>4</asp:ListItem><asp:ListItem>5</asp:ListItem><asp:ListItem>7</asp:ListItem><asp:ListItem selected>10</asp:ListItem><asp:ListItem>12</asp:ListItem><asp:ListItem>15</asp:ListItem><asp:ListItem>22</asp:ListItem>

</asp:DropDownList>

<asp:button text="Change Pagesize" runat="server" OnClick="RePage"/></form>

</body></html>

Note that in our DataGrid Web control we set the PagerStyle's Visible property to False. This is because we are implementing our own paging solution, and don't want to use the default paging style supported by the DataGrid Web control. (For more information on paging database results using the DataGrid's built-in functionality, be sure to read: Paging Database Results in ASP.NET!) Also note that the four LinkButton controls all specify the server-side subroutine PagerButtonClick as the sub to be called when they are clicked; similarly, the "Change Pagesize" button has the RePage subroutine defined as its OnClick event handler.

The event handler for the four LinkButtons (PagerButtonClick) must display the appropriate page of data, be it the next page, the previous page, the first page, or the last page. Which page to display, of course, depends on what LinkButton the user clicked. The PagerButtonClick (shown below) uses the CommandArgument passed in from the LinkButton Web controls to determine which control was clicked, and then takes the appropriate action.

Sub PagerButtonClick(sender As Object, e As EventArgs)'used by external paging UIDim arg As String = sender.CommandArgument

Select argCase "next": 'The next Button was Clicked

If (Pubs.CurrentPageIndex < (Pubs.PageCount - 1)) ThenPubs.CurrentPageIndex += 1

End If

Case "prev": 'The prev button was clickedIf (Pubs.CurrentPageIndex > 0) Then

Pubs.CurrentPageIndex -= 1End If

Case "last": 'The Last Page button was clickedPubs.CurrentPageIndex = (Pubs.PageCount - 1)

Case Else: 'The First Page button was clickedPubs.CurrentPageIndex = Convert.ToInt32(arg)

End Select

'Now, bind the data!BindSQL()

Page 30: Paging in ASP

Paging in ASP.NET Page 30 of 30

Paging in ASP.NET Page 30 of 30

End Sub

The RePage event handler, which is called when the "Change Pagesize" button is clicked, simply resets the DataGrid's CurrentPageIndex property back to 0 and rebinds the database data:

Sub Repage(sender As Object, e As EventArgs)Pubs.CurrentPageIndex = 0BindSQL()

End Sub

Finally, the last two server-side subroutines are two meager helper subroutines, Next_Buttons() and Prev_Buttons(), which display the correct text for each of the LinkButtons. These two subs, which are called from BindSQL(), can be seen below:

Sub Prev_Buttons()Dim PrevSet As String

If Pubs.CurrentPageIndex+1 <> 1 and ResultCount <> -1 ThenPrevSet = Pubs.PageSizePrevButton.Text = ("< Prev " & PrevSet)

If Pubs.CurrentPageIndex+1 = Pubs.PageCount ThenFirstButton.Text = ("<< 1st Page")

End IfEnd If

End Sub

Sub Next_Buttons()Dim NextSet As String

If Pubs.CurrentPageIndex+1 < Pubs.PageCount ThenNextSet = Pubs.PageSizeNextButton.Text = ("Next " & NextSet & " >")

End If

If Pubs.CurrentPageIndex+1 = Pubs.PageCount-1 ThenDim EndCount As Integer EndCount = ResultCount - (Pubs.PageSize * (Pubs.CurrentPageIndex+1))NextButton.Text = ("Next " & EndCount & " >")

End IfEnd Sub

[View a live demo!]

Well, that's it! Be sure to view the complete code, try out the live demo, and read up on the related articles! If you have any questions, please do not hesitate to email me!

Happy Programming! Complete code: http://www.4guysfromrolla.com/webtech/code/CustPaging.aspx.html

Page 31: Paging in ASP

Paging in ASP.NET Page 31 of 31

Paging in ASP.NET Page 31 of 31

Understanding the Differences Among the DataGrid, DataList, and Repeater

IntroductionOne of the many benefits of ASP.NET over its predecessor, ASP, is ASP.NET's data Web controls. The data Web controls are designed to easily display data. For example, the DataGrid Web control is ideal for displaying data in an HTML <table>. (For more information on the DataGrid Web control be sure to read An Extensive Examination of the DataGrid Web Control.) In addition to the DataGrid Web control, there are two additional data Web controls. All in total, the three ASP.NET data Web controls are:

1. The DataGrid, 2. The DataList, and 3. The Repeater

These three controls share much in common, such as the programmatic syntax used for binding data to the Web control, as well as a number of common properties and events. Despite their similarities, though, each of these Web controls has their individual advantages and disadvantages. In designing data-driven Web applications, ASP.NET developers often find themselves wondering which of these three controls is the "best" one.

The answer to that question depends on what it is you are needing to accomplish. In this article we'll examine the merits and demerits of each of these three data Web controls, and look at situations where certain controls have benefits over the others. Additionally, we'll examine not only the different advantages and disadvantages, but what things these three controls have in common.

Looking for a More Detailed Examination of the Data Web Controls' Similarities and Differences?

If you are looking for a much more detailed look at the similarities and differences among the three data Web controls, consider picking up a copy of my book, ASP.NET Data Web Controls Kick Start. Specifically, Chapter 1 presents a thorough comparison and contrasting of these data Web controls. (For more information about ASP.NET Data Web Controls Kick Start, check out DataWebControls.com.)

Examining the Similarities Between the Data Web ControlsThe main similarity between the DataGrid, DataList, and Repeater Web controls is that all three have a DataSource property and a DataBind() method. In order to bind data to one of these data Web controls, the same technique is used:

1. Assign the data to be displayed to the data Web control's DataSource property, and 2. Call the data Web control's DataBind() method.

Another similarity is that each of the data Web controls is composed of a given number of DataWebControlNameItems. That is, a DataGrid is composed of a number of DataGridItems; the DataList of DataListItems; and the Repeater of RepeaterItems. When the data Web control's DataBind() method is called, it iterates through the contents of the DataSource. For each record in the DataSource, a new DataWebControlNameItem instance is created and appended to the data Web control. After the DataWebControlNameItem instance has been created, its DataItem property is assigned the value of the current DataSourcerecord and the DataWebControlNameItem's DataBind() method is called, which binds the columns of the DataSource record to the DataWebControlNameItem. (The DataGridItem, DataListItem, and RepeaterItem differ in a number of ways, which we'll discuss later in this article.)

In addition to these similarities, the data Web controls each also have the following three events:

1. ItemCreated, 2. ItemDataBound, and 3. ItemCommand

Page 32: Paging in ASP

Paging in ASP.NET Page 32 of 32

Paging in ASP.NET Page 32 of 32

The ItemCreated event fires once for every DataWebControlNameItem added to the data Web control. It fires before the DataWebControlNameItem's DataItem property is assigned. The ItemDataBound event also fires once for every DataWebControlNameItem added to the data Web control, but this event fires after the DataWebControlNameItem's DataItemproperty is assigned. Finally, the ItemCommand event fires whenever the Command event for a Button or LinkButton within the data Web control fires.

Demystifying these Similarities With an ExampleIf you are thoroughly confused right about now, I don't blame you! The process a data Web control undergoes when being bound to data can be a bit perplexing at first. However, it is oftentimes much easier to comprehend these complexities by looking at an example. Consider the case where we have a DataGrid that contains two BoundColumns. Such a DataGrid declaration might look like:

<asp:DataGrid runat="server" id="myDataGrid" AutoGenerateColumns="False"><Columns>

<asp:BoundColumn DataField="Field1" /><asp:BoundColumn DataField="Field2" />

</Columns></asp:DataGrid>

Now, imagine that we are binding a DataSet to this DataGrid in the Page_Load event handler. To accomplish this, we might use the following code:

Sub Page_Load(sender as Object, e as EventArgs)Dim myConnection as New SqlConnection(connString)Const strSQL as String = "SELECT Field1, Field2 FROM TableName"

Dim resultsDataSet as New DataSet()Dim myDataAdapter as SqlDataAdapter = New SqlDataAdapter(strSQL, myConnection) myDataAdapter.Fill(resultsDataSet)

myDataGrid.DataSource = resultsDataSetmyDataGrid.DataBind()

End Sub

What happens when the DataGrid's DataBind() method is called? As we discussed earlier, the DataGrid's DataBind() method enumerates the records of the DataSet assigned to its DataSource property (namely, resultsDataSet). Realize that when iterating through the DataSet, in actuality you are iterating through the Rows collection of the DataSet's default DataTable. The Rows collection is comprised of a number of DataRow instances, one for each record returned by the specified SQL query.

Now, for each DataRow, first, a new DataGridItem is created. Then, the DataGrid's ItemCreated event fires. Next, the DataRow is assigned to the DataGridItem's DataItem property. Following this, the DataGrid's ItemDataBound event fires. This process is repeated for all of the DataRows.

This databinding process is common among the three data Web controls, the only difference being that with a DataList or Repeater a DataListItem or RepeaterItem is created in place of a DataGridItem.

A Real-World Use for the ItemDataBound EventAs earlier mentioned, each of the data Web controls fires its ItemDataBound event after the DataWebControlNameItem has been created and its DataItem property assigned. Oftentimes, you might want to conditionally adjust the formatting of a data Web control "row" based on its value. For example, in a DataGrid showing product information, you might want to highlight rows that have products that are on sale. To accomplish this, you can apply the formatting programmatically in the ItemDataBound event handler for rows that meet the desired criteria. For more on this topic, be sure to check out Conditionally Formatting a DataGrid Column's Value.

Page 33: Paging in ASP

Paging in ASP.NET Page 33 of 33

Paging in ASP.NET Page 33 of 33

Now that we have examined the similarities between the DataGrid, DataList, and Repeater, let's turn our attention to their differences. We'll examine what makes each of these controls unique in Part 2.

A Brief Overview of the DataGrid Web ControlThe DataGrid Web control was designed to display data in an HTML <table>. Each row in the DataGrid's DataSource is displayed as a row in the HTML <table>. The DataGrid has an AutoGenerateColumns property, which can be set to either True or False. If AutoGenerateColumns is set to True (the default), each field in the DataSource is displayed as a column in the resulting HTML <table>. If, however, AutoGenerateColumns is set to False, then the developer must explicitly specify what columns should appear.

One downside of a DataGrid is its rather "blocky" display. That is, the DataGrid displays each DataSource record as a row in an HTML <table>, and each field as a column in said table. While the DataGrid allows for the developer to customize the output for a particular column through the <asp:TemplateColumn>, it still is restrictive in that each DataSource record occupies precisely one HTML <table> row. (For an example of customizing a DataGrid's column using templates, see Combining Two DataSource Fields Into One DataGrid Column.)

Despite its limitations on specifying the display of the DataGrid's data, the DataGrid is the most commonly used data Web control due to its impressive feature list. For example, with minimal code and effort, the DataGrid can provide sorting, paging, and in-place editing of its data. (For more on the DataGrid Web control, as well as thorough coverage of its features, be sure to read An Extensive Examination of the DataGrid Web Control.)

A Brief Overview of the DataList Web ControlThe DataList Web control is useful for displaying data that can be highly customized in its layout. By default, the DataList displays its data in an HTML <table>. However, unlike the DataGrid, with the DataList you can specify via the RepeatColumnshow many DataSource records should appear per HTML <table> row. For example, the following code and live demoillustrates having five DataSource records displayed per HTML <table> row.

<asp:DataList runat="server" id="dlPopularFAQs" ...RepeatColumns="5" >

<ItemTemplate><b>Question:</b><br /><%# DataBinder.Eval(Container.DataItem, "Description") %><p><b>Views:</b><br /><%# DataBinder.Eval(Container.DataItem, "ViewCount", "{0:#,###}") %>

</ItemTemplate></asp:DataList>

[View a Live Demo!]

As the code example above shows, DataLists are comprised of a number of templates. Templates can contain a mix of HTML syntax and databinding expressions, as shown in the above ItemTemplate. Databinding expressions are ones delimited by <%#and %>, and contain code that's executed when the DataListItem's DataBind() method is called. The ItemTemplate specifies the template used for each record in the DataSource. The DataList contains other templates, which are listed below:

AlternatingItemTemplate - if specified, each alternating item in DataSource uses this template instead of the ItemTemplate.

EditItemTemplate - the template used when a DataList row is in "edit mode". HeaderTemplate - the template for the DataList's header item. Precisely one header item is created for the DataList

item if the DataList's ShowHeader property is set to True. FooterTemplate - the template for the DataList's footer item. Precisely one footer item is created for the DataList item if

the DataList's ShowFooter property is set to True. SeparatorTemplate - this template, if specified, is applied after each DataListItem has been added.

Page 34: Paging in ASP

Paging in ASP.NET Page 34 of 34

Paging in ASP.NET Page 34 of 34

The DataGrid's TemplateColumnThe DataGrid can consist of TemplateColumns, which allow for more flexible formatting of a particular DataGrid column. However, the DataGrid's TemplateColumn only supports a subset of the templates that the DataList supports. Namely, the DataGrid's TemplateColumn contains support for the EditItemTemplate, HeaderTemplate, FooterTemplate, and ItemTemplate only.

The DataList is capable of performing sorting, paging, and editing of its data, but to do so requires quite a bit more programming than to accomplish these tasks with the DataGrid. Therefore, if you know you will be needing these functionalities, it is likely best to proceed with the DataGrid. If, however, you will not need these functionalities, but do necessarily need the extra control over the formatting, consider a DataList.

A Brief Overview of the Repeater ControlThe Repeater control, unlike the DataGrid and DataList, is not derived from the WebControl class, and therefore does not have the stylistic properties that are common to all Web controls. (These stylistic properties that are common to all Web controls include Font, ForeColor, BackColor, BorderStyle, and so on.)

The Repeater, like the DataList, only supports templates; however, the Repeater has only a subset of the DataList's template options. Specifically, the following templates can be used with the Repeater:

AlternatingItemTemplate, ItemTemplate, HeaderTemplate, FooterTemplate, and SeparatorTemplate

The Repeater control provides the maximum amount of flexibility over the HTML produced. Whereas the DataGrid wraps the DataSource contents in an HTML <table>, and the DataList wraps the contents in either an HTML <table> or <span> tags (depending on the DataList's RepeatLayout property), the Repeater adds absolutely no HTML content other than what you explicitly specify in the templates.

Therefore, the Repeater is a good data Web control choice if you want to display data in, say, an unordered list. As the following code and live demo show, displaying database data in an unordered list using a Repeater is relatively straightforward. All you have to do is add the <ul> tag to the HeaderTemplate, the </ul> tag to the FooterTemplate, and an <li> tag along with the DataSource field you want to display to the ItemTemplate:

<asp:Repeater runat="server" id="rptULforFAQs"><HeaderTemplate>

<ul></HeaderTemplate><ItemTemplate>

<li><%# DataBinder.Eval(Container.DataItem, "Description")%></li></ItemTemplate><FooterTemplate>

</ul></FooterTemplate>

</asp:Repeater>

[View a Live Demo!]

The Repeater is a good choice if you need to display your data in a format different than an HTML <table>. Unfortunately, the Repeater does not provide any built-in means for editing, sorting, or paging of data. However, these mechanisms could be added programmatically, but would result in a lot of code and effort.

Page 35: Paging in ASP

Paging in ASP.NET Page 35 of 35

Paging in ASP.NET Page 35 of 35

ConclusionThis article examined the similarities and differences of the three data Web controls: the DataGrid, DataList, and Repeater. The main similarities between these three controls is in how they iterate through their DataSource, building up a collection of DataWebControlNameItems. Furthermore, the three Web controls all share three events: ItemCreated, ItemDataBound, and ItemCommand.

Each of the data Web controls has its own strengths and weaknesses. The DataGrid, for example, is great for quickly and easily displaying database data in an HTML <table>, and allows for advanced features such as paging, sorting, and in-place editing to be added with little to no programming. However, the DataGrid is quite limited in the general format with which the data is presented.

The DataList allows for more freedom. For example, in an earlier live demo we saw that using the DataList's RepeatColumns property, multiple DataSource records could be displayed in a single HTML <table> row. Additionally, the DataList's content is specified via templates, which allows for a high degree of customization.

The Repeater allows for the utmost flexibility in its output, since the only HTML rendered from a Repeater is the HTML generated in its templates. That is, no additional HTML output is generated, as is the case with both the DataGrid and DataList. The Repeater, however, does not have any built-in support for sorting, paging, or editing of its data.

For a much more thorough and in-depth examination of the similarities and differences of the data Web controls, be sure to pick up a copy of my book, ASP.NET Data Web Controls Kick Start.

Happy Programming!

Efficiently Iterating Through Results from a Database Query using ADO.NET

'Create a connection to the databaseDim objConnSet objConn = Server.CreateObject("ADODB.Connection")objConn.Open connectionString

'Specify a SQL queryconst strSQL = "SELECT * FROM TableName"

'Create a Recordset object, populate it with the query resultsDim objRSSet objRS = Server.CreateObject("ADODB.Recordset")objRS.Open strSQL, objConn

'Loop through the RecordsetDo While Not objRS.EOF

do somethingobjRS.MoveNext

Loop

Here, do something may be any number of things, but most commonly involved displaying all, or parts, of the record (perhaps in an HTML table). To display database results through an ASP.NET page, developers no longer need to loop through the results of a database query; instead, they can use databinding and one of the many ASP.NET data Web controls. There are, however, times when a .NET developer does need to loop through database results one at a time. This article examines how to loop through database results using ADO.NET in an ASP.NET Web page.

First Things First - Connnecting to the Data StoreAs with classic ADO, when you want to retrieve the results of a database query from a data store, the first task is to create a connection to the data store. In classic ADO, developers used the Connection object (as shown in the example above). With

Page 36: Paging in ASP

Paging in ASP.NET Page 36 of 36

Paging in ASP.NET Page 36 of 36

ADO.NET, developers still use a connection object, but should use one appropriate to the data store they are wanting to connect to.

With the .NET Framework, there are two data providers: an OleDb data provider and a SqlClient data provider. The OleDb data provider is for connecting to any OLE-DB compliant data store (Access, Excel, Microsoft SQL Server, Oracle, etc., essentially any modern data store). The SqlClient data provider is specifically for Microsoft's SQL Server, versions 7.0 and up. The SqlClient data provider provides much faster access to SQL Server than the OleDb equivalent; therefore, if you are using Microsoft SQL Server 7.0 or up, you should consider using the SqlClient data provider. (For those who wish to connect via ODBC, there is an ODBC data provider for .NET, but it needs to be downloaded from Microsoft's Web site (here).

Each data provider contains its own connection object class. The OleDb data provider has an OleDbConnection class and the SqlClient data provider has a SqlConnection class. These classes are found in the System.Data.OleDb and System.Data.SqlClient namespaces, respectively, so, depending on what data provider you choose to use, you'll need to Import the appropriate namespace into your ASP.NET Web page (we'll see an example of this shortly).

For this article, we will be connecting to an Access database, so we'll use the OleDbConnection class to connect to the database. To make the connection we need an applicable connection string, which, for Access, is fairly simple: we just need to include the provider name (Microsoft.Jet.OLEDB.4.0 for Access 2000) and the physical path to the .mdb file. The example below illustrates how to connect to an Access database:

<%@ Import Namespace="System.Data" %><%@ Import Namespace="System.Data.OleDb" %><script language="VB" runat="server">

sub Page_Load(sender as Object, e as EventArgs)Dim connString as StringconnString = "PROVIDER=Microsoft.Jet.OLEDB.4.0;DATA SOURCE=" & _

"C:\Inetpub\wwwroot\Projects.mdb;"

Dim objConnection as OleDbConnectionobjConnection = New OleDbConnection(connString)objConnection.Open() 'open the connection

...

Note the Import directive at the top of the code. This is needed so that we can use the OleDbConnection class without having to fully qualify the classname as System.Data.OleDb.OleDbConnection. (We also include the System.Data namespace because we'll need to use classes in it later on in our example.) In our Page_Load event handler (which is fired every time the ASP.NET Web page is viewed) we first create a String variable named connString that holds the connection string information to the database. (Note that we could have used Server.MapPath, like in classic ASP, instead of hard coding the directory information (C:\Inetpub\wwwroot\).)

Next, an instance of the OleDbConnection object is created and the connection string is passed into the constructor. Lastly, the Open method is called to actually open the connection to the database. At this point, we have an open connection to our Access database, Projects.mdb.

Issuing a Database Query

The next step is to issue a database query. In classic ADO, one could create a Command object with the proper SQL statement and then populate a Recordset object from the Command object. Alternatively, developers could just populate a Recordset object directly (as in the first example in this article).

With ADO.NET, you need to create an appropriate Command object (as with the connection objects, this depends on the data provider you are using: you can use the OleDbCommand class or the SqlCommand class). The Command object has two important properties: CommandType, which we use to specify if we are issuing a stored procedure, a specific table name, or a SQL query; and CommandText, where we actually specify the text for the

Page 37: Paging in ASP

Paging in ASP.NET Page 37 of 37

Paging in ASP.NET Page 37 of 37

Command (such as SELECT * FROM Tablename if we are doing a SQL query; or TableName if we are doing a query by table name only).

The following example illustrates how to create an OleDbCommand object to perform a query against our Access database. The code below is a continuation from the previous code example, and should follow the connection-opening code in the Page_Load event handler:

...

'Specify our SQL statementDim strSQL as String = "SELECT * FROM Department"

'Create the Command objectDim objCommand as OleDbCommandobjCommand = New OleDbCommand(strSQL, objConnection)

...

In the above example we first specify our SQL statement, placing it in a String variable named strSQL. Next, we create an OleDbCommand object, passing in the SQL string and connection object in the constructor. (Note that by specifying the CommandText in the constructor (the strSQL variable), you are implicitly setting the CommandType to Text.)

Now that we now how to connect to a database and issue a SQL command, we only need to get the query results back in some useful object to be able to iterate through the results. In Part 2 we'll examine how to do just this!

Getting the Database ResultsNow that you have created a connection and specified a command we need to get the database results conforming to the query. There are two options here that are both worth mentioning, the option you choose depending on what you plan on doing with the data. If you are just needing read, forward-only access to the database query result, then you'll want to use a data reader object. The data reader is akin to the read-only, firehose cursor in classic ADO. (Here, forward-only and firehose cursors mean that when iterating through the cursor you can only move forward through the results. That is, you can't issue .MovePrev calls.)

If you need more flexible access to the data, you'll want to use the DataSet object (which can be found in the System.Datanamespace. DataSets are much "bulkier" than data readers, and are akin to disconnected data stores. You can dump multiple queries into a DataSet, iterate through it however you want, etc. Of course this extra functionality comes at a performance cost -it is not nearly as efficient as the data reader. Therefore, if you plan on just iterating through the results in a firehose, read-only manner, use the data reader. In this article, I'm only going to address the data reader approach; see the documentation for information on the DataSet.

Calling the ExecuteReader method of the Command object returns an appropriate data reader that is ready to iterate through the results of the query. I say "appropriate" because there are different data readers for each data provider! Therefore, an OleDbCommand object's ExecuteReader method returns an OleDbDataReader object; the SqlCommand object's ExecuteReader method returns a SqlDataReader object.

...

' Set an OleDbDataReader to the command's resultsDim objDataReader as OleDbDataReaderobjDataReader = objCommand.ExecuteReader(CommandBehavior.CloseConnection)

...

The code above calls the ExecuteReader method of the OleDbCommand class, passing in CommandBehavior.CloseConnection. Note that this is an optional parameter; including it specifies that when the data reader is closed the connection the data reader is tied to (objConnection) should also be closed.

Page 38: Paging in ASP

Paging in ASP.NET Page 38 of 38

Paging in ASP.NET Page 38 of 38

Need to Display the Data?At this point, if you want to display the database query results, you should use data binding with an appropriate ASP.NET data Web control. While in classic ASP you would iterate through the Recordset, systemmatically building up the HTML to display the results, with ASP.NET this technique is highly discouraged. The following example shows how to bind a data reader to a DataGrid Web control. (Including the needed code for the DataGrid, the total number of lines needed is just three.) Note that to perform data binding we just set the DataSource property of the DataGrid to our OleDbDataReader and then call the DataBind() method. The complete code can be seen below.

<%@ Import Namespace="System.Data" %><%@ Import Namespace="System.Data.OleDb" %><script language="VB" runat="server">

sub Page_Load(sender as Object, e as EventArgs)'Create a connection stringDim connString as StringconnString = "PROVIDER=Microsoft.Jet.OLEDB.4.0;DATA SOURCE=" & _

"C:\Inetpub\wwwrot\Projects.mdb;"

'Open a connectionDim objConnection as OleDbConnectionobjConnection = New OleDbConnection(connString)objConnection.Open()

'Specify the SQL stringDim strSQL as String = "SELECT * FROM Department"

'Create a command objectDim objCommand as OleDbCommandobjCommand = New OleDbCommand(strSQL, objConnection)

'Get a datareaderDim objDataReader as OleDbDataReaderobjDataReader = objCommand.ExecuteReader(CommandBehavior.CloseConnection)

'Do the DataBindingdgResults.DataSource = objDataReaderdgResults.DataBind()

'Close the datareader/db connectionobjDataReader.Close()

end sub</script>

<asp:DataGrid id="dgResults" runat="server" />

Iterating through the Data Reader ResultsDisplaying the results of a database query using ADO.NET through an ASP.NET Web page is simple: just use data binding. However, if you need to iterate through the results (for whatever reason) data binding can't help. With classic ADO, you would loop until the Recordset's EOF property was True (thereby signalling the end of the Recordset had been reached), doing a MoveNext at the end of each loop iteration.

With ADO.NET the protocol is similar, although the syntax differs slightly. The data reader class contains a Read method that reads in the next record from the data store. If there are no more records, Read returns False; otherwise, if there are records, it returns True. If there are records available, calling Read loads the columns of the record into a collection that can then be accessed the same way as you always have with classic ADO's Recordset object: DataReaderObject("ColumnName"). The following code shows how to iterate through the contents of our data reader. Note that the Read method in the While loop functions like both the EOF and MoveNext methods of the classic ADO Recordset object. It loads the next record from the data

Page 39: Paging in ASP

Paging in ASP.NET Page 39 of 39

Paging in ASP.NET Page 39 of 39

store and returns False if there are no more records; hence, we do not need any .MoveNext-type method in the body of the While loop.

...

While objDataReader.Read()do something' e.g.: str = objDataReader("DepartmentName")

End While

...

ConclusionIn this article we examined how to perform a rather common task, iterating through the results of a database query, using ADO.NET. We also looked at how to display the results of a database query through an ASP.NET Web page, which does not require looping through the contents of a database query. Before connecting to a database with ADO.NET you must first decide what data provider to use, because that decision dictates what set of classes we'll use. Next, you need to create the appropriate command object, specifying the SQL query or stored procedure you wish to run. Finally, you use the appropriate command object's ExecuteReader method to get back an appropriate data reader object. Once you have this data reader object you can use it in data binding (to display the database contents) or iterate through its contents. (To learn more about ASP.NET be sure to check out: ASPNET.4GuysFromRolla.com!)

Happy Programming!

Creating Databound DropDown Lists in ASP.NET

IntroductionA very common, everyday task for Web developers is to create an HTML listbox populated with the values from a database table. Such a requirement is most common when attempting to display subsets of information that is spread across multiple, related database tables. For example, imagine that you were working on a database system that maintained information about each employee of the company. You might have the following database tables (among many others, of course):

tblDepartmentDepartmentID int, Primary KeyName varchar(200)

...

tblEmployeeEmployeeID int, Primary KeyDepartmentID int, Foreign KeyName varchar(100)SSN char(9)Salary Currency

...

The tblDepartment table would contain a row for each department in the company (i.e., Sales, Marketing, Executive, IT, etc.); the tblEmployee table would contain a row for each employee in the company with the DepartmentID column representing what department each particular employee works for. (Note that this database design is limited in the sense that each employee can work for, at most, only one department.)

On a report-type screen, your boss may want to be able to list all of the employees from a particular department. One way to do this is create an HTML listbox (<SELECT NAME="listboxName"> ... </SELECT>) and hard-code in the name and DepartmentID of each of the company's departments, like:

Page 40: Paging in ASP

Paging in ASP.NET Page 40 of 40

Paging in ASP.NET Page 40 of 40

<select name="lstDepartment" size="1"><option value="1">Marketing</option><option value="2">Sales</option><option value="4">Executive</option><option value="5">IT</option>...

</select>

Note that you'd need to create an OPTION tag for each department, entering the department's respective DepartmentID in the VALUE parameter. Of course this hard-coding approach would be short-sighted, since if new departments are added or old ones retired, you (or some other unlucky soul) will need to go into the ASP page and edit the HTML by hand.

Ideally, the HTML listbox would populate itself automagically with the results from the database's tblDepartment table. In this article we will look at how to accomplish this common task using ASP.NET. We'll also (briefly) look at how this would be accomplished in classic ASP, so that we can note the awesome reduction in the code needed for ASP.NET! (For information on creating a list of databound radio buttons, be sure to read: Creating a DataBound List of Radio Buttons in ASP.NET!)

Databinding an HTML Listbox in Classic ASPThere are a number of articles and FAQs that have been written that illustrate how to populate an HTML listbox with the contents from a database. For a quick read, check out Populating a Drop-Down List the Easy Way using SQL and VBScript and the FAQ: How do I populate an HTML dropdown box from the results of a database table?

While these approaches don't take an extraordinary amount of time to code, they do shamelessly mix the HTML script and ASP code, which makes the page harder to read and understand. Also, extra ugly code needs to be applied to have the item the user selected stay selected when the form is submitted.

With ASP.NET, as we will see shortly, only a few lines of code are needed and the HTML and code sections are kept separate. Additionally, ASP.NET automatically persists the user's selection across postbacks.

Databinding an HTML Listbox with ASP.NETBinding a database table to a listbox with ASP.NET involves three steps:

1. Creating the listbox Web control in the HTML section.2. Retrieving the data from the proper database table.3. Databinding the resulting database data to the listbox Web control.

Let's look at each of these three steps, one at a time. First, creating the listbox Web control is simple enough, all it takes is one line of code. Note that for our databinding purposes you should specify the column names of the text and value fields for the listbox. The text field, specified via the DataTextField property, is the database table's column value that will appear in the Web page. The value field, specified via the DataValueField property, is the database table's column value that will appear in the OPTION tag's VALUE attribute. So, for this example, we want to set DataTextField to Name (the name of the department: Sales, Marketing, etc.) and DataValueField to DepartmentID (the unique identifier of the department).

<script language="VB" runat="server">Sub Page_Load(sender as Object, e as EventArgs)

'TODO: Get database data and databind it to the listbox Web controlEnd Sub</script>

<html><body>

<b>Departments</b>:<asp:listbox id="lstDepartments" runat="server" Rows="1"

DataTextField="Name" DataValueField="DepartmentID" /></body></html>

Page 41: Paging in ASP

Paging in ASP.NET Page 41 of 41

Paging in ASP.NET Page 41 of 41

Now that we have the listbox Web control done (along with the proper DataTextField and DataValueField settings), we need to move onto step 2: grabbing the database results. We need to grab a snapshot of the tblDepartment table, specifically of the Name and DepartmentID columns. To accomplish this, we will use the OleDb set of classes and an OleDbDataReaderobject. This code will appear in our Page_Load event handler:

<% @Import Namespace="System.Data" %><% @Import Namespace="System.Data.OleDb" %><script language="VB" runat="server">Sub Page_Load(sender as Object, e as EventArgs)

'Create a connectionConst sConnStr as String="Provider=Microsoft.Jet.OLEDB.4.0;" & _

"Data Source=C:\Databases\GrocerToGo.mdb"Dim objConn as New OleDbConnection(sConnStr)

'You must open the connection before populating the DataReaderobjConn.Open()

'Create a command object for the queryConst strSQL as String = "SELECT DepartmentID, Name " & _

"FROM tblDepartment"Dim objCmd as New OleDbCommand(strSQL, objConn)

'Create/Populate the DataReaderDim objDR as OleDbDataReaderobjDR = objCmd.ExecuteReader()

'TODO: Databind the DataReader to the listbox Web controlEnd Sub</script>

<html><body>

<b>Departments</b>:<asp:listbox id="lstDepartments" runat="server" Rows="1"

DataTextField="Name" DataValueField="DepartmentID" /></body></html>

Now that we've read the data into an OleDbDataReader all that's left to do is databind the OleDbDataReader to the listbox Web control. This is painfully easy, requiring just two lines of code - setting the listbox Web control's DataSource property to the OleDbDataReader object and then calling the DataBind() method.

<% @Import Namespace="System.Data" %><% @Import Namespace="System.Data.OleDb" %><script language="VB" runat="server">Sub Page_Load(sender as Object, e as EventArgs)

' ... STEP 2 CODE REMOVED FOR BREVITY ...

'Databind the DataReader to the listbox Web controllstDepartments.DataSource = objDRlstDepartments.DataBind()

End Sub</script>

Page 42: Paging in ASP

Paging in ASP.NET Page 42 of 42

Paging in ASP.NET Page 42 of 42

<html><body>

<b>Departments</b>:<asp:listbox id="lstDepartments" runat="server" Rows="1"

DataTextField="Name" DataValueField="DepartmentID" /></body></html>

You can view a live demo of this technique. The live demo also illustrates how to add a default option to the listbox (something like a "-- Choose an Option --" type entry in the listbox. For more information, be sure to read: Adding a Default ListItem in a Databound Listbox in ASP.NET.

Some subtle improvements could be made to the above code. Alert reader Alex L. offerred the following suggestions:

When calling the OleDbCommand object's ExecuteReader method, pass in the parameter CommandBehavior.CloseConnection, which will automatically close the database connection as soon as the DataReader is closed.

Set the listbox's DataSource property directly to the result of the ExecuteReader method, avoiding an unneeded assignment. I.e.,

'Assign the DataSource property directly to the result'of the ExecuteReader method.lstDepartments.DataSource = objCmd.ExecuteReader(CommandBehavior.CloseConnection)lstDepartments.DataBind()

Thanks, Alex!

ConclusionCreating a databound listbox using ASP.NET is, in my opinion, is both easier and produces more readable code. Note that in this article's example that we've only shown how to databind a listbox Web control. Obviously, a more practical task one would need to complete is to be able to submit the form once a listbox item was selected, and then the Web page could reload and produce output based upon that listbox selection. I'll save that task, though, for a future article!

Happy Programming!

Adding a Default ListItem in a Databound Listbox in ASP.NET

IntroductionIn a previous article I showed how to use databinding to automagically populate an ASP.NET's listbox Web control with just a few lines of code. (If you've not already read the past article, be sure to do so now.) I've received a couple of feedback emailsfrom folks since publishing the article asking how to add a default option to the listbox. That is, they want to add a listitem to the listbox, like: -- Please Choose an Option Below--. In this brief article we'll tackle this topic and more!

Adding a ListItem to a Listbox Web ControlA listbox Web control contains zero to many listitem objects, each listitem representing an option tag in the select form field that the listbox Web control ultimately renders. The listbox Web control provides an Items property, which is an instance of the ListItemsCollection class. This class contains methods for adding listitems to the collection, which in turn add new items to the listbox.

Therefore, to add a default option to a listbox, we must use one of these methods. But first, we should perform our databinding. Databinding a DataReader (or DataSet) to the listbox populates the listbox with the database information we specify in the listbox tag and the SQL query. (This topic was discussed at length in the past article, Creating Databound DropDown Lists in ASP.NET.) Once we successfully databind our database results to the listbox, we want to go ahead and smack a new listitem onto the list.

Page 43: Paging in ASP

Paging in ASP.NET Page 43 of 43

Paging in ASP.NET Page 43 of 43

So how to we add a new listitem to the listbox's Items property? We could use the Add method of the ListItemCollection class, but this would stick the default option at the end of the listbox's list of items. Fortunately the ListItemCollection class contains an Insert method, which allows us to specify where in the ListItemCollection, exactly, we want to insert a new item.

The syntax for adding a new listitem to the ListItemCollection class via the Insert method is as follows:

objListBox.Items.Insert(position, item)

where position is an integer specifying where in the ListItemCollection to add the listitem and item is a ListItem class instance. Since the ListItemCollection is zero-based (as are all collections and arrays in the .NET Framework classes), we want to specify position as 0; for item we can create a new ListItem class instance with the value, --Select an Option-- (or whatever we so please). When using the Insert method be sure to do it after you've already done the databinding.

Below you will see the complete code for creating a databound listbox with a default option. There is also a live demo that you should be sure to check out.

<% @Import Namespace="System.Data" %><% @Import Namespace="System.Data.SqlClient" %><script language="vb" runat="server">

Sub Page_Load(sender as Object, e as EventArgs)BindData()

End Sub

Sub BindData()'1. Create a connectionDim myConnection as New SqlConnection(ConfigurationSettings.AppSettings("connectionString"))

'2. Create the command object, passing in the SQL stringConst strSQL as String = "SELECT FAQID, LEFT(Description, 40) + '...' AS Description " & _

"FROM tblFAQ ORDER BY Description"Dim myCommand as New SqlCommand(strSQL, myConnection)

'3. Create the DataReadermyConnection.Open()

Dim objDR as SqlDataReaderobjDR = myCommand.ExecuteReader(CommandBehavior.CloseConnection)

'Databind the DataReader to the listbox Web controllstFAQs.DataSource = objDRlstFAQs.DataBind()

'Add a new listitem to the beginning of the listitemcollectionlstFAQs.Items.Insert(0, new ListItem("-- Choose a FAQ --"))

End Sub</script>

<b>FAQs at ASPFAQs.com</b>:<asp:listbox id="lstFAQs" runat="server" Rows="1"

DataTextField="Description" DataValueField="FAQID" />

[View the live demo!] That's all there is to it!

Page 44: Paging in ASP

Paging in ASP.NET Page 44 of 44

Paging in ASP.NET Page 44 of 44

ConclusionThis article showed how to add a default option to a databound listbox in ASP.NET, building upon a previous 4Guys article: Creating Databound DropDown Lists in ASP.NET. All-in-all it's pretty simple to do, just add one line of code after you've databound your listbox.

If you are using a default option like this, you might also want to use the RequiredFieldValidator to ensure that the users are notchoosing the default option; rather, that they are choosing one of the options that was automatically populated when the listbox was databound. For more information on ASP.NET's validation controls, be sure to read: Form Validation with ASP.NET - It Doesn't Get Any Easier!

Happy Programming!

Creating a DataBound List of Radio Buttons

IntroductionA common task Web developers face is creating a form element (such as a series of checkboxes, radio buttons, or a listbox) whose members are populated by the contents of a database table. This is quite possible (and quite easy) to accomplish with ASP.NET In fact, in a previous article (Creating Databound DropDown Lists in ASP.NET), we examined how to create a databound listbox. In this article, we'll examine how to create a series of radio buttons whose values are automagically populated from a database table! We'll also look at how to respond to the user selecting a particular option and showing more detailed information based upon the radio button selected.

The ImpetusOn 4Guys there is a Sample On-Line Chapters page, which lists a number of books that have free, on-line sample chapters. All of this information is stored in a database that contains two tables: tblPublishers and tblBooks. The tblPublishers contains a row for each publisher that has a book that appears in the sample chapters listing, while the tblBooks table contains a row for each sample book. Note that the tblBooks table contains a PublisherID column which serves as a foreign key to the tblPublishers table, tying a particular publisher to a particular book.

Wouldn't it be nice to generate a list of radio buttons based upon the publishers in the tblPublishers table? From this list, the user could select a particular publisher, at which point, the user would be shown a listing of books published by that particular publisher. In creating the radio button list, we could hard code the list of publishers, but that would be subject to change any time we added or removed publishers from the tblPublishers table. Ideally, the radio button list would be dynamically generated based upon the values in the tblPublishers table.

If you'd like to see what the end result will look like, check out the live demo. Pretty nice looking, eh? Realize that the radio button list was created dynamically using databind (essentially three lines of code in total). And this entire page, from start to finish, took me about four minutes to write. (I type fast. :-))

Creating the Databound Radio Button ListCreating a databound series of radio buttons is painfully simply with ASP.NET. We only really need to do three things:

1. Create the asp:radiobuttonlist Web control, specifying the database columns we want to bind to the radio buttons' displayed text values and the hidden values that are passed along when the form is submitted.

2. Write the code to populate a DataReader (or DataSet) with the applicable database information.3. Databind the DataReader (or DataSet) to the asp:radiobuttonlist Web control

That's it! So, let's examine each of these three steps. First, create the needed Web control in the HTML section of your ASP.NET Web page.

<html><body>

<form runat="server"><b>Choose a Publisher's Books to View</b><br><asp:radiobuttonlist id="radlstPubs" runat="server"

DataValueField="PublisherID" DataTextField="Name" />

Page 45: Paging in ASP

Paging in ASP.NET Page 45 of 45

Paging in ASP.NET Page 45 of 45

</form></body></html>

The two most important things to notice is that we set the asp:radiobuttonlist Web control's DataValueField and DataTextField properties. Both properties should map to a column name in the database table that we wish to bind the radio button list to. The DataTextField property are the names that will be visually displayed next to each radio button; the DataValueField are the hidden values that are associated with each radio button. Also note that the radio button list was placed within a WebForm (the form tag with a runat="server" attribute).

Now that we've tackled our first step, let's look at performing our remaining two steps: reading the database table tblPublishersinto a DataReader and binding the populated DataReader to the radio button list!

<% @Import Namespace="System.Data" %><% @Import Namespace="System.Data.SqlClient" %><script language="vb" runat="server">

Sub Page_Load(sender as Object, e as EventArgs)If Not Page.IsPostBack then

'*** STEP 1: Populate a DataReader ***'Create a connectionDim myConnection as New _

SqlConnection(ConfigurationSettings.AppSettings("connectionString"))

'Create the command object, passing in the SQL stringConst strSQL as String = "SELECT PublisherID, Name FROM tblPublishers"Dim myCommand as New SqlCommand(strSQL, myConnection)

'Open the connection and set the DataSource to the myConnection.Open()

'Populate the datareaderDim myDataReader as SqlDataReadermyDataReader = myCommand.ExecuteReader(CommandBehavior.CloseConnection)

'*** STEP 2: Bind the DataReader to radlstPubs ***radlstPubs.DataSource = myDataReaderradlstPubs.DataBind()

End If End Sub

...</script>

In the above Page_Load event handler we accomplish our two steps: populating a DataReader (in this case a SqlDataReader) with the contents of the tblPublishers table; and binding the resulting DataReader to our radio button list, radlstPubs. Note that we only wish to run this binding code on the first visit to the page (more on why later), hence we only perform the databinding if Page.IsPostBack is False, meaning that the page has not yet been posted back.

Now that we've looked at how to create a databound radio button list, let's turn our attention to responding to the user's selection of a particular radio button. In Part 2 we'll examine this aspect and the remainder of our application!

Responding to a User's Radio Button ChoiceWhat we'd like to accomplish is to allow the user to choose a radio button option, click a button, and have the chosen publisher's books appear. So, to accomplish this, we'll need to first add a button control after the radio button list control in our HTML section:

Page 46: Paging in ASP

Paging in ASP.NET Page 46 of 46

Paging in ASP.NET Page 46 of 46

<html><body>

<form runat="server"><b>Choose a Publisher's Books to View</b><br><asp:radiobuttonlist id="radlstPubs" runat="server"

DataValueField="PublisherID" DataTextField="Name" /><br>

<asp:button id="btnViewBooks" runat="server"Text="View Published Books" OnClick="btnViewBooks_Click" />

</form></body></html>

Note that the btnViewBooks button control's OnClick event handler was wired up to the btnViewBooks_Click event handler. This is an event handler that we'll create in our server-side script block. This event handler will need to ascertain what radio button option was selected and construct a SQL query based upon that information. (This SQL query will snag all of the books out of the tblBooks table whose PublisherID matches the ID of the selected publisher from the radio button list.) Furthermore, a DataReader (or DataSet) should be populated with the results of this custom SQL query, and that DataReader should be bound to a DataGrid Web control in order to display the books for the publisher.

So, we'll need one more Web control, a DataGrid, which will be used to display the books published by the selected publisher. The DataGrid Web control I used can be seen below, it should come after the button control (which has been removed for brevity). Feel free to make any stylistic changes to the DataGrid:

<html><body>

<form runat="server">... CONTENT REMOVED FOR BREVITY ...

<p align="center"><asp:label id="lblTitle" runat="server" Font-Name="Verdana"

Font-Size="Large" Font-Bold="True" />

<asp:datagrid id="dgBooks" runat="server"Font-Name="Verdana" Font-Size="Smaller"HeaderStyle-BackColor="Purple" HeaderStyle-ForeColor="White"HeaderStyle-Font-Size="Small" HeaderStyle-Font-Bold="True"AutoGenerateColumns="False">

<Columns>

<asp:BoundColumn HeaderText="Book Title"HeaderStyle-HorizontalAlign="Center"DataField="Title" />

<asp:BoundColumn HeaderText="Synopsis"HeaderStyle-HorizontalAlign="Center"DataField="Description" />

</Columns></asp:datagrid></p>

</form></body></html>

Page 47: Paging in ASP

Paging in ASP.NET Page 47 of 47

Paging in ASP.NET Page 47 of 47

Notice that a label Web control, lblTitle, was also added. This will be used to display a nice-looking heading above the DataGrid Web control.

All that's left to tackle is writing the btnViewBooks_Click event handler, which will fire whenever the user clicks the btnViewBooks button control. Remember that this event handler needs to populate a DataReader with a list of books that were published by the selected publisher.

... PAGE_LOAD EVENT HANDLER REMOVED FOR BREVITY ...

Sub btnViewBooks_Click(sender as Object, e as EventArgs)'If the user has not selected an item from the radiobuttonlist,'do nothingIf radlstPubs.SelectedItem Is Nothing then Exit Sub

'Create a connectionDim myConnection as New _

SqlConnection(ConfigurationSettings.AppSettings("connectionString"))

'Create the command object, passing in the SQL stringDim strSQL as String = "SELECT Title, Description FROM tblBooks " & _

" WHERE PublisherID = " & radlstPubs.SelectedItem.Value & _" ORDER BY Title"

Dim myCommand as New SqlCommand(strSQL, myConnection)

myConnection.Open()

dgBooks.DataSource = myCommand.ExecuteReader(CommandBehavior.CloseConnection)dgBooks.DataBind()

lblTitle.Text = "Books Published by " & radlstPubs.SelectedItem.TextEnd Sub

</script>

[View a live demo!]

In the btnViewBooks_Click event handler we begin by making sure that a radio button option was selected. If it wasn't, we simple exit the event handler. Next, we connect to the database and populate a SqlDataReader with the books that were published by the selected publisher. Note that we are creating a custom SQL string, returning records where the PublisherIDcolumn in the tblBooks table is equal to the selected radio button's Value property (which, if you recall, was the PublisherID of the tblPublishers table (the primary key)).

Once we have our populated SqlDataReader, we can simply bind it to the dgBooks DataGrid Web control. This, as you can see, takes only two lines of code! Finally, we set the lblTitle label control to a descriptive title. And... we're done! That's it!

ConclusionAll in all, we created a very useful and visually appealing data-driven Web application in a matter of minutes! Amazing. Of course, radio button lists are not the only form element that can be data bound. Listboxes (see Creating Databound DropDown Lists in ASP.NET) and checkbox lists can also be data bound in a similar fashion. Well, I hope you learned something new and are as excited about ASP.NET as I am!

Happy Programming!

XML, the DataSet, and a DataGrid

Page 48: Paging in ASP

Paging in ASP.NET Page 48 of 48

Paging in ASP.NET Page 48 of 48

IntroductionWith classic ASP, if one wanted to display XML data on an ASP Web page they had a number of options. The simplest option, in my opinion, would be to use the XML support present in ADO 2.5 to populate a Recordset with XML data, and then to display the Recordset data as you would on any other page. Another option would be to use the MSXML component to iterate through the contents of the XML document, perhaps using XSLT. Similarly, if you wanted to write a collection of data, such as database results, to an XML stream would be, again, to use the Recordset object or to use the MSXML component. (For a more information on working with XML with the MSXML component be sure to read: XML and XSL with ASP; for more information on saving a Recordset's contents as XML be sure to read: Creating a Function to Stream a Recordset to XML.)

With ASP.NET you have a number of options. If you are simply wanting to display an XML document formatted via an XSL source you can use the XML server control (<asp:xml runat="server" ... />). If you are needing to iterate through the document piecemeal and perform various operations on each node, you may want to use the classes in the System.Xmlnamespace, which provide similar functionality as the MSXML component. (For more information on using the XML server control be sure to read Using <ASP:XML runat=server> within an ASP.NET Page.)

While the previous two methods will work just fine, I've found it quite simpler to use a tool I'm much more familiar with: the DataGrid. (Be sure to read An Extensive Examination of the DataGrid for more information on this very useful and powerful Web control!) The DataGrid provides a much easier API to control the final output than an XSL document does (in my opinion) and, as we've seen in previous articles on the DataGrid, there is a wide array of useful tasks you can perform with the DataGrid, from sorting to associating custom actions with row-bound buttons.

This article examines how to easily display XML data through a DataGrid in an ASP.NET Web page. It also looks at how to easily write the contents of a database query to XML!

Reading an XML Source into a DataSetIn a previous article, Efficiently Iterating through the Results of a Database Query using ADO.NET, we examined how to use the DataReader Web control to retrieve a set of Database results. In the many parts of the Extensive Examination of the DataGridarticles, we examines how to bind a DataReader to a DataGrid. You can also bind other types of objects to the DataGrid's DataSource property to achieve the same effect. One such object is the DataSet.

Recall that the DataReader comes in various flavors - there's the SqlDataReader, for reading data from the SQL data provider, and an OleDbDataProvider, for reading data from the OleDb data provider. Furthermore, in order to read a DataReader a connection must be established to the database. That is, if you populate a DataReader and then disconnect from the Database, you can no longer read from the DataReader.

The DataSet is just about the exact opposite from the DataReader. The DataSet is provider-neutral, meaning that the same object is used regardless if you are reading the data from a SQL provider, an OleDb provider, or from an XML file. Additionally, the DataSet is a disconnected data storage object, meaning that you can populate the DataSet and then disconnect the data provider and still read from the DataSet. DataSet's are much richer objects than DataReaders; they contain detailed information about the data held within them. Not surprisingly a DataSet provides more power, but is less efficient than a DataReader.

DataSets can very easily be populated from the contents of a valid XML file. Simply call the ReadXml method. The below example shows how to read an XML file from disk and bind it to a DataGrid:

<%@ Import Namespace="System.Data" %><script runat="server">

sub Page_Load(sender as Object, e as EventArgs)Dim myDataSet as New DataSet()

myDataSet.ReadXml(Server.MapPath("books.xml"))

dgBooks.DataSource = myDataSetdgBooks.DataBind()

end sub</script>

<asp:datagrid id="dgBooks" runat="server" />

Page 49: Paging in ASP

Paging in ASP.NET Page 49 of 49

Paging in ASP.NET Page 49 of 49

[View a live demo!]

The above code is fairly straightforward. First, the Import directive is used to specify that the System.Data namespace should be imported (this is the namespace that contains the DataSet class). Next, in the Page_Load event handler a new DataSet is created and the ReadXml method is used to read the contents of the XML file books.xml. Finally, the DataGrid dgBooks's DataSource property is set equal to the DataSet and the DataGrid's DataBind() method is called.

Writing a DataSet's Contents to an XML FileYou can easily populate a DataSet with data from a SQL database or OleDb-Compliant database. I won't delve into the details on how to do this here since there already exists a plethora of articles on various sites on the topic (see here and here, for example). Given a populated DataSet it is very easy to save the contents to an XML file: simply use the WriteXml method.

The WriteXml method can be used as follows:

<%@ Import Namespace="System.Data" %><script runat="server">

sub Page_Load(sender as Object, e as EventArgs)Dim myDataSet as New DataSet()

'Populate the DataSet somehow...

'Write the DataSet's contents to an XML filemyDataSet.WriteXml(filename)

...end sub

</script>

This will write the XML data to the specified filename. (If you want to write the data and the XML data schema to the XML file use: myDataSet.WriteXml(filename, XmlWriteMode.WriteSchema).) Pretty straightforward, eh? While the ADO 2.5 Recordset allowed for this functionality, the XML produced was anything but human-readable. Fortunately the ADO.NET DataSet writes out very readable XML, as can be seen by this live demo.

Happy Programming!

DataGrids, DataSets, and XML Strings

IntroductionNot long ago, Scott Mitchell wrote an informative article on how to populate a DataSet using an XML file (see XML, the DataSet, and a DataGrid). He also showed the converse of that process, namely, writing the XML representation of a DataSet to disk using the DataSet's WriteXml method. There are many uses for such an approach and I was happy to have found Scott's wonderful article on the subject.

Unfortunately for me, however, I often find myself dealing with XML in a string format rather than XML read from a file. This article will build on what Scott explained in his article, illustrating how to accomplish the same task with an XML string.

The End GameBefore we examine how we're going to accomplish converting database output to and from an XML string, let's look at what our output should be. Ideally, when the user first visits the Web page a database access will occur grabbing information and storing it in a DataSet. The user will then have two options, to either view the data in a DataGrid or to view the XML representation of the data (the default choice being the DataGrid display). When the DataGrid display is selected, the DataSet will be bound to a DataGrid Web control, displaying the database output in a visually appealing way. (Be sure to read An Extensive Examination of the DataGrid Web Control for more information on using the DataGrid.) When the "View as XML" option is selected, the user will see the database output represented as textual XML.

Page 50: Paging in ASP

Paging in ASP.NET Page 50 of 50

Paging in ASP.NET Page 50 of 50

This approach is similar to the technique Scott Mitchell used in his XML, the DataSet, and a DataGrid article, the main difference being that instead of persisting the DataSet's XML to disk, we are simply storing it in a page-level string variable. The screenshot to the above right shows the view of the page when the user has opted to view the database's result as a DataGrid (note that this is the default view and will be the view displayed upon the first visit to the page). By clicking the buttons at the top, the user can toggle between the DataGrid view and the XML view, which is shown to the left:

Getting Set UpIn the code available for download at the end of this article, you will notice that I have two global variables declared. One is my connection string pointing to the Northwind database. You may use whichever database you wish for this example, because we won't be modifying any data – just displaying it. The other global variable will be used to store my XML string. (Note that you may want to store information like connection strings in your

Web.config file - see Specifying Configuration Settings in Web.config for more information.)

These two variables are declared in the following code block:

//-------------------------------------------------------------private string m_sConnStr = "Integrated Security=SSPI;Persist " +

"Security Info=False;Initial Catalog=Northwind;Data " +"Source=MySQLServer";

private string m_sXML = "";//-------------------------------------------------------------

Please notice, too, the Label control (lblHidden) found in the page body. This Label control will be used as a placeholder of sorts, to hold the XML string across postbacks to the page so that we only need to hit the database on the first visit to the page. Subsequent postbacks will not need to hit the database because we will be storing the XML output in this Label control. This Label control initially has its Visible property is set to False because upon the user's first visit to the page we want to show the DataGrid display as opposed to the XML display. (Using the Label control as a cross-postback placeholder is not the only possible way to persist data across page postbacks: we could have used session variables (not the best choice) or the ViewState (a better choice). Essentially this Label-placeholder trick is synonymous to using the ViewState, except this technique uses the ViewState implicitly.)

//--------------------------------------------------------------<asp:Label Runat="server" ID="lblHidden" Visible="False" />//--------------------------------------------------------------

On Page_LoadThe first thing we do when the page is loaded is grab whatever string value is currently stored in the invisible Label and stuff it into our global XML string variable.

m_sXML = lblHidden.Text.Trim();

Next, we grab some data from the database, stuff it into a DataSet object, and then bind the DataGrid to the DataSet. This will give us our initial view of the populated DataGrid.

Getting XML From A DataSetWhen the "DataSet To XML" button is clicked, a call is made to the DisplayXMLFromDataSet() procedure. The gist of that procedure is found below.

//----------------------------------------------------------------//If the XML variable is zero-length, get some XML from a DataSetif (sXML.Trim()==string.Empty)

sXML = GetXMLFromDataSet(sConnStr);

//Toggle visibility

Page 51: Paging in ASP

Paging in ASP.NET Page 51 of 51

Paging in ASP.NET Page 51 of 51

dgMain.Visible = false;lblXML.Visible = true;

//Display the XML stringlblXML.Text = FormatXMLToHTML(sXML);//----------------------------------------------------------------

The first time we submit the form we don't have any XML data, so we obtain it by calling the GetXMLFromDataSet() function. The code snippet below shows how we generate the XML string.

//----------------------------------------------------------------//Load up the XML variableDataSet ds;ds = GetCustOrderHistDataSet(sConnStr);sXML = ds.GetXml();return sXML;//----------------------------------------------------------------

Here we see that we must populate a DataSet and then call its GetXml() method in order to produce its XML representation (the GetXml() method of the DataSet was discussed in the XML, the DataSet, and a DataGrid article). Obviously, we don't want to have to go to the database and create a new DataSet object every time the form is submitted, so we only execute the GetXMLFromDataSet() function if the global XML string variable is empty.

The next thing we do is hide the DataGrid and show the Label that we will use for displaying the XML string. We then feed it the XML obtained to be displayed on the screen. Also notice that the last action performed is to set the Text property of the invisible Label to the value of the XML string we just generated. This will provide us with an XML string when the Web form is posted back, thus avoiding another trip to the database.

The main piece of code to remember in this part of the process is the call to the GetXml() method of the DataSet. In Scott's article, he showed us how to use the DataSet's WriteXML() method to save the XML to a file. For our purposes, however, we just want to persist the XML string for later use without placing it into a file.

Getting A DataSet From An XML StringA click on the "XML To DataSet" button will kick off a process opposite of that just illustrated. In other words, we will take an XML string and use it to populate a DataSet and then bind the DataSet to the DataGrid to display the data obtained from the XML string. When the button is clicked, a call is made to the DisplayGridFromXML() procedure.

//---------------------------------------------------------------DataSet ds = new DataSet();StringReader rdr;

//If the XML variable is zero-length, get some XML from a DataSetif (sXML.Trim()==string.Empty)

sXML = GetXMLFromDataSet(sConnStr);

//Create a StringReader to hold the XMLrdr = new StringReader(sXML);

//Stuff XML into a DataSetds.ReadXml(rdr);

//Toggle visibilitydgMain.Visible = true;lblXML.Visible = false;

Page 52: Paging in ASP

Paging in ASP.NET Page 52 of 52

Paging in ASP.NET Page 52 of 52

//Bind DataGrid to show what was in the XML stringdgMain.DataSource = ds;dgMain.DataBind();//---------------------------------------------------------------

The procedure is sent the XML string that we had stored in the hidden Label object. The variable is sent by-reference, by the way, so any changes made to it within the procedure will also be applied to it elsewhere, but that, too, is a topic for another article.

In looking at the code snippet above, we see that the XML string is tested to see if we need to make a trip to the database or not. In this case, we already have some XML, so we don't need to query the database. Next, we create a StringReader object and fill it with our XML string. In Scott's article, we learned that the ReadXml() method of the DataSet object accepts a string as a parameter that indicates a path from which to read an XML file. In our case, though, we want to use a string of XML, not an XML file, so we obviously have no path to feed to the ReadXml() method. Well, it just so happens that ReadXml() is an overloaded method and can accept a TextReader object (as well as quite a few other objects) instead of a path string.

The TextReader class is an abstract class, meaning, for our purposes, that it can't be used directly, but must be inherited from. Abstract classes comprise yet another topic for another article, but suffice it to say that .NET provides a handy object that is already inherited from the TextReader class: the StringReader. Since it is a "descendant" of a TextReader, it can be used as a parameter to send to the ReadXml() method. That makes it very useful to us in this case, because all we have to do is feed the StringReader our XML string and then send it into the ReadXml() method to populate the DataSet.

Once we've filled the DataSet with the XML data, we hide the XML Label and show the DataGrid and bind the DataSet to it and display the data. And with that, we're done!

Conclusion.NET has provided us with many useful tools to assist us in working with XML data. As Scott Mitchell has shown us in a previous article, we can use a combination of DataSets and DataGrids to present data found in XML files. In the example illustrated above, we see that we are not limited to the presentation of XML files alone, but we can also use data found in XML strings. .NET is indeed a veritable cornucopia of tools and possibilities.

Happy Programming!

Adding a DropDownList to an Editable DataGrid

IntroductionMore and more developers are finding out just how useful DataGrids are, but there is so much involved in this unique and powerful tool, that hardly anybody knows exactly what it's capable of. For instance, most people know that you can edit items in-place with a DataGrid, but few know that you can select the type of control that you want to use for the editing. Instead of using the default TextBox that it gives you, you can choose to use a DropDownList, RadioButtonList or any other bindable control!

Please note, this article assumes basic familiarity with DataGrids, and doesn't delve into details about EventHandling, DB functionality, etc. There are plenty of articles out there written by people much smarter than I am.

The Basic DataGridIn the basic data grid you can specify to add BoundColumns, how they look, and what goes in them:

<asp:datagrid id="dgExample" runat="Server" autogeneratecolumns="False"oncancelcommand="DataCancel" onupdatecommand="DataUpdate"datakeyfield="tempField">

<Columns><asp:boundcolumn headertext="Temp Column" datafield="tempField" />

</Columns></asp:datagrid>

Page 53: Paging in ASP

Paging in ASP.NET Page 53 of 53

Paging in ASP.NET Page 53 of 53

Then when a user goes to edit, the system will supply them with a textbox for editing, with no help needed from you. For most cases, this is all you need. But what about the cases where you want them to be able to select from a DropDownList when they're editing?

For this, we use Templates. Instead of the BoundColumn in our previous code sample, we'll use the TemplateColumn to define exactly how to handle the data. In the code below, you'll notice that the TemplateColumn is divided into two parts: an ItemTemplate, and an EditItemTemplate. This allows us to define things for both the display mode, as well as when it's in edit mode.

<asp:datagrid id="dgExample" runat="Server" autogeneratecolumns="False"oncancelcommand="DataCancel" onupdatecommand="DataUpdate"datakeyfield="tempField">

<Columns><asp:TemplateColumn headertext="Status">

<ItemTemplate><asp:Label id="lblLabelName" runat="server"

Text='<%# Container.DataItem("tempField") %>'></asp:Label>

</ItemTemplate>

<EditItemTemplate><asp:DropDownList id=cmbStatuses runat="server" datavaluefield="StatusCode"

datatextfield="Name" DataSource="<%#DropDownDataView%>"></asp:DropDownList>

</EditItemTemplate></asp:TemplateColumn>

<asp:editcommandcolumn Edittext="Edit" canceltext="Cancel" updatetext="OK" /></Columns></asp:datagrid>

For the display mode, we're just using a label, and populating it much the same way as we did with the BoundColumnpreviously. But in the EditItemTemplate, we're defining a DropDownList! There are three key elements to pay attention to here.

1. DataTextField - This is the DB field that will populate the displayed portion of your list.2. DataValueField - This is the DB field that will populate the values for each of the dropdown items.3. DataSource - This is where the control will be getting its data from. Pay attention to the name, you'll be seeing it again.

We've examined the Web control code needed, but we've still yet to look at the ASP.NET source code. In Part 2 we'll examine the Code-behind page and its source code!

The Code-BehindNow let's take a look at what's going on behind the WebForm. In the following code snippet, we have a DataView object that is global to the class, as well as a method, PopulateDropDownList(), which will fill that DataView.

Public Class MyClassInherits System.Web.UI.Page

...

...

...

Protected TempDataView As DataView = New DataView()

Page 54: Paging in ASP

Paging in ASP.NET Page 54 of 54

Paging in ASP.NET Page 54 of 54

Protected Sub PopulateDropDownList()Dim dataAdapter As SqlDataAdapter ' sqldatasetcommandDim DS As New DataSet()Dim NewConnection As SQLConnection = _

New SQLConnection(MainDB.ConnectionString)

dataAdapter = New SqlDataAdapter("sp_getinfo", NewConnection)dataAdapter.SelectCommand.CommandType = _

CommandType.StoredProcedure

dataAdapter.Fill(DS, "TempInfo") 'filldataset

TempDataView = DS.Tables("TempInfo")End Sub

Protected Sub DataEdit(ByVal Sender As Object, _ByVal E As DataGridCommandEventArgs)

PopulateDropDownList()dgExample.EditItemIndex = E.Item.ItemIndex

End SubEnd Class

Here, we're populating the DataSet as you normally would, except instead of binding the DataView to anything, we're passing it off to our TempDataView object. Remember that name? That's the same name we were using in the DataSource for the DropDownList in our second code sample. Now when you handle the Edit event, you simply call PopulateDropDownList, then set the index as you normally would. And Voila! You now have a DropDownList in your DataGrid!

Okay, Great. But How Do I Retrieve The Selected Value?This is where it gets kind of tricky. Normally if you had a DropDownList, you'd just reference it in the Code-behind and pull the SelectedItem.Value from it. But here you can't do it! In fact, you can't even access that control directly at all. Why not? It's defined as a control, it has a name, I referenced it in the code-behind, what's the problem? The problem is that it's a dynamic control. It doesn't exist, except for the exact instances when somebody is editing something. For those of you who are used to dealing with retrieving info from textboxes in DataGrids, it's the exact same process. For those of you who aren't, we have the following code sample:

Public Class MyClassInherits System.Web.UI.Page

...

...

...

Protected Sub DataUpdate(ByVal Sender As Object, _ByVal E As DataGridCommandEventArgs)

Dim TempList As DropDownListDim TempValue As String

TempList = E.Item.FindControl("cmbStatuses")TempValue = TempList.SelectedItem.Value

' Save TempValue to DBEnd Sub

End Class

Page 55: Paging in ASP

Paging in ASP.NET Page 55 of 55

Paging in ASP.NET Page 55 of 55

In order to retrieve the value, we have to use the FindControl method inside of the EventArgs that are passed into DataUpdate. We take the Control that FindControl returns, and then cast it to a DropDownList. After that, we can access everything as we normally would.

And there you go! You now have a fully functional DropDownList in your DataGrid! But remember, this is only one of the fun features in DataGrids. Go out and explore! Try and see exactly what you can get out of them. You'll be pleasantly surprised.

Happy Programming!

Creating Collapsible Detail Regions in a Repeater

IntroductionThere are many ways to make a Web page unusable, from cluttered content to poor page layout to non-contrasting colors. One of the simplest ways to quickly render a page unusable is to present "data overload." A page that suffers from data overload is one that displays way too much information on a single page. For example, one that shows dozens of database records, with each record having dozens of fields.

There are many ways to help alleviate data overload, such as enabling paging of the data, or displaying only a subset of the database fields and providing a "Details" link for each record that, when clicked, displays the multitude of fields for the specific record. Another approach - which is surprisingly easy and, in my opinion, looks very professional - is to have data displayed in a collapsible format. By this I mean showing only a short caption for each database record, and hiding the details. To display the details, the user can click the caption, which will cause the details to be automatically displayed. All of this can be accomplished with a bit of clever DHTML and client-side JavaScript. (To get a sneak peak at the finished product, check out this live demo.)

In this article we'll examine how to display data in collapsible manner using a Repeater. By the end of this article you'll have the complete code and concepts for creating such a slick user interface yourself, and helping prevent against data overload in your Web applications.

Showing and Hiding Content Through Client-Side ActionsFor several years now - since the 4.x browser versions - it is quite possible (and relatively easy) - to have contents on a Web page be hidden or displayed based on a client-side action. For example, the contents of a <div> tag can be dynamically hidden in response to clicking a button, or the contents of a paragraph (that is, the contents inside the <p> tag), could be displayed when mousing over some other content region.

The "trick" behind showing and displaying data in a Web page is setting the element's display and visibility style settings. The following HTML snippet shows how to hide and display content by toggling these style settings for a particular HTML element with the click of a button; you can test out the HTML in the interactive demo following the code snippet.

<script language="JavaScript">function showHideContent(id, show){

var elem = document.getElementById(id);if (elem) {

if (show) {

elem.style.display = 'block';elem.style.visibility = 'visible';

} else{

Page 56: Paging in ASP

Paging in ASP.NET Page 56 of 56

Paging in ASP.NET Page 56 of 56

elem.style.display = 'none';elem.style.visibility = 'hidden';

}}

} </script>

<div id="someRegion">This text will be displayed or hidden when clicking the appropriate button below...

</div>

<input type="button" value = "Hide Content"onclick="showHideContent('someRegion', false);">

<input type="button" value = "Show Content"onclick="showHideContent('someRegion', true);">

Interactive DemoThis text will be displayed or hidden when clicking the appropriate button below...

As the above interactive demo illustrates, with a bit of client-side JavaScript you can change an HTML element's display and visiblity style settings, thereby dynamically showing or hiding the element. This is all done on the client-side, and does not require a postback. Note that to modify the style for an element we use the document.getElementById(id) method to retrieve the element by its ID; next, we set its appropriate properties. A thorough discussion on DHTML is far beyond the scope of this article, especially since there are plenty of great resources on this topic all around the Web.

Displaying Details in a Collapsible InterfaceWith a little effort this concept can be applied to the data displayed in an ASP.NET Web page. If you have a page that needs to show a lot of fields per record, the page can quickly become unwieldy. For example, imagine that we wanted to display information about the FAQs at ASPFAQs.com. Each FAQ is stored as a record in the tblFAQs table, containing information such as the FAQ, the answer, the number of times the FAQ has been viewed, who submitted the FAQ, and other pertinent information. We may want to display this information on an ASP.NET Web page using a Repeater. To accomplish this we could use the following Repeater syntax:

<asp:Repeater id="rptFAQs" runat="server"><ItemTemplate>

<h2><%# DataBinder.Eval(Container.DataItem, "Description") %></h2><br /><b>Submitted By:</b> <%# DataBinder.Eval(Container.DataItem, "SubmittedByName") %><br /><b>Views:</b> <%# DataBinder.Eval(Container.DataItem, "ViewCount", "{0:d}") %><br /><b>FAQ:</b><br /><%# DataBinder.Eval(Container.DataItem, "Answer") %>

</ItemTemplate></asp:Repeater>

[View a Live Demo!]

As the live demo illustrates, this leads to a bit of information overload, in large part because the answer for each FAQ can be quite lengthy.

Rather than display this large amount of data for each FAQ, a more manageable user interface would be to display just the FAQ questions, making them clickable. When a user clicked such a header, it would expand to show the details for the FAQ, using client-side DHTML/JavaScript. This can be accomplished with a bit of clever markup in the Repeater's ItemTemplate.

First, we need to create two <div>s for each record: one that displays the FAQ question, and one that has the detailed information (the answer, the view count, and so on). The first <div> will always be displayed for each record and, when clicked, will toggle the display for its related details <div>. In order to accomplish this, each <div> will need to be given a unique idvalue, which will be referenced in a client-side JavaScript function that toggles the visibility. Specifically, each FAQ question <div> will have the id hindex, where index is the ItemIndex of the Repeater item. Each details <div> will have the id dindex.

Page 57: Paging in ASP

Paging in ASP.NET Page 57 of 57

Paging in ASP.NET Page 57 of 57

The following shows the ItemTemplate for our collapsible Repeater, along with some CSS classes to improve the appearance and the client-side JavaScript function to toggle the display.

<script language="JavaScript">function ToggleDisplay(id){

var elem = document.getElementById('d' + id);if (elem) {

if (elem.style.display != 'block') {

elem.style.display = 'block';elem.style.visibility = 'visible';

} else{

elem.style.display = 'none';elem.style.visibility = 'hidden';

}}

}</script>

<style>.header { font-size: larger; font-weight: bold; cursor: hand; cursor:pointer;

background-color:#cccccc; font-family: Verdana; }.details { display:none; visibility:hidden; background-color:#eeeeee;

font-family: Verdana; }</style>

<asp:Repeater id="rptFAQs" runat="server"><ItemTemplate>

<div id='h<%# DataBinder.Eval(Container, "ItemIndex") %>' class="header"onclick='ToggleDisplay(<%# DataBinder.Eval(Container, "ItemIndex") %>);'>

<%# DataBinder.Eval(Container.DataItem, "Description") %></div>

<div id='d<%# DataBinder.Eval(Container, "ItemIndex") %>' class="details"><b>Submitted By:</b> <%# DataBinder.Eval(Container.DataItem, "SubmittedByName") %><br /><b>Views:</b> <%# DataBinder.Eval(Container.DataItem, "ViewCount", "{0:d}") %><br /><b>FAQ:</b><br /><%# DataBinder.Eval(Container.DataItem, "Answer") %>

</div></ItemTemplate>

</asp:Repeater>

[View a Live Demo!]

There are a couple of things to note in the code. First, notice how the two <div> elements in each ItemTemplate are given an id that includes the RepeaterItem's index value. This index value is taken from the ItemIndex property. Additionally, the header <div> as a client-side onclick event that calls the ToggleDisplay() JavaScript function, passing in the index of the row whose header was clicked. This JavaScript function then toggles the display and visibility style settings for that row's details <div>.

To see the collapsible Repeater in action, be sure to check out the live demo. Hopefully you'll agree that the page is much cleaner and no longer suffers from information overload.

Page 58: Paging in ASP

Paging in ASP.NET Page 58 of 58

Paging in ASP.NET Page 58 of 58

Happy Programming!

Can I send emails without using CDONTS? Submitted By: Scott MitchellViews: 77133FAQ:Heavens yes! CDONTS is just one email component, there are many others. CDONTS has its limitations - it can only be used on Windows NT Server and does not provide very many options in sending emails. According to Microsoft's documentation, CDONTS was designed for sending quick, text-based emails. The nice thing about CDONTS is that it is likely already installed on your Web server and it's free!

Other email components include ASPEmail, SA-SMTPEmail, and others. For more information be sure to read: Sending Emails Without Using CDONTS. How can I determine the length of a string (how many characters are in a string)? Dim strNamestrName = "Scott"

Dim iCharCountiCharCount = Len(strName) 'iCharCount = 5Using VBScript, how can I determine if a variable is a valid date or not? Submitted By: Scott MitchellViews: 21528FAQ:Use the IsDate function. IsDate accepts a variable as a single parameter and returns a boolean value - True if the variable can successfully be converted to a date and false if it cannot.Dim dtValidDate, dtNotValiddtValidDate = "08/01/78"dtNotValid = "Scott"

If IsDate(dtValidDate) thenResponse.Write (dtValidDate & " is valid<BR>")

ElseResponse.Write (dtValidDate & " is NOT valid<BR>")

End If

If IsDate(dtNotValid) thenResponse.Write (dtNotValid & " is valid<BR>")

ElseResponse.Write (dtNotValid & " is NOT valid<BR>")

End If

The output of the above script is:08/01/78 is validScott is NOT valid

For more information be sure to read the technical docs.

How can I find the position of a certain character (or substring) within a string?

Page 59: Paging in ASP

Paging in ASP.NET Page 59 of 59

Paging in ASP.NET Page 59 of 59

Submitted By: Scott MitchellViews: 30583FAQ:Use the InStr function. The InStr function has the following definition:InStr(start, StringToSearch, StringToFind [, compare])

start specifies where to begin searching the StringToSearch looking for StringToFind. If you want to start searching at the beginning of the string, set start to 1.

The optional compare parameter specifies if a binary or textual compare should be performed. If you set compare to the system constant vbBinaryCompare (or the hardcoded value of 0) then a binary search will be performed; setting compare to vbTextCompare (or the hardcoded value of 1), a textual compare will be performed. The difference is as follows: a binary search takes case into consideration while a textual comparison does not. So, if you were seaching for the substring "foo" in the string "FooBar", a textual comparison would find "foo" while a binary comparison would not. (By default, a binary comparison is performed.)

Dim strstr = "No beer and no TV makes Homer something-something."

Response.Write InStr(1, str, "Homer") & "<BR>"Response.Write InStr(1, str, "homer") & "<BR>"Response.Write InStr(1, str, "homer", vbTextCompare) & "<BR>"Response.Write InStr(1, str, "TV") & "<BR>"Response.Write InStr(1, str, "Scott") & "<BR>"Response.Write InStr(1, str, "no", vbBinaryCompare) & "<BR>"Response.Write InStr(1, str, "no", vbTextCompare) & "<BR>"

The output from the above script is:25025160131

For more information on InStr, be sure to read the technical docs! Happy Programming!

How can I easily display a nicely formatted date variable? Submitted By: Scott MitchellViews: 40053FAQ:Use the FormatDateTime function. FormatDateTime takes two input paraemeters, a required date variable and an optional date format. There are five different date formats available:

vbGeneralDate - displays a date as a short date and a time as a long time.vbLongDate - displays a date in the long formatvbShortDate - displays a date in the short format

Page 60: Paging in ASP

Paging in ASP.NET Page 60 of 60

Paging in ASP.NET Page 60 of 60

vbLongTime - displays a time using the long formatvbShortTime - displays a time using the 24-hour format

The long and short formats are specified by the regional settings on the Web server. An example of using the FormatDateTime function can be seen below:Dim dtNowdtNow = Now()

Response.Write FormatDateTime(dtNow, vbGeneralDate) & "<BR>"Response.Write FormatDateTime(dtNow, vbLongDate) & "<BR>"Response.Write FormatDateTime(dtNow, vbShortDate) & "<BR>"Response.Write FormatDateTime(dtNow, vbLongTime) & "<BR>"Response.Write FormatDateTime(dtNow, vbShortTime) & "<BR>"

The output of the above script is:9/24/00 3:23:48 PMSunday, September 24, 20009/24/003:23:48 PM15:23

For more information be sure to read the technical docs! Also be sure to check out this FAQ: How can I display dates in a completely custom format (that might not be supported by FormatDateTime)?

How can I return the current date/time? Submitted By: Scott MitchellViews: 26056FAQ:To retrieve the current date and/or time (according to the time set on the Web server you are executing the script on), use the following functions:

Now() - returns the current date and timeDate() - returns the current dateTime() - returns the current time

For more information be sure to read the technical docs for Now(), Date(), and Time(). What is recursion? Submitted By: Scott MitchellViews: 13882FAQ:Recursion is a powerful technique that can be used when calling subroutines or functions. Simply put, recursion is repeatedly recalling the same function from within the function (or subroutine). There are many applications that can be defined recusively. For example, the factorial function (among many others) can be defined recursively.

Rather than go into the details of recursion here, I will save that for a highly recommended reading: Recursion: Why it's Cool. Be sure to read that article for a good understanding of

Page 61: Paging in ASP

Paging in ASP.NET Page 61 of 61

Paging in ASP.NET Page 61 of 61

recursion!

Source Code

<% @Import Namespace="System.Data" %><% @Import Namespace="System.Data.SqlClient" %><script language="vb" runat="server">

'Create a connectionDim myConnection as New SqlConnection(ConfigurationSettings.AppSettings("connectionString"))

Sub Page_Load(sender as Object, e as EventArgs)If Not Page.IsPostBack then

BindData()End If

End Sub

Sub BindData()'2. Create the command object, passing in the SQL stringConst strSQL as String = "SELECT * FROM tblFAQ WHERE FAQID <= 20"

'Set the datagrid's datasource to the datareader and databindDim resultsDataSet as New DataSet()Dim myDataAdapter as SqlDataAdapter = New SqlDataAdapter(strSQL, myConnection) myDataAdapter.Fill(resultsDataSet)

rptFAQs.DataSource = resultsDataSetrptFAQs.DataBind()

End Sub</script>

<script language="JavaScript">function ToggleDisplay(id){

var elem = document.getElementById('d' + id);if (elem) {

if (elem.style.display != 'block') {

elem.style.display = 'block';elem.style.visibility = 'visible';

} else{

elem.style.display = 'none';elem.style.visibility = 'hidden';

}}

}</script>

<style>

Page 62: Paging in ASP

Paging in ASP.NET Page 62 of 62

Paging in ASP.NET Page 62 of 62

.header { font-size: larger; font-weight: bold; cursor: hand; cursor:pointer;background-color:#cccccc; font-family: Verdana; }

.details { display:none; visibility:hidden; background-color:#eeeeee; font-family: Verdana; }

</style>

<asp:Repeater id="rptFAQs" runat="server"><ItemTemplate>

<div id='h<%# DataBinder.Eval(Container, "ItemIndex") %>' class="header"onclick='ToggleDisplay(<%# DataBinder.Eval(Container, "ItemIndex") %>);'>

<%# DataBinder.Eval(Container.DataItem, "Description") %></div>

<div id='d<%# DataBinder.Eval(Container, "ItemIndex") %>' class="details"><b>Submitted By:</b> <%# DataBinder.Eval(Container.DataItem, "SubmittedByName") %><br /><b>Views:</b> <%# DataBinder.Eval(Container.DataItem, "ViewCount", "{0:d}") %><br /><b>FAQ:</b><br /><%# DataBinder.Eval(Container.DataItem, "Answer") %>

</div></ItemTemplate>

</asp:Repeater>

Adding a New Record to the DataGrid

IntroductionI've done a lot of reading on ASP.NET since it was ASP+, but only recently got around to actually doing anything with it. I started putting together a page with the DataGrid and was pretty happy with the ease of coding all the functionality it provides. Displaying, editing and deleting records is a breeze. (For information on editing the records in a DataGrid, be sure to read: A Thorough Examination of the DataGrid Web Control: Part 6. To see how to delete a record from the DataGrid, be sure to read Part 8.)

When I got to the question of adding new records, though, I seemed to hit a wall. While I found plenty of information in books and on the Internet on editing, deleting and customizing the DataGrid's display, I couldn't find much at all about how to add records without creating another page. Tinkering around with the grid a little I came up with a way to easily add a "New Record" row to the bottom of the grid.

Essentially, I display a series of TextBox Web controls in the Footer of the DataGrid into which the user can enter values for a new record. The screenshot below shows an example Web page using the code I will present in this article. Note that for each record in the DataSource that's bound to the DataGrid, the details are displayed, along with an "Edit" and "Delete" button. In the Footer, there is a TextBox for each column, along with an "Add" button. A user can add a new record by simply entering values into these bottom TextBoxes and clicking the "Add" button.

Page 63: Paging in ASP

Paging in ASP.NET Page 63 of 63

Paging in ASP.NET Page 63 of 63

In this article I use the stores table from the SQL Server pubs database. For simplicity, this page only has five text fields and doesn't include any validation, so mind your data constraints if you try it out as-is. Displaying, editing, and deleting data with the DataGrid has been thoroughly covered already, so I'll limit my explanation here to adding the "New Record" row.

Creating the TextBox Web Controls in the FooterAdding a TextBox Web control in the Footer is a snap if you are using TemplateColumns. TemplateColumns, contain a number of templates. The template that is rendered for a particular row of the DataGrid depends on the type of the row being added. For example, if the row is an ordinary item row, the ItemTemplate or AlternatingItemTemplate is used, depending on whether or not the row being added is an odd or even row. If the row being added is specified as the EditItemIndex row, then the TemplateColumn's EditItemTemplate is used. Finally, if the row being rendered is the Header or Footer, the HeaderTemplate or FooterTemplate is used.

To add a TextBox Web control to the Footer, we simply need to use a TemplateColumn for each column of the DataGrid, and provide a FooterTemplate. Below you will see a DataGrid declaration that uses the FooterTemplate. Before we examine this DataGrid, though, it is important to point out that the DataGrid does not display the Footer row by default. In order to have it displayed you must set the DataGrid's ShowFooter property to True.

Also note that the DataGrid's ItemCommand event is wired up to the event handler doInsert. The DataGrid's ItemCommandevent fires whenever a command button in the DataGrid is clicked. The "Add" button is a command button, and when it is clicked we'll have to execute some code that inserts a new record into the database based on the values provided by the user. Later on in this article we'll be examining the doInsert event handler in detail.

<form id="Form1" method="post" runat="server"><asp:DataGrid id="storeGrid"

...OnItemCommand="doInsert"

ShowFooter="True"runat="server" AutoGenerateColumns="False"...>

<Columns><asp:TemplateColumn HeaderText="Store ID">

<FooterTemplate><asp:TextBox ID="add_storID" Columns="5" Runat="Server" />

</FooterTemplate><ItemTemplate>

<%# Container.DataItem("stor_id") %></ItemTemplate><EditItemTemplate>

<asp:TextBox ID="stor_id" Columns="5" Text='<%# Container.DataItem("stor_id") %>' Runat="server" />

</EditItemTemplate></asp:TemplateColumn>

... Repeat this "template" for every other databound column ...

While this code shows only one TemplateColumn, realize that this standard TemplateColumn would be used for every databound column in the DataGrid. Using the FooterTemplate, a TextBox Web control has been added to the bottom of each column.

The ItemTemplate and EditItemTemplate's are pretty straightforward. The ItemTemplate simply displays the value of the DataSource field this column is to display, while the EditItemTemplate creates a TextBox Web control for the user to edit the data.

Following these TemplateColumns needs to be an EditCommandColumn. The EditCommandColumn is a built-in DataGrid column that displays the Edit button for each row. When a particular row's Edit button is clicked, the row enters "edit mode" and the row's EditItemTemplate is used when the DataGrid is rendered. Additionally, the EditCommandColumn chnages for the row being edited so as to display an Update and Cancel button. This EditCommandColumn is added to our DataGrid like so:

Page 64: Paging in ASP

Paging in ASP.NET Page 64 of 64

Paging in ASP.NET Page 64 of 64

... The TemplateColumns from the last code example would be found here ...

<asp:EditCommandColumnButtonType="PushButton" UpdateText="Update" CancelText="Cancel"EditText="Edit" HeaderText="Edit">

</asp:EditCommandColumn>

...

The final DataGrid column is used to display a Delete button for the populated data rows and the "Add" button for the DataGrid Footer rows. Recall that this "Add" button, when clicked, will add the new record to the DataGrid. Note that the CommandNameproperty for the Button Web control in the Footer is set to Insert. We'll need to use this knowledge in the DataGrid's ItemCommand event handler, which will execute after the user clicks the "Add" button.

... The TemplateColumns from the two code examples ago would be found here ...

... The EditCommandColumn from the last code example would be found here ...

<asp:TemplateColumn HeaderText="Delete"><FooterTemplate>

<asp:Button CommandName="Insert" Text="Add" ID="btnAdd" Runat="server" /></FooterTemplate><ItemTemplate>

<asp:Button CommandName="Delete" Text="Delete" ID="btnDel" Runat="server" /></ItemTemplate>

</asp:TemplateColumn></Columns>

</asp:datagrid></form>

Examining the Source Code for the doInsert Event HandlerThe code-behind (or server side script) for the update, delete or cancel procedures go pretty much unchanged from what you would expect. The only exception is a line of code added to the procedure for displaying the edit row. To make the table more user friendly, the new record row (Footer) is hidden when an existing data row is selected for editing. This is accomplished by simply setting the ShowFooter property to False programmatically:

Sub doEdit(ByVal sender As Object, ByVal e As DataGridCommandEventArgs)storeGrid.ShowFooter = FalsestoreGrid.EditItemIndex = e.Item.ItemIndexBindData()

End Sub

Then the ShowFooter property is set back to True in the procedures for updating and canceling.

Sub doCancel(ByVal sender As Object, ByVal e As DataGridCommandEventArgs)storeGrid.ShowFooter = TruestoreGrid.EditItemIndex = -1BindData()

End Sub

The update event handler would need the storeGrid.ShowFooter = True line inserted as well...

In order to handle the case when the user clicks the "Add" button, we must provide an event handler for the DataGrid's ItemCommand event. This event handler, doInsert, must contain an If statement to check the CommandName property

Page 65: Paging in ASP

Paging in ASP.NET Page 65 of 65

Paging in ASP.NET Page 65 of 65

before the code to insert a new record is executed. This is because all command buttons in the DataGrid, when clicked, cause the DataGrid's ItemCommand event to fire. This includes the Edit, Update, Cancel, and Delete buttons as well. Therefore, we need to make sure that the CommandName property that caused the ItemCommand event to fire is the same as theCommandName property of the Add button.

After this check, we simply need to read in the values of the Footer TextBoxes, issue an appropriate database call, and then rebind the data to the DataGrid. The code for doInsert is given below:

Sub doInsert(ByVal sender As Object, ByVal e As DataGridCommandEventArgs)If e.CommandName = "Insert" Then

Dim storID As StringDim txtStorID As TextBox... declarations for remaining data fields

Dim strSQL As String

'Read in the values of the TextBoxestxtStorID = e.Item.FindControl("add_storID")storID = txtStorID.Text... get values for remaining data fields

'Create the appropriate SQL statementstrSQL = "INSERT INTO stores (stor_id, stor_name, stor_address, " _

& "city, state, zip) VALUES (@storeID, @storeName, @storeAddr, " _& @city, @state, @zip)"

Dim con As SqlConnectionDim cmdExp As SqlCommand

con = New SqlConnection(ConfigurationSettings.AppSettings("conn"))cmdExp = New SqlCommand(strSQL, con)

'Add the parametersDim storNameParam as New SqlParameter("@storName, SqlDbType.VarChar, 50)storNameParam.Value = txtStorNamecmdExp.Parameters.Add(storNameParam)... repeat this for all of the parameters ...

con.Open()cmdExp.ExecuteNonQuery()con.Close()

'Rebind the DataGridstoreGrid.EditItemIndex = -1BindData()

End IfEnd Sub

ConclusionAs you can see, adding that much desired "New Record" row to the DataGrid control couldn't be simpler. You only need to make use of the Footer for the new data entry fields, add a button and include a procedure for inserting the record to the database. Definitely easier than creating a new page!

Adding Paging Support to the Repeater or DataList with the PagedDataSource Class

Page 66: Paging in ASP

Paging in ASP.NET Page 66 of 66

Paging in ASP.NET Page 66 of 66

IntroductionWhen building ASP.NET Web applications, one of the most common tasks is displaying data. ASP.NET offers a bounty of data Web controls that make displaying data a breeze, but the most powerful data Web control - the DataGrid - imposes some limitations on the flexibility of laying out the data on the Web page. Recently I found myself needing a more flexible layout than the DataGrid's rigid column/row orientation, so I decided to go with the Repeater control so I could easily customize the HTML markup emitted.

The Repeater control is great for situations like this, where you need a finer degree of control over the emitted HTML in order to layout the content in a unique or precise manner. One drawback to the Repeater is that it does not have built-in paging capability, a feature the DataGrid offers. Since I would need to display potentially hundreds of records in the catalog, it was essential that I provided paging support for the Repeater.

Fortunately there is a class in the .NET Framework that was designed to provide paged access to a data source. This class, the PagedDataSource class, can be used by either the Repeater or DataGrid to mimic the paging capabilities of the DataGrid. Using the PagedDataSource class you'll have to write a bit more code than you would when using the DataGrid, but the amount and complexity of the code you do have to write is fairly low. In this article we'll examine this class, and see a specific example of how to implement paging with a Repeater control.

Paging with the PagedDataSource ClassThe PagedDataSource class, found in the System.Web.UI.WebControls namespace, encapsulates the properties needed to enable paging for a control. To implement paging in a control with the PagedDataSource class, you'll need to perform the following steps:

1. Get the data that you want to page through. This can be an array, a DataSet, a DataReader, or any other object that can be assigned to a data Web control's DataSource property.

2. Create the PagedDataSource instance, and assign the data to page through to the PagedDataSource's DataSource property.

3. Set the PagedDataSource class's paging properties, such as setting AllowPaging to True, and setting PageSize (to indicate how many records per page to show).

4. Assign the PagedDataSource instance to the data Web control's DataSource property and call the data Web control's DataBind() method.

Example: Creating a Pageable RepeaterTo examine how to use the PagedDataSource class to provide pagination support in a Repeater, let's create a pageable Repeater. First we need to build the HTML content that includes the Repeater control; note that the HTML contains not only a Repeater, but also the paging navigation buttons and a Label indicating the page number. (Notice that the Repeater's ItemTemplate is very simple in this example, and the same output could be possible with a DataGrid; but the concept holds -you could alter the Repeater's markup to allow for a much richer output that would not be possible with the DataGrid.) <table width="100%" border="0">

<tr><td> Repeater control with Paging functionality</td>

</tr><tr>

<td> <asp:label id="lblCurrentPage" runat="server"></asp:label></td></tr><tr>

<td> <asp:button id="cmdPrev" runat="server" text=" << "></asp:button><asp:button id="cmdNext" runat="server" text=" >> "></asp:button></td>

</tr></table>

<table border="1"><asp:repeater id="repeaterItems" runat="server">

<itemtemplate><tr>

<td> <b><%# DataBinder.Eval(Container.DataItem, "ItemName") %></b></td><td> <b><%# DataBinder.Eval(Container.DataItem, "ItemDescription") %></b></td>

Page 67: Paging in ASP

Paging in ASP.NET Page 67 of 67

Paging in ASP.NET Page 67 of 67

<td> <b><%# DataBinder.Eval(Container.DataItem, "ItemPrice") %></b></td><td> <b><%# DataBinder.Eval(Container.DataItem, "ItemInStock") %></b></td>

</tr></itemtemplate>

</asp:repeater></table>

The HTML for a pageable Repeater can be as simple or as involved as you want. The code, though, is pretty straightforward. The first step is to write the code that will do all of the work of displaying the correct page of data in the Repeater. This is accomplished by first reading in the data to be paged through. For this example, I just created an XML file (Items.xml) containing some sample data; this XML file is available in the code download at the end of this article.

// Read sample item info from XML document into a DataSetDataSet Items = new DataSet();Items.ReadXml(MapPath("Items.xml"));

Now that we have the data to page through, we need to create a PagedDataSource instance and specify its DataSourceproperty and other germane properties.

// Populate the repeater control with the Items DataSetPagedDataSource objPds = new PagedDataSource();objPds.DataSource = Items.Tables[0].DefaultView;

// Indicate that the data should be pagedobjPds.AllowPaging = true;

// Set the number of items you wish to display per pageobjPds.PageSize = 3;

The PagedDataSource also has a CurrentPageIndex, which indicates what page of data to display. The following code shows assigning this property. Note that CurrentPageIndex is assigned the value of a page-level property called CurrentPage. We'll discuss this page-level property shortly.

// Set the PagedDataSource's current pageobjPds.CurrentPageIndex = CurrentPage - 1;

Finally, we need to enable/disable the navigation buttons depending if we're on the first/last page, as well as update the Label Web control to indicate what page is currently being viewed. We can easily determine if we're on the first/last page using the PagedDataSource's IsFirstPage and IsLastPage properties.

lblCurrentPage.Text = "Page: " + (CurrentPage + 1).ToString() + " of " + objPds.PageCount.ToString();

// Disable Prev or Next buttons if necessarycmdPrev.Enabled = !objPds.IsFirstPage;cmdNext.Enabled = !objPds.IsLastPage;

Finally, we display the correct page of data by binding the PagedDataSource object to the Repeater.

repeaterItems.DataSource = objPds;repeaterItems.DataBind();

Page 68: Paging in ASP

Paging in ASP.NET Page 68 of 68

Paging in ASP.NET Page 68 of 68

Examining the CurrentPage Page-Level PropertyBack in our earlier code example, we assigned the PagedDataSource object's CurrentPageIndex property to a page-level property called CurrentPage. In order to remember the page of data to display across postbacks, it is important that the page index be maintained in the view state. This page-level property essentially wraps the complexity of reading from / writing to the view state, providing a convenient way to get and set the current page index. Here is the CurrentPage property:

public int CurrentPage{

get{

// look for current page in ViewStateobject o = this.ViewState["_CurrentPage"];if (o == null)

return 0; // default page index of 0else

return (int) o;}

set{

this.ViewState["_CurrentPage"] = value;}

}

Moving Between Pages of DataTo move from one page of data to another, the user visiting the Web page can click the next or previous buttons. These buttons, when clicked, cause a postback, and run server-side code that updates the CurrentPage property and rebinds the data to the Repeater.

private void cmdPrev_Click(object sender, System.EventArgs e){

// Set viewstate variable to the previous pageCurrentPage -= 1;

// Reload controlItemsGet();

}

private void cmdNext_Click(object sender, System.EventArgs e){

// Set viewstate variable to the next pageCurrentPage += 1;

// Reload controlItemsGet();

}

ItemsGet() is a page-level method (whose code we examined earlier) that contains the code to create the PagedDataSourceobject and bind it to the Repeater.

ConclusionAs you can see, adding the paging functionality to the Repeater control is fairly simple thanks to the PagedDataSource. You should now be able to create a paging Repeater control that will fit your needs; the lessons learned here can also be applied to adding pagination support to a DataList. Having the ability to page with a Repeater or DataList control will greatly enhance the usefulness of these data Web controls and, hopefully, you will find yourself using these versatile controls more often.

Page 69: Paging in ASP

Paging in ASP.NET Page 69 of 69

Paging in ASP.NET Page 69 of 69

Happy Programming!

Custom Paging with User Control : http://www.codeproject.com/aspnet/

Introduction

It's not hard to find code examples on how to do custom paging in ASP.NET, but I haven't found any good examples on using a user control to do so. This example shows you how to construct your own user control to perform custom paging; thus, promote code reuse by dropping the control on every page that needs the same functionality.

The user control shown in this example uses several events to control the page navigation, if you're not so familiar with how events/delegates work you'll probably have to get some reference on the topic first.

This solution also requires writing stored procedures that return pieces of data to be display on each page. I borrowed the idea from Zek3vil in his article - Custom Data Paging in ASP.NET(http://www.codeproject.com/aspnet/custompaging.asp?target=custom%7Cpaging). Basically, he used a temp table with an ID column that has the IDENTITY and PRIMARY KEY attribute to hold the returned data. Although the example was written in T-SQL, the same concept could be implemented in PL/SQL as well.

To make it simple, I decided to use the employee table in pubs database on MS SQL Server. Let's start by looking at the stored procedure:

Creating the Stored Procedure

CREATE PROCEDURE Get_Employees( @CurrentPage int,@PageSize int,@TotalRecords int OUTPUT)

AS

Page 70: Paging in ASP

Paging in ASP.NET Page 70 of 70

Paging in ASP.NET Page 70 of 70

-- Turn off count return.Set NoCount On

-- Declare variables.Declare @FirstRec intDeclare @LastRec int

-- Initialize variables.Set @FirstRec = (@CurrentPage - 1) * @PageSizeSet @LastRec = (@CurrentPage * @PageSize + 1)

-- Create a temp table to hold the current page of data-- Add an ID column to count the recordsCreate Table #TempTable(EmpId int IDENTITY PRIMARY KEY,fname varchar(20),lname varchar(30),pub_id char(4),hire_date datetime

)

--Fill the temp table with the remindersInsert Into #TempTable (fname,lname,pub_id,hire_date

)Select fname,

lname,pub_id,hire_date

From employeeOrder By lname

--Select one page of data based on the record numbers aboveSelect fname,lname,pub_id,hire_date

From #TempTableWhere EmpId > @FirstRec And EmpId < @LastRec

--Return the total number of records available as an output parameterSelect @TotalRecords = Count(*)From #TempTableGO

The stored procedure has 2 input and 1 output parameter - @CurrentPage is the current page number and should be greater than 0; @PageSize determines how many records to display on each page; @TotalRecords returns the number of records in total, which is also being used to calculate the total number of pages.

Page 71: Paging in ASP

Paging in ASP.NET Page 71 of 71

Paging in ASP.NET Page 71 of 71

Next, let's talk about the meat - the user control:

Paging User Control

<table width="100%">

<tr>

<td>(Page

<asp:label id="lblCurrentPage" Runat="server"></asp:label>of

<asp:label id="lblTotalPages" Runat="server"></asp:label>)

</td>

<td valign="top" align="right">

Page

<asp:DropDownList id="ddPage" runat="server"

AutoPostBack="true"></asp:DropDownList>

</td>

<td align="right">

<asp:imagebutton id="btnFirst" Runat="server" Enabled="false"

ImageUrl="Images/NavFirstPageDisabled.gif" />

<asp::imagebutton id="btnPrevious" Runat="server" Enabled="false"

ImageUrl="Images/NavPreviousPageDisabled.gif" />

<asp:imagebutton id="btnNext" Runat="server" Enabled="false"

ImageUrl="Images/NavNextPageDisabled.gif" />

<asp:imagebutton id="btnLast" Runat="server" Enabled="false"

ImageUrl="Images/NavLastPageDisabled.gif" />

</td>

</tr>

</table>

The user control consists of 3 parts - labels that show something like "(Page 1 of 10)," a dropdown listbox that allows you to jump from page to page and 4 VCR-type buttons to navigate between First, Previous, Next and Last page.

Page 72: Paging in ASP

Paging in ASP.NET Page 72 of 72

Paging in ASP.NET Page 72 of 72

There are some limitations on ASP.NET image buttons that hopefully will be improved in the next release. First, the image doesn't grey out when the button is disabled. Hence, we'll have to explicitly change the ImageUrl attribute when we enable/disable the button. Second, the mouse cursor remains the same (index finger) regardless of the button state. I didn't do anything to correct this problem in my code but it's a good exercise for you to figure out.

Now let's look at the code behind:

//public deletgates

public delegate void FirstPageEventHandler(object sender,

DataNavigatorEventArgs e);

public delegate void LastPageEventHandler(object sender,

DataNavigatorEventArgs e);

public delegate void PreviousPageEventHandler(object sender,

DataNavigatorEventArgs e);

public delegate void NextPageEventHandler(object sender,

DataNavigatorEventArgs e);

public delegate void PageChangedEventHandler(object sender,

DataNavigatorEventArgs e);

Since each VCR-type button and the dropdown listbox has its own event to be linked to, we have to first declare all the public delegates. Notice that we have our own custom event argument type (DataNavigatorEventArgs) because we need to keep track of the current page and total page number each time we navigate between pages.

Here's the DataNavigatorEventArgs class:

public class DataNavigatorEventArgs : EventArgs

{

private int m_iCurrentPage;

private int m_iTotalPages;

public DataNavigatorEventArgs()

{

}

public int CurrentPage

Page 73: Paging in ASP

Paging in ASP.NET Page 73 of 73

Paging in ASP.NET Page 73 of 73

{

get { return m_iCurrentPage; }

set { m_iCurrentPage = value; }

}

public int TotalPages

{

get { return m_iTotalPages; }

set { m_iTotalPages = value; }

}

}

Then, we declare all public events that are hooked up with those delegates:

//public events

public event FirstPageEventHandler FirstPage;

public event LastPageEventHandler LastPage;

public event PreviousPageEventHandler PreviousPage;

public event NextPageEventHandler NextPage;

public event PageChangedEventHandler PageChanged;

For those image buttons, we have to forward the Click event to our declared delegates. We dothis by the following code inside the InitializeComponent() function:

private void InitializeComponent()

{

this.btnPrevious.Click += new System.Web.UI.ImageClickEventHandler

(this.OnPreviousPageButton);

this.btnNext.Click += new System.Web.UI.ImageClickEventHandler

(this.OnNextPageButton);

this.btnFirst.Click += new System.Web.UI.ImageClickEventHandler

(this.OnFirstPageButton);

this.btnLast.Click += new System.Web.UI.ImageClickEventHandler

Page 74: Paging in ASP

Paging in ASP.NET Page 74 of 74

Paging in ASP.NET Page 74 of 74

(this.OnLastPageButton);

this.ddPage.SelectedIndexChanged += new EventHandler

(this.OnPageChangedButton);

this.Load += new System.EventHandler(this.Page_Load);

}

Now let's take the Next button for example, we forward the Click event to this.OnNextPageButton()function. Here's how that function looks like:

protected void OnNextPageButton(object sender, ImageClickEventArgs e)

{

DataNavigatorEventArgs args = new DataNavigatorEventArgs();

args.CurrentPage = int.Parse(lblCurrentPage.Text);

args.TotalPages = int.Parse(lblTotalPages.Text);

SetDropDownPageNumber(args.CurrentPage + 1);

OnNextPage(args);

}

We instantiate the DataNavigatorEventArgs object, assign its values from the labels on the user control, call the SetDropDownPageNumber() to set the correct page number displayed in the dropdown listbox, and most importantly, forward the call to OnNextPage() to raise the event by invoking delegates.

protected virtual void OnNextPage(DataNavigatorEventArgs args)

{

if (NextPage != null)

{

// Invoke the delegates.

NextPage(this, args);

}

}

Page 75: Paging in ASP

Paging in ASP.NET Page 75 of 75

Paging in ASP.NET Page 75 of 75

Notice the difference in declaration and signature between OnNextPageButton() and OnNextPage(). (Note: the name of your function that raises the event has to be OnEventName such as OnNextPage).

Here's the SetDropDownPageNumber() function that sets the page number for the dropdown listbox:

private void SetDropDownPageNumber(int iCurrentPage)

{

if (ddPage.Items.Count > 0)

// since SelectedIndex is 0-based, we have to

// take the current page number and minus 1

ddPage.SelectedIndex = iCurrentPage - 1;

}

The user control also has public get/set properties that keep track of the current page number, total page number, each image button's state and its image URL. These properties will be used on the ASP.NET page where the control resides.

Using the Control

To use the control, just drag it from the Solution Explorer and drop it on your ASP.NET page. Here's the HTML of the page:

<form id="Form1" method="post" runat="server">

<table width="100%">

<tr>

<td>

<asp:datagrid id="dgEmp" runat="server"

EnableViewState="false"

AlternatingItemStyle-BackColor="Silver"

AllowCustomPaging="true"

Width="100%" HeaderStyle-BackColor="#6633ff"

HeaderStyle-ForeColor="#ffffff"

HeaderStyle-Font-Bold="true"></asp:datagrid>

</td>

Page 76: Paging in ASP

Paging in ASP.NET Page 76 of 76

Paging in ASP.NET Page 76 of 76

</tr>

<tr>

<td><uc1:datanavigator id="ucDataNavigator"

runat="server"></uc1:datanavigator>

</td>

</tr>

</table>

</form>

Now let's take a look at the code behind. First, don't forget to hook up all the event handlers for our control:

private void InitializeComponent()

{

this.ucDataNavigator.FirstPage += new FirstPageEventHandler

(this.FirstPage);

this.ucDataNavigator.PreviousPage += new PreviousPageEventHandler

(this.PreviousPage);

this.ucDataNavigator.NextPage += new NextPageEventHandler

(this.NextPage);

this.ucDataNavigator.LastPage += new LastPageEventHandler

(this.LastPage);

this.ucDataNavigator.PageChanged += new PageChangedEventHandler

(this.PageChanged);

this.Load += new System.EventHandler(this.Page_Load);

this.Init += new EventHandler(this.Page_Init);

}

Remember previously we made the Click event of the Next image button raise the NextPage event? Here we get to define our NextPage event handler:

protected void NextPage(object sender, DataNavigatorEventArgs e)

{

Page 77: Paging in ASP

Paging in ASP.NET Page 77 of 77

Paging in ASP.NET Page 77 of 77

if (e.CurrentPage <= e.TotalPages)

{

// Increment the current page index.

ucDataNavigator.CurrentPage++;

// Get the data for the DataGrid.

BindGrid();

EnableDisableButtons(e.TotalPages);

}

}

Here we first increment the current page index then retrieve the data for the DataGrid, finally, we call EnableDisableButtons() to control the state and image of each VCR-type image button.

Note that I use Page_Init() method above to intialize the CurrentPage property on the user control to 1 since this only has to be done once.

private void Page_Init(object sender, System.EventArgs e)

{

ucDataNavigator.CurrentPage = 1;

}

Inside the BindGrid() method I used the following logic to calculate the number of pages:

if ((totalCount % PAGE_SIZE) == 0)

ucDataNavigator.TotalPages = totalCount/PAGE_SIZE;

else

ucDataNavigator.TotalPages = (totalCount/PAGE_SIZE) + 1;

totalCount's value is from the output parameter of the stored procedure and PAGE_SIZE is a constant defined as 10 in my code. So another good exercise for you to do is to externalize the PAGE_SIZE constant; i.e., instead of hard-coding it, make it table-driven or store the value in a configuration file.

Conclusion

Page 78: Paging in ASP

Paging in ASP.NET Page 78 of 78

Paging in ASP.NET Page 78 of 78

The biggest advantage of using user controls on your ASP.NET pages is code-reuse. This article shows you how you can utilize this concept to avoid writing repeated code. To make it one step further, instead of a user control, you could write a custom control to do this so that it'd be even easier to reuse your code.

Next time I'll show you how to add sorting functionality in our paging user control. Have fun!

Source available here:::::http://www.codeproject.com/aspnet/paginguc/PagingUserControl.zip

Custom Data Paging in ASP.NET : http://www.codeproject.com/aspnet/

Introduction

Even though DataGrid control has a built-in support for paging through the records of the DataSource by enabling the AllowPaging property, Microsoft found that paging this way has a big disadvantage when you have like thousands of records in database or more, since every time you navigate to a new page, those records must be retrieved from the data source into memory. That will reduce the performance painfully. So Microsoft allowed us to implement a custompaging solution to get around this limitation, by enabling the AllowCustomPaging. Instead of retrieving all records to display each page, now you only retrieve the records needed for the current page.

That sounds like it's something we need, but I found that customizing the data paging this way also has its drawback, because it only works with a table that has an identity column and when the identity column is not missing any value. If certain values are missing, the DataGrid will display fewer records for some pages than others. For example, the DataGrid is about to display 10 records with unique IDs from 10 to 20, for some reasons 5 last records are missing. You'll expect the DataGrid will display ten records from 10 to 25, but actually it won't. It'll display exactly 5 records from 10 to 20, and that is not what you want.

Page 79: Paging in ASP

Paging in ASP.NET Page 79 of 79

Paging in ASP.NET Page 79 of 79

So to get around this problem, I will demonstrate here how to implement our own custompaging solution with a bit more work, by using the Repeater and T-SQL programming. We could use DataList or DataGrid instead, but the Repeater control is lightest and also we don't need any special features available in DataList and DataGrid controls.

To follow along this article, we only need to create a database and one web page. Let's begin with creating a database:

Creating a database

We'll creating a database named CustomPaging, one table say Products and then add one stored procedure named GetProductsByPage to retrieve products for a specific page.

CREATE DATABASE CustomPagingGO

Use CustomPagingGO

CREATE TABLE Products (ProductID int IDENTITY (1, 1) NOT NULL ,ProductName varchar (50) NOT NULL

) ON [PRIMARY]GO

CREATE PROCEDURE GetProductsByPage@PageNumber int,@PageSize intAS

CREATE TABLE #TempProducts(

ID int IDENTITY PRIMARY KEY,ProductID int,ProductName varchar(50),

)

-- fill the temp table with all the products for the -- specified products retrieved from the Products tableINSERT INTO #TempProducts (

ProductID,ProductName

)SELECT

ProductID,ProductName

FROM Products

-- declare two variables to calculate the -- range of records to extract for the specified pageDECLARE @FromID intDECLARE @ToID int

-- calculate the first and last ID of the range of topics we need

Page 80: Paging in ASP

Paging in ASP.NET Page 80 of 80

Paging in ASP.NET Page 80 of 80

SET @FromID = ((@PageNumber - 1) * @PageSize) + 1SET @ToID = @PageNumber * @PageSize

-- select the page of recordsSELECT ProductID, ProductName FROM #TempProducts

WHERE ID >= @FromID AND ID <= @ToIDGO

Creating the database and a table in the code is quite straightforward, only the code that creates the stored procedure GetProductsByPage is quite long and hard to understand if you're not familiar with T-SQL. What is does is, first it creates a temporary table with all the columns that matches the Products table and a new identity column. It then inserts all the records from the Products to the temp table, so now the new identity column of the temp table has no missing values. Next we declare two variables to calculate the range of records for a requested page.

So by using this stored procedure we only need to pass two parameters PageNumber and PageSize(records per page) to retrieve the records that need to be displayed for a page. And now what we do is bind those records to the Repeater control.

Binding data to the Repeater control

Next, we're gonna bind data to the Repeater control. So we need to declare a Repeater control in our web form. The following HTML code adds a Repeater control and some buttons to navigate through the pages:

<script language="javascript">function ChangePage(id){

// save the page number clicked to the hidden fielddocument.all.PageNumber.value = id;// call the __doPostBack function to post back // the form and execute the PageClick event__doPostBack('PageClick','');

}</script><body bgcolor="black">

<form id="Topics" method="post" runat="server"><!-- Hidden fields store the current page number

and total pages --><input type="hidden" runat="server"

id="PageNumber" value="1"><input type="hidden" runat="server"

id="Pages" value="0"><!-- Hidden button to handle the click event when

user clicks on a page link--><asp:button ID="PageClick" OnClick="Page_Click"

runat="server" Visible="false"></asp:button><asp:label ID="Info" runat="server"></asp:label><asp:linkbutton ID="FirstPage"

runat="server" CommandName="FirstPage" OnCommand="Page_Changed">First</asp:linkbutton>

<asp:linkbutton ID="PrevPage" runat="server" CommandName="PrevPage"

Page 81: Paging in ASP

Paging in ASP.NET Page 81 of 81

Paging in ASP.NET Page 81 of 81

OnCommand="Page_Changed">Prev</asp:linkbutton><asp:label ID="PagesDisplay"

runat="server"></asp:label><asp:linkbutton ID="NextPage"

runat="server" CommandName="NextPage" OnCommand="Page_Changed">Next</asp:linkbutton>

<asp:linkbutton ID="LastPage" runat="server" CommandName="LastPage" OnCommand="Page_Changed">Last</asp:linkbutton>

<br><br><table width="300" style="border: 1 solid gray" align="center"><tr>

<td bgcolor="gray" style="color: white">Product ID</td>

<td bgcolor="gray" style="color: white">Product Name</td>

</tr><asp:repeater ID="ProductsRepeater" runat="server">

<itemtemplate><tr>

<td><%# DataBinder.Eval(Container.DataItem, "ProductID") %></td>

<td><%# DataBinder.Eval(Container.DataItem, "ProductName") %></td>

</tr></itemtemplate>

</asp:repeater></table>

</form></body>

We have 2 hidden fields to store the current page number and number of pages. Then we have 4 navigation buttons: First, Prev, Next, Last, all share the same command but with unique command names. that means when one of those buttons is clicked, the event handler Paged_Changed will be executed. We also have a Label to display all the pages as links, that helps users to jump to another page a lot easier. Finally we have a Repeater to display the records.

Note that there's a JavaScript function ChangePage() which is called when a user clicks on a page number, to save that page number to the PageNumber hidden field, and then call the __doPostBackfunction (automatically generated by ASP.NET), to post the form back and execute the Page_Clickevent handler to refresh the page with new records.

That's all it takes for the HTML code. So let's now take a look at the following C# code and see how to bind data to the Repeater and implement the paging navigator.

<%@ Page Language="C#" ContentType="text/html"ResponseEncoding="iso-8859-1" %>

<%@ import Namespace="System" %><%@ import Namespace="System.Data" %><%@ import Namespace="System.Data.SqlClient" %><script runat="server">

const int RECORDS_PER_PAGE = 5;int totalRecords;

Page 82: Paging in ASP

Paging in ASP.NET Page 82 of 82

Paging in ASP.NET Page 82 of 82

void Page_Load(){

// get the number of records found in the databasetotalRecords = GetProductsCount();// calculate and save the number of // pages to the Pages hidden fieldPages.Value = _

Math.Ceiling((double)totalRecords/RECORDS_PER_PAGE).ToString();

if (!Page.IsPostBack){

// Bind records to repeaterBindData();

}}

void BindData(){

int pageNumber = int.Parse(PageNumber.Value);int totalPages = int.Parse(Pages.Value);

SqlConnection connection = new SqlConnection("server=(local);database=CustomPaging;uid=sa;pwd=;");

SqlCommand command = new SqlCommand("GetProductsByPage", connection);command.CommandType = CommandType.StoredProcedure;command.Parameters.Add("@PageNumber", pageNumber);command.Parameters.Add("@PageSize", RECORDS_PER_PAGE);

connection.Open();DataSet products = new DataSet();SqlDataAdapter adapter = new SqlDataAdapter();adapter.SelectCommand = command;adapter.Fill(products, "Products");connection.Close();

// Bind Data to the repeaterProductsRepeater.DataSource = products;ProductsRepeater.DataBind();

// Display the page linksPagesDisplay.Text = "";for (int i=1; i<=totalPages; i++){

if (pageNumber != i)PagesDisplay.Text += _"<a href=\"javascript:ChangePage("+i+")\">"+i+"</a> ";

elsePagesDisplay.Text += "[" + i + "] ";

}

// enable/disable the links to navigate through the pagesFirstPage.Enabled = (pageNumber != 1);PrevPage.Enabled = (pageNumber != 1);NextPage.Enabled = (pageNumber != totalPages);LastPage.Enabled = (pageNumber != totalPages);

Info.Text = totalRecords + " records are found and divided into "

Page 83: Paging in ASP

Paging in ASP.NET Page 83 of 83

Paging in ASP.NET Page 83 of 83

+ Pages.Value + " pages<br><br>";}

// return the number of total records in databaseint GetProductsCount(){

SqlConnection connection = new SqlConnection("server=(local);database=CustomPaging;uid=sa;pwd=;");

SqlCommand command = new SqlCommand("SELECT Count(*) FROM Products", connection);

connection.Open();int count = (int)command.ExecuteScalar();connection.Close();

return count;}

// execute when user clicks on the next/prev/first/last buttonvoid Page_Changed(object sender, CommandEventArgs e){

switch (e.CommandName){

case "FirstPage":PageNumber.Value = "1";break;

case "PrevPage":PageNumber.Value = _

(int.Parse(PageNumber.Value) -1).ToString();break;

case "NextPage":PageNumber.Value = _

(int.Parse(PageNumber.Value) +1).ToString();break;

case "LastPage":PageNumber.Value = Pages.Value;break;

}

// rebind dataBindData();

}

// execute when user clicks on the page linkvoid Page_Click(object sender, System.EventArgs e){

// rebind dataBindData();

}</script>

When the page is loaded, it gets the number of total records by calling the GetProductsCount()method, then calculate the number of pages and save to the hidden field of the form. Next, it calls the BindData() method to bind data to the Repeater control and display the page links if the page is not posted back, because we don't need to rebind data if it's posted back.

Page 84: Paging in ASP

Paging in ASP.NET Page 84 of 84

Paging in ASP.NET Page 84 of 84

There are two event handlers in the above code, the first one is Page_Click which only rebind data to the Repeater, the other is Page_Changed executed when user clicks one of the 4 navigation buttons. It detects which button is clicked by the command names and save the page number that's going to be displayed to the hidden field and rebind data.

So that's all it takes to implement our own custom data paging. Thanks for reading.

Source file ::; http://www.codeproject.com/aspnet/CustomPaging/CustomPaging_source.zip

Implementing Custom Paging in ASP.NET DataGrid Control

ASP.NET Datagrid control displays data in a tabular format and also supports features for selecting, sorting, paging, and editing the data. The ASP.NET datagrid control reduces a lot of task that we had been doing in the ASP applications like retrieve a recordset, loop through the results one by one, struggle to format the data in HTML table manipulating the rows and column dynamically. Displaying the data in a tabular format at times sound simpler in comparison with the sorting or paging !!!

The ASP.NET Datagrid control is as simple as drag and drop, set some of the properties like DataSource and bind the datagrid to any of the objects like Dataset / Datareader. Having the Support for paging and sorting we only need to add a few lines of code and we can go ahead... In this article we'll try to achievea) Custom Paging with "First", "Previous", "Next", "Last" buttons.b) Tie up the "Page Up" and "Page Down" keys to the Datagrid.

a) Custom Paging with "First", "Previous", "Next", "Last" buttons.Lets initially begin with the Datagrid Control We'll set the AllowPaging Property to True. We set the PagerStyle-Visible as false as we have chosen to do custom paging. For this sample code we have kept the default page size i.e. 10

<ASP:Datagrid id="DataGrid1" runat="server" AllowPaging="True"PagerStyle-Visible="False" HeaderStyle-BackColor="Blue"HeaderStyle-ForeColor="White" BorderColor="#E7E7FF" BorderStyle="None" BorderWidth="1px" BackColor="White" CellPadding="3" GridLines="Horizontal">

<SelectedItemStyle Font-Bold="True" ForeColor="#F7F7F7" BackColor="#738A9C"></SelectedItemStyle><AlternatingItemStyle BackColor="#F7F7F7"></AlternatingItemStyle><ItemStyle ForeColor="#4A3C8C" BackColor="#E7E7FF"></ItemStyle><HeaderStyle Font-Bold="True" ForeColor="#F7F7F7" BackColor="#4A3C8C"></HeaderStyle><FooterStyle ForeColor="#4A3C8C" BackColor="#B5C7DE"></FooterStyle><PagerStyle Visible="False" HorizontalAlign="Right" ForeColor="#4A3C8C" BackColor="#E7E7FF" Mode="NumericPages"></PagerStyle>

</ASP:Datagrid><br>The simple task to be done is to display the data in the datagrid The coding steps involve following steps :

Step 1:Have a subroutine to Bind data to Datagrid. So in the subroutine BindData() we'll have following steps done:a) Connecting to Database.b) Creating DataAdapter to execute the SQL query using the proper connection.c) Fill the Dataset Based on the DataAdapterd) Assign the DataSource Property of DataGrid to the DataSet

Page 85: Paging in ASP

Paging in ASP.NET Page 85 of 85

Paging in ASP.NET Page 85 of 85

Dim MyConnection As SqlConnectionDim myda As SqlDataAdapterDim ds As DataSetDim sqlStr As String = "SELECT Productid,ProductName,UnitPrice from Products"Dim strConn As String = "server=localhost;uid=sa;pwd=;database=northwind;"Dim TotalRows As IntegerMyConnection = New SqlConnection(strConn)myda = New SqlDataAdapter(sqlStr, MyConnection)ds = New DataSet()myda.Fill(ds, "Datagrid1")DataGrid1.DataSource = dsDataGrid1.DataBind()

Step 2:Now moving on to write the code for the 4 buttons "First", "Previous", "Next", "Last"

<asp:Button id="Firstbutton" Text="First" CommandArgument="0" runat="server" onClick="PagerButtonClick"/><asp:Button id="Prevbutton" Text="Prev" CommandArgument="Prev" runat="server" onClick="PagerButtonClick"/><asp:Button id="Nextbutton" Text="Next" CommandArgument="Next" runat="server" onClick="PagerButtonClick"/><asp:Button id="Lastbutton" Text="Last" CommandArgument="Last" runat="server" onClick="PagerButtonClick"/>Depending upon which button is clicked we'll handle the events. The Buttons will call a server side code i.e PagerButtonClick. Based on the CommandArgument we'll manipulate with the CurrentPageIndex of the Datagrid.

The code goes as below:

Sub PagerButtonClick(ByVal sender As Object, ByVal e As EventArgs)Dim arg As String = sender.CommandArgumentSelect Case arg

Case "Next"If (DataGrid1.CurrentPageIndex < (DataGrid1.PageCount - 1)) Then

DataGrid1.CurrentPageIndex += 1End IfCase "Prev"If (DataGrid1.CurrentPageIndex > 0) Then

DataGrid1.CurrentPageIndex -= 1End IfCase "Last"DataGrid1.CurrentPageIndex = (DataGrid1.PageCount - 1)Case ElseDataGrid1.CurrentPageIndex = Convert.ToInt32(arg)

End Select

BindData()End Sub

Page 86: Paging in ASP

Paging in ASP.NET Page 86 of 86

Paging in ASP.NET Page 86 of 86

b) Tie up the "Page Up" and "Page Down" keys of the keyboard to the Datagrid.To achieve such a functionality we'll need client-side script to handle "Page Up" and "Page Down" So a bit of modification codes in the <body> tag

<body onkeydown="return window_onkeydown()">To trap the values of the Key Pressed we'll have a hidden control <INPUT type="hidden" id="UpDown" name="PageUpDown">The client side script would be as follows <script>function window_onkeydown() {

if (event.keyCode==33 || event.keyCode==34){

Form1.PageUpDown.value=event.keyCode;Form1.submit();

}}</script>In the Code-behind we'll have If Request.Form("PageUpDown") <> "" Then'Checks if Page Up/Page Down Key is Pressed'Page Up =33'Page Down =34If Request.Form("PageUpDown") = 34 Then

If DataGrid1.CurrentPageIndex < DataGrid1.PageCount - 1 ThenDataGrid1.CurrentPageIndex = DataGrid1.CurrentPageIndex + 1

End IfElseIf DataGrid1.CurrentPageIndex > 0 Then

DataGrid1.CurrentPageIndex = DataGrid1.CurrentPageIndex - 1End IfEnd If

End If

SummarizeLet's walk through the Events and the Subroutines in the codebehind.

Page_LoadThe first time the page is loaded, we call subroutine BindData. On post-back, we check for the Any of the events being triggered which could be the "Page Up/Page Down" keys or the buttons that we have added to the form. To check for the Key Pressed we check the value of the hidden textbox and accordingly increment/decrement the CurrentPageIndex.

BindDataThis subroutine just populates the Datagrid with the DataSet.

PagerButtonClickTo navigate through the Datagrid with the help of those buttons we have a Server-side code. Based on the CommandArgument we increment and decrement the CurrentPageIndex and rebind the data.This is all to work with the code.