Frederik Vig – ASP.NET developer

Follow me

Adding different CSS classes when using the EPiServer PageTree control

Another little quick tip. I was browsing the EPiServer World forum and came across a common question.

by David Green

I am using the EpiServer:PageTree control to generate a nested <ul><li> list in the format below.

However I am also using a dropdown menu system called UDM which requires that the class and id “udm” are on the first <ul> tag.

The container object of the PageTree controller exposes the Indent property that we can use for this. Below is a simple example of how to add a CSS class to the first ul in your list (code is based on SubMenu.ascx from the EPiServer Public Templates package).

<%@ Control Language="C#" EnableViewState="false" AutoEventWireup="False" CodeBehind="SubMenu.ascx.cs" Inherits="EPiServer.Templates.Public.Units.Static.SubMenu" %>
<EPiServer:PageTree ShowRootPage="false" runat="server" id="Menu">
    <IndentTemplate>
        <ul <%# AddCssClassToFirstLevel(Container.Indent, "udm") %>>
    </IndentTemplate>
    <ItemHeaderTemplate>
        <li>
    </ItemHeaderTemplate>
    <ItemTemplate>
        <EPiServer:Property PropertyName="PageLink" runat="server" />
    </ItemTemplate>
    <SelectedItemTemplate>
	<EPiServer:Property CssClass="selected" PropertyName="PageName" runat="server" />
    </SelectedItemTemplate>
    <ItemFooterTemplate>
        </li>
    </ItemFooterTemplate>
    <UnindentTemplate>
        </ul>
    </UnindentTemplate>
</EPiServer:PageTree>
using System;
using EPiServer;
using EPiServer.Web.WebControls;
 
namespace EPiServer.Templates.Public.Units.Static
{
    public partial class SubMenu : UserControlBase
    {
        private MenuList _menuList;
 
        /// <summary>
        /// Gets or sets the data source for this control
        /// </summary>
        public MenuList MenuList
        {
            get { return _menuList; }
            set { _menuList = value; }
        }
 
        protected override void OnLoad(System.EventArgs e)
        {
            base.OnLoad(e);
 
            if (MenuList == null)
            {
                return;
            }
            Menu.PageLink = MenuList.OpenTopPage;
            Menu.DataBind();
        }
 
        protected string AddCssClassToFirstLevel(int level, string cssClassName)
        {
            if (level == 1)
            {
                return string.Format("class=\"{0}\"", cssClassName);
            }
 
            return string.Empty;
        }
    }
}

We can easily add more complex logic. We also have access to the HasChildren property, which tells us if the active page has any children.

Hope this helps.

Getting the Page and EPiServer CurrentPage object from HttpContext

Just a little quick tip when needing to use either the Page object or the EPiServer CurrentPage object from a class file. HttpContext.Current will give you access to the current request, what we can do is cast HttpContext.Current.Handler (since Page implements the IHttpHandler interface) to System.Web.UI.Page.

var page = HttpContext.Current.Handler as System.Web.UI.Page;
 
if (page != null)
{
    // do something with the page object
}

We can take this a step further and cast it to EPiServer.PageBase which gives us access to the CurrentPage object.

private static PageData CurrentPage
{
    get
    {
	var page = HttpContext.Current.Handler as EPiServer.PageBase;
 
	if (page == null)
	{
	    return null;
	}
 
	return page.CurrentPage;
    }
}

Visual Studio 2010 EPiServer Snippets

I finally got my hands on a copy of Visual Studio 2010 RC1! After playing around a bit, I stumbled across the new snippet functionality in Visual Studio 2010. You can now use snippets in the markup files as well (in previous versions you could only use the snippet functionality in code files like class/interfaces/code-behind files etc). This is very cool, and Microsoft has even included a few snippets for their ASP.NET controls and for HTML elements like: a, table, img, div etc. Quite a time saver!

To test the snippets out, simply type the shortcut (eg. ‘a’ or ‘table’) and press tab.

Visual Studio 2010 table snippet

Visual Studio 2010 table snippet

This is a simple, but very cool feature. Imaging all the typing you can get rid off!

Creating your own snippets

In Visual Studio 2010, under the Tools menu, you’ll find the Code Snippets Manager (ctrl+k, ctrl+b).

Visual Studio 2010 Code Snippets Manager

Here you can add new folders that contain your custom snippets, or check out the other snippets added by Microsoft. You’ll also see the path to the snippets folder (C:\Program Files (x86)\Microsoft Visual Studio 10.0\Web\Snippets\HTML\1033\ in my case). If you open up that folder in Windows Explorer you should see at least two folders there, ASP.NET and HTML. Inside both of these folders you’ll find the snippets for the ASP.NET web controls and for HTML elements. We can open up one of the files and take a look at the code.

<CodeSnippet Format="1.1.0" xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
  <Header>
    <Title>image</Title>
    <Author>Microsoft Corporation</Author>
    <Shortcut>image</Shortcut>
    <AlternativeShortcuts>
      <Shortcut>imagebutton</Shortcut>
      <Shortcut Value="image">asp:image</Shortcut>
      <Shortcut Value="imagebutton">asp:imagebutton</Shortcut>
    </AlternativeShortcuts>
    <Description>Markup snippet for a control that contains an image</Description>
    <SnippetTypes>
      <SnippetType>Expansion</SnippetType>
    </SnippetTypes>
  </Header>
  <Snippet>
    <Declarations>
      <Literal>
        <ID>imageurl</ID>
        <ToolTip>imageurl</ToolTip>
        <Default>imageurl</Default>
      </Literal>
    </Declarations>
    <Code Language="html"><![CDATA[<asp:$shortcut$ imageurl="$imageurl$" runat="server" />$end$]]></Code>
  </Snippet>
</CodeSnippet>

Very easy and readable XML markup, that we easily can tweak to our needs. Say we’re working on a project where 90% of the tables we create should have a class of “products”. Lets create a snippet for this, so that we don’t have to type the same thing every time.

Create a new snippet called table-products.snippet, open it up in your favorite code editor and add this code.

<CodeSnippet Format="1.1.0" xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
  <Header>
    <Title>table</Title>
    <Author>Frederik Vig</Author>
    <Shortcut>tablep</Shortcut>
    <Description>Markup snippet for a table with class products</Description>
    <SnippetTypes>
      <SnippetType>Expansion</SnippetType>
      <SnippetType>SurroundsWith</SnippetType>
    </SnippetTypes>
  </Header>
  <Snippet>
    <Declarations>
      <Literal>
        <ID>cellspacing</ID>
        <ToolTip>cellspacing</ToolTip>
        <Default>0</Default>
      </Literal>
    </Declarations>
    <Code Language="html"><![CDATA[<table cellspacing="$cellspacing$" class="products">
    <tr>
        <td>$selected$$end$</td>
    </tr>
</table>]]></Code>
  </Snippet>
</CodeSnippet>

I’ve saved table-products.snippet directly in the HTML snippet folder (C:\Program Files (x86)\Microsoft Visual Studio 10.0\Web\Snippets\HTML\1033\HTML in my case), but you can save it anywhere, just remember to add the snippet folder with the Code Snippets Manager in Visual Studio.

We can now test the snippet by typing tablep and pressing tab.

Visual Studio 2010 table products snippet

Visual Studio 2010 table products snippet

Simple example, I know, but you get the idea!.

EPiServer

Naturally, one of the first things I did was add the EPiServer Property web control to the snippet manager ;) . This turned out to be very easy! So I continued on adding snippets for the other EPiServer web controls as well. Here is the list of snippets with their shortcut.

Download the snippets

Guide to font sizing with CSS

Font sizing with CSS and browser support as always been a mystery to me. We used to use pixels, than we switched to ems and percent, then we switched back to pixels again?!.. In this post I’ve tried shedding some light on the matter and explaining the reason for this, and the different ways of doing font sizing on the web.

You have quite a few different measuring units to use in CSS for use with the font-size property (em, ex, px, in, cm, mm, pt, pc, %). We can group these lengths into two groups.

Relative lenghts

  • em: the ‘font-size’ of the relevant font
  • ex: the ‘x-height’ of the relevant font
  • px: pixels, relative to the viewing device (resolution and pixel density)

Absolute lengths

  • in: inches — 1 inch is equal to 2.54 centimeters.
  • cm: centimeters
  • mm: millimeters
  • pt: points — the points used by CSS 2.1 are equal to 1/72nd of an inch.
  • pc: picas — 1 pica is equal to 12 points.

Percentage values are always relative to a length or other value.

To be honest I only use four of these units. I use points (pt) in my print stylesheet, and px, em and % for my screen stylesheet.

Pixels

When using pixels for your font size you don’t have much to think about, 12px is 12px in every browser. What can be a problem, and what is the main reason that organizations like the W3C advice against using pixels for font size measurement, is the lack of support in browsers like Internet Explorer 6 when changing the browser’s default font size. The reason for this is that most browsers used to use text scaling for this, only enlarging the text on the page. However the newest versions of all the major browsers now use page zooming instead. What this does is increase (or decrease) all the elements on the page, not just the text, by zooming.

Lack of support for page zooming in Internet Explorer 6 can be a problem. If it is you should consider using ems and percent, which all browsers support. You can use ems/percent either for all browsers or by using a conditional comment stylesheet for Internet Explorer 6 and using pixels for the rest. I personally have started using pixels again, simple because it saves me a ton of work, and is much more reliable for measurement than using % and ems.

Ems and percent

If your audience still consists of a lot of Internet Explorer 6 users, you might want to stick with using ems or % for measurement. The rule to follow here is target ÷ context = result.

Example

If we give body a font size of 100% (roughly around 16px in most browsers), we can use that as the context. So if we want 12px in font size for normal text, and 20px for headings, this then becomes the target.

  • 12 ÷ 16 = 0,75
  • 20 ÷ 16 = 1,25
body {
    font-size: 100%;
}
p {
    font-size: .75em;
}
 
h1 {
    font-size: 1.25em;
}

If we’d used pixels we would have something like this.

p {
    font-size: 12px;
}
 
h1 {
    font-size: 20px;
}

Okay, say we have some text inside our paragraphs that we want to be 13px. The target becomes 13px, but the context changes, instead of using the body as the context, the paragraph becomes the context. So we get 13 ÷ 12 = 1,0833.

p strong {
    font-size: 1.0833em;
}

By using the target ÷ context = result rule, everything comes down to maths :) .

Here is the live example with ems and percent and here with pixels.

So, what are you using?

EPiServer code walkthrough #1 – 404 handler

This is the first post in a new series called “EPiServer code walkthrough”. What I’ll do is go through one new EPiServer module in each post. Writing a little about what it does, learn by reading its code, and hopefully contributing a little back to the project, if I see any bugs or harmful/unnecessary code that is :) .

I’m a firm believer that reading code helps you become a better developer. There are lots of modules even I just recently came across, that I didn’t know existed just a few weeks back. I think I’m not the only one, especially since they are spread around on various blogs, CodePlex, EPiCode and other places. They all contain valuable code that we as EPiServer developers should read.

I’m starting with a module I personally have not used much before, but that I know a lot of people have used in their projects: the custom 404 handler.

From the project wiki page

This module has a more advanced url redirect feature than the built-in shortcut url feature in EPiServer. It handles extensions and querystring parameters too. If you have a lot of 404 errors in your logs, you can use this to redirect the user to the correct page. Especially useful if you move templates or pages around, or have just installed EPiServer and have a lot of old urls that is no longer available.

404 handlers wiki page explains in detail what you need to do to install the module, so I’m not going to repeat it here. Instead we’ll take a look at the code.

Start by doing a SVN checkout of the source code (the svn url is https://www.coderesort.com/svn/epicode/BVNetwork.404Handler/5.x/).

SVN checkout of 404 handler

When opening up the BVNetwork.404Handler folder you’ll see a readme.txt file explaining the purpose of the module, installation, configuration etc. This is something I like! Not everyone reads the wiki page, and we as developers are especially lazy when it comes to reading documentation. Having a readme.txt with everything you need makes this so much easier, and gives the module author less support questions to answer. When using a new module, always make sure to read the documentation before asking the author questions. Saves both parties time :) .

Lets open up BVNetwork.404Handler.csproj in Visual Studio.

404 handler project structure in Visual Studio

As you can see the project is structured pretty nice, with good separations between the different parts. Very good that the project uses languages files, and that they’ve included them for five different languages. Not all modules use the language files, and if they do, they seldom have any more than one update-to-date English language file. Kudos for this.

What I don’t like is the missing fnf_logo.gif image file in the Images folder. A quick search in Visual Studio shows that this file is no longer in use, and should therefore be removed from the project. A missing image file is not the end of the world, and I know people make mistakes/forget to commit all their files, but this can be very annoying and even lead to a lot of work for other developers using your code or taking over a project.

Always make sure that your project builds on other computers than your own. I recommend manually setting up the project on preferably a new computer to see that everything works as expected, you can also use the same computer, but make sure to do the normal process a new developer would use when setting up the project (new checkout etc). Especially on larger projects that have quite a few things to setup you should do this. You’ll also receive some valuable feedback if your setup process is to complex (again on larger projects this happens quite frequently). Another thing you should use is a build server. There are quite a few out there, I know CodeResort just added Bitten as their build server for CodeResort projects. In a nutshell what a build server does is make sure you’ve included all the files and resources needed to build your project, run various tests (if you have any), deploy your files, create reports, notify team members if their build fails, and a bunch of other stuff, automatically for you. I’ll try to write a blog post up with more information on build and continuous integration servers.

Lets get back to the Visual Studio and the project! One of the things you should start out with is actually making sure the project builds after doing a checkout. So, either press ctrl + shift + b or go to Build -> Build BVNetwork.404Handler. You should have a successful build.

If we take a look through the code we see that most of the code is well documented and follows a nice naming convention making it easy to know what the purpose of the class is (CustomRedirectHandler.cs, CustomRedirectCollection.cs, Custom404Handler.cs etc). But we also see a few minor things we can clean up: unused using statements, commented out code, unused field variables etc. These are minor, but still important things to get rid of. Always strive for a clean code base, commented out code is something you should never commit into a repository, the whole reason for having a source control system is so that you can look at previous changesets and their code, there is no reason to keep the commented out code there, it will only be forgotten by the developer who commented out the code, and the other developers don’t know what to do with it, and will most likely just let it be. When I come across this I show no mercy and just delete it!

You’ll also see a few System.Diagnostics.Debug.WriteLine that are commented out. The project now uses Log4Net to log errors and warnings, System.Diagnostics.Debug.WriteLine is legacy code that we can safely remove. Another thing that I prefer is using string.Empty(); instead of “”, I’ve not updated the code because I feel this is more a personal preference. Same thing when it comes to over-documenting-code, like in this example.

/// <summary>
/// The refering url
/// </summary>
public static string GetReferer(System.Web.UI.Page page)
{
    string referer = page.Request.ServerVariables["HTTP_REFERER"];
    if (referer != null)
    {
	// Strip away host name in front, if local redirect
	string hostUrl = EPiServer.Configuration.Settings.Instance.SiteUrl.ToString();
	if (referer.StartsWith(hostUrl))
	    referer = referer.Remove(0, hostUrl.Length);
    }
    else
	referer = ""; // Can't have null
    return referer;
}

The document header can safely be removed since the method name does a good job at describing what the purpose of the method is. I’ve not removed the document header, since again this is my personal preference.

A thing I just noticed is that this is actually a Visual Studio class project, and not a Web Application project. I can only guess, but this is probably a project that got upgraded from Visual Studio 2003, I’ve previously had some problem upgrading a project from Visual Studio 2003 to Visual Studio 2008. The conversion usually goes okay (just an update to the csproj file mostly), but the ProjectTypeGuids doesn’t always get added. This is easy to fix, open up BVNetwork.404Handler.csproj in notepad or another source editor, and add this line.

<ProjectTypeGuids>{349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}</ProjectTypeGuids>

Save, and open up the project in Visual Studio. You should now see a slightly different icon at the top of the solution explorer window.

New project icon in Visual Studio

This will allow us to add a designer file to default.aspx from the admin folder. To do this simply right-click the file in solution explorer and choose “Convert to Web Application”. We can now delete the “Web Form Designer generated code”-region.

After all these updates its very important to make sure that the project still builds and that everything functions as expected by testing it.

What I liked the most with this project is how it uses logging. I’m personally very bad at using logging, and know that not all projects use logging as much as they should. Kudos to the authors for this!

I hope you’ve learned a few new things while reading this code, and be sure to try out the 404 handler! For SEO and user experience purposes it is crucial that the users have a smooth transition. They shouldn’t even notice the change when switching to EPiServer from another CMS (that uses different urls). A 404 (file not found) error message can also quickly become a major leak of traffic for your clients site.

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

2010

Happy new year everyone! Just got back from an excellent vacation in New York, USA. Feel very rested and looking forward to getting back to work! Some updates: I received the EMVP (EPiServer Most Valued Professional) status, started out on my own as a freelance ASP.NET developer, and created the first EPiServer meetup group in Norway.

The biggest change for me was starting out on my own. I’m about to sign a contract for three months with a local EPiServer partner here in Oslo, for them to contract me 3-4 days a week. The other 1-2 days I’m going to spend working on an EPiServer module (more information to come) for EPiServer CMS 6.

My goal for this blog is to roughly write a blog post once a week (sometimes more, sometimes less). Mostly focusing on ASP.NET and EPiServer stuff, but also more content on other subjects, reflecting what I’m focusing on at the moment.

I’m a firm believer that when you only focus on one thing, you tend to only see things through one perspective. In 2010 I’m going to explore more outside my comfort zone and what I usually work with. Some of the things that I’m exploring at the moment: The Django framework, PHP (building a time tracking application for my freelance business), I just received a few books that I’m reading, and I’m going to explore more of Umbraco (hopefully getting certified in Umbraco this spring). I think all of this will help me become a better developer, and help me with my daily work. I will of course try to share my experiences with you on this blog.

The Create an EPiServer site from scratch was a huge success, with lots of great feedback. Very inspiring for me, giving me lots of ideas for further series. Thanks for all the comments, and keep them coming!

With the EPiServer meetup group in Oslo I hope to meet more of the rest of the community in Norway. One of the things I’ve missed is a community here in Oslo. I think I actually know more EPiServer developers in Sweden than in Norway.. the traffic to this blog actually back that up, 4 times as many from Sweden than from Norway. Hopefully this will change with the meetup group, allowing me to meet all the cool EPiServer developers that I know are out there :) .

Another thing this year is redesigning of my blog. I’m not a designer, so this will be a fun and new experience, again outside my comfort zone, but something I think I’ll learn a lot from. I have a few Photoshop DVD courses that I’m going to go through, learning Photoshop even more, helping me with the redesign and with manipulating the pictures I use in my blog posts.

Since I’m my own boss I’m using Fridays as a “do what you want day”, either taking a long weekend and going somewhere, blogging, redesigning, or any of my other projects. I think this will help me focus and work even better the rest of the week.

Lots of projects and ideas! Very excited to get started and learn even more this year than the last! Thanks for all your support and great comments last year! I’ll do my best to create an even better blog this year!

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! And merry Christmas! :)

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.

© Copyright Frederik Vig. Based on Fluid Blue theme