ASP.NET

Paging a Repeater in ASP.NET: custom pager and the ListView/DataPager alternative

ASP.NET · · Published 2026-09-28

The Repeater gives total control over markup but has no paging. The DataPager control only works with controls that implement IPageableItemContainer, which in the box means the ListView. So you either page the Repeater yourself, or use a ListView, whose templates are as free as a Repeater's.

Option 1: Repeater with PagedDataSource

<asp:Repeater ID="rptNews" runat="server" ItemType="Demo.NewsItem">
    <ItemTemplate>
        <article><h3><%#: Item.Title %></h3><p><%#: Item.Summary %></p></article>
    </ItemTemplate>
</asp:Repeater>

<asp:Repeater ID="rptPager" runat="server" OnItemCommand="rptPager_ItemCommand">
    <HeaderTemplate><nav class="pager" aria-label="Pages"></HeaderTemplate>
    <ItemTemplate>
        <asp:LinkButton runat="server" CommandName="Page" CommandArgument='<%# Container.DataItem %>'
            Text='<%# (int)Container.DataItem + 1 %>'
            Enabled='<%# (int)Container.DataItem != CurrentPage %>' />
    </ItemTemplate>
    <FooterTemplate></nav></FooterTemplate>
</asp:Repeater>
public partial class News : Page
{
    private const int PageSize = 5;

    protected int CurrentPage
    {
        get => (int)(ViewState["page"] ?? 0);
        set => ViewState["page"] = value;
    }

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

    private void Bind()
    {
        List<NewsItem> all = NewsRepository.All(); // ordered list
        var pds = new PagedDataSource
        {
            DataSource = all,
            AllowPaging = true,
            PageSize = PageSize,
            CurrentPageIndex = Math.Min(CurrentPage, Math.Max(0, (all.Count - 1) / PageSize))
        };
        rptNews.DataSource = pds;
        rptNews.DataBind();

        rptPager.DataSource = Enumerable.Range(0, pds.PageCount);
        rptPager.DataBind();
        rptPager.Visible = pds.PageCount > 1;
    }

    protected void rptPager_ItemCommand(object source, RepeaterCommandEventArgs e)
    {
        CurrentPage = Convert.ToInt32(e.CommandArgument);
        Bind();
    }
}

PagedDataSource wraps any IEnumerable that supports counting (a list, a DataView…) and exposes only the current page. The pager is just another Repeater bound to page numbers. A disabled LinkButton renders without a link, marking the current page.

This loads every row and keeps one page; for large tables, query only the page you need with OFFSET … FETCH and compute the page count from a COUNT(*).

Option 2: ListView with DataPager

<asp:ListView ID="lvNews" runat="server" ItemType="Demo.NewsItem"
    SelectMethod="GetNews" ItemPlaceholderID="ph">
    <LayoutTemplate>
        <div class="news"><asp:PlaceHolder ID="ph" runat="server" /></div>
        <asp:DataPager ID="dp" runat="server" PageSize="5" PagedControlID="lvNews">
            <Fields><asp:NumericPagerField ButtonCount="10" /></Fields>
        </asp:DataPager>
    </LayoutTemplate>
    <ItemTemplate>
        <article><h3><%#: Item.Title %></h3><p><%#: Item.Summary %></p></article>
    </ItemTemplate>
</asp:ListView>
public IQueryable<NewsItem> GetNews()
{
    return NewsRepository.All().AsQueryable();
}

With model binding (SelectMethod, .NET 4.5 and later), the ListView handles page changes itself. Return an IQueryable from Entity Framework and the paging is translated to SQL. The DataPager can also stand outside the ListView if PagedControlID points to it.

Which to choose

For new pages, the ListView is less code and gives the same freedom of markup. Keep the Repeater approach when you are adding paging to an existing Repeater with many templates and event handlers. The basics of the Repeater are in Bind a Repeater control.

Official documentation