首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> ASP.NET 基礎 >> ASP.NET 基礎

讓 Repeater 與 GridView 支援 DataPager 分頁:實作 IPageableItemContainer 自訂控制項

     編輯:ASP.NET 基礎

核心問題

ASP.NET 內建 DataPager 僅支援實作 IPageableItemContainer 的控制項(如 ListView)。Repeater、GridView 預設不支援,需自訂控制項繼承並實作介面。

自訂 DataPagerRepeater

[ToolboxData("<{0}:DataPagerRepeater runat=server PersistentDataSource=true>")]
public class DataPagerRepeater : Repeater, IPageableItemContainer, INamingContainer
{
    // IPageableItemContainer 介面屬性
    public int MaximumRows => ViewState["MaximumRows"] as int? ?? -1;
    public int StartRowIndex => ViewState["StartRowIndex"] as int? ?? -1;
    public int TotalRows { get => ViewState["TotalRows"] as int? ?? -1; set => ViewState["TotalRows"] = value; }

    public bool PersistentDataSource { get => ViewState["PersistentDataSource"] as bool? ?? true; set => ViewState["PersistentDataSource"] = value; }
    public bool PagingInDataSource { get => ViewState["PagingInDataSource"] as bool? ?? false; set => ViewState["PagingInDataSource"] = value; }

    public bool NeedsDataSource
    {
        get
        {
            if (PagingInDataSource) return true;
            if (!IsBoundUsingDataSourceID && !Page.IsPostBack) return true;
            if (!IsBoundUsingDataSourceID && !PersistentDataSource && Page.IsPostBack) return true;
            return false;
        }
    }

    // 事件
    public event EventHandler TotalRowCountAvailable;
    public event EventHandler FetchingData;

    // 載入 ViewState 後重新繫結(可選)
    protected override void LoadViewState(object savedState)
    {
        base.LoadViewState(savedState);
        // 若需在 PostBack 還原資料來源,可在此處理
    }

    protected override void OnLoad(EventArgs e)
    {
        if (Page.IsPostBack && NeedsDataSource && FetchingData != null)
        {
            if (PagingInDataSource) SetPageProperties(StartRowIndex, MaximumRows, true);
            FetchingData(this, null);
        }
        if (!IsBoundUsingDataSourceID && PersistentDataSource && ViewState["DataSource"] != null)
        {
            this.DataSource = ViewState["DataSource"];
            this.DataBind();
        }
        if (IsBoundUsingDataSourceID) this.DataBind();
        base.OnLoad(e);
    }

    // DataPager 呼叫設定分頁屬性
    public void SetPageProperties(int startRowIndex, int maximumRows, bool databind)
    {
        ViewState["StartRowIndex"] = startRowIndex;
        ViewState["MaximumRows"] = maximumRows;
        if (TotalRows > -1 && TotalRowCountAvailable != null)
            TotalRowCountAvailable(this, new PageEventArgs(startRowIndex, maximumRows, TotalRows));
    }

    protected override void OnDataPropertyChanged()
    {
        if (MaximumRows != -1 || IsBoundUsingDataSourceID) RequiresDataBinding = true;
        base.OnDataPropertyChanged();
    }

    // 關鍵:Render 時只輸出當前頁資料
    protected override void RenderChildren(HtmlTextWriter writer)
    {
        if (!PagingInDataSource && MaximumRows != -1)
        {
            foreach (RepeaterItem item in Items)
            {
                if (item.ItemType == ListItemType.Item || item.ItemType == ListItemType.AlternatingItem)
                {
                    item.Visible = item.ItemIndex >= StartRowIndex && item.ItemIndex < StartRowIndex + MaximumRows;
                }
            }
        }
        base.RenderChildren(writer);
    }

    // 取得資料時計算總筆數
    protected override IEnumerable GetData()
    {
        var data = base.GetData();
        if (!PagingInDataSource && MaximumRows != -1 && data != null)
        {
            int count = 0;
            foreach (var _ in data) count++;
            ViewState["TotalRows"] = count;
            if (!IsBoundUsingDataSourceID && PersistentDataSource) ViewState["DataSource"] = DataSource;
            SetPageProperties(StartRowIndex, MaximumRows, true);
        }
        if (PagingInDataSource && !Page.IsPostBack) SetPageProperties(StartRowIndex, MaximumRows, true);
        return data;
    }
}

自訂 DataPagerGridView

public class DataPagerGridView : GridView, IPageableItemContainer
{
    private static readonly object EventTotalRowCountAvailable = new object();

    public DataPagerGridView() { PagerSettings.Visible = false; } // 隱藏內建分頁

    protected override int CreateChildControls(IEnumerable dataSource, bool dataBinding)
    {
        int rows = base.CreateChildControls(dataSource, dataBinding);
        if (AllowPaging)
        {
            int totalRowCount = dataBinding ? rows : ((ICollection)dataSource).Count;
            OnTotalRowCountAvailable(new PageEventArgs(StartRowIndex, MaximumRows, totalRowCount));
            if (TopPagerRow != null) TopPagerRow.Visible = false;
            if (BottomPagerRow != null) BottomPagerRow.Visible = false;
        }
        return rows;
    }

    void IPageableItemContainer.SetPageProperties(int startRowIndex, int maximumRows, bool databind)
    {
        int newPageIndex = startRowIndex / maximumRows;
        PageSize = maximumRows;
        if (PageIndex != newPageIndex)
        {
            bool cancelled = false;
            if (databind)
            {
                var args = new GridViewPageEventArgs(newPageIndex);
                OnPageIndexChanging(args);
                cancelled = args.Cancel;
                newPageIndex = args.NewPageIndex;
            }
            if (!cancelled)
            {
                PageIndex = newPageIndex;
                if (databind) OnPageIndexChanged(EventArgs.Empty);
            }
            if (databind) RequiresDataBinding = true;
        }
    }

    int IPageableItemContainer.StartRowIndex => PageSize * PageIndex;
    int IPageableItemContainer.MaximumRows => PageSize;

    public event EventHandler TotalRowCountAvailable
    {
        add => Events.AddHandler(EventTotalRowCountAvailable, value);
        remove => Events.RemoveHandler(EventTotalRowCountAvailable, value);
    }

    protected virtual void OnTotalRowCountAvailable(PageEventArgs e)
    {
        var handler = (EventHandler)Events[EventTotalRowCountAvailable];
        handler?.Invoke(this, e);
    }
}

編譯與工具箱整合

  1. 建立 Class Library 專案,加入上述兩個類別。
  2. 編譯產生 WYJ.Web.Controls.dll。
  3. Visual Studio 工具箱右鍵 → Choose Items → Browse 選取 DLL → 控制項自動出現。

ASPX 頁面使用範例

<%@ Register Assembly="WYJ.Web.Controls" Namespace="WYJ.Web.Controls" TagPrefix="WYJ" %>

<WYJ:DataPagerRepeater ID="rptLeaveword" runat="server" PersistentDataSource="true">
    <ItemTemplate>
        <div class="item">
            <h3><%# Eval("Title") %></h3>
            <p><%# Eval("Content") %></p>
        </div>
    </ItemTemplate>
</WYJ:DataPagerRepeater>

<div class="pager">
    <asp:DataPager ID="DataPager1" PagedControlID="rptLeaveword" runat="server" PageSize="10">
        <Fields>
            <asp:NextPreviousPagerField ShowFirstPageButton="True" FirstPageText="首頁" />
            <asp:NumericPagerField CurrentPageLabelCssClass="current" />
            <asp:NextPreviousPagerField ShowLastPageButton="True" LastPageText="尾頁" />
        </Fields>
    </asp:DataPager>
</div>

後端碼:切換 PageSize

protected void lnkbtn10_Click(object sender, EventArgs e) { DataPager1.PageSize = 10; UpdateCss(lnkbtn10); }
protected void lnkbtn20_Click(object sender, EventArgs e) { DataPager1.PageSize = 20; UpdateCss(lnkbtn20); }
protected void lnkbtn30_Click(object sender, EventArgs e) { DataPager1.PageSize = 30; UpdateCss(lnkbtn30); }

void UpdateCss(LinkButton active)
{
    foreach (var btn in new[] { lnkbtn10, lnkbtn20, lnkbtn30 })
        btn.CssClass = btn == active ? "currentpagesize" : "";
}

大量資料優化:PagingInDataSource = true

預設模式會一次撈回所有資料再前端分頁,資料量大時效能差。設定 PagingInDataSource="true",並處理 FetchingData 事件:

protected void Page_Load(object sender, EventArgs e)
{
    rptLeaveword.PagingInDataSource = true;
    rptLeaveword.FetchingData += (s, args) =>
    {
        int start = rptLeaveword.StartRowIndex;
        int max = rptLeaveword.MaximumRows;
        // 只查詢當頁資料:SELECT ... OFFSET @start ROWS FETCH NEXT @max ROWS ONLY
        var dt = GetPageData(start, max);
        rptLeaveword.TotalRows = GetTotalCount(); // 必須設定總筆數
        rptLeaveword.DataSource = dt;
        rptLeaveword.DataBind();
    };
}

優缺點與替代方案

方案優點缺點
自訂 IPageableItemContainer繼續用 Repeater/GridView 模板、DataPager 宣告式分頁需維護自訂控制項、ViewState 巨大、不支援非同步
改用 ListView原生支援 DataPager、版面彈性高需重寫模板、學習曲線
前端分頁 + Web API效能最佳、關注點分離、支援無限捲動需前端框架、SEO 需額外處理
ASP.NET Core TagHelper原生支援、強型別、可測試需遷移至 Core

總結

透過實作 IPageableItemContainer,讓舊有 Repeater/GridView 也能搭配 DataPager 使用,適合不想大幅重構的維護型專案。新專案建議直接採用 ListView 或前後端分離架構。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved