Paging In ASP.NET, Part 2
By Andrew Merlino
In Part 1 we examined the first event handler in the code-behind class,
the Page_Load event handler. In this part we continue our examination of the code-behind class.
The SetSorting() Method
The 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 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):
|
The SetChangeFlag Event Handler
The
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, 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 Handler
The
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 sender argument to a string, we
are able to determine which server control in Paging.aspx triggered the ChangePage routine:
|
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 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:
|
Paging the Records with the Paging() Method
The
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:
|
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 /:
|
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.
|
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:
|
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:
|
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:
|
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:
|
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:
|
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 , "," )
|
(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:
|
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.
Conclusion
Using 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!
Attachments



