EPiServer  /  CMS August 25, 2009

Developing with Page Type Builder – Advanced Property Access

In this part we’ll look at more advanced ways to access property values in page type classes and how that enables us to create page types that encapsulate application logic.

Objective

In previous parts of the tutorial we have created a sample project called Bananas with two page types, StartPage and ArticlePage. Both of them have a Headline property which we will modify in this part. We’ll also create a third page type which will be used for listing other pages, such as groups of articles.

Modifying the Headline property

In the previous part we specified a headline for the start page but not for our article (about the history of bananas). While specifying the headline for the article is easy it’s redundant in many cases where the editor wants it to be the same as the name of the page. The usual solution for this is to check if the property value is null when rendering the page, and if it is instead render the page name.

From a functional standpoint that’s fine but from an architectural perspective it’s not optimal. First of all, if we have several templates that renders articles (which we do in the sense that both Start.aspx and Article.aspx renders subtypes of EditorialPage) we’ll have to duplicate that logic between them. We could of course create some sort of utility class for figuring out what headline to render but that still doesn’t solve the second problem, namely that we’re putting application logic into the presentation layer. After all, isn’t it up to the page to tell the presentation layer what it’s headline is? I think so, and luckily making it do so is quite easy.

What we’ll have to do is modify the Headline property in EditorialPage so that it returns the value of the Headline property if it exists. Otherwise it should return the name of the page.

Using GetPropertyValue

At the moment the Headline property is implemented as an automatic property (get; set;) and therefore Page Type Builder intercepts calls to it and does the actual property value retrieving.

[PageTypeProperty(Type = typeof(PropertyString), SortOrder = 0)]
public virtual string Headline { get; set; }

There is however another way for us to implement the retrieving of the property’s value, by using the GetPropertyValue extension method. Change the implementation of the Headline property into this:

[PageTypeProperty(Type = typeof(PropertyString), SortOrder = 0)]
public virtual string Headline { 
    get
    {
        return this.GetPropertyValue<EditorialPage, string>(
            page => page.Headline);
    }
}

Let’s dissect the new implementation, “return this.GetPropertyValue<EditorialPage, string>(page => page.Headline);”.

As I mentioned before GetPropertyValue is an extension method and even though it is an extension for the PageData class which EditorialPage is a descendant of we must specify which object it should be called on, that explains the “this” part of the statement.

The next part is the generic parameters <EditorialPage, string>. The first parameter is the type of object the method is invoked on, that is the type of “this” in this case, which is EditorialPage. The second generic parameter is the type that should be retrieved and for PropertyString or XHTML string properties this is usually the string type.

Last but not least the method expects a lambda expression with which it will figure out which property’s value it should retrieve. In other words we could in theory invoke it with “page => page.MainBody” instead and the Headline property would then return the value of the MainBody EPiServer property.

Since the generic parameters in this case matches the object type and property return type they are actually redundant, so you can remove them, making the Headline property instead look like this:

[PageTypeProperty(Type = typeof(PropertyString), SortOrder = 0)]
public virtual string Headline { 
    get
    {
        return this.GetPropertyValue(page => page.Headline);
    }
}

Adding conditional logic

As we now have changed the implementation of the Headline property to something we control (instead of automatic implementation by the compiler and Page Type Builder) we can now modify the property to return the page’s name if no headline is specified.

[PageTypeProperty(Type = typeof(PropertyString), SortOrder = 0)]
public virtual string Headline { 
    get
    {
        string headline = this.GetPropertyValue(page => page.Headline);
        if (!string.IsNullOrEmpty(headline))
            return headline;

        return PageName;
    }
}

Compile the project and head over to the History article and you’ll see that it now has a headline.

ArticleHeadline

More about GetPropertyValue and SetPropertyValue

As you just saw you can use the GetPropertyValue extension method to retrieve property values. There is also a SetPropertyValue extension method that works the other way around, assisting you with setting property values.

It’s important to note that value types (int, DateTime etc) really isn’t implemented as value types, in the sense that they must always have a value, in EPiServer. Instead EPiServer implements them as their corresponding nullable types, and in most cases so should you when working with Page Type Builder. The only scenario where you would not implement an integer property as an int? instead of an int for instance is if you at the same time specify it as required by setting the Required property of it’s PageTypeProperty attribute to true.

Creating the List page type

By now we have page types for the start page and for articles (they are very similar I know, but bear with me here, it’s just a tutorial :-)). The Bananas site also requires a page type that lists other pages, in a first version the children of the list page.

Just like with the headline property for the editorial pages we could implement this logic in the code behind file for List.aspx but we’ll instead separate that logic from the presentation layer and make the page supply a list of pages that should be listed by the presentation layer.

We implement the List page type by creating a new class named ListPage that inherits from BasePageData. To it we add a PageType attribute and specify the path to List.aspx. Finally we create a (code but not EPiServer) property called PagesToList of type IEnumerable<PageData>. The property’s getter fetches the children of the page and filters out any pages that are not published. Here’s how it all looks:

using System.Collections.Generic;
using System.Linq;
using EPiServer;
using EPiServer.Core;
using PageTypeBuilder;

namespace Bananas.PageTypes
{
    [PageType(Filename = "~/Templates/Pages/List.aspx")]
    public class ListPage : BasePageData
    {
        public IEnumerable<PageData> PagesToList
        {
            get
            {
                return DataFactory.Instance.GetChildren(PageLink)
                    .Where(page => page.CheckPublishedStatus(PagePublishedStatus.Published));
            }
        }
    }
}

We also need to implement the presentation logic. Luckily the markup is already finished in List.aspx and most of the logic is also done in the code behind file for List.aspx.

List.aspx looks like this:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="List.aspx.cs" Inherits="Bananas.Templates.Pages.List" MasterPageFile="~/Templates/MasterPages/Main.Master" %>
<asp:Content ContentPlaceHolderID="ContentPlaceHolderMainContent" runat="server">
    <ul>
        <asp:Repeater ID="RepeaterPageList" runat="server">
            <ItemTemplate>
                <li><asp:HyperLink ID="HyperLinkListItem" runat="server" /></li>
            </ItemTemplate>
        </asp:Repeater>
    </ul>
</asp:Content>

And it’s code behind file looks like this:

using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using EPiServer.Core;

namespace Bananas.Templates.Pages
{
    public partial class List : Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            RepeaterPageList.ItemDataBound += RepeaterPageList_ItemDataBound;
            RepeaterPageList.DataBind();
        }

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

            PageData listItem = (PageData) e.Item.DataItem;
            HyperLink hyperLinkListItem = (HyperLink)e.Item.FindControl("HyperLinkListItem");
            hyperLinkListItem.Text = listItem.PageName;
            hyperLinkListItem.NavigateUrl = listItem.LinkURL;
        }
    }
}

As you can see, what we are dealing with here is a repeater that will render links for a number of PageData objects in an unordered list. One thing is missing though and that is it’s data source. To implement that we modify the code behind file to make it inherit from TemplatePage<ListPage> so that the CurrentPage property will be of type ListPage. Then we set the repeaters data source to CurrentPage.PagesToList, like this:

using System;
using System.Web.UI.WebControls;
using EPiServer.Core;
using PageTypeBuilder.UI;
using Bananas.PageTypes;

namespace Bananas.Templates.Pages
{
    public partial class List : TemplatePage<ListPage>
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            RepeaterPageList.DataSource = CurrentPage.PagesToList;
            RepeaterPageList.ItemDataBound += RepeaterPageList_ItemDataBound;
            RepeaterPageList.DataBind();
        }

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

            PageData listItem = (PageData) e.Item.DataItem;
            HyperLink hyperLinkListItem = (HyperLink)e.Item.FindControl("HyperLinkListItem");
            hyperLinkListItem.Text = listItem.PageName;
            hyperLinkListItem.NavigateUrl = listItem.LinkURL;
        }
    }
}

Try it out by creating a new page of type ListPage under the start page and name it Recipes. Add a few “recipes” of type article under it and you’ll see that the Recipes page will render a list of links to them.

ListPage

Recapitulation

In this part we learned about how to gain more control over how property values are retrieved in page type classes, which in turn enabled us to look at two examples of how we could  build “smart properties”. Please note that these examples are not necessarily best practices but just examples of what is possible. Where to put logic is always tricky and must be determined depending on the context. At least Page Type Builder offers us a way to move it from the presentation layer with it’s ASPX’s and user controls to the less cluttered and more easily testable page type classes.

In the next 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.

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