How do I use a DataPager with Server Side Paging?

2019-02-04 18:45发布

问题:

I'm trying to use a DataPager to do Server Side paging. Here is my code

<asp:DataPager ID="pgrFooBars" PagedControlID="lvFooBars" 
    QueryStringField="page" runat="server" >
<Fields>
    <asp:NumericPagerField />
</Fields>
</asp:DataPager>

Code Behind

protected void Page_Load(object sender, EventArgs e)
{
    ConfigureBlogPostListView();
}

private void ConfigureBlogPostListView()
{
    int pageNum;
    int.TryParse(Request.Params["page"], out pageNum);
    int pageSize = 20;

    PagedList<IFooBar> FooBars = FooService.GetPagedFooBars(
        new PagingSettings(pageNum, pageSize));

    ListViewPagedDataSource ds = new ListViewPagedDataSource();
    ds.AllowServerPaging = true;
    ds.DataSource = FooBars;
    ds.MaximumRows = pageSize;
    ds.StartRowIndex = pageNum;
    //TotalCount is the total number of records in the entire set, not just those loaded.
    ds.TotalRowCount = FooBars.TotalCount;

    lvFooBars.DataSource = ds;
    lvFooBars.DataBind();

    pgrFooBars.PageSize = pageSize;
    pgrFooBars.SetPageProperties(pageNum, FooBars.TotalCount, true);
}

PagedList comes from RobConery's useful post http://blog.wekeroad.com/2007/12/10/aspnet-mvc-pagedlistt/.

The problem is that the DataPager appears to be using the Count property of the ListView to determine the total number of records, which in this case is 20. Somehow, it needs to know that there are 1,500, not 20 total records. The DataPager has a property TotalRowCount, but this is read-only.

I have never seen a DataPager example with Server Side paging, but assumed that it could do Server Side Paging, otherwise what good is the QueryStringField attribute?

I am aware that you can do a custom paging solution using methodology like the 4GuysFromRolla did here http://www.4guysfromrolla.com/articles/031506-1.aspx, but I'd first like to know if a solution with the DataPager is possible before creating a custom solution.

UPDATE The more I look at this, the more that I'm coming to the conclusion that this is not possible and that, unfortunately, the datapager is a control meant for small web sites only. What I want to do should really be quite simple if the control were built correctly. I want to be able to say

dpFooBars.TotalRowCountComputed = false;
dpFooBars.TotalRowCount = AnyNumberThatISoChoose;

I've been looking for some hack to accomplish the same thing, but it appears that the datapager's TotalRowCount is computed from the actual number of items in the datasource that it's bound to. It seems very odd to me that Microsoft would create a ListViewPagedDataSource() class and a DataPager at the same time and not have them work correctly together, but this appears to have been what has happened.

UPDATE 2 (AHA MOMENT?) It seems that it has been possible to do server side paging since .Net 2.0 by using an ObjectDataSource and customizing the SelectCountMethod(). I believe it should be possible to customize ObjectDataSource to suit my needs. Hmmm. I'm going away for the weekend, so it'll be a couple of days for me to see if this works. Stay tuned, true believers.

回答1:

It seems the only way to get server-side paging to work with the DataPager is to use a LinqDataSource in your markup (update: this is not true, see below), and set the DataSourceID of your ListView (as in ScottGu's sample - step 6), not via the ListView DataSource property. However I did discover that there is a trick to make this more workable so you can define your LinqDataSource query via code-behind rather than in the markup (note, the article says this will work with any DataSource control but I don't think it is the case).

This will probably not be any help for your situation as I noticed that your calling some sort of service that probably won't (and shouldn't) be returning an IQueryable result which is necessary for this to work. Here it is anyway in-case you're interested ...

aspx markup:

<asp:ListView ID="lvFooBars" DataSourceID="dsLinq" ....>
   ......
</asp:ListView>

<asp:DataPager ID="pgrFooBars" PagedControlID="lvFooBars" 
    QueryStringField="page" runat="server" >
<Fields>
    <asp:NumericPagerField />
</Fields>
</asp:DataPager>

<asp:LinqDataSource id="dsLinq" runat="server" OnSelecting="dsLinq_Selecting" />

code-behind:

protected void dsLinq_Selecting(object sender, LinqDataSourceSelectEventArgs e)
{
    //notice this method on FooService requires no paging variables
    e.Result = FooService.GetIQueryableFooBars(); 
}

Note, MSDN states in regards to the QueryStringField attribute:

Setting this property is useful if you want to have all the pages of data indexed by a search engine. This occurs because the control produces a different URL for each page of data.

Update: infact you can get this to working using an ObjectDataSource control instead of the LinqDataSource control - see this article and download the sample. While using an ObjectDataSource isn't nearly as simple as using a LinqDataSource control, it's uses less magic linq stuff (instead it uses heaps of magic strings that map to business/data layer methods) and allows the exposure of IEnumerable for your data access method instead of IQueryable.

Still, I have never really a fan of embedding any sort of DataSource control in my UI markup (I believe this is necessary), so I would probably steer clear of the DataPager control except for small applications as you have suggested in your first update.

John, one other thing I noticed was that you are trying to marry standard Asp.Net server controls (that rely on ViewState) with the PagedList class which was developed as a helper class for Asp.Net Mvc applications. While this can potentially work, there may be simpler routes to take.



回答2:

Exactly needed what you did - SQL server paging utilizing listview, datapager and linq data source. As you say TotalRowCount is readonly. There is also SetPageProperties method that can be used to set the total row count but that also didn't work for me.

However I managed to trick it this way. I have a dummy hidden listview with empty item template. The pager will be pointing at this dummy listview rather than the original listview. Then we need to bind the dummy listview to a dummy collection with the same number of items as in the original data source. This will ensure rendering the pager correctly. Here is the markup.

<asp:DataPager ID="pdPagerBottom" runat="server" PagedControlID="lvDummy" PageSize="5" QueryStringField="page">
<Fields>
    <asp:NumericPagerField ButtonType="Link" RenderNonBreakingSpacesBetweenControls="true" NextPageText="Next" PreviousPageText="Previous" ButtonCount="40" />
</Fields>
</asp:DataPager>

<asp:ListView ID="lvDummy" runat="server" Visible="false">
<ItemTemplate></ItemTemplate>
</asp:ListView>

<asp:ListView ID="lvPropertyList" runat="server">...</asp:ListView>

and code behind

        var datasourceQuery = context.Properties.OrderBy(x => x.PropertyID).Skip(pdPagerBottom.StartRowIndex).Take(pdPagerBottom.PageSize);

        this.lvPropertyList.DataSource = datasourceQuery;
        this.lvPropertyList.DataBind();

        this.lvDummy.DataSource = new List<int>(Enumerable.Range(0, context.Properties.Count()));
        this.lvDummy.DataBind();

Tested it with 100k records. Works a treat.



回答3:

John, I did a little proof of concept here so that you can see the code. I made this as basic as possible so there's no fluff. Please let me know if there is some attribute to this that you need that is missing and I will revise.

Things that stood out:

  1. The data needs to be bound on the OnPreRender event of the pager when you're not using a DataSource object (xml,Sql,Access...). I am pretty sure this is your issue because this is where I had the most trouble.

  2. Viewstate must be enabled on the ListView and the pager (it is by default)

Code-Behind:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

public partial class _Default : System.Web.UI.Page 
{
    protected void Page_Load(object sender, EventArgs e)
    {
        if (!Page.IsPostBack)
        {
            this.BindListData();
        }
    }

    protected void dp_PreRender(object sender, EventArgs e)
    {
        this.BindListData();
    }

    protected void BindListData()
    {
        List<Nums> n = new List<Nums>();
        for (int i = 0; i <= 100; i++)
        {
            n.Add(new Nums { Num1 = i, Num2 = i * 3 });
        }

        this.lvMyGrid.DataSource = n;
        this.lvMyGrid.DataBind();
    }
}

public class Nums
{
    public int Num1 { get; set; }
    public int Num2 { get; set; }
}

ASPX (left out all the other fluff):

<asp:DataPager OnPreRender="dp_PreRender" runat="server" ID="dpMyPager" QueryStringField="page" PagedControlID="lvMyGrid" PageSize="15">
    <Fields>
        <asp:NumericPagerField />
    </Fields>
</asp:DataPager>

<asp:ListView runat="server" ID="lvMyGrid" DataKeyNames="Num1">
    <LayoutTemplate>
        <asp:Literal runat="server" ID="itemPlaceholder" />
    </LayoutTemplate>
    <ItemTemplate>
        <div style="border: 1px solid red; padding: 3px; margin: 5px; float: left; clear: both;">
        <%# Eval("Num1") %> : <%# Eval("Num2") %>
        </div>
    </ItemTemplate>
</asp:ListView>

Enjoy, let me know if there is something else in this that you need.

mike