Frederik Vig – ASP.NET developer

Follow me

Creating a Custom EPiServer Paging Control

Earlier I wrote about the PageList control from EPiServer, I mentioned in that post that the markup that the PagingControl renders is not the best. For instance it will not work without JavaScript enabled on the client (for search engines the links wont work). I therefor decided to create my own.

To start with I went to the EPiServer SDK to have a look at the PagingControl class. Here I was able to see the namespace and what .dll file it lives in (EPiServer.dll). I then opened up Reflector, to inspect EPiServer.dll and have a closer look at the code.

Description of Reflector by Red Gate.

.NET Reflector enables you to easily view, navigate, and search through, the class hierarchies of .NET assemblies, even if you don’t have the code for them. With it, you can decompile and analyze .NET assemblies in C#, Visual Basic, and IL.

To find the PagingControl class you can either use the search feature or navigate the namespaces.

PagingControl class with Reflector

The great thing is that all the methods are virtual which means that we can override them :) .

Start by creating a new class and inherit from PagingControl (remember to add using EPiServer.Web.WebControls;).

using System;
using System.Text;
using System.Web;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using EPiServer;
using EPiServer.Core;
using EPiServer.Web.WebControls;
 
namespace FV.Templates.FV.Classes
{
    public class CustomPager : PagingControl
    {
    }
}

Then create a new page template with a PageList control.

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Page.aspx.cs" Inherits="FV.Templates.FV.Pages.Page" MasterPageFile="~/Templates/Public/MasterPages/MasterPage.master" %>
<asp:Content runat="server" ContentPlaceHolderID="MainBodyRegion">
    <EPiServer:PageList runat="server" ID="pglList">
        <HeaderTemplate>
            <ul>
        </HeaderTemplate>
        <ItemTemplate>
            <li><EPiServer:Property runat="server" PropertyName="PageLink" /></li>
        </ItemTemplate>
        <FooterTemplate>
            </ul>
        </FooterTemplate>
    </EPiServer:PageList>
</asp:Content>
using System;
using EPiServer;
using EPiServer.Core;
using FV.Templates.FV.Classes;
 
namespace FV.Templates.FV.Pages
{
    public partial class Page : TemplatePage
    {
        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
 
            pglList.Paging = true;
            pglList.PageLink = PageReference.StartPage;
            pglList.PagingControl = new CustomPager();
            pglList.PagesPerPagingItem = 2;
        }
    }
}

Notice that we set the PageList’s PagingControl property to a new instance of our CustomPager web control.

If you open up a browser and run the code, you’ll just get a standard PagingControl with the JavaScript and bad markup (since we haven’t overridden anything yet).

Lets start by adding an ordered list for the paging items.

To do this we have to override the CreatePagingItems, AddSelectedPagingLink and AddUnselectedPagingLink methods.

using System;
using System.Text;
using System.Web;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using EPiServer;
using EPiServer.Core;
using EPiServer.Web.WebControls;
 
namespace FV.Templates.FV.Classes
{
    public class CustomPager : PagingControl
    {
        private HtmlGenericControl Container
        {
            get; set;
        }
 
        private static string Translate(string text)
        {
            return LanguageManager.Instance.Translate("/webcontrols/paging/" + text);
        }
 
        protected override LinkButton AddSelectedPagingLink(int pagingIndex, string text, string altText)
        {
            var li = new HtmlGenericControl("li");
            li.Attributes.Add("class", this.CssClassSelected);
            li.Attributes.Add("title", altText);
            li.InnerText = text;
            this.Container.Controls.Add(li);
            return null;
        }
 
        protected override LinkButton AddUnselectedPagingLink(int pagingIndex, string text, string altText, bool visible)
        {
            var li = new HtmlGenericControl("li");
            var child = this.CreatePagingLink(pagingIndex, text, altText);
            child.CssClass = this.CssClassUnselected;
            li.Visible = visible;
 
            li.Controls.Add(child);
            this.Container.Controls.Add(li);
            return null;
        }
 
        public override void CreatePagingItems(PageDataCollection pages)
        {
            int pagingIndex = this.CurrentPagingItemCount - 1;
            bool visible = this.CurrentPagingItemIndex > 0;
            bool flag2 = this.CurrentPagingItemIndex < pagingIndex;
            if ((pagingIndex != 0) || !this.AutoPaging)
            {
                this.Container = new HtmlGenericControl("ol");
                this.LinkCounter = 0;
                if (this.FirstPagingItemText.Length > 0)
                {
                    this.AddUnselectedPagingLink(0, this.FirstPagingItemText, Translate("firstpage"), visible);
                }
                if (this.PrevPagingItemText.Length > 0)
                {
                    this.AddUnselectedPagingLink(this.CurrentPagingItemIndex - 1, this.PrevPagingItemText, Translate("prevpage"), visible);
                }
                for (int i = 0; i <= pagingIndex; i++)
                {
                    if (i == this.CurrentPagingItemIndex)
                    {
                        this.AddSelectedPagingLink(i, Convert.ToString((int)(i + 1)), Translate("currentpage"));
                    }
                    else
                    {
                        this.AddUnselectedPagingLink(i, Convert.ToString((int)(i + 1)), string.Format(Translate("pagenumber"), i + 1), true);
                    }
                }
                if (this.NextPagingItemText.Length > 0)
                {
                    this.AddUnselectedPagingLink(this.CurrentPagingItemIndex + 1, this.NextPagingItemText, Translate("nextpage"), flag2);
                }
                if (this.LastPagingItemText.Length > 0)
                {
                    this.AddUnselectedPagingLink(pagingIndex, this.LastPagingItemText, Translate("lastpage"), flag2);
                }
 
                this.Controls.Add(this.Container);
            }
        }
    }
}

I added a private property to hold my ordered list (Container), so that I can easily add child controls to it. I Also removed the calls to the AddLinkSpacing method (since we don’t need it).

If you browse the page you’ll see that everything is inside an ordered list now.

To fix the JavaScript links I created a new method CreatePagingHyperLink that will create the HyperLink control with the correct url. I Then added code in the OnInit method to get the query string and set the PageList’s CurrentPagingItemIndex property.

using System;
using System.Text;
using System.Web;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using EPiServer;
using EPiServer.Core;
using EPiServer.Web;
using EPiServer.Web.WebControls;
 
namespace FV.Templates.FV.Classes
{
    public class CustomPager : PagingControl
    {
        private HtmlGenericControl Container
        {
            get; set;
        }
 
        protected override void OnInit(EventArgs e)
        {
            base.OnInit(e);
 
            if (HttpContext.Current.Request.QueryString["p"] == null)
            {
                return;
            }
 
            var p = HttpUtility.HtmlEncode(HttpContext.Current.Request.QueryString["p"]);
            int num;
 
            int.TryParse(p, out num);
            this.CurrentPagingItemIndex = num;
        }
 
        private static string Translate(string text)
        {
            return LanguageManager.Instance.Translate("/webcontrols/paging/" + text);
        }
 
        protected override LinkButton AddSelectedPagingLink(int pagingIndex, string text, string altText)
        {
            var li = new HtmlGenericControl("li");
            li.Attributes.Add("class", this.CssClassSelected);
            li.Attributes.Add("title", altText);
            li.InnerText = text;
            this.Container.Controls.Add(li);
            return null;
        }
 
        protected override LinkButton AddUnselectedPagingLink(int pagingIndex, string text, string altText, bool visible)
        {
            var li = new HtmlGenericControl("li");
            var child = this.CreatePagingHyperLink(pagingIndex, text, altText);
            child.CssClass = this.CssClassUnselected;
            li.Visible = visible;
 
            li.Controls.Add(child);
            this.Container.Controls.Add(li);
            return null;
        }
 
        private static string CreateUrl(int count)
        {
            var url = new UrlBuilder(HttpContext.Current.Request.Url);
 
            if (UrlRewriteProvider.IsFurlEnabled)
            {
                Global.UrlRewriteProvider.ConvertToExternal(url, null, Encoding.UTF8);
            }
 
            return UriSupport.AddQueryString(url.ToString(), "p", count.ToString());
        }
 
        protected HyperLink CreatePagingHyperLink(int pagingIndex, string text, string altText)
        {
            var link = new HyperLink();
            this.LinkCounter++;
            link.ID = "PagingID" + this.LinkCounter;
            link.NavigateUrl = CreateUrl(pagingIndex);
            link.Text = text;
            link.ToolTip = altText;
 
            return link;
        }
 
        public override void CreatePagingItems(PageDataCollection pages)
        {
            int pagingIndex = this.CurrentPagingItemCount - 1;
            bool visible = this.CurrentPagingItemIndex > 0;
            bool flag2 = this.CurrentPagingItemIndex < pagingIndex;
            if ((pagingIndex != 0) || !this.AutoPaging)
            {
                this.Container = new HtmlGenericControl("ol");
                this.LinkCounter = 0;
                if (this.FirstPagingItemText.Length > 0)
                {
                    this.AddUnselectedPagingLink(0, this.FirstPagingItemText, Translate("firstpage"), visible);
                }
                if (this.PrevPagingItemText.Length > 0)
                {
                    this.AddUnselectedPagingLink(this.CurrentPagingItemIndex - 1, this.PrevPagingItemText, Translate("prevpage"), visible);
                }
                for (int i = 0; i <= pagingIndex; i++)
                {
                    if (i == this.CurrentPagingItemIndex)
                    {
                        this.AddSelectedPagingLink(i, Convert.ToString((int)(i + 1)), Translate("currentpage"));
                    }
                    else
                    {
                        this.AddUnselectedPagingLink(i, Convert.ToString((int)(i + 1)), string.Format(Translate("pagenumber"), i + 1), true);
                    }
                }
                if (this.NextPagingItemText.Length > 0)
                {
                    this.AddUnselectedPagingLink(this.CurrentPagingItemIndex + 1, this.NextPagingItemText, Translate("nextpage"), flag2);
                }
                if (this.LastPagingItemText.Length > 0)
                {
                    this.AddUnselectedPagingLink(pagingIndex, this.LastPagingItemText, Translate("lastpage"), flag2);
                }
 
                this.Controls.Add(this.Container);
            }
        }
    }
}

I also added some CSS code to give you an idea of how easy it is to style and change the look.

.PagingContainer ol {
    margin: 0;
    padding: 0;
    overflow: hidden;
}
 
.PagingContainer li {
    list-style: none;
    display: inline;
}
 
.PagingContainer a, .SelectedPagingItem {
    text-decoration: none;
    float: left;
    padding: .2em;
    border: 1px solid #303233;
    color: #303233;
    font-weight: bold;
    margin-right: .1em;
}
 
.PagingContainer .SelectedPagingItem {
    background: #303233;
    color: #fff;
}

The result

Custom paging control

Markup rendered

<div class="PagingContainer">
        <ol>
            <li class="SelectedPagingItem" title="Current page">1</li>
            <li>
                <a id="ctl00_MainRegion_MainContentRegion_MainBodyRegion_pglList_ctl05_PagingID4" title="Page 2" class="UnselectedPagingItem" href="http://fv/en/Test-Paging/?p=1">2</a>
            </li>
            <li>
                <a id="ctl00_MainRegion_MainContentRegion_MainBodyRegion_pglList_ctl05_PagingID5" title="Page 3" class="UnselectedPagingItem" href="http://fv/en/Test-Paging/?p=2">3</a>
            </li>
            <li>
                <a id="ctl00_MainRegion_MainContentRegion_MainBodyRegion_pglList_ctl05_PagingID6" title="Next page" class="UnselectedPagingItem" href="http://fv/en/Test-Paging/?p=1">></a>
            </li>
            <li>
                <a id="ctl00_MainRegion_MainContentRegion_MainBodyRegion_pglList_ctl05_PagingID7" title="Last page" class="UnselectedPagingItem" href="http://fv/en/Test-Paging/?p=2">»</a>
            </li>
        </ol>
    </div>

Download the code

Related Posts:

Share:
  • Twitter
  • DotNetKicks
  • DZone
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • StumbleUpon
  • Digg

14 Comments

  1. Thanks for an excellent tip which many of our SiteSeeker and EPiServer customers will appreciate! A minor tweaking would be to render the current page in the list not as a link but as pure text (as recommended here: http://usability.coi.gov.uk/theme/navigation/basics-of-usable-navigation-bars.aspx). That would require changing the row:

    var child = this.CreatePagingHyperLink(pagingIndex, text, altText);

    to instead render text, and slightly modifying the CSS (“.PagingContainer a”).

  2. Frederik Vig says:

    Thanks for that Adam! I’ve updated the AddSelectedPagingLink method and the CSS code.

    Frederik

  3. [...] The links won’t work without JavaScript (users without JavaScript enabled, like search engines, won’t be able to get to the content). For a solution on this see my post: Creating a Custom EPiServer Paging Control. [...]

  4. Peter says:

    I’ve created the CustomPager-class and assigned it to be used in a pagelist and allthough it renders correctly I get some navigation problems. When pressing the “2″ to go to the second page in the listing I get a odd-looking version of the start page and the url is “…/Templates/Public/Pages/Articles.aspx?id=27&epslanguage=sv&p=1″ instead of “…/sv/Artiklar/?p=2″ that I assumed it would be.
    I’ve fiddled around in the CreateUrl-function and the url-variable inside it and added the “.PathAndQuery” to the “HttpContext.Current.Request.Url”. I don’t know excactly what this does but it gives me the disered look of the url but all the links in the pagination ends with “?p=1″.

    Any idea of what might be the problem?

  5. Nice post..Keep them coming :) Thanks for sharing.

  6. Andrey Lazarev says:

    Hello, Frederik!

    I’ve used your approach to implement the custom paging and it works OK, but…
    Paging works correctly *ONLY* for logged-in users. For anynomous visitor – when you click “2″ in paging the same first page will be just reloaded.

    Any ideas why this could be?

  7. Frederik Vig says:

    @Peter – Sorry for not getting back to you until now.. I’ve updated the code a little now to support both friendly and regular urls (just updated the CreateUrl method)
    @Andrey – Sounds like a databinding problem.. have you tried using EPiServer paging control? I’m guessing you’ll get the same result with it. This is only guessing, I’ll need to take a look at your code to give a proper answer.

  8. Andrey Lazarev says:

    Frederik, you mean – using not custom but ’standard’ paging control? Yes, I tried this too – it is working.
    The only difference from your code is that I didn’t placed the EPiServer.PageList control inside the page template but in the WebControl instead:

    pageTemplate[ WebControl[PageList] ]

    And now I’m trying to apply custom paging to this PageList.

    Also I’ve selected to use simple links instead of …:

    protected override LinkButton AddSelectedPagingLink(int pagingIndex, string text, string altText)
    {
    HtmlGenericControl cntrl = new HtmlGenericControl();
    cntrl.Attributes.Add(“class”, this.CssClassSelected);
    cntrl.InnerText = text;
    HtmlGenericControl cntrlSeparator = new HtmlGenericControl();
    cntrlSeparator.InnerHtml = “    ”;
    this.HtmlContainer.Controls.Add(cntrl);
    this.HtmlContainer.Controls.Add(cntrlSeparator);
    return null;
    }

    protected override LinkButton AddUnselectedPagingLink(int pagingIndex, string text, string altText, bool visible)
    {
    HtmlGenericControl cntrl = new HtmlGenericControl();
    HyperLink hlChild = this.CreatePagingHyperLink(pagingIndex, text, altText);
    hlChild.CssClass = this.CssClassUnselected;
    hlChild.Visible = visible;
    HtmlGenericControl hlChildSeparator = new HtmlGenericControl();
    hlChildSeparator.InnerHtml = “    ”;

    cntrl.Controls.Add(hlChild);
    cntrl.Controls.Add(hlChildSeparator);
    this.HtmlContainer.Controls.Add(cntrl);
    return null;
    }

    // Code for this: http://world.episerver.com/Forum/Pages/Thread.aspx?id=19742&epslanguage=en#RE:Custom%20pagin%20template
    private static string CreateUrl(int count)
    {
    UrlBuilder url = new UrlBuilder(HttpContext.Current.Request.Url.PathAndQuery);
    Global.UrlRewriteProvider.ConvertToExternal(url, null, UTF8Encoding.UTF8);

    return UriSupport.AddQueryString(url.ToString(), “p”, count.ToString());
    }

    protected HyperLink CreatePagingHyperLink(int pagingIndex, string text, string altText)
    {
    HyperLink link = new HyperLink();
    this.LinkCounter++;
    link.ID = “PagingID” + this.LinkCounter;
    link.NavigateUrl = CreateUrl(pagingIndex);
    link.Text = text;
    link.ToolTip = altText;

    return link;
    }

    And – removed first-last, prev-next links

    I have a public property in my webcontrol named ‘EnablePaging’ and I’m using it to select how PageList should be rendered:

    if (EnablePaging == true)
    {
    this.epiNewsListSimple.Paging = true;
    this.epiNewsListSimple.PageLink = newssource;
    this.epiNewsListSimple.PagingControl = new {%namespace here%}.CustomPager();
    this.epiNewsListSimple.PagesPerPagingItem = 3;
    this.epiNewsListSimple.MaxCount = -1;
    this.epiNewsListSimple.EnableViewState = true;
    }
    else
    {
    this.epiNewsListSimple.Paging = false;
    this.epiNewsListSimple.PageLink = newssource;
    this.epiNewsListSimple.MaxCount = 3;
    }

    this.epiNewsListSimple.DataBind();

  9. Andrey Lazarev says:

    Re-tested once again – still same strange behavior. Standard paging is working for PageList and the custom one – isn’t. :(

  10. Frederik Vig says:

    If you remove all the code inside the CustomPager class, so you only have something like this left.

    public class CustomPager : PagingControl
    {
    }

    The paging should work. After making sure it works try adding a few more methods and check that it still works, then some more etc, until you find the source of the problem.

  11. Andrey Lazarev says:

    Frederik, it looks like a dumb joke, but I got it working making this change:

    if (EnablePaging == true)
    {
    this.epiNewsListSimple.Paging = true;
    this.epiNewsListSimple.PageLink = newssource;
    this.epiNewsListSimple.PagingControl = new CustomPager();
    this.epiNewsListSimple.PagesPerPagingItem = 3;

    if (Request.QueryString["p"] != null)
    {
    this.epiNewsListSimple.PagingControl.CurrentPagingItemIndex = int.Parse(Request.QueryString["p"].ToString().Trim());
    }
    }
    else
    {
    this.epiNewsListSimple.Paging = false;
    this.epiNewsListSimple.PageLink = newssource;
    this.epiNewsListSimple.MaxCount = 3;
    }

    this.epiNewsListSimple.DataBind();

    So, for me it looks like changes to the paging control made from the custom class didn’t change anything in fact :(
    Very strange…

    Anyway – the control is working as expected now.

  12. Øyvind says:

    Thanx for sharing. If the datasource of your news list is a pagedatacollection, you need to set the lstNewsList.PagingControl.CurrentPagingItemIndex before the Init in the custom pager. But else it works out of the box.

  13. Peter says:

    I totally forgot to follow up on this page so no harm done. I’ve got it working now thanks to your friendly url-support. To start out I did have the same problem as Andrey had but it was easily fixed. Thanks!

  14. Peter says:

    How would you go about to make the pager only show 10 pages at a time? E.g. “1 2 3 4 5 6 7 8 9 10 >”, “”. At the moment my pager shows 27 pages with 10 pages on each one of them and my guess is that when these get to about 45 the page listing will continue on outside the graphics and will be unreachable.

Leave a Reply

Spam Protection by WP-SpamFree

© Copyright Frederik Vig. Based on Fluid Blue theme