EPiServer  /  CMS August 25, 2009

Developing with Page Type Builder – Using Interfaces and Advanced Inheritance

In the this part we’ll finish the Bananas site by allowing some pages to have a specific menu name and adding meta descriptions to all pages. In the process we’ll see an example of how we can work with interfaces and an advanced example of using page type inheritance.

An example of using interfaces with page types

At the time of this writing me and many of my colleagues are working on a site that has a lot of editorial content. It’s so much content that the editorial staff expressed a need to prefix names of article pages with codes to gain a better overview. At the same time they wanted to be able to have long headlines which would not be suitable to display in menus. So, we needed to add another property to some page types called MenuName. If a page has this property it will be used when rendering links to that page in menus.

If we hadn’t been using Page Type Builder we would probably check if that property existed, or rather if it was null or not when iterating through a list of pages to display in a menu. That would however be a problem if we ever wanted to change the name of the property as the access would not be strongly typed, and any logic such as “if no menu name exists return the page name” would have to be put into the loop, making the code rather cluttered.

As we are using Page Type Builder we could instead check if a page where of a specific type and if it was we could cast it to that type and then access the MenuName property in a strongly typed way. That would however require a nasty if-else or case block that would have to be modified whenever we added a new page type with a MenuName property.

Instead we created an interfance, IMenuName, that the page types that had the MenuName property implemented. When displaying a menu we could then simple check if a page was of a type that implemented IMenuName and if so cast it to the interface and access the MenuName property.

For the Bananas site this might seem a bit overkill, but hey, we’re building a site about bananas, can anything really be overkill?

Creating IMenuName

The IMenuName interface is very simple. It just states that anyone who implements it must have a MenuName property of type string with a public getter. To create it add a new interface called IMenuName to the PageTypes folder and add the property. Here’s how it should look when done:

namespace Bananas.PageTypes
{
    public interface IMenuName
    {
        string MenuName { get; }
    }
}

Implementing IMenuName

Let’s make our ArticlePage class implement IMenuName. We do so by using techniques described in previous parts of the tutorial so I won’t go into the details. When done the ArticlePage class should look like the code below. The bold parts is what was added to implement IMenuName.

using EPiServer.Core;
using PageTypeBuilder;

namespace Bananas.PageTypes
{
    [PageType(Filename = "~/Templates/Pages/Article.aspx")]
    public class ArticlePage : EditorialPage, IMenuName 
    {
        [PageTypeProperty(Type = typeof(PropertyString))]
        public string MenuName
        {
            get
            {
                string menuName = this.GetPropertyValue(page => page.MenuName);
                if (!string.IsNullOrEmpty(menuName))
                    return menuName;

                return PageName;
            }
        }
    }
}

Modifying the menu control

The main menu for the Bananas site is displayed by the MainMenu control. The markup part, MainMenu.ascx, looks like this:

<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="MainMenu.ascx.cs" Inherits="Bananas.Templates.Controls.MainMenu" %>
<ul>
    <asp:Repeater ID="RepeaterMainMenu" runat="server">
        <ItemTemplate>
            <li><asp:HyperLink ID="HyperLinkMenuLink" runat="server" /></li>
        </ItemTemplate>
    </asp:Repeater>
</ul>

And the code behind part looks like this:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.UI.WebControls;
using EPiServer;
using EPiServer.Core;

namespace Bananas.Templates.Controls
{
    public partial class MainMenu : UserControlBase
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            IEnumerable<PageData> mainMenuItems = 
                from page in GetChildren(PageReference.StartPage)
                  where page.VisibleInMenu
                  && page.CheckPublishedStatus(PagePublishedStatus.Published)
                  select page;

            RepeaterMainMenu.DataSource = mainMenuItems;
            RepeaterMainMenu.ItemDataBound += RepeaterMainMenu_ItemDataBound;
            RepeaterMainMenu.DataBind();
        }

        void RepeaterMainMenu_ItemDataBound(object sender, RepeaterItemEventArgs e)
        {
            if(e.Item.DataItem == null)
                return;

            PageData menuItem = (PageData) e.Item.DataItem;
            
            HyperLink hyperLinkMenuLink = (HyperLink)e.Item.FindControl("HyperLinkMenuLink");
            hyperLinkMenuLink.Text = menuItem.PageName;

            hyperLinkMenuLink.NavigateUrl = menuItem.LinkURL;
        }
    }
}

As you can see the menu is made up of a repeater whose data source is the start page’s children filtered by published status and the VisibleInMenu property. In the RepeaterMainMenu_ItemDataBound method the bound item’s DataItem property gets casted to PageData and a hyperlink control in the repeaters item template get’s initialized.

What we want to do is, in the RepeaterMainMenu_ItemDataBound method, check if the menuItem variable, which is the PageData object that we’re dealing with, is of a type that implements IMenuName. If it is, we want to change the text property of the hyperlink control to the value of the MenuName property of the menuItem. Here’s how to do that:

void RepeaterMainMenu_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
    if(e.Item.DataItem == null)
        return;

    PageData menuItem = (PageData) e.Item.DataItem;
    
    HyperLink hyperLinkMenuLink = (HyperLink)e.Item.FindControl("HyperLinkMenuLink");
    hyperLinkMenuLink.Text = menuItem.PageName;

    if (menuItem is IMenuName)
        hyperLinkMenuLink.Text = ((IMenuName)menuItem).MenuName;

    hyperLinkMenuLink.NavigateUrl = menuItem.LinkURL;
}

To make the above code work we also have to add a using statement for Bananas.PageTypes but I omitted showing that for brevity.

Try it out by editing the History page and entering a name, such as “History of Bananas”, in the MenuName field.

MenuName

Adding a meta description to all pages

We’re almost done with the Bananas site. It might not have a lot of content but once we find an editor who’s passionate for bananas I’m sure it will look magical. In preparation for that we might also want to ensure that the editor is able to control what meta description is rendered for the pages so that he or she can do some basic search engine optimizations.

What I’m thinking here is that all page types should have a MetaDescription property which allows the editor to manually enter a description. This might become tedious if we have a lot of articles though so it might be a good idea to ensure that pages of the ArticlePage page type always have a description. If the editor haven’t entered one we should strip the MainBody property from HTML tags and crop the text and use that as meta description.

Adding a meta description property to all page types

Remember that we created a class called BasePageData that all page types inherit from in the second part of this tutorial? Thanks to that it’s now easy to add a new property to all page types. Open up the BasePageData class and add a new property named MetaDescription. While the description should be short and concise short string properties are edited in so small textboxes that I think it would be better to use a XHTML string property. We don’t want the editor to be able to enter any HTML markup though so we set the PageTypeProperty’s ClearAllLongStringSettings property to true. This way the property will be edited in a large text area but the editor can only write plain text.

using PageTypeBuilder;

namespace Bananas.PageTypes
{
    public abstract class BasePageData : TypedPageData
    {
        [PageTypeProperty(ClearAllLongStringSettings = true)]
        public virtual string MetaDescription { get; set; }
    }
}

Overriding the MetaDescription property for articles

Like I said before, if an article doesn’t have the MetaDescription property set the code property should instead return a part of the MainBody property. There is a couple of extension methods for the string type in the Bananas project that will help us with transforming the MainBody text, RemoveHtml and EllipseText.

Let’s get to it. Open up the ArticlePage class and override the MetaDescription property. Here’s how it should look:

[PageTypeProperty(ClearAllLongStringSettings = true)]
public override string MetaDescription
{
    get
    {
        string metaDescription = this.GetPropertyValue(page => page.MetaDescription);
        if (!string.IsNullOrEmpty(metaDescription))
            return metaDescription;

        return MainBody.RemoveHtml().EllipseText(150);
    }
    set
    {
        base.MetaDescription = value;
    }
}

We should also add a using statement for Bananas.ExtensionMethods which I omitted for brevity.

Rendering the MetaDescription property

The master page already renders a control for meta data, MetaData.ascx, but the control doesn’t have any output. To correct that we open up the code behind file for it and make it inherit from UserControlBase<BasePageData>. Then we add a description meta tag to the markup file which renders CurrentPage.MetaDescription as value.

The code behind file, MetaData.ascx.cs, should look like this:

using System;
using PageTypeBuilder.UI;
using Bananas.PageTypes;

namespace Bananas.Templates.Controls
{
    public partial class MetaData : UserControlBase<BasePageData>
    {
        protected void Page_Load(object sender, EventArgs e) {}
    }
}

And the markup file, MetaData.ascx should look like this:

<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="MetaData.ascx.cs" Inherits="Bananas.Templates.Controls.MetaData" %>
<meta name="description" content="<%= CurrentPage.MetaDescription %>" />  

If you compile the project and go to the History article and display the source code you'll see something like this:

<meta name="description" content="Bananas have a long and glorious history. Here we tell you all about it." />

 

Recapitulation

This part concludes the tutorial. All page types now have a MetaDescription property that the editor can edit and we have also ensured that articles always have a meta description even if the editor haven’t specified one. We have also seen a simple example of how we can work with interfaces now that our pages are not just PageData objects but of a type that we have created ourselves.

I hope you found this tutorial useful and that it has given you a few ideas about how to work with EPiServer CMS and Page Type Builder. You should know that there is more to Page Type Builder though. You can for instance define tabs in code and specify a lot of settings for page types and properties. There is also a lot to say about how to rename and remove page types and properties as well as changing the type of properties as Page Type Builder will not do that automatically for us (as it should never do anything that can be harmful). I will however leave those topics to future blog posts or tutorials.

Any feedback is greatly appreciated!

Parts in this tutorial

  1. Gettings started
  2. Inheritance and Specifying Property Types
  3. Advanced Property Access
  4. Using Interfaces and Advanced Inheritance

    Source code

    You can download the source code, both the starting point and the finished site, here.

PS. For updates about new posts, sites I find useful and the occasional rant you can follow me on Twitter. You are also most welcome to subscribe to the RSS-feed.

Joel Abrahamsson

Joel Abrahamsson

I'm a passionate web developer and systems architect living in Stockholm, Sweden. I work as CTO for a large media site and enjoy developing with all technologies, especially .NET, Node.js, and ElasticSearch. Read more

Comments

comments powered by Disqus

My book

Want a structured way to learn EPiServer 7 development? Check out my book on Leanpub!

More about EPiServer CMS