Reputation: 21365
I was reading the following blogs about ASP.Net Async pages
And a question popped on my head, please consider the following scenario:
GridView
control on the Page_PreRendercomplete
At this point, I have the data paged on my page ready to be bound and displayed back to the user (returning only the records needed to be displayed and the number of Virtual Rows Count)
So with this information, I would like to bind it to my GridView
control, but I have not figured it out how to display the paging results on my GridView
I tried using the following code:
protected override void OnPreRenderComplete(EventArgs e)
{
if (this.shouldRefresh)
{
var pagedSource = new PagedDataSource
{
DataSource = this.Jobs,
AllowPaging = true,
AllowCustomPaging = false,
AllowServerPaging = true,
PageSize = 3,
CurrentPageIndex = 0,
VirtualCount = 20
};
this.gv.DataSource = pagedSource;
this.gv.DataBind();
}
base.OnPreRenderComplete(e);
}
But the GridView
control simply ignores the VirtualCount
property and the pager is never shown, this is what I get:
<%@ Page Async="true" AsyncTimeout="30" ....
...
<asp:GridView runat="server" ID="gv" DataKeyNames="job_id"
AllowPaging="true" PageSize="3"
>
<Columns>
<asp:CommandField ShowSelectButton="true" />
</Columns>
<SelectedRowStyle Font-Bold="true" />
</asp:GridView>
protected void Page_Load(object sender, EventArgs e)
{
if (!this.IsPostBack)
{
this.shouldRefresh = true;
}
}
public IAsyncResult BeginAsyncOperation(object sender, EventArgs e, AsyncCallback callback, object state)
{
var operation = new MyClassResult(callback, Context, state);
operation.StartAsync();
return operation;
}
public void EndAsyncOperation(IAsyncResult result)
{
var operation = result as MyClassResult;
this.Jobs = operation.Jobs;
}
Notes:
I'm not interested on jQuery async posts to the server to get the data
MyClassResult
implements IAsyncResult
and returns data from the database server
I would love to use an ObjectDataSource
if possible
Upvotes: 2
Views: 2293
Reputation: 9166
I think I have something that could be at least a good starting point for further exploration. I have made a sample (furher down) to illustrate my method, which is based on a couple of ideas:
ObjectDatasource
should used. That way it's possible to tell the GridView
how many rows there are in total.ObjectDataSource
access the data we have fetched once it is available.The idea I came up with in order to solve 2. was to define an interface that the page where the GridView
is located can implement. Then the ObjectDataSource
can use a class that relays the calls for fetching data to the Page itself. When called too early, empty data will be returned, but it will be replaced by real data later on.
Let's look at some code.
Here's my aspx file:
<%@ Page Title="" Language="C#" MasterPageFile="~/Site.master" AutoEventWireup="true"
CodeFile="GridViewTest.aspx.cs" Inherits="GridViewTest" %>
<asp:Content ID="Content1" ContentPlaceHolderID="HeadContent" runat="Server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="Server">
<asp:GridView ID="jobsGv" runat="server" AutoGenerateColumns="false" AllowPaging="true"
PageSize="13" OnPageIndexChanging="jobsGv_PageIndexChanging" DataSourceID="jobsDataSource">
<Columns>
<asp:TemplateField HeaderText="Job Id">
<ItemTemplate>
<asp:Literal ID="JobId" runat="server" Text='<%# Eval("JobId") %>'></asp:Literal>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Job description">
<ItemTemplate>
<asp:Literal ID="Description" runat="server" Text='<%# Eval("Description") %>'></asp:Literal>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Min level">
<ItemTemplate>
<asp:Literal ID="MinLvl" runat="server" Text='<%# Eval("MinLvl") %>'></asp:Literal>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
<asp:ObjectDataSource ID="jobsDataSource" runat="server" TypeName="JobObjectDs" CacheDuration="0"
SelectMethod="GetJobs" EnablePaging="True" SelectCountMethod="GetTotalJobsCount">
</asp:ObjectDataSource>
<asp:Button ID="button" runat="server" OnClick="button_Click" Text="Test postback" />
</asp:Content>
And the code behind:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.UI.WebControls;
public partial class GridViewTest : System.Web.UI.Page, IJobDsPage
{
bool gridNeedsBinding = false;
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
gridNeedsBinding = true;
}
}
protected void jobsGv_PageIndexChanging(object sender, GridViewPageEventArgs e)
{
var gv = (GridView)sender;
newPageIndexForGv = e.NewPageIndex;
gridNeedsBinding = true;
}
private int newPageIndexForGv = 0;
protected void Page_PreRendercomplete(object sender, EventArgs e)
{
if (gridNeedsBinding)
{
// fetch data into this.jobs and this.totalJobsCount to simulate
// that data has just become available asynchronously
JobDal dal = new JobDal();
jobs = dal.GetJobs(jobsGv.PageSize, jobsGv.PageSize * newPageIndexForGv).ToList();
totalJobsCount = dal.GetTotalJobsCount();
//now that data is available, bind gridview
jobsGv.DataBind();
jobsGv.SetPageIndex(newPageIndexForGv);
}
}
#region JobDsPage Members
List<Job> jobs = new List<Job>();
public IEnumerable<Job> GetJobs()
{
return jobs;
}
public IEnumerable<Job> GetJobs(int maximumRows, int startRowIndex)
{
return jobs;
}
int totalJobsCount;
public int GetTotalJobsCount()
{
return totalJobsCount;
}
#endregion
protected void button_Click(object sender, EventArgs e)
{
}
}
And finally some classes to tie it together. I have bunched these together in one code file in App_Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
/// <summary>
/// Simple POCO to use as row data in GridView
/// </summary>
public class Job
{
public int JobId { get; set; }
public string Description { get; set; }
public int MinLvl { get; set; }
//etc
}
/// <summary>
/// This will simulate a DAL that fetches data
/// </summary>
public class JobDal
{
private static int totalCount = 50; // let's pretend that db has total of 50 job records
public IEnumerable<Job> GetJobs()
{
return Enumerable.Range(0, totalCount).Select(i =>
new Job() { JobId = i, Description = "Descr " + i, MinLvl = i % 10 }); //simulate getting all records
}
public IEnumerable<Job> GetJobs(int maximumRows, int startRowIndex)
{
int count = (startRowIndex + maximumRows) > totalCount ? totalCount - startRowIndex : maximumRows;
return Enumerable.Range(startRowIndex, count).Select(i =>
new Job() { JobId = i, Description = "Descr " + i, MinLvl = i % 10 }); //simulate getting one page of records
}
public int GetTotalJobsCount()
{
return totalCount; // simulate counting total amount of rows
}
}
/// <summary>
/// Interface for our page, so we can call methods in the page itself
/// </summary>
public interface IJobDsPage
{
IEnumerable<Job> GetJobs();
IEnumerable<Job> GetJobs(int maximumRows, int startRowIndex);
int GetTotalJobsCount();
}
/// <summary>
/// This will be used by our ObjectDataSource
/// </summary>
public class JobObjectDs
{
public IEnumerable<Job> GetJobs()
{
var currentPageAsIJobDsPage = (IJobDsPage)HttpContext.Current.CurrentHandler;
return currentPageAsIJobDsPage.GetJobs();
}
public IEnumerable<Job> GetJobs(int maximumRows, int startRowIndex)
{
var currentPageAsIJobDsPage = (IJobDsPage)HttpContext.Current.CurrentHandler;
return currentPageAsIJobDsPage.GetJobs(maximumRows, startRowIndex);
}
public int GetTotalJobsCount()
{
var currentPageAsIJobDsPage = (IJobDsPage)HttpContext.Current.CurrentHandler;
return currentPageAsIJobDsPage.GetTotalJobsCount();
}
}
So what does it all do?
Well, we have the Page that is implementing the IJobDsPage
interface. On the page we have the GridView
which is using the ObjectDataSource
with id jobsDataSource
. That is in turn using the class JobObjectDs
to fetch data. And that class is in turn taking the currently executing Page from the HttpContext
and casting it to the IJobDsPage
interface and calling the interface methods on the Page.
So the result is that we have a GridView
which uses an ObjectDataSource
which calls methods in the Page to get data. If these methods are called too early, empty data is returned (new List<Job>()
with a corresponding total row count of zero). But this is no problem since we are anyway manually binding the GridView when we have reached a phase in the page processing when the data IS available.
All in all my sample works although it's far from superb. As it stands, the ObjectDataSource
will call its associated Select
method multiple times during the request. This is not as bad as it first sounds though, because the actual fethcing of data will still happen only once. Also, the GridView
will be bound to the same data twice when changing to the next page.
So there is room for improvement. But it is a least a starting point.
Upvotes: 2