Best Coding Practices - EPiServer CMS

Views: 26100
Number of votes: 22
Average rating:

This might be obvious stuff for seasoned EPiServer developers but I still think it is worth writing about because I see these dangerous mistakes whenever I do a code review of an EPiServer project. Please note that this article is based on my previous blog posts on the subject of coding best practice.

Contents

Part 1 - Dos & Don'ts with EPiServer Properties

Null values

Firstly remember that all EPiServer properties with an empty value are never stored in the database. If you access it from code, it will always be null - not an empty string, 0 or false as you may expect it to be.

Why null? It is by design and is very convenient if you want to check if something is not set by an editor or does not exist on this page. You just have to compare with null regardless of data type.

Example - Do & Do not

This will throw NullReferenceException if the value is empty or missing:
sb.Append(CurrentPage["MyProperty"].ToString()); //DO NOT!

StringBuilder.Append accepts null objects so this is better:
sb.Append(CurrentPage["MyProperty"]);

This will throw NullReferenceException if the value is empty or missing:
<%= CurrentPage.Property["MyProperty"].Value.ToString()%> //DO NOT!

Markup will accept any type and will convert it to a string - so cast is not needed:
<%= CurrentPage ["MyProperty"] %>

Other examples with a fallback

If a string must be returned use the ?? operator:
string x = CurrentPage["MyProperty"] as string ?? string.Empty;

And for Value Type it is written like this:
DateTime x = (DateTime)(CurrentPage["MyProperty"] ?? DateTime.Now);
int i = (int)(CurrentPage["MyProperty"] ?? 0);

If you need a fallback in markup the ?? Operator can be used:
<%= CurrentPage["Heading"] ?? CurrentPage.PageName %>

Use Default indexer

Remember that there is a default indexer on PageData that also returns the value - see the below:

Example - Do & Don't
This expression:
<%= CurrentPage.Property["MyProperty"].Value %>

…is equivalent to:
<%= CurrentPage["MyProperty"] %>

Common EPiServer Property Names

It is very common that Page Types in EPiServer have the following properties:

Heading (string)

Used for the h1-tag of the page. If not defined the page is coded to fall back to the PageName. (See public templates for example implementation). This makes it possible to use PageName for a shorter text in menus and the meta title-tag and have a little longer text for the actual h1-header.

MainIntro (string)

This property is commonly used to describe a page in Teasers on other pages and in search results. It is usually also used to generate the meta description-tag which can affect the description external search engines like Google show.

There are functions in EPiServer that uses MainIntro – for example the PreviewText() method on PageTemplateContainer (the type of Container object in EPiServer PageList, PageTree, etc). It implements a fallback if no MainIntro exists to strip MainBody of HTML and it uses the first 400 characters.

MainBody (string)

MainBody is almost always used for the body text of an EPiServer page.

PART 2 - How to render valid xhtml

How do you inject dynamic content into your web page? The following is a list of the most common methods I see when doing quality and code reviews, I have added some comments and tried to highlight the dangerous pitfalls:

How to render valid xhtml #1: Inline Expressions with Code Render Blocks

Using inline expressions is a shortcut for calling the Write method:
Example - Do not:

<h2><%= CurrentPage.PageName %></h2>
<img src="<%= CurrentPage["ImageUrl"] %>" alt="<%= CurrentPage["MainIntro"] %>" />
<div>      <%= CurrentPage["MainBody"] %>
</div>  

Since xhtml is just text, writing some more in-between existing static blocks is fast and simple. Probably the easiest way to inject dynamic content but it is also dangerous!

Example - Do - always use HtmlEncode:
<h2><%= HttpUtility.HtmlEncode(CurrentPage.PageName) %></h2>

What happens if our PageName property contains a “<” or “&” character in the example above? Your page will not validate and you are at risk that it breaks down. It can also be exposed to script injection if the content is user generated.

To be on the safe side you should always call HttpUtility.HtmlEncode on all strings that you inject with inline expressions.

Example – Do always use a fallback for src-attributes
<img src="<%= CurrentPage["ImageUrl"] ?? "/Missing.png" %>" />
If your ImageUrl property is empty you would get a img-tag with an empty src-attribute. This can lead to the page being rendered twice. FireFox interpret a null src-attribute as a relative url. One strategy to handle this is to generate the whole img-tag in a code behind method instead.

How to render valid xhtml #2: With EPiServer:Property Web control

Using the EPiServer Property Web Controls instead of inline expressions has several benefits. It could be used for rendering the PageName and MainBody in the example above.

Example – Do use EPiServer:Property web control
<h2><EPiServer:Property PropertyName="PageName" runat="server" /></h2>
<EPiServer:Property CssClass="mainarea" PropertyName="MainBody" runat="server" />

1) Most EPiServer Property Controls uses EPiServer’s ToWebString() when rendering their content. This does almost the same job as always calling HtmlEncode in markup yourself.

public virtual void CreateDefaultControls()
{
    Label target = new Label();
    target.Text = PropertyData.ToWebString();
    CopyWebAttributes(target);
    Controls.Add(target);
}

2) Using EPiServer Property control enables the Simple Edit feature for the editors without any extra work. (If you do not know what “Simple Edit” is, you should read the Editors Manual for EPiServer CMS.

3) Different property types get different rendering automatically. This is commonly used for PageReferences and Url’s that are rendered as a Hyperlink.

Example – DO use EPiServer:Property for rendering links if possible:
<EPiServer:PageList PageLinkProperty="NewsArchivePage" MaxCount="5" runat="server" >
  <HeaderTemplate>
    <h3><EPiServer:Property PropertyName="PageLink" runat="server" /></h3>
    <ul>
  </HeaderTemplate>
  <ItemTemplate>
    <li><EPiServer:Property PropertyName="PageLink" runat="server" /></li>
  </ItemTemplate>
  <FooterTemplate>
    </ul>
  </FooterTemplate>
</EPiServer:PageList>

4) Use PageLinkProperty to follow a Page-property to another page and render a value. It takes care of all hassle and calls GetPage() and check for nulls for you.

Example – DO use EPiServer:Property to render values from another page
<h3><EPiServer:Property PageLinkProperty="FeatureArticlePage" PropertyName="PageLink" runat="server" /></h3>
<div><EPiServer:Property PageLinkProperty="FeatureArticlePage" PropertyName="MainIntro" runat="server" /></div>

This article is based on the blog posts Dos and Don'ts with EPiServer Properties and How to Render Valid xhtml

 

Comments

Hi Fredrik, Great article as allways. One question though:

How can I get DOPE on a XHTMLongString property declared this way instead of using the <EPiServer:Property control?

I would like to access DOPE rendering valid XHTML code like this:

<span><%= HttpUtility.HtmlEncode(CurrentPage.Property["MyXHTMLLongStringProperty"] ?? "")%></span>

Hi Fredrik,
Good article which I see is now included as part of the SDK:
http://sdk.episerver.com/library/cms6/Developers%20Guide/Core%20Features/Properties/Working%20with%20page%20properties.htm

I like the 'clean' fallback option:
string x = CurrentPage["MyProperty"] as string ?? string.Empty;

but get a build error (Cannot convert type 'EPiServer.Core.PropertyData' to 'string' via a reference conversion, boxing conversion, unboxing conversion, wrapping conversion, or null type conversion) when attempting the same with a Property Data Collection's page data:
PageData pd = pdc[0]; // contrived - just showing the pd comes from a page data collection
string x= pd.Property["SomeProperty"] as string ?? string.Empty;

I must add the .value to the property request:
string x= pd.Property["SomeProperty"].value as string ?? string.Empty;

pd and CurrentPage are both PageData ...
So, I'm left wondering why pd.Property["SomeProperty"] as string does not appear equivalent to pd.Property["SomeProperty"].Value as string