views:

158

answers:

2

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  A: 

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

Jerzakie
Unfortunately, this is not what I need. I need to do server side paging, that is, I have 1000 total records in the database, but in order to save bandwidth, I'm only going to download 15 records from the database. Let's say that I have 10,000 records in the database. I don't want to grab all 10,000 records from the database just so that the datapager can get the correct count.
John
Ok, that makes sense why this is more complicated. What you are probably going to have to do is create a custom collection like an ICollection<FooBar> FooBarCollection and override the Count method so that it returns your entire count. I am not sure if this will work or not, but likely you're going to have to do something like that to trick the DataPager into thinking there's more data there than there is.
Jerzakie
+1  A: 

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.

Bermo
Thanks for tip. Microsoft's fascination with tightly coupling their data access layer with the application layer continues on.
John
Thanks, I'll take a look. It appears to be what I am needing. I didn't really consider PagedList to be so much of an MVC thing, just more of a Rob Conery thing. Before I found PagedList, the people before me had been using methods like List<FooBar> GetFoobars(string id, out TotalRowCount). To me, PagedList was just a good O-O way of doing something instead of using an out, which I never feel comfortable with.
John
I think that the simpler route would just to be to use MVC to begin with, but the team isn't ready to convert this site yet (we're beginning use elsewhere). My personal feeling is that MVC is much easier than web forms in anything but the simplest of sites.
John
Yeah I agree, MVC is the way to go in my opinion - it will make the codebase much more maintainable than Webforms in the future.
Bermo