Best way of Listview custom paging with Datapager and objectdatasource

Best way of Listview custom paging with Datapager and objectdatasource
2 votes, 5.00 avg. rating (96% score)

Introduction

Listview with paging is very frequent usage for asp.net webapplication. In this post we will see how we can achieve best performance paging with listview.
I ended up explaining to some of the senior developers what is meant by paging and why we implement. This basic and very important thing should always be remembered.
Hope this post will cover how to implement paging for any large amount of data.

While binding listview, the best way is get only current page data. When user clicks next page, the next page data should be fetched.
Pagination display should be managed by getting total number of records in the datasource table.
If the code does not follow this basic logic, then the purpose of using paging implementation is not achieved.

What are covered in this article

  • Listview paging using datapager and objectdatasource the optimized way
  • Getting only current page contents (do not get full data)
  • Setting pager control by getting total number of records
  • Refresh objectdatasource data if any update happens
  • Load specific page
  • Access page controls in selectmethod of objectdatasource
  • Manage dispose of objectdatasource

Listview paging using datapager and objectdatasource the optimized way


<asp:ListView ID="lstFeeds" runat="server" DataKeyNames="Id" DataSourceID="ObjectDataSource1"  
         ItemPlaceholderID="itemPlaceHolder1">
        <LayoutTemplate>
            <table data-toggle="table"  data-height="299">
                <thead>
                    <tr>
                        <th>Feed Title</th>
                    </tr>
                </thead>
                <asp:PlaceHolder ID="itemPlaceHolder1" runat="server"></asp:PlaceHolder>
            </table>

        </LayoutTemplate>
        <ItemTemplate>
            <tr>
                <td>
                    <asp:Label ID="FeedTitle" runat="server" Text='<%#Eval("FeedTitle") %>' />
                </td>
            </tr>
        </ItemTemplate>
    </asp:ListView>
    <asp:DataPager ID="DataPager1" runat="server"  PagedControlID="lstFeeds" PageSize="10">
        <Fields>
            <asp:NumericPagerField ButtonType="Link" />
        </Fields>
    </asp:DataPager>
     <asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
  EnablePaging="true" OnObjectDisposing="ObjectDataSource1_ObjectDisposing"
  TypeName="RssFeed.UI.DataPagerTest"
  SelectMethod="GetFeedItems" SelectCountMethod="GetTotalFeedItemsCount"  StartRowIndexParameterName="startindex" MaximumRowsParameterName="pagesize" />
    <br />
    <asp:Button ID="btnRefresh" runat="server" OnClick="btnRefresh_Click" Text="Refresh" />

Above listview uses ObjectDataSource as datasource (property DataSourceID=”ObjectDataSource1″). This is the only property for the listview to bind with data.

For ObjectDataSource object,
SelectMethod will have method name which will return the data from DB table (assume now the table name is Feeds)
The method should take 2 paramerters same as the values in StartRowIndexParameterName and MaximumRowsParameterName (check the below code snippet)
StartRowIndexParameterName will have method name which will return total records of DB table (assume now the table name is Feeds)
TypeName is the codebehind full class name given. You can give any custom class also here. Using code behind class will make better sense

 public static List GetFeedItems(int startindex, int pagesize)
{
	DAOFeeds daoFeed = new DAOFeeds();
	var allFeeds = daoFeed.GetAllFeeds(startrows, pagesize);
	return allFeeds;
}

//Getting only current page contents (do not get full data) 
//DAO Method
public List GetAllFeeds(int fromIndex, int TotalItemsToFetch)
{
	List allFeeds = null;
	//RssFeedAggregatorDBEntities is the entity framework context
	using (RssFeedAggregatorDBEntities db = new RssFeedAggregatorDBEntities())
	{
		try
		{
		    var _allFeeds = (from t in db.FEEDS orderby t.CreatedDateTime descending select t).Skip(fromIndex).Take(TotalItemsToFetch);
			if (_allFeeds != null)
			{
				allFeeds = new List();
				allFeeds.AddRange(_allFeeds);
			}
			return allFeeds;
		}
		catch (Exception exp)
		{
			throw new Exception("ERROR: Unable to get Feeds - " + exp.Message.ToString(), exp);
		}
	}

}


I have used entity framework with LINQ to SQL with Skip and Take that gets only the current page records(you can use your own logic or any raw sql to get only specific page records).
If Sql profiler is used, we can see the TOP and row_number query with pagesize. So, LINQ to SQL with Skip and Take will return only the data required to show for the users in the page.
Startindex will automatically increments when next page is clicked. No math to use from your end to add or substract.

 public int GetTotalFeedItemsCount()
{
	int totalFeedsCount = 0;
	if (Session["TotalFeedCount"] == null)
	{
		DAOFeeds daoFeed = new DAOFeeds();
		totalFeedsCount = daoFeed.GetAllFeedsCount();
		Session["TotalFeedCount"] = totalFeedsCount;
	}
	totalFeedsCount = Convert.ToInt32(Session["TotalFeedCount"]);

	return totalFeedsCount;

}


//DAO Method
 public int GetAllFeedsCount()
{
	int feedCount = 0;
	//RssFeedAggregatorDBEntities is the entity framework context
	using (RssFeedAggregatorDBEntities db = new RssFeedAggregatorDBEntities())
	{
		try
		{
			feedCount = (from t in db.FEEDS orderby t.CreatedDateTime descending select t).Count();
			return feedCount;
		}
		catch (Exception exp)
		{
			throw new Exception("ERROR: Unable to get Feeds Count - " + exp.Message.ToString(), exp);
			
		}
	}

	return feedCount;

}

GetTotalFeedItemsCount will always be called when next page link is clicked. So, its ideal to save the total records count in session or viewstate. This will avoid subsequent database calls. Make sure if any udate in the database, this session should be cleared

Refresh objectdatasource data if any update happens

If any update in the DB table happens and to refresh the listview just call DataBind method. This call will trigger the select method automatically

lstFeeds.DataBind(); //this will have new call to select method, call this when data is saved

Load specific page

If any specific page is to be loaded, call setpageproperties method of Datapager. During pageload for the firstime, we might need to load any pse

 DataPager1.SetPageProperties(0, DataPager1.PageSize, false); //this will load page 1
 

If paging should be managed by query string, then set QueryStringField property for DataPager. This will add pagenumber in the querystring.
If url structure is to be modified, DataPager does not have any option. It has to be done by url rewrite module.

Access page controls in SelectMethod of objectdatasource

Very often,we might require to load the listview based on search criteria. At that time we have to feed the selectmethod with criteria reading from the page controls. Eventhough the TypeName attribute is set to codebehind of the current page, we cannot access page controls in the selectmethod. We have to add one line of code for accessing controls to let know the objectdatasource which instance it has to check

  ObjectDataSource1.ObjectCreating += (s, o) => { o.ObjectInstance = this; };

ObjectDataSource1 is the id of the ObjectDatasource which I used in the page. Use this code in pageload. We are now good to access the pagecontrols in the select method.
Note that if you are binding listview on search criteria in a search button click, its better to set the DataSourceID property to listview in the button click rather than in aspx page. This ensures selectmethod will not be called in the pageload automatically. However you can avoid that by some conditions check though

Manage dispose of objectdatasource

If you observer html(aspx) code there is one event OnObjectDisposing=”ObjectDataSource1_ObjectDisposing” attached. This is needed to prevent page disposal suddenly. Without this event, you might observe javascript errors with objectdatasource. This is because objectdatasource disposes the class it is attached to. Here it is attached to page code behind. So, do not allow objectdatasource to manage page disposal.
Without this event I will never attach code behind class to objectdatasource. This is one of the most important point to consider

protected void ObjectDataSource1_ObjectDisposing(object sender, ObjectDataSourceDisposingEventArgs e)
        {
            e.Cancel = true;
        }

Full Code

namespace RssFeed.UI
{
    public partial class DataPagerTest : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            if(!IsPostBack)
            {
                //load specific page , can also use if paging is done by url
                DataPager1.SetPageProperties(0, DataPager1.PageSize, false); 
            }
        }

        public static List<FEED> GetFeedItems(int startindex, int pagesize)
        {
            DAOFeeds daoFeed = new DAOFeeds();
            var allFeeds = daoFeed.GetAllFeeds(startindex, pagesize);
            return allFeeds;

        }

        public int GetTotalFeedItemsCount()
        {
            int totalFeedsCount = 0;
            if (Session["TotalFeedCount"] == null)
            {
                DAOFeeds daoFeed = new DAOFeeds();
                totalFeedsCount = daoFeed.GetAllFeedsCount();
                Session["TotalFeedCount"] = totalFeedsCount;
            }
            totalFeedsCount = Convert.ToInt32(Session["TotalFeedCount"]);

            return totalFeedsCount;

        }

        

        protected void btnRefresh_Click(object sender, EventArgs e)
        {
            Session["TotalFeedCount"] = null;
            DataPager1.SetPageProperties(0, 1, false);
            lstFeeds.DataBind(); //this will have new call to select method, call this when data is saved
        }

        protected void ObjectDataSource1_ObjectDisposing(object sender, ObjectDataSourceDisposingEventArgs e)
        {
            e.Cancel = true;
        }
    }
}
 

Points to note

  • Using listview and datapager alone does not fit for real paging as we cannot set datapager totalposts count. It is readonly property and will be set only when total items are bound. If we bind total items, then our point of performance is already lost. Only with objectdatasource combined with listview and datapager we will achieve the real paging.

Conclusion

I have tested the above code with sql profiler and observed what will the backend query. So, it is the best way to achieve Listview paging for any amount of data.
Do not just carry on with binding the whole data to Listview and set paging. Implement with the ideal way and improve the performance of the application.

October 18, 2015 В· Adi В· No Comments
Posted in: Asp.Net, C#, Sharepoint 2007, Sharepoint 2010, SharePoint 2013, Tools

Leave a Reply

What is 10 + 4 ?
Please leave these two fields as-is:
IMPORTANT! To be able to proceed, you need to solve the following simple math (so we know that you are a human) :-)