ASP.NET 4.5 GridView: PostBack of last page
As I was not satisfied with my proposed solution (brought too many issues for future development), I have decided to go the "Control development" way to ensure everything is created correctly. It occurs no matter the type of PagerTemplate I am using - I am using one, the postback does not fire from Last page. Hopefully I am not the only one :-)
For those who are experiencing the same issues, I am providing custom control that works OK (of course, does not implement PagerSettings and PagerTemplates, but brings the basic functionality).
public class ExtendedGridView : System.Web.UI.WebControls.GridView{ protected override void InitializePager(System.Web.UI.WebControls.GridViewRow row, int columnSpan, System.Web.UI.WebControls.PagedDataSource pagedDataSource) { HtmlGenericControl ul = new HtmlGenericControl("ul"); ul.Attributes.Add("class", "pagination pull-right"); AddPager(ul, commandArgument: "First", text: "<span class='glyphicon glyphicon-fast-backward'></span>"); for (int i = 0; i < PageCount; i++) { AddPager(ul, i); } AddPager(ul, commandArgument: "Last", text: "<span class='glyphicon glyphicon-fast-forward'></span>"); row.CssClass = "table-footer"; row.Cells.Add(new System.Web.UI.WebControls.TableCell()); row.Cells[0].ColumnSpan = columnSpan; row.Cells[0].Controls.AddAt(0, ul); } protected virtual void navigate_Click(object sender, EventArgs e) { string commandArgument = ((System.Web.UI.WebControls.LinkButton)sender).CommandArgument.ToString(); int pageIndex = 0; if (!int.TryParse(commandArgument, out pageIndex)) { switch (commandArgument) { case "First": pageIndex = 0; break; case "Last": pageIndex = PageCount - 1; break; case "Prev": pageIndex = PageIndex - 1; break; case "Next": pageIndex = PageIndex + 1; break; } } OnPageIndexChanging(new System.Web.UI.WebControls.GridViewPageEventArgs(pageIndex)); } private void AddPager(System.Web.UI.Control parentControl, int pageIndex = -1, string commandArgument = null, string text = null) { HtmlGenericControl li = new HtmlGenericControl("li"); if (pageIndex == PageIndex) li.Attributes.Add("class", "active"); System.Web.UI.WebControls.LinkButton button = new System.Web.UI.WebControls.LinkButton(); button.CommandName = "Page"; if (text == null) button.Text = (pageIndex + 1).ToString(); else button.Text = text; if (string.IsNullOrWhiteSpace(commandArgument)) button.CommandArgument = string.Format("{0}", pageIndex); else button.CommandArgument = commandArgument; button.Click += navigate_Click; li.Controls.Add(button); parentControl.Controls.Add(li); }}
Just make sure your markup is: - AllowPaging="true" - AllowCustomPaging="false" - PageSize="whatever" - and you still provide VirtualItemCount in code behind
Code behind using SelectMethod could be like this:
protected void Page_Load(object sender, EventArgs e){}public IEnumerable SearchResults_GetData(int startRowIndex, int maximumRows, out int totalRowCount, string sortByExpression){ int pageIndex = (int)(startRowIndex / maximumRows); return Membership.GetAllUsers(pageIndex, maximumRows, out totalRowCount);}protected void SearchResults_PageIndexChanging(object sender, GridViewPageEventArgs e){ SearchResults.PageIndex = e.NewPageIndex; SearchResults.DataBind();}
As this is several time I am created serverside controls for .NET using excellent Bootstrap framework, I created a git here https://github.com/Gitzerai/Bootstrap.NET where I put controls that render in a bootstrap proper way.
I found this to happen for me too. On a postback, the last page would "fill-out" the last page with rows from the first page, except the first row after the data. Example: if page size was 10 and I had 25 rows. The last page would show rows 21-25 initially, and then on a postback it would add rows 7-10.
I added the following "hack" to the gridview's RowCreated to prevent drawing these phantom rows. GV is the gridview. DataRowCount is a function which returns the number of rows from the datasource. PageIndex is a property uses a session to hold the current Page Index.
If e.Row.RowType = DataControlRowType.DataRow Then Dim RowsLeft As Integer = DataRowCount() - (GV.PageSize * PageIndex) Dim RowsExpected As Integer If RowsLeft > GV.PageSize Then RowsExpected = GV.PageSize Else RowsExpected = RowsLeft End If If e.Row.RowIndex >= RowsExpected Then 'Last page isn't full, need to force writing nothing out for extra rows e.Row.SetRenderMethodDelegate(New RenderMethod(AddressOf RenderNothing)) End If End If
And then I added the following funtion:
Public Sub RenderNothing(writer As HtmlTextWriter, container As Control)End Sub
Since RowCreated happens before ViewState is loaded, the GV's PageIndex wasn't available. So I created a property to hold the PageIndex. So my code now updates the new property and the property saves it to session and updates the GV's property. And this is the property I added
Private Const SS_PagerControl_PageIndex As String = "SSPagerControl_PageIndex"<Bindable(True), CategoryAttribute("Paging"), DefaultValue("0")>Public Property PageIndex As Integer Get If Session(SS_PagerControl_PageIndex) Is Nothing Then Return 0 End If Return CInt(Session(SS_PagerControl_PageIndex)) End Get Set(ByVal value As Integer) Session(SS_PagerControl_PageIndex) = value GV.PageIndex = value RebindGrid() End SetEnd Property