Loading...

Alternate Templates

Umbraco's alternate templates allows us to provide different content for the same page based upon the type of request, whether it be AJAX, JSON or one of those pesky no-javascript users. All we need is to create an AJAX or JSON template.

The difficulty arises in how to deal with different content. You don't really want a massive switch statement in the template serving different content based on the Document Type. The solution we've used is to check whether an alternative template has been applied and, if so, check for the existence of a template that corresponds to both the original and the alternative template, eg. if the alternate template Ajax has been applied to a page that normally uses the template Homepage then does a template exist called AjaxHomepage? If so it assigns the new template.

Here's the relevant C# code:

// currentPage is of type umbraco.page and has already had the new template applied
// pageDoc has the original template
var pageDoc = new Document(currentPage.PageID);
var allTemplates = Template.GetAllAsList();

var originalTemplate = allTemplates.FirstOrDefault(
tmp => tmp.Id == pageDoc.Template);
var currentTemplate = allTemplates.FirstOrDefault(
tmp => tmp.Id == currentPage.Template);

if (originalTemplate != null && currentTemplate != null
 && currentTemplate.Id != originalTemplate.Id)
{
  string templateAlias = currentTemplate.Alias + originalTemplate.Alias;

  var newTemplate = allTemplates.FirstOrDefault(tmp => tmp.Alias == templateAlias);

  if (newTemplate != null)
  {
    MasterPageFile = template.GetMasterPageName(newTemplate.Id);
}
}

How to get UmbracoDefault's secrets

We've applied this with a custom default.aspx, with a custom class, HashBangDefault, that inherits from UmbracoDefault. Unfortunately, most of the fields in UmbracoDefault are private. The one we needed in particular was m_umbPage. As it turns out this is passed in the BeforeRequestInit event, so we hooked into that and saved it to a new field -- not ideal, but preferable to modifying the Umbraco source.

page currentPage;

public HashBangDefault() : base()
{
BeforeRequestInit += new RequestInitEventHandler(HashBangDefault_BeforeRequestInit);
}

void HashBangDefault_BeforeRequestInit(object sender, RequestInitEventArgs e)
{
currentPage = e.Page;
}

Do it once

Finally, it didn't make sense to go through all this work every time, so the results are cached and then cleared anytime a template is saved or deleted. Here's the complete code:

using System;
using System.Linq;
using System.Runtime.Caching;
using umbraco;
using umbraco.cms.businesslogic.template;
using umbraco.cms.businesslogic.web;

namespace HashBang
{
    public partial class HashBangDefault : UmbracoDefault
    {
        public HashBangDefault() : base()
        {
            BeforeRequestInit +=
 new RequestInitEventHandler(HashBangDefault_BeforeRequestInit);

            Template.AfterSave +=
 new Template.SaveEventHandler(Template_AfterSaveOrDelete);
            Template.AfterDelete +=
new Template.DeleteEventHandler(Template_AfterSaveOrDelete);
        }

        void HashBangDefault_BeforeRequestInit(object sender, RequestInitEventArgs e)
        {
            currentPage = e.Page;
        }

        void Template_AfterSaveOrDelete(Template sender, EventArgs e)
        {
            foreach (var cacheItem in MemoryCache.Default)
            {
                if (cacheItem.Key.StartsWith(cachePrefix))
                {
                    MemoryCache.Default.Remove(cacheItem.Key);
                }
            }
        }

        protected override void OnPreInit(EventArgs e)
        {
            base.OnPreInit(e);

            if (currentPage != null)
            {
                var cache = MemoryCache.Default;

                var key = string.Format(cacheFormat,
                    cachePrefix, currentPage.PageID, currentPage.Template);

                object templateId = cache.Get(key);

                if (templateId != null)
                {
                    if (templateId.GetType() == typeof(int) && (int)templateId > 0)
                    {
                        MasterPageFile = template.GetMasterPageName((int)templateId);
                    }
                }
                else
                {
                    var pageDoc = new Document(currentPage.PageID);
                    var allTemplates = Template.GetAllAsList();

                    var originalTemplate = allTemplates.FirstOrDefault(
 tmp => tmp.Id == pageDoc.Template);
                    var currentTemplate = allTemplates.FirstOrDefault(
 tmp => tmp.Id == currentPage.Template);

                    if (originalTemplate != null && currentTemplate != null
 && currentTemplate.Id != originalTemplate.Id)
                    {
                        string templateAlias = currentTemplate.Alias + originalTemplate.Alias;

                        var newTemplate = allTemplates.FirstOrDefault(
 tmp => tmp.Alias == templateAlias);

                        if (newTemplate != null)
                        {
                            MasterPageFile = template.GetMasterPageName(newTemplate.Id);
                            cache.Add(key, newTemplate.Id, DateTimeOffset.Now.AddDays(1));
                        }
                        else
                        {
                            cache.Add(key, 0, DateTimeOffset.Now.AddDays(1));
                        }
                    }
                }
            }
        }

        const string cachePrefix = "Template_";
        const string cacheFormat = "{0}{1}_{2}";
        page currentPage;
    }
}

Final thoughts

This new ASPX page stands alone and should still work with subsequent versions of Umbraco. We could see this being used for purposes other than AJAX requests -- RSS feeds anyone?

This demo is powered by

blog comments powered by Disqus