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.
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?
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!
Great tip! Thanks!