Question

I have discovered an issue with GridView pager in ASP.NET 4.5 and 4.5.1 version. Since .NET 2 - 4 I have never experienced such problem.

To the point, I have a gridview that I am populating with data in code behind like this:

protected int CurrentPage { get { return SearchResults.PageIndex + 1; } }

protected void Page_Load(object sender, EventArgs e)
{
    if(!IsPostBack)
         BindGrid();
}

private void BindGrid()
{
    int totalRowCount = 0;
    SearchResults.DataSource = GetPageData(SearchResults.PageIndex, SearchResults.PageSize, out totalRowCount);
    SearchResults.VirtualItemCount = totalRowCount;                   
    SearchResults.DataBind();
}

private IEnumerable GetPageData(int start, int count, out int totalRowCount)
{
    return Membership.GetAllUsers(start, count, out totalRowCount);
}

protected void SearchResults_PageIndexChanging(object sender, GridViewPageEventArgs e)
{            
    SearchResults.PageIndex = e.NewPageIndex;            
    BindGrid();
}

The problem is that if I hit the last page of GridView and I try to return to any other page, my PageIndexChanging does not fire. The problem occurs only if the last page does not have same count of records as PageSize. The behavior is that my page gets reloaded, the page of gridview is filled with empty data rows up to the PageSize. The VirtualItemCount represents correctly total ItemCount.

Markup, if you find something there:

<asp:GridView runat="server" CellPadding="0" CellSpacing="0" GridLines="None" CssClass="table table-condensed table-striped table-footer"
        ID="SearchResults" AllowCustomPaging="true" AllowPaging="true" PageSize="6" OnPageIndexChanging="SearchResults_PageIndexChanging" AutoGenerateColumns="false" UseAccessibleHeader="true">
...
<PagerTemplate>
            <span class="pull-left">
                <strong><%= SearchResults.PageIndex * SearchResults.PageSize + 1 %></strong> - <strong><%= CurrentPage * SearchResults.PageSize %></strong>
            </span>
            <span class="pull-left">
                Total records: <strong><%= SearchResults.VirtualItemCount %></strong>
            </span>
            <ul class="pagination pull-right">
                <li><asp:LinkButton runat="server" CommandName="Page" CommandArgument="First"><span class="glyphicon glyphicon-backward"></span></asp:LinkButton></li>

                <li><asp:LinkButton runat="server" CommandName="Page" CommandArgument="<%# CurrentPage - 2 %>" Visible="<%# CurrentPage > 2 %>"><%= CurrentPage - 2 %> </asp:LinkButton></li>
                <li><asp:LinkButton runat="server" CommandName="Page" CommandArgument="<%# CurrentPage - 1 %>" Visible="<%# CurrentPage > 1 %>"><%= CurrentPage - 1 %> </asp:LinkButton></li>
                <li class="active"><a href="#"><%= CurrentPage %></a></li>
                <li><asp:LinkButton runat="server" CommandName="Page" CommandArgument="<%# CurrentPage + 1 %>" Visible="<%# CurrentPage < SearchResults.PageCount %>"><%= CurrentPage + 1 %></asp:LinkButton></li>
                <li><asp:LinkButton runat="server" CommandName="Page" CommandArgument="<%# CurrentPage + 2 %>" Visible="<%# CurrentPage < SearchResults.PageCount - 1 %>"><%= CurrentPage + 2 %></asp:LinkButton></li>

                <li><asp:LinkButton runat="server" CommandName="Page" CommandArgument="Last"><span class="glyphicon glyphicon-forward"></span></asp:LinkButton></li>
            </ul>
        </PagerTemplate>
</asp:GridView>

Thank you very much, I have been dealing with this for days. Of course I could use QueryString approach, but since I will be using a lot of tables, I would like to stick with the postback approach, if possible...


EDIT:

Simpliest workaround I found was doing a BindGrid on every Page_Load. For some reason the PageIndexChanging just not fire on last page unless LastPageSize == PageSize. Then the DataBind is not recalled in order to bind CommandArguments, hence I cannot postback correctly.

On the other hand, it is not very clear and could possibly cause issues... At least double binding = double calls to SQL for data on pagechange... Otherwise, I have no idea how to force PageIndexChanging here and seems like a new .NET issue to me.

Was it helpful?

Solution

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.

OTHER TIPS

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 Set
End Property
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top