Skip to main content

Optimizely

The importance of “Branch Templates” within Episerver

Branch Template Hero Image

In modern day CMS systems we rarely create pages that have fixed layouts with zero flexibility.  Often times we give the marketing team the power to define experiences by placing a number of configurable components on the page in the layout that they see fit.  This is empowering, but can also be intimidating if you’re constantly starting with a blank slate.  Branch Templates are a concept familiar in other CMS systems that allow developers to define page types that come with pre-existing components whenever a new page is created.  This concept is also available to Episerver if you know where to look.

The Problem & Solution

When creating pages in Episerver, they would typically come with no pre-defined blocks.  When building pages, this can lead to difficulty as you need to define each and every block that the page requires.  Of course, as developers we can hardcode a few elements such as heroes onto the page itself, but this leads to inflexibility and puts you down the path of a very static look and feel.  Ontop of this, hardcoded page elements lose the flexibility of Episerver’s block paradigm.

Empty Epi Page

 

To work around this, we can utilize Episerver’s event system to define a number of blocks that the page should be created with by default.  This has the benefit of allowing for a much more streamlined content population experience, while still maintaining the flexibility and power of Episerver’s block system.  This looks like a much more convenient page to populate content on, right?

Populated Epi Page

 

A bit of code

To pull this off, first we must define and tap into the Created event.  To do this, I create an Initialization Module like so:

[InitializableModule]
[ModuleDependency(typeof(EPiServer.Web.InitializationModule))]
public class EventsInitialization : IInitializableModule
{
    public void Initialize(InitializationEngine context)
    {
        var events = context.Locate.ContentEvents();
        events.CreatedContent += CreatedContentEvent;
        events.CreatingContent += CreatingContentEvent;
    }

    public void Uninitialize(InitializationEngine context)
    {
        var events = context.Locate.ContentEvents();
        events.CreatingContent -= CreatingContentEvent;
        events.CreatedContent -= CreatedContentEvent;
    }

    private void CreatingContentEvent(object sender, ContentEventArgs e)
    {
        if (e.Content is ICreateEvent page)
        {
            page.OnCreating(sender, e);   
        }
    }

    private void CreatedContentEvent(object sender, ContentEventArgs e)
    {
        if (e.Content is ICreateEvent page)
        {
            page.OnCreated(sender, e);
        }
    }
}

public interface ICreateEvent
{
    void OnCreated(object sender, ContentEventArgs e);

    void OnCreating(object sender, ContentEventArgs e);
}

Now, any time an item is created, I check if it implements ICreateEvent.  If the item does, I call the corresponding OnCreated and OnCreating events.  This is a pretty slick way of handling Episerver events that Daved Artemik shared with me earlier this year.  You could put the creation logic in this initialization module, but I don’t find that to be a very SOLID approach.

After that is set up, we simply need to have our page type implement ICreateEvent and implement the methods accordingly.  In general, your page should have one or more content areas which you are going to be placing blocks into programatically.  Here’s a simplified version of my page:

[ContentType(
    GroupName = "General Pages",
    DisplayName = "Standard Content Page",
    GUID = "28f6892f-f86d-4154-89d5-dfeaef7cae2c",
    Description = "Standard content pages come with a Hero and Content Spot")]
public class StandardContentPage : PageData, ICreateEvent
{
    [Display(
        GroupName = SystemTabNames.Content,
        Name = "Main Content Area",
        Order = 320)]
    // in my case, IStripeBlock and IPageContentBlock are interface types that the SCORE Block Library define and implement
    // this allows us to create new types of blocks which should be available to be inserted everywhere, without defining
    // the AllowedTypes in all locations.  IStripeBlock is for edge to edge content, IPageContentBlock is for container
    // bound elements in CSS
    [AllowedTypes(typeof(IStripeBlock), typeof(IPageContentBlock))]
    public virtual ContentArea MainContentArea { get; set; }

    public void OnCreated(object sender, ContentEventArgs e)
    {
        // dependencies
        var contentRepository = ServiceLocator.Current.GetInstance<IContentRepository>();
        var contentAssetHelper = ServiceLocator.Current.GetInstance<ContentAssetHelper>();
        var blockService = ServiceLocator.Current.GetInstance<IBlockService>();

        // folder where all blocks should be created
        ContentAssetFolder folder = contentAssetHelper.GetOrCreateAssetFolder(ContentLink);

        // create hero and content spot
        var hero = blockService.CreateBlock<HeroBlock>(folder.ContentLink);
        var contentSpot = blockService.CreateBlock<ContentSpotBlock>(folder.ContentLink);

        // add blocks to content area
        var clone = CreateWritableClone() as StandardContentPage;
        if (clone.MainContentArea == null)
            clone.MainContentArea = new ContentArea();
        clone.MainContentArea.Items.Add(new ContentAreaItem { ContentLink = hero.ContentLink });
        clone.MainContentArea.Items.Add(new ContentAreaItem { ContentLink = contentSpot.ContentLink });

        // save page back
        contentRepository.Save(clone, SaveAction.Default, AccessLevel.NoAccess);
    }

    public void OnCreating(object sender, ContentEventArgs e)
    {
    }
}

And IBlockService is a simple helper service to remove some boilerplate code:

public class BlockService : IBlockService
{
    private readonly IContentRepository _contentRepository;
    private readonly IContentTypeRepository _contentTypeRepository;

    public BlockService(IContentRepository contentRepository, IContentTypeRepository contentTypeRepository)
    {
        _contentRepository = contentRepository;
        _contentTypeRepository = contentTypeRepository;
    }

    public IContent CreateBlock<T>(ContentReference parentFolder)
    {
        var type = _contentTypeRepository.Load<T>();
        var block = _contentRepository.GetDefault<IContent>(parentFolder, type.ID);
        block.Name = type.DisplayName;
        _contentRepository.Save(block, SaveAction.Publish, AccessLevel.NoAccess);
        return block;
    }
}

Now, any time we create pages of this type we receive pre-defined blocks.  Most importantly, the hero isn’t hardcoded to the page so we gain the ability to personalize it per visitor group.  This is extremely powerful, and I encourage you to take this approach for all page types within your system.  Your content authors will thank you!

Tags

Thoughts on “The importance of “Branch Templates” within Episerver”

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Dylan McCurry, Solutions Architect

I am a certified Sitecore developer, code monkey, and general nerd. I hopped into the .NET space 10 years ago to work on enterprise-class applications and never looked back. I love building things—everything from from Legos to software that solves real problems. Did I mention I love video games?

More from this Author

Categories
Follow Us
TwitterLinkedinFacebookYoutubeInstagram