Frederik Vig – ASP.NET developer

Follow me

Posts tagged ‘EPiServer’

A developer’s guide to PageTypeBuilder

Page Type Builder allows developers to define EPiServer page types in code which eliminates the need to synchronize page types between different servers. As page types are declared in code it also enables inheritance between page types and strongly typed property access.

From the PageTypeBuilder project

Joel Abrahamsson has done a superb job with the project and is continually working on it. I’ve earlier blogged a little about the project in my Create an EPiServer site from scratch series, but not done a proper walk-through.

New page type

Creating a new page type is very easy. You create a new class, inherit from TypedPageData, and add the [PageType] attribute to the class declaration (living inside the PageTypeBuilder namespace). You have properties for setting the different page type settings.

  • AvailableInEditMode – bool (default true)
  • AvailablePageTypes – Type[]
  • DefaultArchiveToPageID – int (default -1)
  • DefaultChildSortOrder – FilterSortOrder
  • DefaultFrameID – int
  • DefaultPageName – string
  • DefaultSortIndex – int (default -1)
  • DefaultStartPublishOffsetMinutes – int
  • DefaultStopPublishOffsetMinutes – int
  • DefaultVisibleInMenu – bool (default true)
  • Description – string
  • Filename – string
  • Name – string
  • SortOrder – string (default 100)

Example

using PageTypeBuilder;
 
namespace EPiServer.Templates.Public.PageTypes
{
    [PageType(Name = "[Public] Document list", SortOrder = 1140, Filename = "/Templates/Public/Pages/DocumentList.aspx", AvailablePageTypes = new [] { typeof(Document)})]
    public class DocumentList : TypedPageData
    {
    }
}

For a nice overview of how these properties map to the UI in EPiServer admin mode see: How the PageTypeAttribute’s Properties Map to EPiServer CMS’ Admin

Adding properties

You add properties to a page type by adding new public virtual properties to the class with the [PageTypeProperty] attribute. You have properties for setting the various EPiServer property settings.

  • ClearAllLongStringSettings – bool
  • DefaultValue – string
  • DefaultValueType – DefaultValueType
  • DisplayInEditMode – bool (default true)
  • EditCaption – string
  • HelpText – string
  • LongStringSettings – EditorToolOption
  • Required – bool
  • Searchable – bool
  • SortOrder – int
  • Tab – Type
  • Type – Type
  • UniqueValuePerLanguage – bool

Example

[PageTypeProperty(
    EditCaption = "Secondary body",
    HelpText = "The contents of this property will be shown in the right column of the page, you can use both text and images for layout. Note that images should not be larger than the right area.",
    Searchable = true,
    UniqueValuePerLanguage = true,
    Type = typeof(PropertyXhtmlString),
    LongStringSettings = (
	EditorToolOption.All ^
	EditorToolOption.Font),
    Tab = typeof(Advanced))]
public virtual string SecondaryBody
{
    get;
    set;
}

This will add a new property named SecondaryBody of type PropertyXhtmlString (XHTML string (>255)), with all editor options available, except for font. Use ^ for not available, or just add all the options you like with | between.

LongStringSettings = (EditorToolOption.DynamicContent | EditorToolOption.ToggleHtml)

Only DynamicContent and ToggleHtml will now be available for editors.

UniqueValuePerLanguage = false

When setting UniqueValuePerLanguage to false, and using the auto get setter property (like above), you’ll get null values returned on properties in other languages than the master language. To fix this you can use the GetPropertyValue extension method.

[PageTypeProperty(UniqueValuePerLanguage = false)]
public string MyProperty
{
    get 
    { 
        return this.GetPropertyValue(page => page.MyProperty, true) 
    }
}

You should now get the proper value in all languages, not just in the master language.

Tabs

To create a new tab, create a new class and inherit from Tab (PageTypeBuilder.Tab). You now need to implement three abstract properties

  • Name – string
  • RequiredAccess – AccessLevel
  • SortIndex – int

Example

using EPiServer.Security;
using PageTypeBuilder;
 
namespace EPiServer.Templates.Public.Tabs
{
    public class Advanced : Tab
    {
        public override string Name
        {
            get { return "Advanced"; }
        }
 
        public override AccessLevel RequiredAccess
        {
            get { return AccessLevel.Edit; }
        }
 
        public override int SortIndex
        {
            get { return 20; }
        }
    }
}

You can now add properties to this tab by setting the PageTypeProperty’s Tab property.

[PageTypeProperty(Tab = typeof(Advanced))]
public virtual string MyProperty
{
    get; set;
}

Dynamic properties

At the moment you cannot create dynamic properties with PageTypeBuilder. You can however access them.

public virtual string MetaDescription
{
    get
    {
	return this.GetPropertyValue(page => page.MetaDescription, true);
    }
}

As you’ll see when going through EPiServer public templates with PageTypeBuilder code, I’ve added the dynamic properties to a base class that the other page types inherit from. That way all page types have access to the dynamic properties.

Also be sure to read: Working with Dynamic Properties and Page Type Builder.

Accessing properties in templates

One of the great things about the PageTypeBuilder is that you not get strongly typed access to your properties.

Web forms (.aspx)

Inherit from the generic TemplatePage (living inside the PageTypeBuilder.UI namespace), where the type is set to the page type this template is for.

using PageTypeBuilder.UI;
 
namespace EPiServer.Templates.Public.Pages
{
    public partial class Document : TemplatePage<PageTypes.Document>
    {
    }
}

When typing CurrentPage. you’ll get access to all the public properties that Document exposes. CurrentPage.SecondaryBody for instance.

User controls (.ascx)

Inherit from the generic UserControlBase (living inside the PageTypeBuilder.UI namespace), where the type is set to the page type this user control is for. If the user control is used by multiple page types, simply find a common class to set as the type (a base class).

using PageTypeBuilder.UI;
 
namespace EPiServer.Templates.Public.Units.Placeable
{
    public partial class Document : UserControlBase<PageTypes.BasePageData>
    {
    }
}

Same as with web forms, you’ll get access the the properties that BasePageData exposes.

EPiServer public templates with PageTypeBuilder

I’ve started updating the EPiServer public templates to use PageTypeBuilder. This is not a complete conversion, but merely a nice starting place for you to see how I’ve structured my files and how the code looks when used with an existing project.

Download the code

Other resources

Part 8: Preparing for launch – Create an EPiServer site from scratch

This is the last part of Create an EPiServer site from scratch. We’ve come a long way, almost finishing our site! What we have left is doing some testing and deploying it to our production server.

Client side performance testing

When doing performance testing, we as ASP.NET developers, mostly focus on the server side stuff. EPiServer has done a superb job with making sure that EPiServer looks after server side performance for you, and gives you the tools to improve it even more. But one area of performance we don’t necessarily focus so much on is client side performance.

With client side performance I mean how fast the transfer to the client (user) is. Most of the users time will not be spent waiting for a SQL query to run, but actually downloading all the sites resources (images, css, JavaScript files etc).

There are some easy steps we can take to improve our site even more. Lets start by taking a look at Yahoo!’s Best Practices rules for Speeding up our site. There are some very good rules here that we’ll implement. Yahoo! has also developed a Firefox extension YSlow for Firebug.

YSlow

Install YSlow, and run it by pressing the YSlow button in the bottom right corner of Firefox.

YSlow in Firefox

Click the YSlow tab and Run Test.

YSlow result

Overall we have a result of C, which is fine, but as you can see we have a few F’s around.

ETags

From YSlow rules

Entity tags (ETags) are a mechanism web servers and the browser use to determine whether a component in the browser’s cache matches one on the origin server. Since ETags are typically constructed using attributes that make them unique to a specific server hosting a site, the tags will not match when a browser gets the original component from one server and later tries to validate that component on a different server.

To remove the ETags in IIS 7, open up the IIS Manager, go to the Jungle site, and click HTTP Response Headers, click Add and type ETag into the name and “” into the Value field.

Remove ETags in IIS 7

Expires HTTP headers

Per Bjurström has written on this before, Configuring cache expiration on IIS 7.

I updated the staticContent sections clientCache to.

<staticContent>
    <clientCache cacheControlMode="UseExpires" httpExpires="Sun, 29 Mar 2020 00:00:00 GMT" />
</staticContent>

Which sets the expiration date to 2020 on static content (CSS, JavaScript, PNGs etc).

Expires header with Network monitor in Firebug

If your using IIS 6, you can use a custom static file handler.

using System;
using System.Web;
 
namespace Jungle
{
    public class JungleStaticFileHandler : EPiServer.Web.StaticFileHandler
    {
        protected override void SetCachePolicy(HttpContext context, DateTime fileChangedDate)
        {
            if (fileChangedDate > DateTime.UtcNow)
            {
                fileChangedDate = DateTime.UtcNow;
            }
            context.Response.Cache.SetLastModified(fileChangedDate);
            context.Response.Cache.SetCacheability(HttpCacheability.Public);
            context.Response.Cache.SetExpires(DateTime.UtcNow.AddYears(1));
            context.Response.Cache.SetValidUntilExpires(true);
            context.Response.Cache.VaryByParams.IgnoreParams = true;
            context.Response.Cache.SetOmitVaryStar(true);
        }
    }
}

For some reason you can only add 1 year when doing this with a custom handler. You also need to update the web.config file to use JungleStaticFileHandler instead of StaticFileHandler.

CDN Support

Per Bjurström has another terrific post explaining how to enable CDN for EPiServer. I’ve not enabled it for my local site, but if you do, just remember that you don’t need to use JungleStaticFileHandler anymore, since CdnStaticFileHandler does the same thing.

Fewer HTTP requests

We can easily reduce our HTTP requests by combining our CSS or JavaScript files into one, and by using CSS Sprites. To help with the CSS Sprites there are quite a few generators out there. I encourage you to take a look and try it out, this is one of the most important steps you can take for better client side performance.

Compressing CSS and JavaScript

To compress CSS and JavaScript code we can add either add an HTTP Handler that does this for us, use an online compressor, or add a build task to Visual Studio.

There are a number of resources to help you with compressing CSS and JavaScript files, many of them also combine CSS or JavaScript files into one.

From local development to remote server

We’re now ready to deploy our application to a remote server. Open up UnleashIt (install it if you haven’t done so already).

Create a new profile by going to: File, Profiles, Profile Configuration. Select the Quick Deploy Profile, duplicate it, and give it a name of Jungle. Click OK, and go back to the main UnleashIt window. Under Source destination select your wwwroot folder and choose a destination folder.

UnleashIt profile

If this is the first time you use UnleashIt you need to add a few File Masks (files UnleashIt copies over to the deployment folder). To add new ones go back to Profile Configuration (File, Profiles, Profile Configuration). You have a text field where you can type new file masks, add them by clicking the Add as file mask button.

My file masks

  • *.asax
  • *.ascx
  • *.ashx
  • *.asmx
  • *.aspx
  • *.browser
  • *.config
  • *.css
  • *.dll
  • *.gif
  • *.htm
  • *.html
  • *.ico
  • *.jpg
  • *.js
  • *.master
  • *.png
  • *.skin
  • *.txt
  • *.xml

Database

Take a backup of your development database by using the SQL Server Management Studio (can be installed with Web Platform installer). Right-click the database, and choose Tasks, Back Up (make sure the backup type is set to full).

Database backup with SQL Server Management Studio

Connect to your production database server/instance, create a new database and restore the database by going to: Tasks, Restore, Database. Select from Device and choose our database (dbJungle.bak in my case), check the checkbox next to the backup set to restore. Go to Options and check the checkbox next to Overwrite the existing database, also update the Restore the database files as paths.

Great, we can now click OK, and the restore will start. After you’ve restored successfully, remember to add a user to the db_owner schema. Expand the the database and choose Security, Users, and either add a new one or update the existing one.

Add user to database

Remote server

In my case, I’m deploying the application to a Windows 2008 Server with IIS 7 (same IIS version as my development one).

Either use remote desktop or login to your remote server in another way. Make sure to install EPiServer on the server if it isn’t already installed (see Part 1: Setting up the development environment for more information).

Copy the deployed files to the remote server, I usually use a folder system like this on the remote server: Customers\CustomerName\Internet(Intranet)\wwwroot. Also make sure to copy the VPP folders.

Open up the IIS Manager and create a new application pool by going to Application Pools and Add Application Pool, use the default settings (.NET 2, Integrated mode etc).

Now we can create the site in IIS, right-click Sites, and Add Web Site, give it a name and choose our newly created application pool, choose the wwwroot folder, under host names add the host name you wish to use.

New Site in IIS 7

We need to update connectionStrings.config with the new connection information (new database, user name, password etc), and web.config with the new VPP paths. Just search for VPP and the first hit should match the PageFiles path (there are three paths to update). Last step is updating siteUrl under siteSettings, to our new host name.

You should now have a running site! Test by clicking a little around to make sure everything works as expected.

Sometimes it takes a while for the DNS of the new domain to get updated everywhere, a little trick is to update your local hosts file (C:\Windows\System32\drivers\etc\hosts), and add the host name with the remote servers IP address.

Uptime

There are various tools, both free and commercial, that’ll tell you when a site goes down. One I’ve used in the past is the free BasicState, you also have Pingdom and Uptrends both are commercial. I recommend giving them a try!

Logging exceptions

ELMAH (Error Logging Modules and Handlers) is an application-wide error logging facility that is completely pluggable. It can be dynamically added to a running ASP.NET web application, or even all ASP.NET web applications on a machine, without any need for re-compilation or re-deployment.

ELMAH is a free and easy to use and setup, and you can subscribe to a RSS feed for updates.

ASP.NET Exception Reporter

Another free tool that is based on ELMAH is ASP.NET Exception Reporter.

ASP.NET Exception Reporter (based on ELMAH) is pluggable application-wide error logging facility. Plug it in to your existing webapps, webservices, …
The goal is to centralize the different exceptions that are generated by ELMAH to one web application.
It is developed in C#.

Great for when you need to monitor multiple applications, not just the one.

ASP.NET Exception Reporter

Conclusion

There are of course numerous tasks and functionality we didn’t do or add in this series. I’ve deliberately kept things as simple as possible, and instead tried to give you pointers on how you might extend the site even more. I highly recommend playing around more with the site, and implementing some of the functionality we didn’t implement. Both the footer and the thumbnail images, are a great place to start.

I hope you’ve enjoyed this series, and do be sure to come back to this site for further updates and new blog posts!

Thanks for reading!

Part 7: Creating the Sitemap page – Create an EPiServer site from scratch

We’re soon ready to launch or site. One thing that is missing is the Sitemap page. This is a page that will help our users find what they’re looking for, and give them a nice overview of the site structure.

Create a new page type with these settings and properties.

using EPiServer.Core;
using PageTypeBuilder;
 
namespace Jungle.Templates.Jungle.PageTypes
{
    [PageType(
        Name = "[Jungle] Sitemap page",
        Filename = "/Templates/Jungle/Pages/Sitemap.aspx",
        SortOrder = 1060)]
    public class SitemapPage : EditorialPage
    {
        [PageTypeProperty(
            EditCaption = "Sitemap root page",
            Type = typeof(PropertyPageReference))]
        public virtual PageReference SitemapRoot
        {
            get;
            set;
        }
    }
}

We can now create Sitemap.aspx. I’m using a PageTree control for generating the site structure (nested ordered lists).

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Sitemap.aspx.cs" Inherits="Jungle.Templates.Jungle.Pages.Sitemap" MasterPageFile="~/Templates/Jungle/MasterPages/Site.Master" %>
<%@ Register TagPrefix="Jungle" TagName="MainBody" Src="~/Templates/Jungle/Units/MainBody.ascx" %>
<asp:Content ContentPlaceHolderID="MainContentRegion" runat="server">
 
    <EPiServer:PageTree runat="server" ExpandAll="true" ID="SitemapTree">
        <HeaderTemplate>
            <div id="Sitemap">
        </HeaderTemplate>
        <IndentTemplate>
            <ol>
        </IndentTemplate>
        <ItemHeaderTemplate>
            <li>
        </ItemHeaderTemplate>
        <ItemTemplate>
            <EPiServer:Property PropertyName="PageLink" runat="server" />
        </ItemTemplate>
        <ItemFooterTemplate>
            </li>
        </ItemFooterTemplate>
        <UnindentTemplate>
            </ol>
        </UnindentTemplate>
        <FooterTemplate>
            </div>
        </FooterTemplate>
    </EPiServer:PageTree>
</asp:Content>

Notice ExpandAll=”true” property, this will expand all levels. The code-behind just gets the start position from the SitemapRoot property, if its not set it uses the start page as the start position.

using EPiServer.Core;
using Jungle.Templates.Jungle.PageTypes;
using PageTypeBuilder.UI;
 
namespace Jungle.Templates.Jungle.Pages
{
    public partial class Sitemap : TemplatePage<SitemapPage>
    {
        protected override void OnLoad(System.EventArgs e)
        {
            base.OnLoad(e);
 
            this.SitemapTree.PageLink = CurrentPage.SitemapRoot ?? PageReference.StartPage;
            this.SitemapTree.DataBind();
        }
    }
}

Build, and go to edit mode, and create a new Sitemap page. Make sure that everything works.

Sitemap page

This is a very common and standard sitemap page. Earlier the sitemap page was used by search engines for having a link to every page on the site. Fortunately Google, Microsoft, Yahoo! got together to create a Sitemap XML standard that the most popular search engines use. On EPiCode we have the Search Engine Sitemap module that creates an XML file according to the Sitemap XML standard for EPiServer sites. I recommend using it on all your sites for some free SEO boost.

After the introduction of the Search Engine Sitemap we have had some change on what the purpose and usage of the sitemap for a site would be. I’ve seen a lot of great sitemaps that do a much better job when it comes to usability than the traditional sitemap. One I like very much is Washtenaw Community College service index.

In this series I’ve tried keeping things simple, to give you a foundation that you can build and extend upon. This is especially true for the sitemap page type. I encourage you to extend it with your own code and functionality, making it even more usable.

Cleaning up

We’re using MainBody.ascx on most of our page templates. Some of the functionality we only need for Page.aspx. Lets create a new user control with the name ArticleMainBody.ascx, and copy paste everything from MainBody (except the first line), into ArticleMainBody.ascx. Update Page.aspx to use ArticleMainBody.ascx instead.

Lets remove most of the code from MainBody.ascx, we should have something like this left.

<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="MainBody.ascx.cs" Inherits="Jungle.Templates.Jungle.Units.MainBody" %>
<h2><%= CurrentPage.Heading() %></h2>
<EPiServer:Property runat="server" PropertyName="MainBody" DisplayMissingMessage="false" EnableViewState="false" />
using Jungle.Templates.Jungle.PageTypes;
using PageTypeBuilder.UI;
 
namespace Jungle.Templates.Jungle.Units
{
    public partial class MainBody : UserControlBase<BasePageData>
    {
    }
}

Build and make sure everything works.

Refactoring

Always try to improve and refactor your code along the way. I usually make it work, and then refactor my code. If you write the same code twice, try to take out the common code and place it somewhere where both can use it, either in a base class, a helper util class, extension method or something else.

I recommend checking out the 31 Days of Refactoring series and the book Professional Refactoring in C# & ASP.NET, you also have the classic Refactoring: Improving the Design of Existing Code.

In the last post in this series we’ll prepare for launching the site.

Part 6: Creating the XForm page – Create an EPiServer site from scratch

The XForm page will help our editors collect data from their users. Editors can easily customize their forms the way that they want, and decide how to store the data (in a database, by email etc).

Lets create a new page type.

using EPiServer.Core;
using EPiServer.XForms;
using PageTypeBuilder;
 
namespace Jungle.Templates.Jungle.PageTypes
{
    [PageType(
        Name = "[Jungle] XForm page",
        Filename = "/Templates/Jungle/Pages/Xform.aspx",
        SortOrder = 1020)]
    public class XFormPage : EditorialPage
    {
        [PageTypeProperty(EditCaption = "XForm", Searchable = false, Type = typeof(PropertyXForm))]
        public virtual XForm XForm
        {
            get;
            set;
        }
    }
}

Like with StandardPage and SearchPage, we derive from EditorialPage, with the Heading and MainBody properties. We only have to add the XForm property for letting the editor choose which XForm she wishes to use.

Lets create the Xform.aspx page in Visual Studio, delete the default markup and add the master page.

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="XForm.aspx.cs" Inherits="Jungle.Templates.Jungle.Pages.XForm" MasterPageFile="~/Templates/Jungle/MasterPages/Site.Master" %>
using Jungle.Templates.Jungle.PageTypes;
using PageTypeBuilder.UI;
 
namespace Jungle.Templates.Jungle.Pages
{
    public partial class XForm : TemplatePage<XFormPage>
    {
 
    }
}

After you’ve done that create a new user control called XFormControl.

<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="XFormControl.ascx.cs" Inherits="Jungle.Templates.Jungle.Units.XFormControl" %>
<asp:Panel runat="server" CssClass="xForm">
	<XForms:XFormControl ID="FormControl" runat="server" ValidationGroup="XForm" />
</asp:Panel>

The code-behind file has a public property for the XForm EPiServer property. This way we can easily reuse this user control in various page types. The other thing to note is how we create the form. We have the XFormControl, what we need to do is set its FormDefinition property to our form (which we need to create). The Form is a property that uses the XFormProperty property to get the form guid, which it then uses to create a new XForm instance.

using System;
using EPiServer.XForms;
using Jungle.Templates.Jungle.PageTypes;
using PageTypeBuilder.UI;
 
namespace Jungle.Templates.Jungle.Units
{
    public partial class XFormControl : UserControlBase<BasePageData>
    {
        private XForm _form;
 
        protected override void OnInit(EventArgs e)
        {
            base.OnInit(e);
 
            if (this.Form == null)
            {
                return;
            }
 
            FormControl.FormDefinition = this.Form;
        }
 
        public XForm Form
        {
            get
            {
                if (_form == null)
                {
                    string formGuid = CurrentPage[this.XFormProperty] as string;
                    if (!string.IsNullOrEmpty(formGuid))
                    {
                        _form = XForm.CreateInstance(new Guid(formGuid));
                    }
                }
 
                return _form;
            }
            set
            {
                _form = value;
            }
        }
 
        public string XFormProperty
        {
            get;
            set;
        }
    }
}

For more information about the XFormControl see the SDK.

Remember to add the usercontrol to XForm.aspx

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="XForm.aspx.cs" Inherits="Jungle.Templates.Jungle.Pages.XForm" MasterPageFile="~/Templates/Jungle/MasterPages/Site.Master" %>
<%@ Register TagPrefix="Jungle" TagName="MainBody" Src="~/Templates/Jungle/Units/MainBody.ascx" %>
<%@ Register TagPrefix="Jungle" TagName="XFormControl" Src="~/Templates/Jungle/Units/XFormControl.ascx" %>
 
<asp:Content ContentPlaceHolderID="MainContentRegion" runat="server">
    <Jungle:MainBody runat="server" />
 
    <Jungle:XFormControl
            runat="server" 
            XFormProperty="XForm" />
</asp:Content>

Build and create a new XForm page with a test form.

Confirmation email

So far so good, but we also want to send a confirmation email to the user when they’ve sent the form. Just a thank you for your feedback message, or something similar. I’ve written about this previously: Sending confirmation email to the user when using EPiServer XForms. Read the blog post or download the code to see how I’ve implemented it.

One thing I’ve done, that differs from the original post, is that I’ve added a property that lets the editor edit the email text. For this I added a new property to [Jungle] XForm page:

[PageTypeProperty(
    EditCaption = "Confirmation email text",
    Searchable = true,
    Type = typeof(PropertyXhtmlString),
    LongStringSettings = (
	EditorToolOption.All ^
	EditorToolOption.Font))]
public virtual string ConfirmationEmailText
{
    get;
    set;
}

Enter key

A problem we’re facing is when users are filling out our form, and use the enter key. Instead of sending the form back to the server, they’re actually triggering the quick search button instead. The reason for this behavior is that ASP.NET web forms just uses one form for everything. Unfortunately there are no good fixes for this. One that I’ve used in the past is adding some JavaScript that detects if the enter key is pressed, and then triggers the first submit buttons click event.

The jQuery code for this:

$(function() {
    $('#id_matrix input').keypress(function(e) {
        if (e.keyCode && e.keyCode == 13) {
            $('#id_matrix :submit:first').click();
            return false;
        } else {
            return true;
        }
    });
});

You can of course modify the code to look for a button with the class “default” instead. But in my experience editors tend to forget this little detail, and they mostly only have one button anyway.

I’ve placed this code in an external JavaScript file, and added some code in XFormControl.ascx to add it.

protected override void OnInit(EventArgs e)
{
    ...
 
    if (!Page.ClientScript.IsClientScriptIncludeRegistered("Xform"))
    {
	Page.ClientScript.RegisterClientScriptInclude("Xform", Page.ResolveUrl("~/Templates/Jungle/Scripts/Xform.js"));
    }
}

Our users can now use the enter key! The drawback is that the submit button must be the first button, and that this only works when users have JavaScript enabled.

Great! We now have our form in place. Next up is Part 7: Creating the Sitemap page.

Part 5: Creating the search page – Create an EPiServer site from scratch

Today we’re implementing our sites search page. This will be a very standard search page with nothing fancy. Instead I’ve included some links that’ll help you extend this search page even more.

Quick search

We already have the markup for the quick search box placed in quicksearch.ascx, we’ll start by converting it to use ASP.NET controls instead.

Open up quicksearch.ascx. Lets replace the form element with a fieldset and give it the same id. Replace the input text element with an ASP.NET TextBox control and the input submit element with an ImageButton control. You should now have something like this.

<fieldset id="quick-search">
	<p>
	<label for="qsearch">Search:</label>
	<asp:TextBox CssClass="tbox" runat="server" ID="txtQuickSearch" ToolTip="Start typing and hit ENTER" Text="Search..." />
	<asp:ImageButton runat="server" CssClass="btn" ID="btnQuickSearch" CausesValidation="false" ToolTip="Search" ImageUrl="/Templates/Jungle/images/search.png" />  
	</p>
</fieldset>

Notice the CausesValidation=”false” on the ImageButton. The reason for this is that ASP.NET Validation controls that are used on the page would otherwise get triggered when the user uses our quicksearch button. Setting the CausesValidation property to false bypasses all validation.

Next we need to fix the label, and make sure that our users can use the enter key after filling out a search phrase. The reason that this doesn’t work out of the box in ASP.NET Web forms is that everything is placed inside one form, which means that only one button can be the default on. Though as you’ll see we can fix this for users that have JavaScript enabled.

<asp:Panel runat="server" DefaultButton="btnQuickSearch">
<fieldset id="quick-search">
	<p>
	<asp:Label runat="server" AssociatedControlID="txtQuickSearch">Search:</asp:Label>
	<asp:TextBox CssClass="tbox" runat="server" ID="txtQuickSearch" ToolTip="Start typing and hit ENTER" Text="Search..." />
	<asp:ImageButton runat="server" CssClass="btn" ID="btnQuickSearch" CausesValidation="false" ToolTip="Search" ImageUrl="/Templates/Jungle/images/search.png" />  
	</p>
</fieldset>
</asp:Panel>

For the enter key issue I added a Panel control and set its DefaultButton to the ImageButton’s ID. This will add the necessary JavaScript code for us. For the label I added a Label control and sat its AssociatedControlID to the TextBox’s ID. This will render as a HTML Label element and set the for attribute to the correct input’s id (ASP.NET Label controls normally render as span elements).

I also had to update the CSS code in Jungleland.css a little to make the selectors work with the slightly changed markup. I just removed the form prefix (form#quick-search).

/* header quick search */
#header #quick-search {
	position: absolute;
	top: 95px; right: 15px;
	z-index: 999999;
	padding: 0; margin: 0;
	border: none;
	width: 222px; height: 32px;
	background: url(../images/header-search.png) no-repeat;		
}
#header #quick-search p {
	margin: 0; padding: 0;		
	border: none;
}
#header #quick-search input {
	float: left;
	margin: 0; padding: 5px;
	border: none;
	background: transparent;
	color: #4a413c;	
}
#header #quick-search .tbox {
	margin: 6px 0 0 5px; 
	width: 170px;	
	display: inline;		
}
#header #search #quick-search .btn{
	width: 25px; height: 25px;		
}
#header #quick-search label {
	display: none;
}

Lets add a click event method to the button and the necessary code for redirecting the user to our search page.

<asp:ImageButton runat="server" OnClick="btnQuickSearch_Click" CssClass="btn" ID="btnQuickSearch" CausesValidation="false" ToolTip="Search" ImageUrl="/Templates/Jungle/images/search.png" />
using System;
using System.Web;
using EPiCode.Extensions;
using EPiServer.Core;
using Jungle.Templates.Jungle.PageTypes;
using PageTypeBuilder.UI;
 
namespace Jungle.Templates.Jungle.Units
{
    public partial class QuickSearch : UserControlBase<BasePageData>
    {
        protected void btnQuickSearch_Click(object sender, EventArgs e)
        {
            var startPage = PageReference.StartPage.GetPage() as StartPage;
 
            if (startPage == null || PageReference.IsNullOrEmpty(startPage.SearchPage))
            {
                return;
            }
 
            PageData searchPage = startPage.SearchPage.GetPage();
 
            if (searchPage != null)
            {
                Response.Redirect(HttpUtility.UrlPathEncode(string.Format("{0}&q={1}", searchPage.LinkURL, txtQuickSearch.Text)));
            }
        }
    }
}

As you can see we’re getting the search page from the dynamic property SearchPage.

I’ve also added suggestion functionality to the quick search field. See my post Extending search field with suggestion box, for more information. All the code is also available on EPiCode.

Quick Search Suggestion in action

Remember to remove the Visible=”false” property from QuickSearch in PageHeader.ascx.

Search page

We can now start creating the search page. Create the Search page type by creating a new class with the name SearchPage, and the proper attribute declaration.

using PageTypeBuilder;
 
namespace Jungle.Templates.Jungle.PageTypes
{
    [PageType(
        Name = "[Jungle] Search page ",
        Filename = "/Templates/Jungle/Pages/Search.aspx",
        SortOrder = 1030)]
    public class SearchPage : BasePageData
    {
    }
}

For this page type we only need three properties. Heading, MainBody, and SearchRoot. Since we already have the code for Heading and MainBody in StandardPage, we’re going to refactor and create a super class that StandardPage and SearchPage can derive from.

using EPiServer.Core;
using EPiServer.Editor;
using EPiServer.SpecializedProperties;
using PageTypeBuilder;
 
namespace Jungle.Templates.Jungle.PageTypes
{
    public abstract class EditorialPage : BasePageData
    {
        [PageTypeProperty(Searchable = true, Type = typeof(PropertyString))]
        public virtual string Heading
        {
            get;
            set;
        }
 
        [PageTypeProperty(
            EditCaption = "Main body",
            Searchable = true,
            Type = typeof(PropertyXhtmlString),
            LongStringSettings = (
                EditorToolOption.All ^
                EditorToolOption.Font))]
        public virtual string MainBody
        {
            get;
            set;
        }
    }
}

We can now update StandardPage to derive from EditorialPage instead of BasePageData, and delete the Heading and MainBody property.

using EPiServer.SpecializedProperties;
using PageTypeBuilder;
 
namespace Jungle.Templates.Jungle.PageTypes
{
    [PageType(
        Name = "[Jungle] Standard page ",
        Filename = "/Templates/Jungle/Pages/Page.aspx",
        SortOrder = 1010)]
    public class StandardPage : EditorialPage
    {
        [PageTypeProperty(EditCaption = "List image", Type = typeof(PropertyImageUrl))]
        public virtual string ListImage
        {
            get; set;
        }
 
        [PageTypeProperty(EditCaption = "Featured image", Type = typeof(PropertyImageUrl))]
        public virtual string FeaturedNewsImage
        {
            get;
            set;
        }
    }
}

Lets also update SearchPage to derive from EditorialPage and add the SearchRoot property.

using EPiServer.Core;
using PageTypeBuilder;
 
namespace Jungle.Templates.Jungle.PageTypes
{
    [PageType(
        Name = "[Jungle] Search page ",
        Filename = "/Templates/Jungle/Pages/Search.aspx",
        SortOrder = 1030)]
    public class SearchPage : EditorialPage
    {
        [PageTypeProperty(
            EditCaption = "Search root page",
            Type = typeof(PropertyPageReference))]
        public virtual PageReference SearchRoot
        {
            get;
            set;
        }
    }
}

Go back to edit mode and create the search page, just set the SearchRoot to our start page. Then go to the start page and point the SearchPage dynamic property to the search page (remember to check the checkbox that tells EPiServer to inherit this to sub pages). After you’ve done that make sure that quick search redirects to the correct page.

Search.aspx

We can now create Search.aspx.

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Search.aspx.cs" Inherits="Jungle.Templates.Jungle.Pages.Search" MasterPageFile="~/Templates/Jungle/MasterPages/Site.Master" %>
using Jungle.Templates.Jungle.PageTypes;
using PageTypeBuilder.UI;
 
namespace Jungle.Templates.Jungle.Pages
{
    public partial class Search : TemplatePage<SearchPage>
    {
    }
}

Standard stuff :) .

The code for our search page consists of a TextBox, Button, ListView for the result, some text if there are no results, and a SearchDataSource control for searching.

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Search.aspx.cs" Inherits="Jungle.Templates.Jungle.Pages.Search" MasterPageFile="~/Templates/Jungle/MasterPages/Site.Master" %>
<%@ Import Namespace="EPiServer.Core"%>
<asp:Content runat="server" ContentPlaceHolderID="MainContentRegion">
    <h2><%= CurrentPage.Heading() %></h2>
    <EPiServer:Property runat="server" PropertyName="MainBody" DisplayMissingMessage="false" EnableViewState="false" />
 
    <div id="SearchArea">
        <asp:Label runat="server" AssociatedControlID="txtSearch" CssClass="accessibility">Search:</asp:Label>
        <asp:TextBox ID="txtSearch" text="Search..." runat="server" />
        <asp:Button ID="btnSearch" CssClass="button" runat="server" OnClick="btnSearch_Click" Text="Search" /><br />
    </div>
 
        <div id="ResultArea">
 
            <asp:ListView ID="SearchResult" DataSourceID="SearchDataSource" runat="server" Visible="false" ItemPlaceholderID="itemPlaceHolder">
                <ItemTemplate>
                    <li>
                        <h3><EPiServer:Property runat="server" PropertyName="PageLink" /></h3>
                        <p>
                             <%# ((PageData)Container.DataItem).PreviewText(150)%>
                        </p>
                    </li>
                </ItemTemplate>
                <LayoutTemplate>
                    <h2>Search result</h2>
                    <ol>
                        <asp:PlaceHolder runat="server" ID="itemPlaceHolder" />
                    </ol>
                </LayoutTemplate>
                <EmptyDataTemplate>
                    <p>No matching results were found</p>
                </EmptyDataTemplate>
            </asp:ListView>
        </div>
 
        <EPiServer:SearchDataSource ID="SearchDataSource" runat="server" EnableVisibleInMenu="false">
        <SelectParameters>
            <EPiServer:PropertyParameter Name="PageLink" PropertyName="SearchRoot" />
            <asp:controlparameter name="SearchQuery" controlid="txtSearch" propertyname="Text"/>
        </SelectParameters>
    </EPiServer:SearchDataSource>
</asp:Content>
using System;
using Jungle.Templates.Jungle.PageTypes;
using PageTypeBuilder.UI;
 
namespace Jungle.Templates.Jungle.Pages
{
    public partial class Search : TemplatePage<SearchPage>
    {
        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
            Form.DefaultButton = btnSearch.UniqueID;
 
            if (!IsPostBack)
            {
                string query = Request.QueryString["q"];
                if (!String.IsNullOrEmpty(query))
                {
                    txtSearch.Text = query;
                    SearchResult.Visible = true;
                }
            }
        }
 
        protected void btnSearch_Click(object sender, EventArgs e)
        {
            SearchResult.Visible = true;
            SearchResult.DataBind();
        }
    }
}

A very simple search page that looks something like this when used.

Search result

The next post: Part 6: Creating the XForm page.

Other resources

Part 4: Creating the standard page – Create an EPiServer site from scratch

In this part we’re going to create the standard page. We’ll use this page type for most of our site, from news to other stuff. As always start by taking a look at what you’re about to implement: style.html. It contains all the common scenarios that editors might use, tables, lists, quotes, links, images etc.

After taking a look at what we’re about to create, we can start planning. Special information includes: title, author name, categories, the main text and a sub menu.

Lets start by creating a new class with the name StandardPage, derive from BasePageData, and add the PageType attribute to the class declaration.

using PageTypeBuilder;
 
namespace Jungle.Templates.Jungle.PageTypes
{
    [PageType(
        Name = "[Jungle] Standard page ",
        Filename = "/Templates/Jungle/Pages/Page.aspx",
        SortOrder = 1010)]
    public class StandardPage : BasePageData
    {
    }
}

Lets now add the four properties: Heading, MainBody, ListImage, and FeaturedNewsImage.

using EPiServer.Core;
using EPiServer.Editor;
using EPiServer.SpecializedProperties;
using PageTypeBuilder;
 
namespace Jungle.Templates.Jungle.PageTypes
{
    [PageType(
        Name = "[Jungle] Standard page ",
        Filename = "/Templates/Jungle/Pages/Page.aspx",
        SortOrder = 1010)]
    public class StandardPage : BasePageData
    {
        [PageTypeProperty(Searchable = true, Type = typeof(PropertyString))]
        public virtual string Heading
        {
            get;
            set;
        }
 
        [PageTypeProperty(
            EditCaption = "Main body",
            Searchable = true,
            Type = typeof(PropertyXhtmlString),
            LongStringSettings = (
                EditorToolOption.All ^
                EditorToolOption.Font))]
        public virtual string MainBody
        {
            get;
            set;
        }
 
        [PageTypeProperty(EditCaption = "List image", Type = typeof(PropertyImageUrl))]
        public virtual string ListImage
        {
            get; set;
        }
 
        [PageTypeProperty(EditCaption = "Featured image", Type = typeof(PropertyImageUrl))]
        public virtual string FeaturedNewsImage
        {
            get;
            set;
        }
    }
}

Next up is creating the page template in Visual Studio. Right-click Pages, and choose New Item. Choose Web form and give it a name of Page.aspx. Delete all the markup (everything except the first line), and set MasterPageFile to Site.Master.

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Page.aspx.cs" Inherits="Jungle.Templates.Jungle.Pages.Page" MasterPageFile="~/Templates/Jungle/MasterPages/Site.Master" %>

Open up your code-behind file and derive from TemplatePage<StandardPage> so that we can use our four properties (remember to add using PageTypeBuilder.UI;). Also remove the Page_Load method.

If we build (ctrl + b), go back to edit mode, and create a new page based on our standard page type, we should get something like this.

Something is seriously wrong – this is not how our prototype looked at all. When opening up Firebug and inspecting the form element (surrounding the content), you’ll see some CSS rules that get triggered. They are on line 136 in Jungleland.css, for now just comment out the form and form p selectors.

/* form elements */
/*form {
	margin: 10px 20px 10px 20px; 
	padding: 15px 25px 25px 25px; 
	border: 1px solid #251a14;
	background-color: #130d0a;	
}
form p {
	border-bottom: 1px solid #221813; 
	margin: 0;
	padding: 13px 5px 8px 5px;		
	color: #fff;
}*/

Save, and go back to your browser, you should now see a better result. But still some problems with the quick search field at the top. Lets just hide that for now. Go to PageHeader.ascx and set the QuickSearch’s Visible property to false.

<Jungle:QuickSearch runat="server" Visible="false" />

There we go!

Main body

Create a new user control, MainBody.ascx. We’ll use it to hold the title, author, category, main text and date. The reason for putting this inside a user control and not directly into Page.aspx, is so that we can easily reuse the same functionality in other page types, and more cleanly separate things.

Copy <div class=”post”>, and everything inside it, into MainBody.ascx. Replace the text with the EPiServer Property control, and set the PropertyName to MainBody. Add a new extension method for getting the heading of the page and using the PageName as a fallback.

public static string Heading(this PageData page)
{
    if (page.PageLink == null || page.PageLink.ID < 1)
    {
	return string.Empty;
    }
 
    if (page.IsValue("Heading"))
    {
	return page["Heading"].ToWebString();
    }
 
    return page.PageName.ToWebString();
}

We can also use the Author extension method that we created in the previous post. You should now have something like this.

<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="MainBody.ascx.cs" Inherits="Jungle.Templates.Jungle.Units.MainBody" %>
<%@ Import Namespace="Jungle.Templates.Jungle.Util"%>
<div class="post">
    <h2><%= CurrentPage.Heading() %></h2>
    <p class="post-info">
        Posted by <a href="index.html"><%= CurrentPage.Author() %></a> | Filed under <a href="index.html">templates</a>,
        <a href="index.html">internet</a></p>
    <EPiServer:Property runat="server" PropertyName="MainBody" DisplayMissingMessage="false" EnableViewState="false" />
    <p class="postmeta">
        <a href="index.html" class="readmore">Read more</a> | <a href="index.html" class="comments">
            Comments (3)</a> | <span class="date">August 20, 2009</span>
    </p>
</div>

One thing that I find annoying is having to add <%@ Import Namespace="Jungle.Templates.Jungle.Util"%> everywhere I’m using something from our util classes. Lets add the namespace to our web.config file instead. We can do the same with EPiCode.Extensions.

<pages validateRequest="false" enableEventValidation="false">
    <controls>
    ...
    </control>
    <namespaces>
	...
	<add namespace="Jungle.Templates.Jungle.Util"/>
	<add namespace="EPiCode.Extensions"/>
    </namespaces>
</pages>

Before testing this new functionality, we need to register MainBody with Page.aspx.

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Page.aspx.cs" Inherits="Jungle.Templates.Jungle.Pages.Page" MasterPageFile="~/Templates/Jungle/MasterPages/Site.Master" %>
<%@ Register TagPrefix="Jungle" TagName="MainBody" Src="~/Templates/Jungle/Units/MainBody.ascx" %>
 
<asp:Content runat="server" ContentPlaceHolderID="MainContentRegion">
    <Jungle:MainBody runat="server" />
</asp:Content>
 
After testing and making sure we didn't break anything, we can continue implementing. I've added the date of the article and its categories.
<pre lang="csharp">
<div class="post">
    <h2><%= CurrentPage.Heading() %></h2>
    <p class="post-info">
        Posted by <%= CurrentPage.Author() %>| Filed under <%= CurrentPage.GetCategoryNamesSeparated(",") %></p>
    <EPiServer:Property runat="server" PropertyName="MainBody" DisplayMissingMessage="false" EnableViewState="false" />
    <p class="postmeta">
        <span class="date"><%= CurrentPage.StartPublish.ToString("dd MMMM yyyy")%></span>
    </p>
</div>

The GetCategoryNamesSeparated extension method.

public static string GetCategoryNamesSeparated(this PageData page, string separator)
{
    if (page.PageLink == null || page.PageLink.ID < 1)
    {
	return string.Empty;
    }
 
    string result = string.Empty;
 
    int categoryCount = page.Category.Count;
    int categoryNumber = 1;
 
    CategoryList categories = page.Category;
    const string format = "{0}{1} ";
    foreach (var categoryId in categories)
    {
	var category = Category.Find(categoryId);
 
	if (category == null)
	{
	    break;
	}
 
	if (categoryNumber++ == categoryCount)
	{
	    result += string.Format(format, category.Name, string.Empty);
	    break;
	}
 
	result += string.Format(format, category.Name, separator);
    }
 
    return result;
}

After adding some dummy text in edit mode I get something like this.

Standard page with dummy text

Sub menu

I’ve in a previous blog post written about the PageTree control. That post describes it pretty well, so I’m not going to repeat it here. Instead I’ll just show you the code, and encourage you to check out the post for more information.

<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="SubMenu.ascx.cs" Inherits="Jungle.Templates.Jungle.Units.SubMenu" %>
<EPiServer:PageTree ShowRootPage="false" runat="server" id="Menu">
    <HeaderTemplate>
        <div class="sidemenu">	
	        <h3>Sidebar Menu</h3>
    </HeaderTemplate>
	<IndentTemplate>
		<ul>
	</IndentTemplate>
 
	<ItemHeaderTemplate>
			<li>
	</ItemHeaderTemplate>
 
	<ItemTemplate>
				<EPiServer:Property PropertyName="PageLink" runat="server" />
	</ItemTemplate>
 
	<SelectedItemTemplate>
	            <EPiServer:Property CssClass="current" PropertyName="PageLink" runat="server" />
	</SelectedItemTemplate>
 
	<ItemFooterTemplate>
			</li>
	</ItemFooterTemplate>
 
	<UnindentTemplate>
		</ul>
	</UnindentTemplate>
	<FooterTemplate>
	    </div>
	</FooterTemplate>
</EPiServer:PageTree>
using System;
using EPiServer.Web.WebControls;
using Jungle.Templates.Jungle.PageTypes;
using PageTypeBuilder.UI;
 
namespace Jungle.Templates.Jungle.Units
{
    public partial class SubMenu : UserControlBase<BasePageData>
    {
        public MenuList MenuList
        {
            get;
            set;
        }
 
        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
 
            if (this.MenuList == null)
            {
                return;
            }
 
            this.Menu.PageLink = MenuList.OpenTopPage;
            this.Menu.DataBind();
        }
    }
}

I also had to update PageHeader.ascx.cs and add a public property so that I could access the main menu

public MainMenu MainMenu
{
    get
    {
	return Menu;
    }
}

We can now register SubMenu in Site.master. We also need to give PageHeader an id, so that we get access it from Site.master.cs.

I added code for setting SubMenu’s MenuList property in Site.masters OnLoad method

protected override void OnLoad(EventArgs e)
{
    base.OnLoad(e);
 
    if (SubMenu != null && PageHeader.MainMenu != null)
    {
	SubMenu.MenuList = PageHeader.MainMenu.MenuList;
    }
 
    var page = (PageBase)Page;
    Page.Title = string.Format(_title, page.CurrentPage.Property["PageName"].ToWebString(), TitleSeparator, EPiServer.Configuration.Settings.Instance.SiteDisplayName);
 
    this.HtmlElement.Attributes["lang"] = page.CurrentPage.LanguageBranch;
}

Build and test that everything works as expected. Also make sure that the functionality we created on the start page works as expected, by creating some dummy pages.

The start page with some dummy content

ShareIt

ShareIt is custom property that lets you easily share an article with social media sites like Twitter, Facebook etc. I already have a post with some background on ShareIt. Be sure to have a look at it.

I added a new property to the start page under the Site configuration tab. To be able to add properties to any other tab than information (default), you have to create a new class with the tab, and derive from PageTypeBuilder’s tab class.

using EPiServer.Security;
using PageTypeBuilder;
 
namespace Jungle.Templates.Jungle.Tabs
{
    public class SiteConfiguration : Tab
    {
        public override string Name
        {
            get { return "SiteConfiguration"; }
        }
 
        public override AccessLevel RequiredAccess
        {
            get { return AccessLevel.Edit; }
        }
 
        public override int SortIndex
        {
            get { return 30; }
        }
    }
}

Notice that I’ve placed SiteConfiguration.cs in a new folder called Tabs (under Templates and Jungle).

We can now add our property to the start page and place it under the SiteConfiguration tab.

[PageTypeProperty(
    Type = typeof(ShareIt),
    Tab = typeof(SiteConfiguration))]
public virtual string ShareIt
{
    get;
    set;
}

The code for displaying ShareIt in MainBody.ascx. Remember to define which social media sites you want to display under the site configuration tab on the start page.

<div class="postmeta">
    <EPiServer:Property runat="server" ID="ShareIt" PropertyName="ShareIt" />
    <span class="date"><%= CurrentPage.StartPublish.ToString("dd MMMM yyyy")%></span>
</div>

Notice that I updated <p class=”postmeta”> to <div class=”postmeta”>

protected override void OnLoad(EventArgs e)
{
    base.OnLoad(e);
 
    this.ShareIt.PageLink = PageReference.StartPage;
}

I also added some CSS code to Jungleland.css.

/* ShareIt */
.shareit {
    clear: both;
    float: right;
}
.shareit ul
{
    overflow: hidden;
    margin: 0;
    padding: 0;
    float: left;
    margin-right: 15px;
}
 
.shareit h4 {
    float: left;
    font-size: 12px;
    margin: 2px;
    margin-top: 3px;
    font-weight: normal;
    color: #91807F;
}
 
.shareit .showmore {
    font-size: 12px;
    color: #000;
    line-height: 20px;
}
 
.shareit li
{
    float: left;
    margin-left: 2px;
    margin-top: 2px;
    list-style: none;
}
 
.shareit li a
{
    text-indent: -9999px;
    display: block;
    width: 16px;
    height: 16px;
    background: url(/FV.ShareIt/Images/all-16x16.png) no-repeat top left;
}
 
.shareit .facebook
{
    background-position: 0 -85px;
}
 
.shareit .feed 
{
    background-position: 0 -102px;
}
 
.shareit .twitter
{
    background-position: 0 -357px;
}
 
.shareit .bebo
{
    background-position: 0 0;
}
 
.shareit .delicious
{
    background-position: 0 -17px;
}
 
.shareit .digg
{
    background-position: 0 -34px;
}
 
.shareit .email
{
    background-position: 0 -68px;
}
 
.shareit .google
{
    background-position: 0 -119px;
}
 
.shareit .linkedin
{
    background-position: 0 -136px;
}
 
.shareit .messenger
{
    background-position: 0 -153px;
}
 
.shareit .mixx
{
    background-position: 0 -170px;
}
 
.shareit .myspace
{
    background-position: 0 -187px;
}
 
.shareit .nettby {
    background-position: 0 -204px;
}
 
.shareit .netvibes
{
    background-position: 0 -221px;
}
 
.shareit .newsvine
{
    background-position: 0 -238px;
}
 
.shareit .pdf {
	background-position: 0 -255px;
}
 
.shareit .print {
	background-position: 0 -272px;
}
 
.shareit .reddit
{
    background-position: 0 -289px;
}
 
.shareit .share
{
    background-position: 0 -306px;
}
 
.shareit .stumbleupon
{
    background-position: 0 -323px;
}
 
.shareit .technorati
{
    background-position: 0 -340px;
}
 
 
.shareit .yahoo
{
    background-position: 0 -374px;
}
 
.shareit .yahoobuzz
{
    background-position: 0 -391px;
}

And the result.

ShareIt

RSS feed

To create RSS feeds we need to add a new page type that allows our editors to choose of what they wish to create a RSS feed of.

using EPiServer.Core;
using PageTypeBuilder;
 
namespace Jungle.Templates.Jungle.PageTypes
{
    [PageType(Name = "[Jungle] RSS feed", Description = "Source of a rss feed", Filename = "/Templates/Jungle/Pages/RssFeed.aspx", SortOrder = 1080)]
    public class RssFeedPage : BasePageData
    {
        [PageTypeProperty(UniqueValuePerLanguage = true, Searchable = true, Type = typeof(PropertyString), EditCaption = "Main heading", HelpText = "This heading will present the main title of this page, if this property is not set the page name will be used as a heading instead.")]
        public virtual string Heading
        {
            get;
            set;
        }
 
        [PageTypeProperty(UniqueValuePerLanguage = true, Searchable = true, Type = typeof(PropertyString), HelpText = "The description of the rss feed")]
        public virtual string Description
        {
            get;
            set;
        }
 
        [PageTypeProperty(UniqueValuePerLanguage = true, Searchable = false, Type = typeof(PropertyPageReference), EditCaption = "Rss source", HelpText = "RSS-source page")]
        public virtual PageReference RssSource
        {
            get;
            set;
        }
    }
}

RssFeed.aspx looks like this.

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="RssFeed.aspx.cs" Inherits="Jungle.Templates.Jungle.Pages.RssFeed" %>
using System;
using System.Xml;
using EPiCode.Extensions;
using EPiServer.Core;
using EPiServer.Filters;
using Jungle.Templates.Jungle.PageTypes;
using Jungle.Templates.Jungle.Util;
using PageTypeBuilder.UI;
 
namespace Jungle.Templates.Jungle.Pages
{
    public partial class RssFeed : TemplatePage<RssFeedPage>
    {
        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
 
            Response.ContentType = "text/xml";
            Response.Clear();
            if (!PageReference.IsNullOrEmpty(CurrentPage.RssSource))
            {
                this.WriteRssDocument();
            }
 
            Response.End();
        }
 
        private void WriteRssDocument()
        {
            var doc = new XmlDocument();
            XmlNode outerNode = doc.CreateElement("rss");
            XmlAttribute uriInfo = doc.CreateAttribute("xmlns:dc");
            uriInfo.Value = "http://purl.org/dc/elements/1.1/";
            outerNode.Attributes.Append(uriInfo);
            doc.AppendChild(outerNode);
 
            XmlAttribute versionInfo = doc.CreateAttribute("version");
            versionInfo.Value = "2.0";
            outerNode.Attributes.Append(versionInfo);
 
            XmlNode channel = doc.CreateElement("channel");
            outerNode.AppendChild(channel);
 
            XmlNode title = doc.CreateElement("title");
            title.InnerText = CurrentPage.PageName;
            channel.AppendChild(title);
 
            XmlNode link = doc.CreateElement("link");
            link.InnerText = EPiServer.Configuration.Settings.Instance.SiteUrl.ToString();
 
            channel.AppendChild(link);
 
            XmlNode description = doc.CreateElement("description");
            description.InnerText = CurrentPage["Description"] as string;
            channel.AppendChild(description);
 
            var children = GetChildren(CurrentPage.RssSource);
            FilterForVisitor.Filter(children);
            foreach (var page in children)
            {
                var item = doc.CreateNode(XmlNodeType.Element, "item", null);
 
                XmlNode itemTitle = doc.CreateElement("title");
                itemTitle.InnerText = page.PageName;
                item.AppendChild(itemTitle);
 
                XmlNode itemLink = doc.CreateElement("link");
 
                itemLink.InnerText = page.GetExternalUrl();
                item.AppendChild(itemLink);
 
                XmlNode itemDescription = doc.CreateElement("description");
                itemDescription.InnerText = page.PreviewText(400);
                item.AppendChild(itemDescription);
 
                XmlNode itemGuid = doc.CreateElement("guid");
                itemGuid.InnerText = page.GetExternalUrl();
                item.AppendChild(itemGuid);
 
                XmlNode itemPubDate = doc.CreateElement("pubDate");
                itemPubDate.InnerText = page.Changed.ToString("r");
                item.AppendChild(itemPubDate);
 
                foreach (var category in page.Category)
                {
                    XmlNode itemCategory = doc.CreateElement("category");
                    itemCategory.InnerText = page.Category.GetCategoryName(category);
                    item.AppendChild(itemCategory);
                }
 
                channel.AppendChild(item);
            }
 
            doc.Save(Response.OutputStream);
        }
    }
}

We can now create a RSS feed of our news. Go to edit mode, create a new page of type [Jungle] RSS feed, choose the News container page.

News RSS feed

Last step is adding the RssContainer property to the start page.

[PageTypeProperty(
    EditCaption = "Rss container page",
    Type = typeof(PropertyPageReference),
    Tab = typeof(SiteConfiguration))]
public virtual PageReference RssContainer
{
    get; set;
}

Go to edit mode and the start page, and choose the Rss container page you created with the news. This will add the RSS feeds to the HTML head, which will allow for automatic detection of RSS feeds by RSS readers like Google Reader.

RSS feed link

<head>
    ...
    <link rel="alternate" type="application/rss+xml" href="/en/RSS/News-RSS/" title="News RSS" />
</head>

That’s it! The next post: Part 5: Creating the search page.

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.

JungleLand start page

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? :)

Properties for [Jungle] start page in admin mode

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.

Start page id 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).

New start page

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.

Inspecting the start page markup with Firebug

Inspecting the default markup with Firebug

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 &raquo;</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
                            &raquo;</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 &raquo;</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 &raquo;</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, remove everything inside the class.

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.

Extending PageData with some cool Html Helpers

I’ve been reading up on ASP.NET MVC recently. One of the things I love about ASP.NET MVC is the control we as developers have over our markup. ASP.NET MVC doesn’t use server controls, instead it relies heavily on extension methods for generating markup. For some time now I’ve been playing with the idea of adding similar functionality to EPiServer and the EPiCode.Extensions project.

This evening I started adding some of these Html Helpers to the project. I’ve started by focusing on generating links (anchors, hyperlinks). Nothing special, but something that I think will save a lot of repetitive coding.

Update: 08.12.2009

I wrote another blog post explaining my thoughts more: Html Helpers vs Server controls.

LinkExtensions

using System;
using System.Collections.Generic;
using System.Web.Routing;
using EPiCode.Extensions.Helper;
using EPiServer.Core;
 
namespace EPiCode.Extensions
{
    public static partial class PageDataExtensions
    {
        public static string HtmlLink(this PageData page)
        {
            return HtmlLink(page, page.PageName, null);
        }
 
        public static string HtmlLink(this PageData page, string linkText)
        {
            return HtmlLink(page, linkText, null);
        }
 
        public static string HtmlLink(this PageData page, Func<PageData, string> linkText)
        {
            return HtmlLink(page, linkText(page), null);
        }
 
        public static string HtmlLink(this PageData page, object attributes)
        {
            return HtmlLink(page, page.PageName, new RouteValueDictionary(attributes));
        }
 
        public static string HtmlLink(this PageData page, string linkText, object attributes)
        {
            return HtmlLink(page, linkText, new RouteValueDictionary(attributes));
        }
 
        public static string HtmlLink(this PageData page, Func<PageData, string> linkText, object attributes)
        {
            return HtmlLink(page, linkText(page), new RouteValueDictionary(attributes));
        }
 
        private static string HtmlLink(PageData page, string linkText, IDictionary<string, object> attributes)
        {
            if (page.PageLink == null || page.PageLink.ID < 1)
            {
                return string.Empty;
            }
 
            return HtmlHelper.GenerateLink(page.LinkURL, linkText, attributes);
        }
    }
}

Usage

    // Method signatur
    public static string HtmlLink(this PageData page)
 
    // Usage
    CurrentPage.HtmlLink();
 
    // Renders
    <a href="/en/mypage/">My PageName</a>
    // Method signatur
    public static string HtmlLink(this PageData page, string linkText)
 
    // Usage
    CurrentPage.HtmlLink("Some text");
 
    // Renders
    <a href="/en/mypage/">Some text</a>
    // Method signatur
    public static string HtmlLink(this PageData page, Func<PageData, string> linkText)
 
    // Usage    
    CurrentPage.HtmlLink(p => p.GetProperty("Heading|PageName").Value.ToString());
 
    // Renders
    <a href="/en/mypage/">My Heading</a>
    // Method signatur
    public static string HtmlLink(this PageData page, object attributes)
 
    // Usage
    CurrentPage.HtmlLink(new { style = "color:red;" });
 
    // Renders
    <a href="/en/mypage/" style="color:red;">My PageName</a>
    // Method signatur
    public static string HtmlLink(this PageData page, string linkText, object attributes)
 
    // Usage
    CurrentPage.HtmlLink("Some text", new { target = "_blank", title = "A very cool link"});
 
    // Renders
    <a href="/en/mypage/" target="_blank" title="A very cool link">Some text</a>
    // Method signatur
    public static string HtmlLink(this PageData page, Func<PageData, string> linkText, object attributes)
 
    // Usage
    CurrentPage.HtmlLink(p => p.StartPublish.ToShortDateString(), new { style = "text-decoration: none;", @class = "myclass" });
 
    // Renders
    <a class="myclass" href="/en/mypage/" style="text-decoration: none;">11/3/2009</a>

This will of course be extra cool combined with the Page Type Builder project.

So what do you think? Cool? Spaghetti code? Don’t care? Comments are very much appreciated! :)

Part 2: Creating a foundation – Create an EPiServer site from scratch

In this part we’re going to build our sites foundation. The first thing we’re going to do is take a look at the ready made template that we’re tasked with implementing. This is the template that we’re going to implement: Jungleland. Go over and take a look at it, click a little around, and make yourself familiar with it.

This is where Firebug comes in. It allows us to easily inspect any element, and see where in the HTML structure we are. I usually start out with this on a new project, to get a feeling of how the HTML is structured, which will help is when we’re implementing it later on.

Getting started

Lets download the files, unzip, and open the folder. Also make sure that you have your Visual Studio project open (see the Part 1: Setting up the development environment for more information). We’re now going to drag the CSS files into our Styles folder in Visual Studio.

Next up are the images. Open up the images folder and drag all the files into your Images folder.

Masterpage

Create a new masterpage by right-clicking the MasterPages folder, and choosing Add Item. Choose MasterPage and give it a name of Site.master.

Create a new Master Page

Lets delete all the HTML code, and instead copy all the code from the file style.html.

In the masterpage we’re placing everything that is common in most of our page templates. Lets delete all the code inside <div id=”main”>. You should now have something like this:

<%@ Master Language="C#" AutoEventWireup="true" CodeBehind="Site.master.cs" Inherits="Jungle.Templates.Jungle.MasterPages.Site" %>
 
<!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" xml:lang="en" lang="en">
 
<head>
 
<title>Jungleland</title>
 
<meta http-equiv="content-type" content="application/xhtml+xml; charset=UTF-8" />
<meta name="author" content="Erwin Aligam - styleshout.com" />
<meta name="description" content="Site Description Here" />
<meta name="keywords" content="keywords, here" />
<meta name="robots" content="index, follow, noarchive" />
<meta name="googlebot" content="noarchive" />
 
<link rel="stylesheet" type="text/css" media="screen" href="css/screen.css" />
<!--[if IE 6]><link rel="stylesheet" type="text/css" href="css/ie6.css" media="screen" /><![endif]-->
 
</head>
 
<body>
 
<!-- wrap -->
<div id="wrap">
 
	<!-- header -->
	<div id="header">			
 
		<a name="top"></a>
 
		<h1 id="logo-text"><a href="index.html" title="">jungleland</a></h1>		
		<p id="slogan">Just Another Styleshout CSS Template... </p>					
 
		<div  id="nav">
			<ul>
				<li><a href="index.html">Home</a></li>
				<li id="current"><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="index.html">Support</a></li>
				<li><a href="index.html">About</a></li>		
			</ul>		
		</div>		
 
		<p id="rss-feed"><a href="index.html" class="feed">Grab the RSS FEEd</a></p>	
 
		<form id="quick-search" action="index.html" method="get" >
			<p>
			<label for="qsearch">Search:</label>
			<input class="tbox" id="qsearch" type="text" name="qsearch" value="Search..." title="Start typing and hit ENTER" />
			<input class="btn" alt="Search" type="image" name="searchsubmit" title="Search" src="images/search.png" />
			</p>
		</form>	
 
	<!-- /header -->					
	</div>
 
	<!-- content -->
	<div id="content-wrap" class="clear">
 
		<div id="content">		
 
			<!-- main -->
			<div id="main">	
 
			<!-- /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 -->	
<div id="footer">	
 
	<!-- footer-outer -->	
	<div id="footer-outer" class="clear"><div id="footer-wrap">
 
		<div class="col-a">
 
			<h3>Contact Info</h3>
 
			<p>
			<strong>Phone: </strong>+1234567<br/>
			<strong>Fax: </strong>+123456789
			</p>
 
			<p><strong>Address: </strong>123 Put Your Address Here</p>
 
			<p><strong>E-mail: </strong>me@jungleland.com</p>
			<p>Want more info - go to our <a href="#">contact page</a></p>			
 
			<h3>Follow Us</h3>
 
			<div class="footer-list">
				<ul>				
					<li><a href="index.html" class="rssfeed">RSS Feed</a></li>
					<li><a href="index.html" class="email">Email</a></li>
					<li><a href="index.html" class="twitter">Twitter</a></li>									
				</ul>
			</div>					
 
		</div>
 
		<div class="col-a">			
 
			<h3>Site Links</h3>
 
			<div class="footer-list">
				<ul>				
					<li><a href="index.html">Home</a></li>
					<li><a href="index.html">Style Demo</a></li>
					<li><a href="index.html">Blog</a></li>
					<li><a href="index.html">Archive</a></li>
					<li><a href="index.html">About</a></li>		
					<li><a href="index.html">Template Info</a></li>		
					<li><a href="index.html">Site Map</a></li>					
				</ul>
			</div>					
 
		</div>
 
		<div class="col-a">
 
			<h3>Web Resource</h3>
 
			<p>Morbi tincidunt, orci ac convallis aliquam, lectus turpis varius lorem, eu 
			posuere nunc justo tempus leo. </p>
 
			<div class="footer-list">
				<ul>				
					<li><a href="http://net.tutsplus.com/">NetTuts</a></li>
					<li><a href="http://www.smashingmagazine.com/">Smashing Magazine</a></li>
					<li><a href="http://psd.tutsplus.com/">PSDTuts</a></li>
					<li><a href="http://www.tutorial9.net/">Tutorial9</a></li>
					<li><a href="http://www.webdesignerwall.com/">Web Designer Wall</a></li>		
					<li><a href="http://bestwebgallery.com/">Best Web Gallery</a></li>		
					<li><a href="http://webcreme.com/">Web Creme</a></li>	
					<li><a href="http://cssmania.com/">CSS Mania</a></li>	
				</ul>
			</div>			
 
		</div>		
 
		<div class="col-b">
 
			<h3>About</h3>			
 
			<p>
			<a href="index.html"><img src="images/gravatar.jpg" width="40" height="40" alt="firefox" class="float-left" /></a>
			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. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Donec libero. Suspendisse bibendum. 
			Cras id urna. <a href="index.html">Learn more...</a></p>			
 
		</div>		
 
		<div class="fix"></div>
 
		<!-- footer-bottom -->		
		<div id="footer-bottom">
 
			<div class="bottom-left">
				<p>
				&copy; 2009 <strong>Copyright Info Here</strong>&nbsp; &nbsp; &nbsp;
				Design by: <a href="http://www.styleshout.com/">styleshout</a> 			
				</p>
			</div>		
 
			<div class="bottom-right">
				<p>		
					<a href="http://jigsaw.w3.org/css-validator/check/referer">CSS</a> | 
		   		<a href="http://validator.w3.org/check/referer">XHTML</a>	|			
					<a href="index.html">Home</a> |
					<strong><a href="#top" class="back-to-top">Back to Top</a></strong>								
				</p>
			</div>
 
		<!-- /footer-bottom -->		
		</div>
 
	<!-- /footer-outer -->		
	</div></div>		
 
<!-- /footer -->
</div>
 
</body>
</html>

That’s better! I usually start at the top and work my way down. Lets do that now. The doctype is XHTML strict, that’s fine, but we have to add a few configuration settings to our web.config file to make the ASP.NET controls render in that mode (default is transitional). Open up web.config and add this code right inside system.web:

<system.web>
      <xhtmlConformance mode="Strict" />
      <browserCaps>
          <case match="W3C_Validator*">
            TagWriter = System.Web.UI.HtmlTextWriter
            W3CDomVersion = 1.0
          </case>
      </browserCaps>
...

Lets continue with Site.master by adding a server side form tag right inside <body> and before </body>.

...
<body>
<form runat="server">
...
</form>
</body>
</html>

Create a new user control, by right-clicking Units and choosing Add Item. Name it Header.ascx and cut/paste the meta and link code from the head section of Site.master into Header.ascx. Register Header.ascx in Site.master and add it inside the head element.

...
<%@ Register TagPrefix="Jungle" TagName="Header" Src="~/Templates/Jungle/Units/Header.ascx" %>
...
<head>
    <title>Jungleland</title>
 
    <Jungle:Header runat="server" />
</head>
...

Now add the runat=”server” attribute to the head and title elements, and remove the title text. Last thing now is to add runat=”server to the html element and give it an id of HtmlElement.

You should have something like this now.

...
<html xmlns="http://www.w3.org/1999/xhtml" runat="server" id="HtmlElement">
 
<head runat="server">
    <title runat="server"></title>
 
    <Jungle:Header runat="server" />
</head>
...

Go to Site.masters code-behind file and add this code.

using System;
using System.Web.UI;
using EPiServer;
 
namespace Jungle.Templates.Jungle.MasterPages
{
    public partial class Site : MasterPage
    {
        private const string _title = "{0}{1}{2}";
        private string _titleSeparator = " - ";
 
        /// <summary>
        /// Gets or sets the title separator.
        /// </summary>
        public string TitleSeparator
        {
            get { return _titleSeparator; }
            set { _titleSeparator = value; }
        }
 
        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
 
            var page = (PageBase)Page;
            Page.Title = string.Format(_title, page.CurrentPage.Property["PageName"].ToWebString(), TitleSeparator, EPiServer.Configuration.Settings.Instance.SiteDisplayName);
 
            this.HtmlElement.Attributes["lang"] = page.CurrentPage.LanguageBranch;
        }
    }
}

Create a new user control called PageHeader.ascx (inside Units), cut/paste the <div id=”header”> from Site.master, and everything inside it. Register and add it to Site.master (same as we did with Header.ascx). Now do the same with the footer, name it PageFooter.ascx and cut/paste <div id=”footer”>…</div>.

Your Site.master file should now look like this:

<%@ 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" />
 
	<!-- content -->
	<div id="content-wrap" class="clear">
 
		<div id="content">		
 
			<!-- main -->
			<div id="main">	
 
			<!-- /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>

Much smaller, cleaner and more manageable :) .

External projects

We need to add two external projects to our project. EPiCode.Extensions and PageTypeBuilder. EPiCode.Extensions consists of a set of extension methods that’ll help us code. Page Type Builder is a project that helps us with strongly typed EPiServer property access and more. For more information about EPiCode.Extensions see my blog post: EPiCode.Extensions new EPiCode Community Project, for more information on the Page Type Builder project see the projects home page. Especially be sure to read through the introduction posts on the Page Type Builder project. We’re going to use both projects throughout this series.

We have a problem though, since our projects root folder is also the sites root folder, everything we add will be accessible through our web application (not what we want!).

Lets clean up a little by placing everything except JungleSite.sln into a new folder called wwwroot (placed inside the JungleSite folder).

Update IIS to point to wwwroot instead, by right clicking the JungleSite in IIS, choosing Manage Web Site, and Advanced Settings (for IIS 7), update the physical path by adding wwwroot.

Back in our JungleSite folder we can now create two new folders: References and External. References will have all the compiled assemblies (.dll files) that we use, and External will have the external projects that we use (their source code).

If you have your code in a subversion repository, add both of these folders to the repository, and then right click the External folder, TortoiseSVN, and Properties. Choose New and svn:externals from the drop down list.

Type this in Property Value:

https://PageTypeBuilder.svn.codeplex.com/svn PageTypeBuilder
https://www.coderesort.com/svn/epicode/EPiCode.Extensions/trunk/ EPiCode.Extensions

After you’ve added these settings, you can do a SVN update to retrieve these projects.

If you don’t have your code in a Subversion repository, just to a SVN Checkout for both of these projects inside the External folder instead.

After downloading both of these projects we can open them up in Visual Studio and add references to the EPiServer version that we’re using (6.0.382.1). To do that expand References inside Visual Studio, and delete everything that is from EPiServer, then right-click References, and choose Add Reference, navigate to our sites bin folder and choose the same EPiServer dlls you just deleted (though now in the correct version). After you’ve done that you can build everything (make sure to build in release mode, by going to the Build menu, Configuration Manager, and choose Release).

Do the same for both projects and copy all the .dll files to the References folder.

Before we can open up our project in Visual Studio, we need to edit our solution file with the new path to JungleSite.csproj.

Microsoft Visual Studio Solution File, Format Version 10.00
# Visual Studio 2008
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JungleSite", "wwwroot/JungleSite.csproj", "{86592C94-5E09-417B-B1E2-AFDB757B24D4}"
EndProject
Global
	GlobalSection(SolutionConfigurationPlatforms) = preSolution
		Debug|Any CPU = Debug|Any CPU
		Release|Any CPU = Release|Any CPU
	EndGlobalSection
	GlobalSection(ProjectConfigurationPlatforms) = postSolution
		{86592C94-5E09-417B-B1E2-AFDB757B24D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{86592C94-5E09-417B-B1E2-AFDB757B24D4}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{86592C94-5E09-417B-B1E2-AFDB757B24D4}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{86592C94-5E09-417B-B1E2-AFDB757B24D4}.Release|Any CPU.Build.0 = Release|Any CPU
	EndGlobalSection
	GlobalSection(SolutionProperties) = preSolution
		HideSolutionNode = FALSE
	EndGlobalSection
EndGlobal

We should now be able to open up the project again. Before we can use EPiCode.Extensions and PageTypeBuilder, we need to add a reference to it in our project. Right-click references and choose Add reference, navigate to our References folder and choose all the .dlls in the References folder.

Great! We can now use both projects in our code.

Header.ascx

Lets start with Header.ascx, delete all the meta information. Next use the ResolveUrl method to link to the stylesheets.

<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="Header.ascx.cs" Inherits="Jungle.Templates.Jungle.Units.Header" %>
<asp:PlaceHolder ID="plhMetaDataArea" runat="server" />
 
<link rel="stylesheet" type="text/css" media="screen" href="<%= ResolveUrl("~/Templates/Jungle/Styles/screen.css") %>" />
<!--[if IE 6]><link rel="stylesheet" type="text/css" href="<%= ResolveUrl("~/Templates/Jungle/Styles/ie6.css") %>" media="screen" /><![endif]-->
<EPiServer:PageList ID="RssList" PageLinkProperty="RssContainer" runat="server">
    <ItemTemplate>
        <%#GetRss(Container.CurrentPage)%>
    </ItemTemplate>
</EPiServer:PageList>

I’ve also added a PageList for the RSS feeds (see Templates/Public/Units/Static/Header.ascx).

Lets now create a BasePageData for our page types. Create a new folder under Templates and Jungle, and give it a name of PageTypes. Inside PageTypes create a new class and name it BasePageData, make it derive from TypedPageData from the PageTypeBuilder project.

using PageTypeBuilder;
 
namespace Jungle.Templates.Jungle.PageTypes
{
    public abstract class BasePageData : TypedPageData
    {
    }
}

I’m assuming that you have worked with the Page Type Builder project before. If not be sure to at least read the introduction posts by Joel Abrahamsson, and my A Developer’s guide to PageTypeBuilder post.

When we created this EPiServer site we added the Public Templates with some standard content. Lets go to admin mode (http://jungle/cms/UI/admin/default.aspx), login with your local administrator account. Go to Page Type, and Dynamic Properties. Here you’ll see a bunch of standard dynamic properties that the Public Templates created for us. Delete the MainMenuContainer and LoginPage properties, since we don’t need them. Edit each property and copy its name into BasePageData.

using PageTypeBuilder;
 
namespace Jungle.Templates.Jungle.PageTypes
{
    public abstract class BasePageData : TypedPageData
    {
        /*
         * MetaAuthor
         * MetaDescription
         * MetaKeywords
         * SearchPage
         * UIEditorCssPaths
         */
    }
}

Standard code for accessing dynamic properties with PageTypeBuilder looks like this:

public virtual string MetaAuthor
{
    get
    {
        return this.GetPropertyValue(page => page.MetaAuthor, true);
    }
}

For more information be sure to take a look at Working with Dynamic Properties and Page Type Builder.

using EPiServer.Core;
using PageTypeBuilder;
 
namespace Jungle.Templates.Jungle.PageTypes
{
    public abstract class BasePageData : TypedPageData
    {
        public virtual string MetaAuthor
        {
            get
            {
                return this.GetPropertyValue(page => page.MetaAuthor, true);
            }
        }
 
        public virtual string MetaDescription
        {
            get
            {
                return this.GetPropertyValue(page => page.MetaDescription, true);
            }
        }
 
        public virtual string MetaKeywords
        {
            get
            {
                return this.GetPropertyValue(page => page.MetaKeywords, true);
            }
        }
 
 
 
        public virtual PageReference SearchPage
        {
            get
            {
                return this.GetPropertyValue(page => page.SearchPage, true);
            }
        }
 
        public virtual string UIEditorCssPaths
        {
            get
            {
                return this.GetPropertyValue(page => page.UIEditorCssPaths, true);
            }
        }
    }
}

We can now update all the code-behind files for Header, PageHeader, and PageFooter to derive from PageTypeBuilders generic UserControlBase and setting the type to BasePageData. Which gives us access to the dynamic properties we added to BasePageData.

using System;
using System.Web.UI.HtmlControls;
using EPiCode.Extensions;
using EPiServer.Core;
using EPiServer.Core.Html;
using Jungle.Templates.Jungle.PageTypes;
using PageTypeBuilder.UI;
 
namespace Jungle.Templates.Jungle.Units
{
    /// <summary>
    /// The header of the website. Generates metadata tags, rss and css links.
    /// </summary>
    public partial class Header : UserControlBase<BasePageData>
    {
        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
 
            CreateMetaData();
        }
 
        /// <summary>
        /// Gets the link to a RSS source page
        /// </summary>
        /// <param name="page">The RSS source page</param>
        /// <returns>A string representation of a link</returns>
        protected string GetRss(PageData page)
        {
            string rssLinkTag = "<link rel=\"alternate\" type=\"application/rss+xml\" href=\"{0}\" title=\"{1}\" />";
            return string.Format(rssLinkTag, Server.HtmlEncode(page.LinkURL), page.PageName.ToWebString());
        }
 
 
        /// <summary>
        /// Creates the metadata tags for the website
        /// </summary>
        private void CreateMetaData()
        {
            string metaDescription = CurrentPage.MetaDescription;
            if (string.IsNullOrEmpty(metaDescription))
            {
                metaDescription = CurrentPage["MainIntro"] as string ?? string.Empty;
                if (!string.IsNullOrEmpty(metaDescription))
                {
                    metaDescription = TextIndexer.StripHtml(metaDescription, 0);
                    if (metaDescription.Length > 255)
                    {
                        metaDescription = metaDescription.Substring(0, 252) + "...";
                    }
                }
            }
            if (!string.IsNullOrEmpty(metaDescription))
            {
                CreateMetaTag("description", metaDescription, false);
            }
 
            // Keywords
            CreateMetaTag("keywords", CurrentPage.MetaKeywords, false);
 
            // Author
            CreateMetaTag("author", CurrentPage.MetaAuthor, false);
 
            // Rating
            CreateMetaTag("rating", "General", false);
 
            // Revisit each month
            CreateMetaTag("revisit-after", "4 weeks", false);
 
            // Generator
            CreateMetaTag("generator", "EPiServer", false);
 
            // Robots
            CreateMetaTag("robots", "all", false);
 
            // Charset
            CreateMetaTag("Content-Type", string.Format("text/html; charset={0}", "UTF-8"), true);
 
            // Created - GMT format
            if (CurrentPage.Created != DateTime.MinValue)
            {
                CreateMetaTag("creation_date", CurrentPage.Created.ToString("R"), false);
            }
            // Last modified data, in GMT format - Note, same as revised
            if (CurrentPage.Changed != DateTime.MinValue)
            {
                CreateMetaTag("last-modified", CurrentPage.Changed.ToString("R"), false);
            }
            // Revised - GMT format
            if (CurrentPage.Changed != DateTime.MinValue)
            {
                CreateMetaTag("revised", CurrentPage.Changed.ToString("R"), false);
            }
            CreateMetaTag("Content-Language", CurrentPage.LanguageBranch, true);
        }
 
        /// <summary>
        /// Adds a meta tag control to the page header
        /// </summary>
        /// <param name="name">The name of the meta tag</param>
        /// <param name="content">The content of the meta tag</param>
        /// <param name="httpEquiv">True if the meta tag should be HTTP-EQUIV</param>
        private void CreateMetaTag(string name, string content, bool httpEquiv)
        {
            var tag = new HtmlMeta();
            if (httpEquiv)
            {
                tag.HttpEquiv = name;
            }
            else
            {
                tag.Name = name;
            }
            tag.Content = content;
            plhMetaDataArea.Controls.Add(tag);
        }
    }
}

PageHeader

I’ve split the code into separate user controls, MainMenu and QuickSearch. If you like to learn more about some of the techniques used in this code, I recommend checking out my blog posts: EPiServer web controls: Property and EPiServer web controls: MenuList and PageTree.

<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="PageHeader.ascx.cs" Inherits="Jungle.Templates.Jungle.Units.PageHeader" %>
<%@ Register TagPrefix="Jungle" TagName="MainMenu" Src="~/Templates/Jungle/Units/MainMenu.ascx" %>
<%@ Register TagPrefix="Jungle" TagName="QuickSearch" Src="~/Templates/Jungle/Units/QuickSearch.ascx" %>
 
<div id="header">			
 
	<a name="top"></a>
 
	<h1 id="logo-text"><asp:HyperLink accesskey="1" ID="lnkLogotype" runat="server" /></h1>					
 
	<Jungle:MainMenu runat="server" />	
 
	<p id="rss-feed"><EPiServer:Property runat="server" ID="RssFeed" PropertyName="RssContainer" /></p>	
 
	<Jungle:QuickSearch runat="server" />
 
<!-- /header -->					
</div>
using System;
using EPiServer;
using EPiServer.Core;
using Jungle.Templates.Jungle.PageTypes;
using PageTypeBuilder.UI;
 
namespace Jungle.Templates.Jungle.Units
{
    public partial class PageHeader : UserControlBase<BasePageData>
    {
        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
 
            var startPage = DataFactory.Instance.GetPage(PageReference.StartPage);
 
            lnkLogotype.NavigateUrl = startPage.LinkURL;
            lnkLogotype.ToolTip = Translate("/navigation/startpagelinktitle");
            lnkLogotype.Text = Translate("/navigation/startpagelinktitle");
 
            this.RssFeed.PageLink = startPage.PageLink;
        }
    }
}

That’s it for this part! :) . All the code is also available on EPiCode. See Part 1: Setting up the development environment, for details on how to connect.

The next post – Part 3: Creating the start page.

Part 1: Setting up the development environment – Create an EPiServer site from scratch

In this first part I’m taking you through setting up everything on a new computer. Most likely you’ll have done some of this before, in that case feel free to skip to the parts that interest you. If all this is new to you, I would install everything in the order described.

Update 08.12.2009

Ted Nyberg posted a nice blog post: How to set up an EPiServer website, that you should definitely check out (after you’ve read this one of course :) ).

Installing IIS, ASP.NET, SQL Server 2008, and Visual Studio

Microsoft as a great tool for quickly installing everything you need, its called the Web Platform Installer. Download and install it, after its installed, launch it and choose Web Platform. Here you’ll be able to choose the web server, frameworks and runtimes, and database server, that you’ll need.

Web Platform Installer

For more information about the Web Platform Installer see Scott Guthrie’s introduction post and the official site.

I’m using Visual Studio 2008 Professional in this series, but you can use any version you like, including the free Visual Web Developer 2008.

Installing EPiServer

Go to EPiServer World and download the latest version of EPiServer, I’m using EPiServer CMS 6.

Unzip, and click setup to start. When asked choose Application Files (we’re adding the site later).

Choose Application Files

Subversion

Subversion is a popular open source version control system. It will help us get the latest version of our code, track changes, and a whole lot of other things. For more information I recommend taking a look at the free book: Version Control with Subversion.

Throughout this series I’ll update and keep all the code in EPiCode’s subversion repository. To work with EPiCode’s repository we need a Subversion client. My favorite is TortoiseSVN, but you can use any you like. You’ll need to download and install it (or any other subversion client). After the installation and restart you should have a few new options in Windows Explorer.

TortoiseSVN shell extension in Windows Explorer

When following this series I recommend building your own project, and just use the project on EPiCode for reference when you’re stuck. Or just want to copy some files/folders.

To do a checkout of the repository, right click in Windows Explorer inside the folder you’d like to place the code, and choose SVN Checkout. Type in the URL https://www.coderesort.com/svn/epicode/JungleLand (note: you need to register on EPiCode first, if you don’t already are).

SVN Checkout of JungleLand from EPiCode

Other tools

My main development browser is Firefox, the reason for that is that you have access to a ton of great extensions that’ll help you a lot when developing a new site. One of those extensions is Firebug. Download and install both Firefox and Firebug if you don’t already have them. For more information, take a look at this screencast: Introduction to Firebug.

With Visual Studio I have two other tools: VisualSVN and ReSharper. VisualSVN to help me with Subversion, and ReSharper for helping me with the code. You don’t need all these tools to follow along with this series, but I recommend trying them out.

Also install UnleashIt. UnleashIt is used for removing subversion files (hidden files that Subversion uses to keep track), code-behind, class files etc, when deploying to our production server (everything is compiled inside .dll files).

The last program we need is Reflector. This is one of my favorite tools when working with EPiServer. It’ll allow you to open up any .dll file and inspect the code it contains. This is great for when you’re stuck on a problem and need to see how something behaves, or for just having fun and peeking inside EPiServer :) .

Reflector in action

Installing the site

Start the deployment center: go to Start, EPiServer, and EPiServer Deployment Center. Choose the EPiServer version you’d like to install (I’m using 6.0.382.1), and click, “Install site and SQL Server database”.

Step 1

Nothing special here, I’m using http://jungle as my local site url – for that to work I need to update my hosts file, located under C:\Windows\System32\drivers\etc\hosts (edit it with notepad or any other code editor). I’ve added the line: 127.0.0.1 jungle

Step 2

If you have a local SQL Server Express instance you can most likely just type .\SQLEXPRESS. If that doesn’t work, open up services (under Administrative tools in control panel) and check what your local SQL Server instance is called.

If you have other problems connecting, make sure that TCP/IP is enabled for your SQL Server instance. Open up Configuration Manager: go to Start, SQL Server 2008, Configuration tools, and SQL Server Configuration Manager.

Step 3

I just used the default VPP path suggested.

Step 4

Make sure to install the Public Templates with only the English content.

Step 5

If you like you can order a developer license, I’m just going to continue without adding a license file.

Step 6

Great, we’re now ready to install everything!

Time for a little coffee break – when we come back everything should be installed!

After installation

When the installation completes you should see something like this in your default browser.

Project folder structure

Great, we now have most of our environment configured and setup. Lets go to our projects root folder (C:\EPiServer\Sites\JungleSite) and rename PublicTemplates.csproj to JungleSite.csproj. Then open it in Visual Studio (double-click on JungleSite.csproj). You should have a structure similar to this:

Lets expand the Templates folder, and add a new folder called Jungle. Under Jungle, add the following folders:

  • Images
  • MasterPages
  • Pages
  • Scripts
  • Styles
  • Units
  • Util

Next step is deleting the App_Data and Service_References folders. After you’ve done that expand Properties and open up AssemblyInfo.cs, and update the information here.

// General Information about an assembly is controlled through the following 
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("JungleSite")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Frederik Vig")]
[assembly: AssemblyProduct("JungleSite")]
[assembly: AssemblyCopyright("� 2009-2010 by Frederik Vig. All rights reserved")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
 
// Setting ComVisible to false makes the types in this assembly not visible 
// to COM components.  If you need to access a type in this assembly from 
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
 
// Version information for an assembly consists of the following four values:
//
//      Major Version
//      Minor Version 
//      Build Number
//      Revision
//
// You can specify all the values or you can default the Revision and Build Numbers 
// by using the '*' as shown below:
[assembly: AssemblyVersion("1.0.0.0")]

Last step is renaming the assembly and default namespace. Right click JungleSite and choose Properties.

Make sure it builds by pressing either ctrl + shift + b, or going to: Build, Build JungleSite. It should build fine (since we didn’t change any of the code). Last thing to remember is to delete the old dll file. Go to your bin folder and delete the file called EPiServer.Templates.Public.dll.

All the code is also available on EPiCode for you to checkout and download.

That’s it! In the next post, we’ll start building the site by implementing our sites foundation.