Telerik RadGrid Custom Paging

Feb 12, 2013
Carlos Araujo

Custom Paging in Telerik RadGrid for large data sets can be somewhat challenging.  The native functionality requires entity objects to be instantiated for each row in the grid, which works fine for small data sets, but makes large data sets render very slowly.  The following shows how I overcame this performance problem.

The problem that I have to face here is the process of creating the data source. Creating 20000 records involves processes before populating the grid. What I mean is that for an entity creation (for a record), I need to access two different sources (Databases), there is a delay on entity creation that was not possible to avoid, imagine creating 20000 different objects of that entity.

I browsed different scenarios on how to solve this problem, but I was sure I didn’t want to lose grid filtering and all the features grid offers. So I decided to look for a custom paging functionality for the grid. The good news is that telerik radgrid lets me do that, the bad news is that I would have to take care of some already built-in functionality on my own. I would have to handle pagination functionality and querying databases for the portion of data that I need. I would have to handle also filtering on my own, and have queries ready to handle possible filtering. This for sure involves a lot of work, but radgrid also provides help through this process.

So let’s start with the recipe.

  1. In the .ascx file set AllowPaging = true and AllowCustomPaging = true for your grid instance.

  1. Set the PageSize, if not set the default would be 10.

  1. The total number of records in the grid is defined by the variable VirtualItemCount property of the MasterTableView instance. Through this the grid understands that the data source contains the specified number of records, and the functionality only fetches merely part of them. This variable can be set in the .ascx file or dynamically in code.

 

  1. Implement code logic to extract only portion of total records and present them in the grid structure. This step involves inner steps. We need a function to handle the current page size
  1. This is possible through the OnPageIndexChanged event. Set the function name in the grid instance.

  1. Implement RadGrid1_PageIndexChanged function. It should be something like

The important line is the CurrentPageIndex assignment, then is just rebinding the grid with the appropriate partial data source.

We need a function to handle the data source and all subgroups of the data source

  1. This function controls the paging functionality. Here the entire data source is partitioned in subgroups and it gives the grid only the part it needs to show.
  2. We need to handle two variables for pagination. The startRowIndex  and the  maximumRows variable.
  3. Here is an example on how to do it.

  1. The  ShouldApplySortFilterOrGroup function is a Boolean function that let me know if  I should give the grid a filtered datasource.

Create functionality to handle filter queries.

  1. Using the same filter options radgrid offer, we need to intercept the filter and based on it query database to get the filtered data source. This data source is also paginated by our custom pagination.

To get the query, just get the FilterExpression property from the mastertableview.



Using this approach the data grid has an excellent performance since it only shows the portion of the data source in every page. The filtering functionality doesn’t get affected either and we have created an approach to handle large data sources with no impact in functionality.

UPDATED:  Source code for this control is here.  It will not compile because there are references to libraries that aren't present.

ASCX

<%@ Register Assembly="Telerik.Web.UI" Namespace="Telerik.Web.UI" TagPrefix="telerik" %>
<div class="admin-content">
 <h1>
 Library Search - Search Results</h1>
 <div id="asearch">
 </div>
 <div class="clr">
 </div>
 <asp:Panel runat="server" ID="searchresultPanel" Visible="false">
 <p class="results-desc">
 The following is a list of libraries based upon your search criteria. You may choose
 a library by clicking the branch below.<br />
 </p>
 <div class="clr">
 </div>
 <telerik:RadAjaxManager runat="server" ID="RadAjaxManager1" DefaultLoadingPanelID="RadAjaxLoadingPanel1">
 <AjaxSettings>
 <telerik:AjaxSetting AjaxControlID="RadGrid1">
 <UpdatedControls>
 <telerik:AjaxUpdatedControl ControlID="RadGrid1" />
 <telerik:AjaxUpdatedControl ControlID="RadWindowManager1" />
 <telerik:AjaxUpdatedControl ControlID="RadInputManager1" />
 <telerik:AjaxUpdatedControl ControlID="Message" />
 </UpdatedControls>
 </telerik:AjaxSetting>
 </AjaxSettings>
 </telerik:RadAjaxManager>
 <telerik:RadCodeBlock ID="RadCodeBlock1" runat="server">
 </telerik:RadCodeBlock>
 <telerik:RadAjaxLoadingPanel runat="server" ID="RadAjaxLoadingPanel1" />
 <telerik:RadGrid runat="server" ID="RadGrid1" CssClass="contactgrid" AutoGenerateColumns="False"
 AllowPaging="True" AllowCustomPaging = "true" VirtualItemCount="100000" PagerStyle-AlwaysVisible="true" PagerStyle-Visible="true"
 OnNeedDataSource="RadGrid1_NeedDataSource" OnGroupsChanging="RadGrid1_GroupsChanging" OnPageIndexChanged="RadGrid1_PageIndexChanged"
 OnUpdateCommand="RadGrid1_UpdateCommand" OnItemCreated="RadGrid1_ItemCreated" OnInit="RadGrid1_Init"
 GridLines="Both" AllowMultiRowSelection="false" AutoGenerateEditColumn="false"
 OnSelectedIndexChanged="RadGrid_SelectedIndexChanged" PageSize="25">
 <GroupingSettings CaseSensitive="false" />
 <MasterTableView DataKeyNames="ID" EditMode="EditForms" AllowAutomaticUpdates="true"
 AllowFilteringByColumn="True">
 <NoRecordsTemplate>
 There isn't any data.</NoRecordsTemplate>
 <CommandItemSettings ExportToPdfText="Export to Pdf" />
 <Columns>
 <telerik:GridBoundColumn DataField="ID" HeaderText="ID" Visible="false" UniqueName="LibraryID"
 ReadOnly="true" />
 <telerik:GridBoundColumn DataField="Name" HeaderText="Library Name" FilterControlWidth="50px"
 CurrentFilterFunction="Contains" ShowFilterIcon="true" AutoPostBackOnFilter="true" />
 <telerik:GridBoundColumn DataField="RBLibraryID" HeaderText="Library ID" FilterControlWidth="50px"
 CurrentFilterFunction="Contains" ShowFilterIcon="true" AutoPostBackOnFilter="true" />
 <telerik:GridBoundColumn DataField="City" HeaderText="City" FilterControlWidth="50px"
 CurrentFilterFunction="Contains" ShowFilterIcon="true" AutoPostBackOnFilter="true" />
 <telerik:GridBoundColumn DataField="StateProvince" HeaderText="State/Province" FilterControlWidth="50px"
 CurrentFilterFunction="Contains" ShowFilterIcon="true" AutoPostBackOnFilter="true" />
 <telerik:GridBoundColumn DataField="CountryName" HeaderText="Country" FilterControlWidth="50px"
 CurrentFilterFunction="Contains" ShowFilterIcon="true" AutoPostBackOnFilter="true" />
 <telerik:GridBoundColumn DataField="PostalCode" HeaderText="Zip/Postal" FilterControlWidth="50px"
 CurrentFilterFunction="Contains" ShowFilterIcon="true" AutoPostBackOnFilter="true" />
 <telerik:GridBoundColumn DataField="Consortium" HeaderText="Consortium" ShowFilterIcon="true" AllowFiltering="false" />
 <telerik:GridBoundColumn DataField="Contact" HeaderText="Contact" AllowFiltering="false" />
 </Columns>
 </MasterTableView>
 <PagerStyle Mode="NextPrevAndNumeric" CssClass="radfixTiny" />
 <ClientSettings EnableRowHoverStyle="true">
 <Selecting AllowRowSelect="True" />
 <Resizing AllowColumnResize="true" />
 </ClientSettings>
 </telerik:RadGrid>
 <telerik:RadInputManager runat="server" ID="RadInputManager1" Enabled="true">
 <telerik:TextBoxSetting BehaviorID="TextBoxSetting1">
 </telerik:TextBoxSetting>
 <%--name--%>
 <telerik:TextBoxSetting BehaviorID="TextBoxSetting2">
 </telerik:TextBoxSetting>
 <%--email--%>
 <telerik:TextBoxSetting BehaviorID="TextBoxSetting3">
 </telerik:TextBoxSetting>
 <%--phone--%>
 <telerik:TextBoxSetting BehaviorID="TextBoxSetting4">
 </telerik:TextBoxSetting>
 <%--zip--%>
 <telerik:TextBoxSetting BehaviorID="TextBoxSetting5">
 </telerik:TextBoxSetting>
 </telerik:RadInputManager>
 <telerik:RadWindowManager ID="RadWindowManager1" runat="server" />
 <div>
 <asp:Label runat="server" ID="Message" ForeColor="Red"></asp:Label></div>
 <div class="clr">
 </div>
 <div class="button-container">
 <div class="srch-btn">
 <asp:Button runat="server" ID="UpdateButton" OnClick="Update_Click" Text="Update"
 CssClass="button1" />
 </div>
 </div>
 </asp:Panel>
</div>

Code Behind

 
 public class LibrarySearchWrapper
 {
 public int ID { get; set; }
 public String Name { get; set; }
 public int RBLibraryID { get; set; }
 public String City { get; set; }
 public String StateProvince { get; set; }
 public String County { get; set; }
 public String CountryName { get; set; }
 public String PostalCode { get; set; }
 public String Consortium { get; set; }
 public String Contact { get; set; }
 public LibraryEntity Library { get; set; }

 public LibrarySearchWrapper(int _libraryID)
 {
 this.Library = LibraryManager.Instance.GetLibraryByID(_libraryID);
 if (this.Library != null)
 {
 this.Name = Library.Name;
 this.ID = _libraryID;
 this.RBLibraryID = Library.RBLibraryID;
 this.City = Library.City;
 this.StateProvince = this.Library.StateProvince;
 this.County = this.Library.County;
 this.CountryName = this.Library.CountryName;
 try
 {
 if (this.Library.AccountTypeID == (byte)LibraryType.Consortium)
 this.Consortium = string.Empty;
 else if (this.Library.AccountTypeID == (byte)LibraryType.System)
 this.Consortium = this.Library.ParentLibraryEntity.Name;
 else if (this.Library.AccountTypeID == (byte)LibraryType.Library)
 this.Consortium = this.Library.ParentLibraryEntity.ParentLibraryEntity.Name;
 }
 catch (Exception)
 {
 this.Consortium = String.Empty;
 }
 this.PostalCode = this.Library.PostalCode;
 }
 }
 }

 #endregion

 public partial class Advanced_Search_Library1 : System.Web.UI.UserControl
 {
 private static int pageIndex = 0;
 public SearchType SearchType { get; set; }
 public List WrapperList
 {
 get
 {
 return Session["LibrarySearchWrapper"] != null ? ((List)Session["LibrarySearchWrapper"]) : null;
 }
 set
 {
 Session["LibrarySearchWrapper"] = value;
 }
 }
 public List LibrariesIDs
 {
 get
 {
 return Session["LibrariesIDs"] != null ? ((List)Session["LibrariesIDs"]) : null;
 }
 set
 {
 Session["LibrariesIDs"] = value;
 }
 }
 public int? SelectedID
 {
 get { return Session["Selected"] != null ? int.Parse(Session["Selected"].ToString()) : 0; }
 set { Session["Selected"] = value; }

 }

 protected void Page_Load(object sender, EventArgs e)
 {
 if (!IsPostBack)
 {
 RadGrid1.Rebind();
 }
 }

 private void SetSearchType()
 {
 SitecoreUser.User currentUser = global::Sitecore.Context.User;

 if (currentUser.IsInRole("rbUsers\\Super User"))
 this.SearchType = SearchType.All;
 else if (currentUser.IsInRole("rbUsers\\Consortium Admin"))
 this.SearchType = SearchType.Consortium;
 else if (currentUser.IsInRole("rbUsers\\System Admin"))
 this.SearchType = SearchType.System;
 else
 this.SearchType = SearchType.Library;

 }

 protected void RadGrid1_Init(object sender, System.EventArgs e)
 {
 GridFilterMenu menu = RadGrid1.FilterMenu;
 int i = 0;

 while (i < menu.Items.Count)
 {
 if (menu.Items[i].Text == "NoFilter" || menu.Items[i].Text == "Contains" || menu.Items[i].Text == "EqualTo" ||
 menu.Items[i].Text == "StartsWith" || menu.Items[i].Text == "IsEmpty")
 {
 i++;
 }
 else
 {
 menu.Items.RemoveAt(i);
 }
 }

 }

 protected void RadGrid1_NeedDataSource(object source, GridNeedDataSourceEventArgs e)
 {
 LoadData();
 RadGrid1.DataSource = this.WrapperList;
 RadGrid1.Visible = true;
 this.searchresultPanel.Visible = true;

 }
 bool isGrouping = false;
 protected void RadGrid1_GroupsChanging(object source, GridGroupsChangingEventArgs e)
 {
 isGrouping = true;
 if (e.Action == GridGroupsChangingAction.Ungroup && RadGrid1.CurrentPageIndex > 0)
 {
 isGrouping = false;
 }
 }

 protected void LoadData()
 {
 this.WrapperList = new List();
 searchresultPanel.Visible = true;

 if (!ShouldApplySortFilterOrGroup())
 {
 this.LibrariesIDs = LibraryManager.Instance.GetAllLibrariesIdsByCriteria();


 int startRowIndex = (ShouldApplySortFilterOrGroup()) ?
 0 : this.RadGrid1.CurrentPageIndex * RadGrid1.PageSize;

 int maximumRows = (ShouldApplySortFilterOrGroup()) ?
 LibrariesIDs.Count : RadGrid1.PageSize;

 RadGrid1.AllowCustomPaging = !ShouldApplySortFilterOrGroup();
 RadGrid1.VirtualItemCount = LibrariesIDs.Count;

 int i = startRowIndex;
 var librariesID = LibrariesIDs.ToArray();
 while (i < Math.Min(LibrariesIDs.Count, startRowIndex + maximumRows))
 {
 WrapperList.Add(new LibrarySearchWrapper(librariesID[i]));
 i++;
 }
 }
 else
 {
 var strFilter = RadGrid1.MasterTableView.FilterExpression;

 if (strFilter.Contains("CountryName"))
 strFilter = strFilter.Replace("CountryName", "Country_Name");
 else if (strFilter.Contains("LibraryID"))
 strFilter = strFilter.Replace("RBLibraryID", "SourceLibraryID");

 this.LibrariesIDs = LibraryManager.Instance.GetLibrariesByFilterCriteria(strFilter);

 int startRowIndex = this.RadGrid1.CurrentPageIndex * RadGrid1.PageSize;

 int maximumRows = RadGrid1.PageSize;

 RadGrid1.VirtualItemCount = LibrariesIDs.Count;

 int i = startRowIndex;
 var librariesID = LibrariesIDs.ToArray();
 while (i < Math.Min(LibrariesIDs.Count, startRowIndex + maximumRows))
 {
 WrapperList.Add(new LibrarySearchWrapper(librariesID[i]));
 i++;
 }
 }
 }

 public bool ShouldApplySortFilterOrGroup()
 {
 return RadGrid1.MasterTableView.FilterExpression != "" ||
 (RadGrid1.MasterTableView.GroupByExpressions.Count > 0 || isGrouping) ||
 RadGrid1.MasterTableView.SortExpressions.Count > 0;
 }

 protected void RadGrid1_ItemCreated(object sender, GridItemEventArgs e)
 {
 if (e.Item is GridFilteringItem)
 {
 GridFilteringItem fItem = (GridFilteringItem)e.Item;
 foreach (GridColumn col in RadGrid1.MasterTableView.Columns)
 {
 try
 {
 (fItem[col.UniqueName].Controls[0] as TextBox).Attributes.Add("onkeyup", "CheckChar(this, event)");
 }
 catch
 {
 continue;
 }
 }

 }
 }

 protected void RadGrid1_UpdateCommand(object source, GridCommandEventArgs e)
 {
 //throw new NotImplementedException();
 }

 protected void clearButton_Click(object sender, EventArgs e)
 {
 searchresultPanel.Visible = false;
 //LoadConsortiumCombo();
 this.WrapperList = null;
 this.LibrariesIDs = null;
 Message.Text = "";
 Response.Redirect(Request.RawUrl);
 }

 protected void searchButton_Click(object sender, EventArgs e)
 {
 SearchLibraries();
 }

 public void SearchLibraries()
 {
 //try
 //{
 // Message.Text = "";
 // this.WrapperList = new List();
 // searchresultPanel.Visible = true;

 // //this.LibrariesIDs = LibraryManager.Instance.GetLibrariesByCriteria(int.Parse(consortiumComboBox.SelectedValue));

 // this.LibrariesIDs = LibraryManager.Instance.GetLibrariesByCriteriaTwo();

 // //this.LibrariesIDs = LibraryManager.Instance.RetrieveAllLibrartiesLite();

 // foreach (int librariesID in LibrariesIDs.GetRange(0, 30))
 // {
 // WrapperList.Add(new LibrarySearchWrapper(librariesID));
 // }
 // RadGrid1.Rebind();
 //}
 //catch (Exception)
 //{

 // Message.Text = "Error on searching...";
 //}
 }

 protected void Update_Click(object sender, EventArgs e)
 {
 if (RadGrid1.SelectedItems.Count == 0)
 {
 Message.Text = "Please select a Library to update";
 return;
 }


 try
 {
 SelectedID =
 int.Parse(
 RadGrid1.SelectedItems[0].OwnerTableView.DataKeyValues[RadGrid1.SelectedItems[0].ItemIndex]["ID"].ToString());
 }
 catch (Exception)
 {
 SelectedID = null;
 return;

 }

 Message.Text = "";
 if (SelectedID == null)
 return;

 var libraryEntity = LibraryManager.Instance.GetLibraryByID(SelectedID.Value);

 if (!CanRedirect(libraryEntity))
 return;

 Response.Redirect("http://" + libraryEntity.AdminURL + "/Login.aspx");
 }

 protected bool CanRedirect(LibraryEntity library)
 {
 if (library == null)
 return false;

 if (library.AccountTypeID != 0)
 return false;

 if (String.IsNullOrEmpty(library.AdminURL))
 {
 this.Message.Text = "Cannot redirect library is not fully configured, missing admin URL";
 this.Message.Visible = true;
 return false;
 }

 return true;
 }

 protected void RadGrid_SelectedIndexChanged(object sender, EventArgs e)
 {

 }

 protected void RadGrid1_PageIndexChanged(object source, GridPageChangedEventArgs e)
 {
 if (!ShouldApplySortFilterOrGroup())
 {
 this.RadGrid1.CurrentPageIndex = e.NewPageIndex;
 LoadData();
 RadGrid1.DataSource = this.WrapperList;
 RadGrid1.DataBind();
 RadGrid1.Rebind();
 }
 }

 }

Post created by Carlos Araujo, Oshyn Inc. employee. Some information gathered from telerik website.