Part 3: Creating the start page – Create an EPiServer site from scratch
In this part we’re creating the start page for our site. Lets start by taking a look at the page we’re implementing.
We have one featured news story at the top, followed by a list of 4 news stories. Lets start with that. Create the page type, by creating a new class in your PageTypes folder in Visual Studio, give it a name of StartPage. Make it derive from BasePageData.
namespace Jungle.Templates.Jungle.PageTypes { public class StartPage : BasePageData { } }
Add a PageType attribute to StartPage.
[PageType( Name = "[Jungle] Start page", Filename = "/default.aspx", SortOrder = 1200)] public class StartPage : BasePageData { }
This does the same as manually going to admin mode, creating a new page type there, and adding the information below.
| Field | Value |
|---|---|
| Name | [Jungle] Start page |
| File name | /default.aspx |
| Sort index | 1200 |
Page Type Builder will do this for us, so no more repetitive creating Page Types with the same properties and information
. If you wish to learn more about the Page Type Builder project see its project home page.
Remember that you have an easy reference to the start page with PageReference.StartPage, which makes it a great place to store properties that get used through out the site. You can of course also use dynamic properties for this, but it is recommend to not overuse them because of the performance implications (recursively goes up the page structure to find the properties value).
We’ll probably come back here later and add new properties, but for now lets just add properties for the news functionality. We need a property that holds a reference to the featured news story, one that references the news container page, and one with how many news stories to display.
using EPiServer.Core; using EPiServer.DataAbstraction; using PageTypeBuilder; namespace Jungle.Templates.Jungle.PageTypes { [PageType( Name = "[Jungle] Start page", Filename = "/default.aspx", SortOrder = 1200)] public class StartPage : BasePageData { [PageTypeProperty( EditCaption = "Featured news story", Type = typeof(PropertyPageReference))] public virtual PageReference FeaturedNews { get; set; } [PageTypeProperty( EditCaption = "Page to get news from", Type = typeof(PropertyPageReference))] public virtual PageReference NewsContainerPage { get; set; } [PageTypeProperty( EditCaption = "Number of news stories to display", DefaultValueType = DefaultValueType.Value, DefaultValue = "4", Type = typeof(PropertyNumber))] public virtual int NewsCount { get; set; } } }
If you build and login to admin mode, you should see our new Page Type created there, with the three properties we just created. Pretty cool huh?
Lets create our start page by going to edit mode (http://jungle/cms/UI/edit/), right-click the root folder, and choose Create new. Choose our newly created [Jungle] Start Page, give it a name of Jungle start, and Save and Publish it.
After creating our start page, we need to update the start page id in the web.config file to use it instead of the one created by Public Templates. Get the id by hovering over Jungle start in edit mode.

Open up web.config and search (ctrl + f) for pagestartid. Replace “3″ with the new start pages id (26 in my case). Save, and verify the change by going back to edit mode (notice that the globe is now next to Jungle start).

We can now go back to admin mode and edit [Jungle] Start page, click Settings, and uncheck Available in Edit mode. No need to have unnecessary page types available.
Implementing
Delete default.aspx, right-click JungleSite, and choose Add, New Item. Choose Web Form and give it the name default.aspx. Delete everything except the first line, and add MasterPageFile=”~/Templates/Jungle/MasterPages/Site.Master”. You should now have something like this:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="default.aspx.cs" Inherits="Jungle._default" MasterPageFile="~/Templates/Jungle/MasterPages/Site.Master" %>
Since our master page doesn’t have any ContentPlaceHolders yet, we need to add some. Before we can do that, we need to know what markup differences there is between style.html (which we use in site.master), and index.html.
I find the quickest and easiest approach is to use Firebug for this. Open up index.html and styles.html from the Jungleland downloaded files, in Firefox. Start Firebug by pressing F12. Use Firebugs inspect tool to inspect the markup of both.
They are both pretty similar as you can see. The featured news story is placed right after <div id=”header”> and the content inside <div id=”main”> is a bit different. The rest is the same.
Open up Site.master and add two ContentPlaceHolders. One right after PageHeader, and one inside <div id=”main”>.
<%@ Master Language="C#" AutoEventWireup="true" CodeBehind="Site.master.cs" Inherits="Jungle.Templates.Jungle.MasterPages.Site" %> <%@ Register TagPrefix="Jungle" TagName="Header" Src="~/Templates/Jungle/Units/Header.ascx" %> <%@ Register TagPrefix="Jungle" TagName="PageHeader" Src="~/Templates/Jungle/Units/PageHeader.ascx" %> <%@ Register TagPrefix="Jungle" TagName="PageFooter" Src="~/Templates/Jungle/Units/PageFooter.ascx" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" runat="server" id="HtmlElement"> <head runat="server"> <title runat="server"></title> <Jungle:Header runat="server" /> </head> <body> <form runat="server"> <!-- wrap --> <div id="wrap"> <!-- header --> <Jungle:PageHeader runat="server" /> <asp:ContentPlaceHolder runat="server" ID="FeaturedContentRegion" /> <!-- content --> <div id="content-wrap" class="clear"> <div id="content"> <!-- main --> <div id="main"> <asp:ContentPlaceHolder runat="server" ID="MainContentRegion" /> <!-- /main --> </div> <!-- sidebar --> <div id="sidebar"> <div class="sidemenu"> <h3>Sidebar Menu</h3> <ul> <li><a href="index.html">Home</a></li> <li><a href="index.html#TemplateInfo">TemplateInfo</a></li> <li><a href="style.html">Style Demo</a></li> <li><a href="blog.html">Blog</a></li> <li><a href="archives.html">Archives</a></li> <li><a href="http://www.styleshout.com/">More Free Templates</a></li> <li><a href="http://www.4templates.com/?aff=ealigam">Premium Templates</a></li> </ul> </div> <div class="sidemenu"> <h3>Sponsors</h3> <ul> <li><a href="http://themeforest.net?ref=ealigam">ThemeForest</a><span>Your Choice for Site Templates, Wordpress, Joomla and CMS Themes</span></li> <li><a href="http://www.4templates.com/?aff=ealigam">4templates</a><span>Low Cost Hi-Quality Templates</span></li> <li><a href="http://store.templatemonster.com?aff=ealigam">TemplateMonster</a><span>Delivering the Best Templates on the Net!</span></li> <li><a href="http://tinyurl.com/3cgv2m">Text Link Ads</a><span>Monetized your website</span></li> <li><a href="http://www.fotolia.com/partner/114283">Fotolia</a><span>Free stock images or from $1</span></li> <li><a href="http://www.dreamhost.com/r.cgi?287326">Dreamhost</a><span>Premium webhosting</span></li> </ul> </div> <h3>Image Gallery </h3> <p class="thumbs"> <a href="index.html"><img src="images/thumb.jpg" width="40" height="40" alt="thumbnail" /></a> <a href="index.html"><img src="images/thumb.jpg" width="40" height="40" alt="thumbnail" /></a> <a href="index.html"><img src="images/thumb.jpg" width="40" height="40" alt="thumbnail" /></a> <a href="index.html"><img src="images/thumb.jpg" width="40" height="40" alt="thumbnail" /></a> <a href="index.html"><img src="images/thumb.jpg" width="40" height="40" alt="thumbnail" /></a> <a href="index.html"><img src="images/thumb.jpg" width="40" height="40" alt="thumbnail" /></a> <a href="index.html"><img src="images/thumb.jpg" width="40" height="40" alt="thumbnail" /></a> <a href="index.html"><img src="images/thumb.jpg" width="40" height="40" alt="thumbnail" /></a> </p> <!-- /sidebar --> </div> <!-- /content --> </div> <!-- /content-wrap --> </div> <!-- /wrap --> </div> <!-- footer --> <Jungle:PageFooter runat="server" /> </form> </body> </html>
Back in default.aspx we can now add the Content controls.
<asp:Content runat="server" ContentPlaceHolderID="FeaturedContentRegion"> </asp:Content> <asp:Content runat="server" ContentPlaceHolderID="MainContentRegion"> </asp:Content>
Lets open up index.html in a code editor, and copy everything that is inside <div id=”main”> into MainContentRegion in default.aspx.
If we take a closer look at the markup we just copied, we see that most of it is pretty similar. After every second news story we have a <div class=”fix”></div> (for clearing the floats), we also need to add a class of either odd or even. We can delete the comments markup (our site will not have comment functionality).
<a href="index.html" class="comment">2 Comments</a>
Lets add a Repeater control, its great for lists like this.
<asp:Repeater runat="server"> <ItemTemplate> </ItemTemplate> <AlternatingItemTemplate> </AlternatingItemTemplate> </asp:Repeater>
Place the first news story in the ItemTemplate and the second in the AlternatingItemTemplate.
<asp:Repeater runat="server"> <ItemTemplate> <div class="block odd"> <a title="" href="index.html"> <img src="images/thumb-1.jpg" class="thumbnail" alt="img" width="240px" height="100px" /></a> <div class="blk-top"> <h4> <a href="index.html">Aliquam Risus Justo Lorem Ipsum Dolor Sit Amet</a></h4> <p> <span class="datetime">August 27, 2009</span></p> </div> <div class="blk-content"> <p> Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Donec libero. Suspendisse bibendum. Cras id urna. Morbi tincidunt, orci ac convallis aliquam, lectus turpis varius lorem, eu posuere nunc justo tempus leo. Donec mattis, purus nec placerat bibendum, dui pede condimentum odio, ac blandit ante orci ut diam. Cras fringilla magna. Phasellus suscipit, leo a pharetra condimentum, lorem tellus eleifend magna, eget fringilla velit magna id neque. </p> <p> <a class="more" href="index.html">continue reading »</a></p> </div> </div> </ItemTemplate> <AlternatingItemTemplate> <div class="block even"> <a title="" href="index.html"> <img src="images/thumb-2.jpg" class="thumbnail" alt="img" width="240px" height="100px" /></a> <div class="blk-top"> <h4> <a href="index.html">Aliquam Risus Justo</a></h4> <p> <span class="datetime">August 26, 2009</span></p> </div> <div class="blk-content"> <p> Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Donec libero. Suspendisse bibendum. Cras id urna. Morbi tincidunt, orci ac convallis aliquam, lectus turpis varius lorem, eu posuere nunc justo tempus leo. Donec mattis, purus nec placerat bibendum, dui pede condimentum odio, ac blandit ante orci ut diam. Cras fringilla magna. Phasellus suscipit, leo a pharetra condimentum, lorem tellus eleifend magna, eget fringilla velit magna id neque. <a class="more" href="index.html">continue reading »</a> </p> <p> </p> </div> </div> <div class="fix"> </div> </AlternatingItemTemplate> </asp:Repeater>
Lets clean up a little and place everything that is inside <div class=”block”> into its own user control. Name it NewsStory. Register the user control in default.aspx and add it to ItemTemplate and AlternatingItemTemplate.
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="default.aspx.cs" Inherits="Jungle._default" MasterPageFile="~/Templates/Jungle/MasterPages/Site.Master" %> <%@ Register TagPrefix="Jungle" TagName="NewsStory" Src="~/Templates/Jungle/Units/NewsStory.ascx" %> <asp:Content runat="server" ContentPlaceHolderID="FeaturedContentRegion"> </asp:Content> <asp:Content runat="server" ContentPlaceHolderID="MainContentRegion"> <h3> Recent Articles</h3> <asp:Repeater runat="server"> <ItemTemplate> <div class="block odd"> <Jungle:NewsStory runat="server" /> </div> </ItemTemplate> <AlternatingItemTemplate> <div class="block even"> <Jungle:NewsStory runat="server" /> </div> <div class="fix"></div> </AlternatingItemTemplate> </asp:Repeater> </asp:Content>
Go back to NewsStory.ascx, and open up the code-behind file. Replace System.Web.UI.UserControl with UserControlBase<BasePageData>.
Delete everything inside the NewsStory class, and add a new public property called NewsPage of type PageData.
// using EPiServer.Core; public PageData NewsPage { get; set; }
Go back to NewsStory.ascx, and replace all the href attributes with
href="<%= NewsPage.LinkURL %>"
Add the PageName and PageStartPublish date as well.
<a href="<%= NewsPage.LinkURL %>"> <img src="images/thumb-2.jpg" class="thumbnail" alt="img" width="240px" height="100px" /> </a> <div class="blk-top"> <h4><a href="<%= NewsPage.LinkURL %>"><%= NewsPage.PageName %></a></h4> <p><span class="datetime"><%= NewsPage.StartPublish.ToString("dd MMMM yyyy")%></span></p> </div> <div class="blk-content"> <p> Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Donec libero. Suspendisse bibendum. Cras id urna. Morbi tincidunt, orci ac convallis aliquam, lectus turpis varius lorem, eu posuere nunc justo tempus leo. Donec mattis, purus nec placerat bibendum, dui pede condimentum odio, ac blandit ante orci ut diam. Cras fringilla magna. Phasellus suscipit, leo a pharetra condimentum, lorem tellus eleifend magna, eget fringilla velit magna id neque. <a class="more" href="<%= NewsPage.LinkURL %>">continue reading »</a> </p> </div>
Here’s a nice overview of the DateTime.ToString() patterns.
We now have the image and text left. For this we’re going to use Extension Methods. Create a new class in the Util folder, call it Extensions.cs. Make the class static, and add a new public static method called HtmlImage.
public static string HtmlImage(this PageData page, string imagePropertyName, string imageAltTextPropertyName) { }
We need to add some code for getting the list image and return the correct markup.
public static string HtmlImage(this PageData page, string imagePropertyName, string imageAltTextPropertyName) { if (page.PageLink == null || page.PageLink.ID < 1) { return string.Empty; } if (page.IsValue(imagePropertyName)) { string imageAlternativeText = page.PageName.ToWebString(); if (page.IsValue(imageAltTextPropertyName)) { imageAlternativeText = page[imageAltTextPropertyName].ToWebString(); } return string.Format("<img src=\"{0}\" alt=\"{1}\"", page[imagePropertyName], imageAlternativeText); } return string.Empty; }
The next extension method we need to add is for the text.
public static string PreviewText(this PageData page, int length) { ]
Most of the code is taken from PageList.ascx in the Public Templates, and modified a little for our use.
public static string PreviewText(this PageData page, int length) { string previewText = string.Empty; if (page.PageLink == null || page.PageLink.ID < 1) { return previewText; } if (page.IsValue("MainIntro")) { return StripPreviewText(page["MainIntro"].ToWebString(), length); } if (page.IsValue("MainBody")) { previewText = page["MainBody"].ToWebString(); } if (string.IsNullOrEmpty(previewText)) { return previewText; } //If the MainBody contains DynamicContents, replace those with an empty string var regexPattern = new StringBuilder(@"<span[\s\W\w]*?classid="""); regexPattern.Append(DynamicContentFactory.Instance.DynamicContentId.ToString()); regexPattern.Append(@"""[\s\W\w]*?</span>"); previewText = Regex.Replace(previewText, regexPattern.ToString(), string.Empty, RegexOptions.IgnoreCase | RegexOptions.Multiline); return TextIndexer.StripHtml(previewText, length); } /// <summary> /// Strips a text to a given length without splitting the last word. /// </summary> /// <param name="previewText">The string to shorten</param> /// <returns>A shortened version of the given string</returns> private static string StripPreviewText(string previewText, int maxLength) { if (previewText.Length <= maxLength) { return previewText; } previewText = previewText.Substring(0, maxLength); // The maximum number of characters to cut from the end of the string. int maxCharCut = (previewText.Length > 15 ? 15 : previewText.Length - 1); int previousWord = previewText.LastIndexOfAny(new char[] { ' ', '.', ',', '!', '?' }, previewText.Length - 1, maxCharCut); if (previousWord <= 0) { previewText = previewText.Substring(0, previousWord); } return previewText + " ..."; }
Great, we can now update NewsStory.ascx, to use the extension methods instead.
<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="NewsStory.ascx.cs" Inherits="Jungle.Templates.Jungle.Units.NewsStory" %> <%@ Import Namespace="Jungle.Templates.Jungle.Util"%> <a href="<%= NewsPage.LinkURL %>"> <%= NewsPage.HtmlImage("ListImage", "PageName") %> </a> <div class="blk-top"> <h4><a href="<%= NewsPage.LinkURL %>"><%= NewsPage.PageName %></a></h4> <p><span class="datetime"><%= NewsPage.StartPublish.ToString("dd MMMM yyyy")%></span></p> </div> <div class="blk-content"> <p> <%= NewsPage.PreviewText(200) %> <a class="more" href="<%= NewsPage.LinkURL %>">continue reading »</a> </p> </div>
Some of the code I don’t like.
<h4><a href="<%= NewsPage.LinkURL %>"><%= NewsPage.PageName %></a></h4>
For one, we don’t Html encode the PageName. We could of course do something like this.
h4><a href="<%= NewsPage.LinkURL %>"><%= NewsPage.PageName.ToWebString() %></a></h4>
ToWebString() is an extension method that does the same as the HtmlEncode method, but escapes some tags (which are set in web.config).
Lets instead use EPiCode.Extensions new HtmlLink methods, which does all this for us. We now only have to type NewsPage.HtmlLink().
<h4><%= NewsPage.HtmlLink() %></h4>
Back in default.aspx we have to set the NewsPage property.
<asp:Content runat="server" ContentPlaceHolderID="MainContentRegion"> <h3>Recent Articles</h3> <asp:Repeater runat="server"> <ItemTemplate> <div class="block odd"> <Jungle:NewsStory runat="server" NewsPage="<%# Container.DataItem %>" /> </div> </ItemTemplate> <AlternatingItemTemplate> <div class="block even"> <Jungle:NewsStory runat="server" NewsPage="<%# Container.DataItem %>" /> </div> <div class="fix"> </div> </AlternatingItemTemplate> </asp:Repeater> </asp:Content>
We now need to data bind our Repeater. Go the code-behind file (default.aspx.cs), derive from TemplatePage
using Jungle.Templates.Jungle.PageTypes; using PageTypeBuilder.UI; namespace Jungle { public partial class _default : TemplatePage<StartPage> { } }
Lets retrieve the children of NewsContainerPage, Filter for visitors, Sort them, and only add as many as specified by the editor.
For more on EPiServer filtering see: EPiServer filter – part 1 and EPiServer filter – part 2: create your own filter.
protected override void OnLoad(System.EventArgs e) { base.OnLoad(e); if (!PageReference.IsNullOrEmpty(CurrentPage.NewsContainerPage)) { var news = CurrentPage.NewsContainerPage.GetChildren(); if (news == null) { return; } FilterForVisitor.Filter(news); new FilterPropertySort("PageStartPublished", FilterSortDirection.Descending).Filter(news); if (CurrentPage.NewsCount != 0) { new FilterCount(CurrentPage.NewsCount); } this.rptNewsList.DataSource = news; this.rptNewsList.DataBind(); } }
Featured news story
Great, we can now start on the featured news story!
Lets copy the featured markup from index.html and paste it into FeaturedContentRegion in default.aspx:
<asp:Content runat="server" ContentPlaceHolderID="FeaturedContentRegion"> <div id="featured"> <div id="featured-block" class="clear"> <div id="featured-ribbon"> </div> <a name="TemplateInfo"></a> <div class="image-block"> <a href="index.html" title=""> <img src="images/img-featured.jpg" alt="featured" width="350px" height="250px" /></a> </div> <div class="text-block"> <h2> <a href="index.html">Read me first</a></h2> <p class="post-info"> Posted by <a href="index.html">erwin</a> | Filed under <a href="index.html">templates</a>, <a href="index.html">internet</a></p> <p> <strong>JungleLand 1.0</strong> is a free, W3C-compliant, CSS-based website template by <a href="http://www.styleshout.com/">styleshout.com</a>. This work is distributed under the <a rel="license" href="http://creativecommons.org/licenses/by/2.5/">Creative Commons Attribution 2.5 License</a>, which means that you are free to use and modify it for any purpose. All I ask is that you include a link back to <a href="http://www.styleshout.com/"> my website</a> in your credits. For more free designs, you can visit <a href="http://www.styleshout.com/"> my website</a> to see my other works. </p> <p> Good luck and I hope you find my free templates useful!</p> <p> <a href="index.html" class="more-link">Read More</a></p> </div> </div> </div> </asp:Content>
In the code-behind we need to add a property for the featured news page.
using EPiCode.Extensions; using EPiServer.Core; using EPiServer.Filters; using Jungle.Templates.Jungle.PageTypes; using PageTypeBuilder.UI; namespace Jungle { public partial class _default : TemplatePage<StartPage> { private PageData _featuredNewsPage; protected PageData FeaturedNewsPage { get { if (_featuredNewsPage == null) { if (!PageReference.IsNullOrEmpty(CurrentPage.FeaturedNews)) { _featuredNewsPage = CurrentPage.FeaturedNews.GetPage(); } } return _featuredNewsPage; } } protected override void OnLoad(System.EventArgs e) { base.OnLoad(e); this.NewsStories(); } private void NewsStories() { if (!PageReference.IsNullOrEmpty(CurrentPage.NewsContainerPage)) { var news = CurrentPage.NewsContainerPage.GetChildren(); if (news == null) { return; } FilterForVisitor.Filter(news); new FilterPropertySort("PageStartPublished", FilterSortDirection.Descending).Filter(news); if (CurrentPage.NewsCount != 0) { new FilterCount(CurrentPage.NewsCount); } this.rptNewsList.DataSource = news; this.rptNewsList.DataBind(); } } } }
As you can see I’ve refactored the code a little, which makes it easier to read and manage.
In default.aspx we can now replace the dummy text.
<asp:Content runat="server" ContentPlaceHolderID="FeaturedContentRegion"> <div id="featured"> <div id="featured-block" class="clear"> <div id="featured-ribbon"> </div> <a name="TemplateInfo"></a> <div class="image-block"> <a href="<%= FeaturedNewsPage.LinkURL %>"> <%= FeaturedNewsPage.HtmlImage("FeaturedNewsImage", "PageName")%> </a> </div> <div class="text-block"> <h2><%= FeaturedNewsPage.HtmlLink() %></h2> <p class="post-info"> Posted by <a href="<%= FeaturedNewsPage.LinkURL %>"><%= FeaturedNewsPage.Author() %></a></p> <p><%= FeaturedNewsPage.PreviewText(300) %></p> <p> <a href="<%= FeaturedNewsPage.LinkURL %>" class="more-link">Read More</a></p> </div> </div> </div> </asp:Content>
The Author extension method looks like this.
public static string Author(this PageData page) { if (page.PageLink == null || page.PageLink.ID < 1) { return string.Empty; } return page.IsValue("MetaAuthor") ? page["MetaAuthor"].ToWebString() : string.Empty; }
Again, I’m not a fan of this code.
<a href="<%= FeaturedNewsPage.LinkURL %>"><%= FeaturedNewsPage.Author() %></a>
Lets again use the HtmlLink extension method instead.
<%= FeaturedNewsPage.HtmlLink(p => p.Author()) %>
Less to type, and more readable.
Thats it! The next post: Part 4: Creating the standard page.
Related Posts:
- Html Helpers vs Server controls
- Part 7: Creating the Sitemap page – Create an EPiServer site from scratch
- Part 2: Creating a foundation – Create an EPiServer site from scratch
- Part 5: Creating the search page – Create an EPiServer site from scratch
- Part 6: Creating the XForm page – Create an EPiServer site from scratch

![Properties for [Jungle] start page in admin mode](http://www.frederikvig.com/wp-content/uploads/jungle-site/part3/properties.png)


Loving it! Keep up the great work!
[...] The next post – Part 3: Creating the start page. [...]
Agree with Allan, keep it up with this good stuff!
I think a download after each part would be great, too!
Thanks guys! Good idea on the downloads Martin. I’ll see what I can do
.
Great work – again – Frederik!
As for our ongoing discussion about extensions, markup and server controls, I guess it does not come as a surprise that I don’t agree about your last statement on readability of that link. Your extension (with the lamda) is shorter, but not more readable in my opinion.
But I don’t have to agree on everything you write in order to respect the huge amount of work and effort you put into sharing your knowledge. Keep up the good work!
Thanks Steve!
.
Would be so boring if everyone agrees on everything!
[...] In this post we’ll create the sites start page. I’ll show you how to retrieve data from other pages and how we can improve this by using extension methods. Third part is out. [...]