I have following code for doing custom paging from an asp.net web application.
Points of interest
- It uses Link Buttons as suggested in https://stackoverflow.com/questions/14335067/passing-search-parameters-to-same-page-when-hyperlink-clicked
- The link buttons are added in Page_Load itself as listed in https://stackoverflow.com/questions/14364332/dynamic-controls-event-handlers-working
- It is made as user control for reuse
QUESTIONS
- Is there any pitfalls in this approach?
- Is there any improvement suggestions?
User Control Markup
<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="PagingUserControl.ascx.cs"
Inherits="PagingTestWebApplication.PagingUserControl" %>
<div class="pagingSection" id="pagingSection" runat="server">
<asp:LinkButton ID="lnkPrevious" runat="server" CssClass='page-numbers prev' Visible="false" OnClick="LinkButton_Click">Prev</asp:LinkButton>
<asp:LinkButton ID="lnkFirst" runat="server" CssClass='page-numbers' Visible="false"
OnClick="LinkButton_Click">1</asp:LinkButton>
<asp:Label runat="server" ID="lblFirstDots" CssClass="page-numbers prev" Visible="false"
Text="..."></asp:Label>
<asp:PlaceHolder ID="plhDynamicLink" runat="server"></asp:PlaceHolder>
<asp:Label runat="server" ID="lblSecondDots" Visible="false" CssClass="page-numbers prev"
Text="..."></asp:Label>
<asp:LinkButton ID="lnkLast" runat="server" CssClass='page-numbers' Visible="false"
OnClick="LinkButton_Click">Last</asp:LinkButton>
<asp:LinkButton ID="lnkNext" runat="server" CssClass='page-numbers next' Visible="false" OnClick="LinkButton_Click">Next</asp:LinkButton>
</div>
User Control Code Behind
public partial class PagingUserControl : System.Web.UI.UserControl
{
protected void Page_Load(object sender, EventArgs e)
{
}
public int PreviousIndex { get; set; }
public int CurrentClickedIndex { get; set; }
public event EventHandler PaginationLinkClicked;
protected void LinkButton_Click(object sender, EventArgs e)
{
//Assumption: Text of the LinkButton will be same as index
LinkButton clickedLinkButton = (LinkButton)sender;
if (String.Equals(clickedLinkButton.Text, "Next"))
{
//Next Page index will be one greater than current
//Note: If the current index is the last page, "Next" control will be in disabled state
CurrentClickedIndex = PreviousIndex + 1;
}
else if (String.Equals(clickedLinkButton.Text, "Prev"))
{
//Previous Page index will be one less than current
//Note: If the current index is the first page, "Prev" control will be in disabled state
CurrentClickedIndex = PreviousIndex - 1;
}
else
{
CurrentClickedIndex = Convert.ToInt32(clickedLinkButton.Text, CultureInfo.InvariantCulture);
}
//Raise event
if (this.PaginationLinkClicked != null)
{
this.PaginationLinkClicked(clickedLinkButton, e);
}
}
public void PreAddAllLinks(int tableDataCount,int pageSize, int currentIndex)
{
if (tableDataCount > 0)
{
PagingInfo info = PagingHelper.GetAllLinks(tableDataCount, pageSize, currentIndex);
//Remove all controls from the placeholder
plhDynamicLink.Controls.Clear();
if (info.PaginationLinks != null)
{
foreach (LinkButton link in info.PaginationLinks)
{
//Adding Event handler must be done inside Page_Laod /Page_Init
link.Click += new EventHandler(LinkButton_Click);
//Validation controls should be executed before link click.
link.ValidationGroup = "Search";
this.plhDynamicLink.Controls.Add(link);
}
}
}
}
public void AddPageLinks(int tableDataCount, int pageSize, int index)
{
if (tableDataCount > 0)
{
pagingSection.Visible = true;
PagingInfo info = PagingHelper.GetPageLinks(tableDataCount, pageSize, index);
//Remove all controls from the placeholder
plhDynamicLink.Controls.Clear();
if (info.PaginationLinks != null)
{
lnkPrevious.Visible = info.PaginationLinks.Count > 0 ? true : false;
lnkNext.Visible = info.PaginationLinks.Count > 0 ? true : false;
foreach (LinkButton link in info.PaginationLinks)
{
//Validation controls should be executed before link click.
link.ValidationGroup = "Search";
this.plhDynamicLink.Controls.Add(link);
}
}
//Dots visiblity
if (info.IsEndDotsVisible != null)
{
lblSecondDots.Visible = Convert.ToBoolean(info.IsEndDotsVisible, CultureInfo.InvariantCulture);
}
else
{
lblSecondDots.Visible = false;
}
if (info.IsStartDotsVisible != null)
{
lblFirstDots.Visible = Convert.ToBoolean(info.IsStartDotsVisible, CultureInfo.InvariantCulture);
}
else
{
lblFirstDots.Visible = false;
}
//First and Last Links
if (info.IsFirstLinkVisible != null)
{
lnkFirst.Visible = Convert.ToBoolean(info.IsFirstLinkVisible, CultureInfo.InvariantCulture);
}
else
{
lnkFirst.Visible = false;
}
if (info.IsLastLinkVisible != null)
{
lnkLast.Visible = Convert.ToBoolean(info.IsLastLinkVisible, CultureInfo.InvariantCulture);
lnkLast.Text = info.NumberOfPagesRequired.ToString(CultureInfo.InvariantCulture);
}
else
{
lnkLast.Visible = false;
}
//For first page, there is no previous
if (index != 1 && info.NumberOfPagesRequired != 1)
{
lnkPrevious.Enabled = true;
}
else
{
lnkPrevious.Enabled = false;
}
//For last page there is no Next
if (index != info.NumberOfPagesRequired && info.NumberOfPagesRequired != 1)
{
lnkNext.Enabled = true;
}
else
{
lnkNext.Enabled = false;
}
}
else
{
pagingSection.Visible = false;
}
}
}
DTO
public class PagingInfo
{
public Collection<LinkButton> PaginationLinks { get; set; }
public bool? IsEndDotsVisible { get; set; }
public bool? IsStartDotsVisible { get; set; }
public bool? IsFirstLinkVisible { get; set; }
public bool? IsLastLinkVisible { get; set; }
public int NumberOfPagesRequired { get; set; }
}
Helper
public static class PagingHelper
{
public static PagingInfo GetAllLinks(int totalRecordsInTable, int pageSize, int previousIndex)
{
string LinkButtonIDPrefix = "lnK";
PagingInfo pagingInfo = new PagingInfo();
pagingInfo.PaginationLinks = new Collection<LinkButton>();
if (totalRecordsInTable > 0)
{
int itemsBeforePage = 4;
int itemsAfterPage = 2;
int dynamicDisplayCount = itemsBeforePage + 1 + itemsAfterPage;
Double numberOfPagesRequired = Convert.ToDouble(totalRecordsInTable / pageSize);
if (totalRecordsInTable % pageSize != 0)
{
numberOfPagesRequired = numberOfPagesRequired + 1;
}
if (numberOfPagesRequired == 0)
{
numberOfPagesRequired = 1;
}
//Note: This function adds only the probable Links that the user can click (based on previous click).
//This is needed sice dynamic controls need to be added while Page_Load itself for event handlers to work
//In case of any bug, easiest way is add all links from 1 to numberOfPagesRequired
//Following is an optimized way
int endOfLeftPart = dynamicDisplayCount;
//User may click "1". So the first 7 items may be required for display. Hence add them for event handler purpose
for (int i = 1; i <= endOfLeftPart; i++)
{
//Create dynamic Links
LinkButton lnk = new LinkButton();
lnk.ID = LinkButtonIDPrefix + i.ToString(CultureInfo.InvariantCulture);
lnk.Text = i.ToString(CultureInfo.InvariantCulture);
pagingInfo.PaginationLinks.Add(lnk);
}
int startOfRighPart = Convert.ToInt32(numberOfPagesRequired) - dynamicDisplayCount + 1;
//User may click the last link. So the last 7 items may be required for display. Hence add them for event handler purpose
for (int i = startOfRighPart; i <= Convert.ToInt32(numberOfPagesRequired); i++)
{
//Links already added should not be added again
if (i > endOfLeftPart)
{
//Create dynamic Links
LinkButton lnk = new LinkButton();
lnk.ID = LinkButtonIDPrefix + i.ToString(CultureInfo.InvariantCulture);
lnk.Text = i.ToString(CultureInfo.InvariantCulture);
pagingInfo.PaginationLinks.Add(lnk);
}
}
//User may click on 4 items before current index as well as 2 items after current index
for (int i = (previousIndex - itemsBeforePage); i <= (previousIndex + itemsAfterPage); i++)
{
//Links already added should not be added again
if (i > endOfLeftPart && i < startOfRighPart)
{
//Create dynamic Links
LinkButton lnk = new LinkButton();
lnk.ID = LinkButtonIDPrefix + i.ToString(CultureInfo.InvariantCulture);
lnk.Text = i.ToString(CultureInfo.InvariantCulture);
pagingInfo.PaginationLinks.Add(lnk);
}
}
}
return pagingInfo;
}
public static PagingInfo GetPageLinks(int totalRecordsInTable, int pageSize, int currentIndex)
{
string LinkButtonIDPrefix = "lnK";
PagingInfo pagingInfo = new PagingInfo();
pagingInfo.PaginationLinks = new Collection<LinkButton>();
if (totalRecordsInTable > 0)
{
int itemsBeforePage = 4;
int itemsAfterPage = 2;
int dynamicDisplayCount = itemsBeforePage + 1 + itemsAfterPage;
Double numberOfPagesRequired = Convert.ToDouble(totalRecordsInTable / pageSize);
if (totalRecordsInTable % pageSize != 0)
{
numberOfPagesRequired = numberOfPagesRequired + 1;
}
if (numberOfPagesRequired == 0)
{
numberOfPagesRequired = 1;
}
//Generate dynamic paging
int start;
if (currentIndex <= (itemsBeforePage + 1))
{
start = 1;
}
else
{
start = currentIndex - itemsBeforePage;
}
int lastAddedLinkIndex = 0;
int? firtsAddedLinkIndex = null;
for (int i = start; i < start + dynamicDisplayCount; i++)
{
if (i > numberOfPagesRequired)
{
break;
}
//Create dynamic Links
LinkButton lnk = new LinkButton();
lnk.ID = LinkButtonIDPrefix + i.ToString(CultureInfo.InvariantCulture);
lnk.Text = i.ToString(CultureInfo.InvariantCulture);
lastAddedLinkIndex = i;
if (firtsAddedLinkIndex == null)
{
firtsAddedLinkIndex = i;
}
//Check whetehr current page
if (i == currentIndex)
{
lnk.CssClass = "page-numbers current";
}
else
{
lnk.CssClass = "page-numbers";
}
pagingInfo.PaginationLinks.Add(lnk);
}
if (numberOfPagesRequired > dynamicDisplayCount)
{
//Set dots (ellipse) visibility
pagingInfo.IsEndDotsVisible = lastAddedLinkIndex == numberOfPagesRequired ? false : true;
pagingInfo.IsStartDotsVisible = firtsAddedLinkIndex <= 2 ? false : true;
//First and Last Page Links
pagingInfo.IsLastLinkVisible = lastAddedLinkIndex == numberOfPagesRequired ? false : true;
pagingInfo.IsFirstLinkVisible = firtsAddedLinkIndex == 1 ? false : true;
}
pagingInfo.NumberOfPagesRequired = Convert.ToInt32(numberOfPagesRequired);
}
return pagingInfo;
}
}
ASPX Page
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="LijosTest.aspx.cs" Inherits="PagingTestWebApplication.LijosTest" %>
<%@ Register TagPrefix="CP" TagName="LijosPager" Src="~/PagingUserControl.ascx" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
<style type="text/css">
.page-numbers
{
border: 1px solid #CCC;
color: #808185;
display: block;
float: left;
font-size: 9pt;
margin-right: 3px;
padding: 4px 4px 3px;
text-decoration: none;
}
.page-numbers.current
{
background-color: #808185;
border: 1px solid #808185;
color: white;
font-weight: bold;
}
.page-numbers.next, .page-numbers.prev
{
border: 1px solid white;
font-size: 12pt;
}
</style>
</head>
<body>
<form id="form1" runat="server">
<asp:TextBox ID="txtEmpName" runat="server"></asp:TextBox>
<asp:Button ID="btnSearch" runat="server" Text="Button" ValidationGroup="Search"
OnClick="btnSearch_Click" />
<div>
<asp:GridView ID="grdEmployee" runat="server">
</asp:GridView>
</div>
<CP:LijosPager ID="uclPager" runat="server" />
<asp:HiddenField ID="hdnCurrentIndex" runat="server" Value="Blank Value" />
</form>
</body>
</html>
ASPX Code Behind
public partial class LijosTest : System.Web.UI.Page
{
private int pageSize = 25;
private int pageIndex = 1;
protected void Page_Load(object sender, EventArgs e)
{
//Register event handler for user control event
this.uclPager.PaginationLinkClicked += new EventHandler(paginationLink_Click);
if (Page.IsPostBack)
{
//During all postbacks - Add the pagination links to the page
int tableDataCount = DatabaseSimulator.GetEmployeesCount(txtEmpName.Text);
string defaultInitialValueForHiddenControl = "Blank Value";
int indexFromPreviousDataRetrieval = 1;
if (!String.Equals(hdnCurrentIndex.Value, defaultInitialValueForHiddenControl))
{
indexFromPreviousDataRetrieval = Convert.ToInt32(hdnCurrentIndex.Value, CultureInfo.InvariantCulture);
}
//Set property of user control
uclPager.PreviousIndex = indexFromPreviousDataRetrieval;
//Call method in user control
uclPager.PreAddAllLinks(tableDataCount, pageSize, indexFromPreviousDataRetrieval);
}
}
protected void btnSearch_Click(object sender, EventArgs e)
{
//When Search is clicked, reset the index to 1 (first page)
pageIndex = 1;
BindBusinessProcessActivitiesData();
}
protected void paginationLink_Click(object sender, EventArgs e)
{
//When link is clicked, set the pageIndex from user control property
pageIndex = uclPager.CurrentClickedIndex;
BindBusinessProcessActivitiesData();
}
private void BindBusinessProcessActivitiesData()
{
string name = txtEmpName.Text;
List<Employee> searchResult = DatabaseSimulator.GetData(name, pageSize, pageIndex).ToList();
grdEmployee.DataSource = searchResult;
grdEmployee.DataBind();
//Index
hdnCurrentIndex.Value = pageIndex.ToString(CultureInfo.InvariantCulture);
//Get total number of records
int tableDataCount = DatabaseSimulator.GetEmployeesCount(name);
uclPager.AddPageLinks(tableDataCount, pageSize, pageIndex);
}
}
Database Part
public static class DatabaseSimulator
{
public static IEnumerable<Employee> GetData(string name, int pageSize,int index)
{
IEnumerable<Employee> employeesSource = SearchEmployees(name);
int skipUpto = ((index-1) * pageSize);
IEnumerable<Employee> searchResult = employeesSource.Skip(skipUpto).Take(pageSize);
return searchResult;
}
public static int GetEmployeesCount(string name)
{
List<Employee> employees = GetEmployees();
int employeesCount = 0;
if (String.IsNullOrEmpty(name))
{
employeesCount = employees.Count;
}
else
{
List<Employee> selectedEmployees = employees.Where(r => r.Name == name).ToList();
employeesCount = selectedEmployees.Count;
}
return employeesCount;
}
private static IEnumerable<Employee> SearchEmployees(string name)
{
List<Employee> employees = GetEmployees();
if (String.IsNullOrEmpty(name))
{
return employees;
}
return employees.Where(r => r.Name == name);
}
private static List<Employee> GetEmployees()
{
List<Employee> employees = new List<Employee>();
for (int i = 1; i <= 400; i++)
{
Employee emp = new Employee();
emp.EmpID = i;
if (i % 2 == 0)
{
emp.Name = "Divisible by 2";
}
else if (i % 3 == 0)
{
emp.Name = "Divisible by 3";
}
else if (i % 5 == 0)
{
emp.Name = "Divisible by 5";
}
else if (i % 7 == 0)
{
emp.Name = "Divisible by 7";
}
else
{
emp.Name = "Other -- "+ i.ToString();
}
employees.Add(emp);
}
return employees;
}
}
public class Employee
{
public int EmpID { get; set; }
public string Name { get; set; }
}