我在使用MSDN 上 Dino Esposito 的分页组件对DataGrid进行操作的时候,发现在 PagingMode="NonCached" 时候,最后一页的时候,会报错误:Index 1 is not non-negative and below total rows count.

      查了很久也没找到原因,今天请教了思归和微软动力营的专家。发现是分页组件中用到了:PagedDataSource 类  。
由于DataGrid会根据数据源PagedDataSource的PageSize属性来判断要有多少条记录来显示,所以如果PagedDataSource中实际出现的记录数少于PageSize的设定值,DataGrid试图读取不存在的记录时,就会出现这个问题。
也就是说,其实这个类的PageSize属性不仅仅定义当前分页一页显示多少条,也定义当前页目前有多少条。
 
解决这个问题的方法就是在FetchAllData函数中修改以下代码:
 
_dataSource.PageSize = ItemsPerPage;
===>
_dataSource.PageSize = data.Rows.Count;
 
这个组件可以在如下地址看到:
 
中文介绍文章:

http://www.microsoft.com/china/msdn/library/dnaspp/html/PagerControls.asp
英文介绍文章:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnaspp/html/pagercontrols.asp

Creating a Pager Control for ASP.NET

Dino Esposito
Wintellect

October 2003

Applies to:
   Microsoft® ASP.NET

Summary: Tackles the problem of adding paging capabilities to any ASP.NET control. Also gives a number of tips and tricks useful when developing composite ASP.NET controls. (18 printed pages)

Download the source code for this article.

From the programmer's perspective, one of the worst drawbacks of Microsoft® SQL Server™ queries is that they often return much more rows than actually fit in the application's user interface. This unfortunate circumstance puts developers in a dilemma. Should they create a very long page that takes users a while to scroll through, or is the issue better addressed by setting up a manual paging mechanism?

Which solution is better depends mostly upon the nature of the data you retrieve. A long list of items—for example, the result of a search—is preferably rendered through equally sized, relatively short pages. A long, single item, like the text of an article, is more comfortably consumed if inserted entirely in the page. In the final analysis, the decision should be made in light of the overall usability of the application. So, how does Microsoft® ASP.NET face the problem of data paging?

ASP.NET provides powerful data-bound controls to format the results of a query into HTML markup. However, only one of these data-bound controls—specifically, the DataGrid control—natively supports paging. Other controls, such as the DataList, the Repeater, or the CheckBoxList, just don't page. These and other list controls don't page, not because they are structurally unable to page, but because they, unlike the DataGrid, don't contain any specific code that handles paging. However, the code to handle paging is relatively boilerplate and can be added to any of these controls.

Scott Mitchell covered DataGrid paging in a recent article titled Creating a Pageable, Sortable DataGrid. The article also references other useful information around the Web that gives you the basics, and more, relating to grid paging. If you want to see an example of how to make the DataList control page, have a look at this article. It demonstrates how to create a custom DataList control that features current index and page-size properties and fires a page-changed event.

The same code can be reworked to serve the paging needs of other list controls, such as the ListBox and the CheckBoxList. Nevertheless, adding paging capabilities to individual controls is not really all that great of an idea, for as mentioned, the paging code is rather boilerplate. So what could be better for a smart programmer, than stuffing it all into a new general-purpose pager control?

In this article, I'll build a pager control that will make a buddy list control page over the results of a SQL Server query. The control is named SqlPager and supports two types of buddy controls—list controls and base data list controls.

Highlights of the SqlPager Control

The SqlPager control is an ASP.NET composite control that contains a one-row table. The row, in turn, contains two cells—the navigation bar and the page descriptor. The user interface of the control is a strip that ideally is the same width as the buddy control. The navigation bar section provides the clickable elements to move through pages; the page descriptor section gives users some feedback about the currently displayed page.

Figure 1. The SqlPager control as it shows up in the Visual Studio .NET page designer

Just like the embedded pager of the DataGrid control, the SqlPager control has two navigation modes—next/previous and numeric pages. An ad hoc property, PagerStyle, lets you choose the more convenient style. The control works in conjunction with a list control. You assign the pager such a buddy control through the ControlToPaginate string property.

SqlPager1.ControlToPaginate = "ListBox1";

Basically, the pager gets the results of a SQL Server query, prepares an appropriate page of records, and displays that through the DataSource property of the buddy control. When the user clicks to view a new page, the pager retrieves the requested data and shows it again through the buddy. The paging mechanism is completely transparent to the list control. The data source of the list control is programmatically updated and contains, at any time, only the records that fit into the current page.

The paging engine of the control features quite a few public properties, such as CurrentPageIndex, ItemsPerPage, and PageCount to get and set the index of the current page, the size of each page, and the total number of displayed pages. The pager manages any logic needed for data retrieval and paging.

The SelectCommand property sets the text of the command to use for fetching data. The ConnectionString property defines name and location of the database, plus the credentials to connect. How the query is executed depends on the value of the PagingMode property. Feasible values for the property are the values of the homonym PagingMode enumeration—Cached and NonCached. If the Cached option is selected, the entire result set is retrieved using a data adapter and a DataTable object. The result set is optionally placed in the ASP.NET Cache object and reused until it expires. If the NonCached option is selected, the query retrieves only the records that fit into the current page. No data is placed in the ASP.NET Cache this time. The NonCached mode is nearly identical to the custom paging mode of the DataGrid control.

The full programming interface of the SqlPager control is shown in the table below.

Table 1. The programming interface of the SqlPager control

Name Type Description
CacheDuration Property Indicates how many seconds the data should stay in the ASP.NET cache. Used only in Cached mode. Default is 60 seconds.
ConnectionString Property The connection string to access the SQL Server database of choice.
ControlToPaginate Property The ID of the control in the same .aspx page that will show the pages of records retrieved by the pager. This is the buddy control.
CurrentPageIndex Property Gets and sets the 0-based index of the page
ItemsPerPage Property Gets and sets the number of records to display per page. Default is 10 items per page.
PagerStyle Property A value that indicates the style of pager's user interface. It can be one of the values in the PagerStyle enum: NextPrev and NumericPages. In NextPrev mode, VCR-like buttons are displayed to reach the first, previous, next, and last page. In the NumericPages mode, a drop-down list is filled with the indexes of all available pages.
PagingMode Property A value that indicates how data is retrieved. It can be one of the values in the PagingMode enum: Cached and NonCached. If it is Cached, a data adapter is used and the whole result set is parked in the ASP.NET cache. If it is NonCached, only the records in the current page are retrieved. No caching takes place in this case.
SelectCommand Property The command text of the query. It is expected to be in the form SELECT-FROM-WHERE. The ORDER BY clause is not supported. Sorting is otherwise provided through the SortField property.
SortField Property The name of the field to sort by. This field is used to prepare a dynamic ORDER BY clause for the query. Sorting is performed by SQL Server.
ClearCache Method Removes any data stored in the ASP.NET cache.
PageIndexChanged Event Default event, occurs when the pager moves to a new page. The event data structure is the PageChangedEventArgs class and contains the index of the old and new page.

Since the SqlPager control inherits WebControl, it also features a bunch of UI-related properties to manage fonts, borders, and colors.

Building the SqlPager Control

I'll build the SqlPager control as a composite control and have it inherit the WebControl class. Composite controls are a special flavor of ASP.NET server controls that result from the combination of one or more constituent server controls.

public class SqlPager : WebControl, INamingContainer
{ ... }

Unless you are building a completely custom control or extending an existing one, most of the time when you create a new control, you are actually building a composite control. To create the SqlPager I will assemble a Table control and either a few LinkButton controls or a DropDownList control, depending on the pager style.

There are a few guidelines to bear in mind to build composite controls. First off, you have to override the CreateChildControls protected method. The CreateChildControls method is inherited from Control and is called when server controls have to create child controls for rendering or after a postback.

protected override void CreateChildControls()
{// Clear existing child controls and their viewstateControls.Clear();ClearChildViewState();// Build the control treeBuildControlHierarchy();
}

When overriding this method, you should do a couple of important things. You create and initialize any required instance of the child controls and add them to the Controls collection of the parent control. Before you go with the new control tree, though, you should remove any existing child controls and clear any viewstate information child controls may have left around.

A composite component should also implement the INamingContainer interface, so that the ASP.NET runtime can create a new naming scope for it. This ensures that all controls in the composite control have a unique name. This will also ensure that the postback data of child controls is handled automatically.

Being a naming container is particularly important for the SqlPager control. SqlPager, in fact, contains some LinkButton controls and needs to catch and handle their click events in order to navigate pages. Just like any other control in an ASP.NET page, a LinkButton is given an ID, which is used to identify the control that will handle the postback event.

When handling a postback, the ASP.NET runtime attempts to find a match between the event target ID and the ID of any control that is a direct child of the main form. Our LinkButton is a child of the pager and is subsequently unable to run its server-side code. Does this mean that only direct children of the form can fire and handle server events? Of course not, as long as you use naming containers.

By making the SqlPager control implement the INamingContainer interface, you change the actual ID of the embedded link button from, say, First to SqlPager1:First. When users click to view a new page, the postback event has SqlPager1:First as the target control. The algorithm that the runtime uses to identify the target control is actually a bit more sophisticated than I described a moment ago. The runtime considers the name of the event target as a colon-separated string. The match is actually sought between the children of the form and the first token of a colon-separated string, such as SqlPager1:First. Since the pager is a child of the form, the match is found and the pager gets the click event. If you find this explanation inadequate or confusing, just download the source code of the SqlPager control, remove the INamingContainer marker interface, and recompile. You'll see that the pager posts back but is unable to handle the click event internally.

The INamingContainer interface is a method-less, marker interface whose implementation doesn't require you to do more than to specify the name in the class declaration.

Another important aspect of composite controls is that they normally don't require custom logic for rendering. The rendering of a composite control follows from the rendering of constituent controls. When building a composite control, you normally don't have to override the Render method.

The SqlPager tree of controls consists of a table with one row and two cells. The table inherits most of the visual settings of the pager—foreground and background colors, borders, font information, and width. The first cell contains the navigation bar whose structure depends on the value of the PagerStyle property. If the pager style is NextPrev, the navigation bar is made of four VCR-like link buttons. Otherwise, it will consist of a drop-down list.

private void BuildControlHierarchy()
{// Build the surrounding table (one row, two cells)Table t = new Table();t.Font.Name = this.Font.Name;t.Font.Size = this.Font.Size;t.BorderStyle = this.BorderStyle;t.BorderWidth = this.BorderWidth;t.BorderColor = this.BorderColor;t.Width = this.Width;t.Height = this.Height;t.BackColor = this.BackColor;t.ForeColor = this.ForeColor;// Build the table rowTableRow row = new TableRow();t.Rows.Add(row);// Build the cell with the navigation barTableCell cellNavBar = new TableCell();if (PagerStyle == this.PagerStyle.NextPrev)BuildNextPrevUI(cellNavBar);elseBuildNumericPagesUI(cellNavBar);row.Cells.Add(cellNavBar);// Build the cell with the page indexTableCell cellPageDesc = new TableCell();cellPageDesc.HorizontalAlign = HorizontalAlign.Right;BuildCurrentPage(cellPageDesc);row.Cells.Add(cellPageDesc);// Add the table to the control treethis.Controls.Add(t);
}

It is extremely important for the correct rendering of the pager that you add each control to the proper Controls collection. The outermost table must be added to the Controls collection of the pager. The link buttons and the drop-down list must be added to the Controls collection of the respective table cell.

The following code gives an idea of the code used to build the link buttons navigation bar. Each button is rendered with a Webdings character, disabled as needed, and bound to an internal Click event handler.

private void BuildNextPrevUI(TableCell cell)
{bool isValidPage = ((CurrentPageIndex >=0) && (CurrentPageIndex <= TotalPages-1));bool canMoveBack = (CurrentPageIndex>0);bool canMoveForward = (CurrentPageIndex<TotalPages-1);// Render the << buttonLinkButton first = new LinkButton();first.ID = "First";first.Click += new EventHandler(first_Click);first.Font.Name = "webdings";first.Font.Size = FontUnit.Medium;first.ForeColor = ForeColor;first.ToolTip = "First page";first.Text = "7";   first.Enabled = isValidPage && canMoveBack;cell.Controls.Add(first);:
}

The alternative style for the pager—numeric pages listed in a drop-down list—is built as follows:

private void BuildNumericPagesUI(TableCell cell)
{// Render a drop-down list  DropDownList pageList = new DropDownList();pageList.ID = "PageList";pageList.AutoPostBack = true;pageList.SelectedIndexChanged += new EventHandler(PageList_Click);pageList.Font.Name = this.Font.Name;pageList.Font.Size = Font.Size;pageList.ForeColor = ForeColor;if (TotalPages <=0 || CurrentPageIndex == -1){pageList.Items.Add("No pages");pageList.Enabled = false;pageList.SelectedIndex = 0; }else // Populate the list{for(int i=1; i<=TotalPages; i++){ListItem item = new ListItem(i.ToString(), (i-1).ToString());pageList.Items.Add(item);}pageList.SelectedIndex = CurrentPageIndex;}
}

All event handlers—Click and SelectedIndexChanged—end up changing the currently displayed page. Both methods fall into a common GoToPage private method.

private void first_Click(object sender, EventArgs e)
{GoToPage(0);
}
private void PageList_Click(object sender, EventArgs e)
{DropDownList pageList = (DropDownList) sender;int pageIndex = Convert.ToInt32(pageList.SelectedItem.Value);GoToPage(pageIndex);
}
private void GoToPage(int pageIndex)
{// Prepares event dataPageChangedEventArgs e = new PageChangedEventArgs();e.OldPageIndex = CurrentPageIndex;e.NewPageIndex = pageIndex;// Updates the current indexCurrentPageIndex = pageIndex;// Fires the page changed eventOnPageIndexChanged(e);// Binds new dataDataBind();
}

Handlers for other navigation buttons differ from first_Click only for the page number they pass to the GoToPage method. The GoToPage method is responsible for the PageIndexChanged event and for firing the data-binding process. It prepares the event data (old and new page index) and triggers the event. The GoToPage is defined as private but you can programmatically change the displayed page using the CurrentPageIndex property.

public int CurrentPageIndex
{get {return Convert.ToInt32(ViewState["CurrentPageIndex"]);}set {ViewState["CurrentPageIndex"] = value;}
}

CurrentPageIndex property, like all the properties listed in Table 1, has a pretty simple implementation. It saves its content to, and restore it from, the viewstate. The page index is validated and used during the data binding step.

The Data Binding Step

The DataBind method is common to all ASP.NET controls and, for data-bound controls, it triggers the refresh of the user interface to reflect new data. The SqlPager control uses this method to start the data retrieval operation based on the values of the SelectCommand and ConnectionString properties. It goes without saying that the process aborts if any of these properties are blank. Likewise, the data binding step is canceled if the buddy control doesn't exist. To locate the buddy control, the DataBind method uses the FindControl function on the Page class. It stems from this that the buddy control must be a direct child of the main form.

The control to paginate can't be an arbitrary ASP.NET server control. It must be either a list control or a base data list. More generally, the buddy control must expose the DataSource property and implement the DataBind method. These are actually the only requirements for a potential pageable control. All controls in the Microsoft® .NET Framework that inherit from either ListControl or BaseDataList fulfill the first requirement; all Web controls, instead, by design meet the DataBind requirement. With the current implementation, you can't use the SqlPager control to page a Repeater. Unlike its companion controls, DataList and DataGrid, the Repeater doesn't inherit BaseDataList, nor does it provide the features of a list control. The table below lists the controls you can page with SqlPager.

Table 2. Data-bound controls that can be paged by the SqlPager control

Control Description
CheckBoxList Derived from ListControl, renders as a list of check boxes.
DropDownList Derived from ListControl, renders as a drop-down list of strings.
ListBox Derived from ListControl, renders a scrollable list of strings.
RadioButtonList Derived from ListControl, renders as a list of radio buttons.
DataList Derived from BaseDataList, represents a list of templated data items.
DataGrid Derived from BaseDataList, represents a tabular grid of data items. The DataGrid is the only ASP.NET control to embed a rich paging engine.

The code below illustrates the data binding process as implemented by the SqlPager control.

public override void DataBind()
{// Fires the data binding eventbase.DataBinding();// Controls must be recreated after data binding ChildControlsCreated = false;// Ensures the control exists and is a list control_controlToPaginate = Page.FindControl(ControlToPaginate);if (_controlToPaginate == null)return;if (!(_controlToPaginate is BaseDataList || _controlToPaginate is ListControl))return;// Ensures enough info to connect and query is specifiedif (ConnectionString == "" || SelectCommand == "")return;// Fetch dataif (PagingMode == PagingMode.Cached)FetchAllData();elseFetchPageData();// Bind data to the buddy controlBaseDataList baseDataListControl = null;ListControl listControl = null;if (_controlToPaginate is BaseDataList){baseDataListControl = (BaseDataList) _controlToPaginate;baseDataListControl.DataSource = _dataSource; baseDataListControl.DataBind();return;}if (_controlToPaginate is ListControl){listControl = (ListControl) _controlToPaginate;listControl.Items.Clear(); listControl.DataSource = _dataSource; listControl.DataBind();return;}
}

A different fetch routine is called according to the value of the PagingMode property. In any case, the resultset is bound to an instance of the PagedDataSource class. This class provides some facilities to page data. In particular, when the whole data set is cached, the class automatically retrieves the records for the current page and returns boolean values to give information about the first and the last page. I'll come back to the internal structure of this class shortly. In the listing above, the helper PagedDataSource object is represented by the _dataSource variable.

Next, the SqlPager control figures out the type of the buddy control and binds the contents of the PagedDataSource object to the buddy's DataSource property.

At a certain point, the DataBind method shown above also resets the ChildControlsCreated property to false. Why is it so?

When the page that contains the pager posts back, all controls are recreated; the pager is no exception. Normally, all controls and their children are created by the time the page is ready for rendering. A moment before each control receives the OnPreRender notification, the protected EnsureChildControls method is called so that each control can build its control tree. When this happens, the data binding process is complete and the new data has been cached.

However, when the page posts back because one of the pager constituent controls is clicked (that is, a user clicked to change the page), the control tree of the pager is built, long before the rendering stage. In particular, the tree must be in place when the related server-side event is processed, and consequently, before the data binding starts. The rub is that the data binding modifies the page index and this must be reflected in the user interface. If you don't take countermeasures, the page index in the pager won't be refreshed when users switch to another page.

There are various ways to fix this, but it's important to be aware of the issue and its real causes. You can avoid building a control tree and generate all the output in the Render method. Alternatively, you can modify the portions of the tree affected by the data binding changes. I've chosen a third way, one that requires less code and works whatever portions of the control's user interface are affected by the data binding changes. By setting the ChildControlsCreated property to false, you invalidate any previously created tree of controls. As a result, the tree will be recreated prior to rendering.

The Paging Engine

The SqlPager control supports two ways of retrieving data—cached and non-cached. In the former case, the select command executes as is and the whole resultset is bound to the internal paged data source object. The PagedDataSource object will automatically return the records that fit into a particular page. The PagedDataSource class is also the system component working behind the DataGrid default paging mechanism.

Retrieving all the records to display only the few that fit into a page is not always a smart approach. Due to the stateless nature of Web applications, in fact, the potentially large query runs every time the page is requested. To be effective, the cached approach must rely on some sort of cache object and the ASP.NET Cache object is an excellent candidate. The use of caching techniques make the application run faster, but also offers a snapshot of the data that doesn't reflect the most recent changes. In addition, more memory is used on the Web server. Paradoxically, this might even create a scalability problem if large a amount of data is cached on a per-session basis. The Cache container is global to the application; if the data is stored in it on a per-session basis, you also need to generate session-specific entry names.

On the up side of the Cache object is full support for expiration policies. In other words, the data stored in the cache can be automatically released after a certain duration. The following code illustrates a private method of the SqlPager class that fetches data and stores it in the cache.

private void FetchAllData()
{// Looks for data in the ASP.NET CacheDataTable data;data = (DataTable) Page.Cache[CacheKeyName];if (data == null){// Fix SelectCommand with order-by infoAdjustSelectCommand(true);// If data expired or has never been fetched, go to the databaseSqlDataAdapter adapter = new SqlDataAdapter(SelectCommand, ConnectionString);data = new DataTable();adapter.Fill(data);Page.Cache.Insert(CacheKeyName, data, null, DateTime.Now.AddSeconds(CacheDuration), System.Web.Caching.Cache.NoSlidingExpiration);}// Configures the paged data source componentif (_dataSource == null)_dataSource = new PagedDataSource(); _dataSource.DataSource = data.DefaultView;  _dataSource.AllowPaging = true;_dataSource.PageSize = ItemsPerPage;TotalPages = _dataSource.PageCount; // Ensures the page index is valid ValidatePageIndex();if (CurrentPageIndex == -1){_dataSource = null;return;}// Selects the page to view_dataSource.CurrentPageIndex = CurrentPageIndex;
}

The name of the cache entry is unique to the control and request. It includes the URL of the page and the ID of the control. The data is bound to the cache for the specified number of seconds. To give items an expiration, you must use the Cache.Insert method. The following, and simpler, code will add the item to the cache but doesn't include any expiration policy.

Page.Cache[CacheKeyName] = data;

The PagedDataSource object gets the data to page through its DataSource property. It is worth noting that the DataSource property of the PagedDataSource class accepts only IEnumerable objects. The DataTable doesn't meet this requirement; that's why I resort to the DefaultView property.

The SelectCommand property determines the query run against the SQL Server database. This string is expected to be in the form SELECT-FROM-WHERE. No ORDER BY clause is supported and, if specified, is stripped off. This is just what the AdjustSelectCommand method does. Any sorting information can be specified using the SortField property. The AdjustSelectCommand method itself adds a proper ORDER BY clause based on the value of SortField. Is there a reason for this?

When the pager works in NonCached mode, the original query is modified to ensure that only the records for the current page are retrieved. The real query text that hits SQL Server takes the following form.

SELECT * FROM
(SELECT TOP ItemsPerPage * FROM
(SELECT TOP ItemsPerPage*CurrentPageIndex * FROM
(SelectCommand) AS t0
ORDER BY SortField ASC) AS t1
ORDER BY SortField DESC) AS t2
ORDER BY SortField

The query makes up for the lack of a ROWNUM clause in SQL Server 2000 and reorders records in such a way that only the n.th block of x items is returned properly sorted. You specify the base query and the pager breaks it up into smaller pages. Only the records that fit into a page are returned. As you can see, the query above needs to handle the sort field aside from the query. That's why I added the SortField as a separate property. The only drawback of this code is that it defaults to ascending order. By making the ASC/DESC keywords parametric, you can make this code virtually perfect:

private void FetchPageData()
{// Need a validated page index to fetch data.// Also need the virtual page count to validate the page indexAdjustSelectCommand(false);VirtualRecordCount countInfo = CalculateVirtualRecordCount();TotalPages = countInfo.PageCount;// Validate the page number (ensures CurrentPageIndex is valid or -1)ValidatePageIndex();if (CurrentPageIndex == -1)return;// Prepare and run the commandSqlCommand cmd = PrepareCommand(countInfo);if (cmd == null)return;SqlDataAdapter adapter = new SqlDataAdapter(cmd);DataTable data = new DataTable();adapter.Fill(data);// Configures the paged data source componentif (_dataSource == null)_dataSource = new PagedDataSource(); _dataSource.AllowCustomPaging = true;_dataSource.AllowPaging = true;_dataSource.CurrentPageIndex = 0;_dataSource.PageSize = ItemsPerPage;_dataSource.VirtualCount = countInfo.RecordCount;_dataSource.DataSource = data.DefaultView;
}

In NonCached mode, the PagedDataSource object doesn't hold the whole data source and can't figure out the total number of pages to page through. For this reason, you have to flag the AllowCustomPaging property and provide a virtual count of the records in the data source. The virtual count is normally retrieved using a SELECT COUNT(*) query. This model is nearly identical to DataGrid custom paging. Finally, the current page index to select in the PagedDataSource object is always 0, because one page of records is actually stored.

So much for the implementation of the SqlPager control, let's have a look at how you would use it.

Working with the SqlPager Control

Let's consider a sample page that contains a ListBox control. To use the pager, make sure the .aspx page properly registers the control's assembly.

<%@ Register TagPrefix="expo" Namespace="DevCenter" Assembly="SqlPager" %>

The control's markup depends on the properties actually set. The following markup is a reasonable example:

<asp:listbox runat="server" id="ListBox1" Width="300px" Height="168px"DataTextField="companyname" />
<br>
<expo:SqlPager runat="server" id="SqlPager1" Width="300px" ControlToPaginate="ListBox1" SelectCommand="SELECT customerid, companyname FROM customers" ConnectionString="SERVER=localhost;DATABASE=northwind;UID=..."SortKeyField="companyname" />
<br>
<asp:button runat="server" id="LoadFirst1" Text="Load first page" />

Besides the pager, the page contains a listbox and a button. The listbox will show the contents of each page; the button simply serves to populate the listbox for the first time. The button has a click event handler defined as follows.

private void LoadFirst1_Click(object sender, EventArgs e) {SqlPager1.CurrentPageIndex = 0;SqlPager1.DataBind();
}

Figure 2 shows the page in action.

Figure 2. The SqlPager control works with a ListBox control.

An even more interesting example can be built using a DataList control. The idea is to use the pager to navigate through the personal record of each Northwind employee. The DataList looks like in the following listing.

<asp:datalist runat="server" id="DataList1" Width="300px" Font-Names="Verdana" Font-Size="8pt">
<ItemTemplate>
<table bgcolor="#f0f0f0" style="font-family:verdana;font-size:8pt;"><tr><td valign="top"><b><%# DataBinder.Eval(Container.DataItem, "LastName") + ", " + DataBinder.Eval(Container.DataItem, "firstname") %></b></td></tr><tr><td><span style="color:blue;"><i><%# DataBinder.Eval(Container.DataItem, "Title")%></i></span><p><img style="float:right;" src='image.aspx?id=<%# DataBinder.Eval(Container.DataItem, "employeeid")%>' /><%# DataBinder.Eval(Container.DataItem, "Notes") %></td></tr>
</table>
</ItemTemplate>
</asp:datalist>

It displays name and title of the employee in the first row of the table and then the picture surrounded by the notes. The picture is retrieved using a special .aspx page that returns JPEG data fetched from the database.

The pager can be placed anywhere in the page. In this case let's place it just above the buddy DataList control.

Figure 3. The SqlPager pages a DataList control

Does it make sense using the SqlPager control with a DataGrid control? It depends. The DataGrid already comes with an embedded paging engine based on the same PagedDataSource object I used here. So as long as you need to page through a single set of records displayed in a tabular format, the SqlPager is unnecessary. However, in master/detail scenarios, using the two controls together is not a farfetched idea. For example, if you add to the previous screenshot a DataGrid to display the orders managed by the employee, you have two related paging engines in the same page—one that pages through employees and one to scroll the related orders.

Summary

No matter what type of application you are building—a Web application, a Microsoft® Windows® application, or a Web service—you can rarely afford downloading and caching the whole data source you are expected to display. Sometimes, test environments might lead you to believe such a solution works great and is preferable. But test environments can be misleading. The size of the data source does matter, and the more the application must be scaled, the more size matters.

In ASP.NET, only the DataGrid control has built-in paging capabilities. However, a paging engine is made of rather boilerplate code and, with a bit of work, can be generalized and adapted to work with several different controls. The SqlPager control presented in this article does just that. It takes care of downloading the data and cuts it into pages to display through a buddy control. The control can retrieve and cache the whole data set or just ask SQL Server for the few records to display in the selected page. I said SQL Server, and that's another important point. The SqlPager works only with SQL Server and can't be used to retrieve data using OLE DB or ODBC. Nor can you use it to access Oracle or DB2 archives.

To make a really generic SQL pager component, you should generalize the data access layer and build a sort of factory class that creates connections, commands, and adapters using the appropriate data provider. On the other hand, bear in mind that setting up a paging engine for various SQL sources is worse than your worst nightmare. The approach presented here works only for SQL Server 7.0 and newer. The TOP clause is the discriminatory feature. Using server cursors and temporary tables, it can be adapted to a larger range of DBMS systems. But that would make your code significantly more complex.

About the Author

Dino Esposito is a trainer and consultant based in Rome, Italy. Member of the Wintellect team, Dino specializes in ASP.NET and ADO.NET, and spends most of his time teaching and consulting across Europe and the United States. In particular, Dino manages the ADO.NET courseware for Wintellect and writes the Cutting Edge column for MSDN Magazine.

转载于:https://www.cnblogs.com/kaixin110/archive/2008/02/19/1073886.html

Creating a Pager Control for ASP.NET以及Dino Esposito 分页组件的一个 Bug相关推荐

  1. 似乎是发现了asp.net ajaxToolkit中TAB控件的一个BUG

    刚才想整理一下文章,不知道怎么搞的,一下子就没了,从新帖. 在一个项目当中,使用了asp.net ajaxToolkit中的TAB控件. 现在TAB中做布局,内容等等,没发现问题. 开始处理逻辑了,问 ...

  2. asp.net core 排序过滤分页组件:sieve(2)表达式树的复习

    在Sieve组件中使用了很多关于表达式树的知识,但在我们日常的工作中写表达式树的机会是非常少的,至少在我的编程生涯中没怎么写过表达式树(可能也就是3,4次).所以,为了能够看懂Sieve里面的源代码, ...

  3. Error Creating Control when creating a custom control

    如果你在创建ASP.NET的Server Control 是遇到报错: "Error Creating Control" when creating a custom contro ...

  4. A CAPTCHA Server Control for ASP.NET C# 版

    今晚看到了A CAPTCHA Server Control for ASP.NET , 一个非常棒的CAPTCHA控件, 因为它原来是VB写的, 我照着用C#抄了一遍.... 原版下载地址: Down ...

  5. Asp.Net Web API 2第十七课——Creating an OData Endpoint in ASP.NET Web API 2(OData终结点)...

    原文:Asp.Net Web API 2第十七课--Creating an OData Endpoint in ASP.NET Web API 2(OData终结点) 前言 很久没更新博客了,加上刚过 ...

  6. 嵌入Windows User Control到ASP.NET web form

    嵌入Windows User Control到ASP.NET web form 实现步骤: 新建Windows User Control项目,加入自己想做的事情(一些控件.方法.属性) 新建或打开We ...

  7. 创建定制的ASP.NET AJAX非可视化客户端组件

    一.简介 在本文中,我们将共同讨论如何创建一个由基类Sys.Component派生的ASP.NET AJAX非可视化客户端组件,并将展示它在Web页面中的用法. 具体说来,你将学习如何实现: ◆使用p ...

  8. 通过 ASP.NET 2.0 的数据增强功能来提高负载和减少代码 --作者:Dino Esposito

    [导读]在ASP.NET 2.0中引入了数据源组件,相对于1.x版本,在开发效率和运行效率上都得到了极大的提升,而本文将详细介绍新的数据控件,包括编程接口.用法和实现. 绝大多数Web应用程序都会使用 ...

  9. 让asp.net默认的上传组件支持进度条反映(转)

    对于web下的上传,实际上更多的时候不用上传太大东西,asp.net默认的上传组件足够用了,美中不足就是没有上传进度反映,所以现在要做的就是在asp.net默认的上传基础上加上进度反映. 关于web上 ...

最新文章

  1. Pygame Rect区域位置(图解)
  2. 无状态会话bean(1)---定义
  3. 兰州交通大学计算机科学与技术排名,兰州交通大学怎么样 全国排名是多少
  4. css学习_css书写规范
  5. 5个导致主线程卡顿较鲜为人知的元凶
  6. bzoj1831 逆序对 (dp+树状数组)
  7. 使用ssh连接虚拟机保姆级教程
  8. Linux socks5转http
  9. 如何设计一个应用软件
  10. fdisk分区以及格式化磁盘简要步骤
  11. opencv图像对比度
  12. 洲际酒店集团与国际青年成就达成全球合作
  13. flask+pytorch部署深度学习(图像识别)项目
  14. 学校机房的网屏蔽了某些网站无法访问怎么办?
  15. 【论文笔记】An End-to-End Model for QA over KBs with Cross-Attention Combining Global Knowledge
  16. 记录一下pageX,offsetX,clientX,offsetLeft,offsetWidth,pageYoffset,scrollTop,scrollY,等。原文摘自MDN文档库。保证正确。
  17. 如何定制B2C电商网站
  18. 阿里云混合云精彩亮相中国信通院2021混合云大会
  19. Huhu Command 正式发布!实用命令行存取工具
  20. 排序方法python实现_八字排盘,四柱八字排盘算命,免费排八字,卜易居在线排盘系统...

热门文章

  1. 提高学生对计算机学科学习兴趣的研究的结题报告,《如何在体育教学中运用体育游戏》的研究结题报告...
  2. kali卸载firefox_kali 安装最新firefox的悲惨经历
  3. ionic4 ngFor中使用ngIf
  4. linux命令vi作用,详解Linux常用命令的用法(二)――――文本编辑器命令vi/vim
  5. 如何获取登录token值_Token认证,如何快速方便获取用户信息
  6. pcb钻孔披锋改善报告_铜基板的小孔加工改善研究
  7. linux防火墙规则命令意思,linux防火墙iptables配置规则分享
  8. aptx android8,秒杀苹果无线音频!Android 8.0蓝牙音质支持aptxHD/LDAC
  9. linux c语言 utf8读写,关于在C程序中处理UTF-8文本的方法详解
  10. c++fabs函数_C语言中常用的标准库函数有哪些?