Building a Sitecore Base Page Template


Sitecore is a Web Content Management system on the C# Microsoft .Net framework. If you’re not familiar with it then this posts isn’t going to make much sense to you.  I’m playing around with the it for fun and thought I’d throw out there what your basic page template should look like from both a C# and Sitecore point of view .

If something in Sitecore has a Layout(.aspx) then it’s almost always a Page. I say almost always because I’ve played around with using a page to stream a resource as a lazy mans http handler but I didn’t roll that to a production site and later rewrote it to be use a HTTP Handler.

Pages all have some basic things so why not build a base page template and a base C# Class to support these functions.

Video Demo

I’ll give some guidance on how to implement it, feel free to give your own thoughts and throw out some things I could be missing in the comments.

Title : Every Page has a title. This is the text that shows up in the top of the browser window.

Description : At a minimum you want this for SEO. It’ll show up as the text next to your link in a search engine if gets indexed. Maybe your page will get indexed, maybe it won’t, but leave that up to the business and content authors to decide and make sure you have code in there to support it.  A lot of navigation controls have items next to them with some short description text. So you can use this in some of your own display modules.

Keywords: Another thing you want for SEO but it may also come in handy if you want to some day implement search within your site.

Show in Navigation : At some point you’re going to build out navigation objects and you are going to want to check to see if the item should be shown in the menu. Rather than code against template types you can just check to see if the item inherits the ‘Page’ template and check to see if this option is checked.

Navigation Image : This can be used to display an image that represents the page as part of some type of navigation area. Might need a large and a small depending on requirements but if you have none I say it’s worth it to put something in at 30 by 30.

CSS and Javascript Files: In an ideal solution you have code with CSS and Javascript that stays on the server that belongs to the programmers and you have seperate CSS and JS script files that belong to the Content Authors that is seperate from the developers code.  If you can store this in sitecore you can deploy these objects without needing to push code to a server. In some enviornments that takes time. You wouldn’t believe how many times someone in marketing tells me they got a javascript api they’d like to implement on some pages today and they can’t wait for a release so they are pasting a ton of javascript into a html field.  This will give them a place to put the javascript that they can reuse without waiting for a build. I’d like CSS in there because I think a talented author could do a lot with it.

Here’s my Sitecore Page Template, I’d recommend setting Title to $name so it picks up the Item’s name.

For inserting the CSS and JS script files I’ll need a template that can hold the text or point to a path. I’m using a template I call ‘Text Resource’ that has the following fields.

I’m going to make a CSS File and JS File that inherit this ‘Text Resource’ template and they’re going to set the mime type accordingly in the Standard Values so that doesn’t have to get filled out often.

There’s some work that needs to be done in C# to support the templates and the streaming of resources. We need to create a HTTP Handler and assign it as the layout on the standard values of the ‘Text Resource’ template and we’re going to want to make a base Page class that all our pages will later inherit.

Here’s my base Page class. All it’s really doing at this point is setting content in the header and adding the resources.

using System;
using System.Collections.Generic;
using System.Web.UI;
using Sitecore.Data.Items;
using Sitecore.Links;
using System.Web.UI.HtmlControls;

namespace CMF
{
    public class ContentPage : Page
    {
        private Item PageItem = Sitecore.Context.Item;

        protected override void OnPreRender(EventArgs e)
        {

            base.Title = PageItem.Fields["Title"].GetValue(true);
            base.Header.Controls.Add(new HtmlMeta { Content = PageItem.Fields["Description"].GetValue(true), Name = "description" });
            base.Header.Controls.Add(new HtmlMeta { Content = PageItem.Fields["Keywords"].GetValue(true), Name = "keywords" });

            // for css files and scripts
            ShowCSSPaths();
            ShowJSPaths();

            base.OnPreRender(e);
        }

        //We load the text resources from the context because we may want to add them in other places and not just from the page
        //I append a version number so I can create a new version and the change in query string value will cause the CDN to clear
        //it's cache. I may change this to use updated date but I'm not seeing that readily available on the item.
        private void ShowCSSPaths()
        {
            CMF.Context.Current.LoadCssItems(PageItem);
            List<string> addedPaths = new List<string>();
            foreach (Item item in CMF.Context.Current.CssItems)
            {
                string path = string.Empty;
                if (item.Fields["Path"] != null && item.Fields["Path"].GetValue(true) != string.Empty)
                {
                    path = item.Fields["Path"].GetValue(true);
                }
                else
                {
                    path = LinkManager.GetItemUrl(item).Replace(".aspx", ".css") + "?v=" + item.Version.ToString();
                }

                if (path != string.Empty && !addedPaths.Contains(path))
                {
                    Header.Controls.Add(new LiteralControl(string.Format("<link rel=\"stylesheet\" type=\"text/css\" href=\"{0}\">", path)));
                    addedPaths.Add(path);
                }
            }
        }

        private void ShowJSPaths()
        {
            CMF.Context.Current.LoadJsItems(PageItem);
            List<string> addedPaths = new List<string>();
            foreach (Item item in CMF.Context.Current.JsItems)
            {
                string path = string.Empty;
                if (item.Fields["Path"] != null && item.Fields["Path"].GetValue(true) != string.Empty)
                {
                    path = item.Fields["Path"].GetValue(true);
                }
                else
                {
                    path = LinkManager.GetItemUrl(item).Replace(".aspx", ".js") + "?v=" + item.Version.ToString();
                }

                if (path != string.Empty && !addedPaths.Contains(path))
                {
                    Header.Controls.Add(new LiteralControl(string.Format("<script src=\"{0}\" type=\"text/javascript\"></script>", path)));
                    addedPaths.Add(path);
                }
            }
        }
    }
}

You should notice the Text Resource Items are stored in the HttpContext.Current cache so user controls can later use this functionality.


using System.Collections.Generic;
using System.Linq;
using System.Web;
using Sitecore.Data.Items;

namespace CMF.Context
{
    public class Current
    {
        public static List<Item> CssItems
        {
            get
            {
                if (HttpContext.Current.Items["cssItems"] == null)
                {
                    HttpContext.Current.Items.Add("cssItems", new List<Item>());
                }
                return HttpContext.Current.Items["cssItems"] as List<Item>;
            }
        }

        public static List<Item> JsItems
        {
            get
            {
                if (HttpContext.Current.Items["jsItems"] == null)
                {
                    HttpContext.Current.Items.Add("jsItems", new List<Item>());
                }
                return HttpContext.Current.Items["jsItems"] as List<Item>;
            }
        }

        public static void AddCssItem(Item item)
        {
            if (!CMF.Context.Current.CssItems.Contains<Item>(item)) { CMF.Context.Current.CssItems.Add(item); }
        }

        public static void AddJsItem(Item item)
        {
            if (!CMF.Context.Current.JsItems.Contains<Item>(item)) { CMF.Context.Current.JsItems.Add(item); }
        }

        public static void LoadCssItems(Item item)
        {
            if (item.Fields["CSS Links"] != null && item.Fields["CSS Links"].GetValue(true) != null)
            {
                string[] aItem = item.Fields["CSS Links"].GetValue(true).Split('|');
                foreach (string linkId in aItem)
                {
                    Item linkedItem = Sitecore.Context.Database.Items[linkId];
                    if (linkedItem != null)
                    {
                        CMF.Context.Current.AddCssItem(linkedItem);
                    }
                }
            }
        }

        public static void LoadJsItems(Item item)
        {
            if (item.Fields["JS Links"] != null && item.Fields["JS Links"].GetValue(true) != null)
            {
                string[] aItem = item.Fields["JS Links"].GetValue(true).Split('|');
                foreach (string linkId in aItem)
                {
                    Item linkedItem = Sitecore.Context.Database.Items[linkId];
                    if (linkedItem != null)
                    {
                        CMF.Context.Current.AddJsItem(linkedItem);
                    }
                }
            }
        }
    }
}

Your ashx for the Text Resource Template’s layout should look like this

<%@ WebHandler Language="C#" Class="TextResource" %>

using System;
using System.Web;

public class TextResource : IHttpHandler {

    bool IHttpHandler.IsReusable
    {
        get { return true; }
    }

    void IHttpHandler.ProcessRequest(HttpContext context)
    {
        Sitecore.Data.Items.Item item = Sitecore.Context.Item;
        if (item != null && item.Fields["Resource Text"] != null)
        {

            context.Response.Write(item.Fields["Resource Text"].GetValue(true));
            if (item.Fields["Mime Type"] != null && item.Fields["Mime Type"].GetValue(true) != string.Empty)
            {
                context.Response.ContentType = item.Fields["Mime Type"].GetValue(true);
            }
            context.Response.Cache.SetCacheability(System.Web.HttpCacheability.Public);
            context.Response.Cache.SetMaxAge(new TimeSpan(1, 0, 0));
        }
    }

}

Your going to need to add support for the extensions in web config.

<param desc=”Allowed extensions (comma separated)”>aspx, ashx, asmx, css, js</param>

Go into your .aspx pages code behind file and change it to inherit your new base page class instead of System.Web.UI.Page.

In the content editor create the path Sitecore/Content/Resources/CSS and Sitecore/Content/Resoureces/JS and add options for you new CSS File and JS File  templates respectively.

At this point if you configure the page and publish everything you should see the Javascript and Css being included in the header along with the Meta Tags and Title.

Notes :

Should probably change the CMF namespace and assembly to something  that goes along with  your project.

I tend to check that fields exists rather than checking to see if an item inherits a template. I may change this but that’s how it is now because I’m going to check the field anyway and didn’t want to add overhead of looping through the base templates of an item.

If there’s other important things in your base page template I’m missing I’d like to hear about them.

If you put the files on the server and build links to them that way you can use source control and the small editing box isn’t ideal for large scripts and css files. Every thing does cache properly at the browser level and that page is loading in 17ms locally.

Advertisements

About Kevin Buckley
.Net web developer with a lot of experience in CMS. Currently working at Sitecore as Solutions Engineer.

5 Responses to Building a Sitecore Base Page Template

  1. Andrew says:

    Hi Kevin – great post, this is exactly what were hoping to do to support our marketing dept. I have implemented your solution in sitecore 6.5 and everything works as expected except I have one problem with resolving the js file.
    I get 404 when the page tries to resolve

    http://localhost/sitecore/content/resources/js/sayhi.js?v=1

    But if I change the extension to ‘aspx’ it works, for example

    http://localhost/sitecore/content/resources/js/sayhi.aspx?v=1

    Any idea as to why this is happening? I changed the “Allowed Extensions” param to include js and CSS in webconfig.

  2. Andrew says:

    I got it to work by setting the addAspxExtension=”false” on the linkManager provider in web.config (which requires mapping .* -> aspnet_isapi.dll in IIS 6). I guess this allows sitecore to respond to the ‘js’ extension – without it iis would try to serve a static file from disk which of course cannot be found.

    Again thanks for the great solution!

  3. Cool, I was running on IIS 7 so I didn’t run into that. Thanks for the tip.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: