Skip to main content

Outline

At a glance:
  • Templates are MVC renderers registered for content (controller and view, or another renderer)
  • Hierarchy is how the runtime chooses among multiple templates, not the content tree
  • The usual decision points are context (full page vs. partial), type match, then tags/channels
  • Inheritance keeps content models and controllers reusable and consistent
  • DisplayTemplates are the workhorse for block and property rendering, especially via @Html.PropertyFor

Overview

This article gives you a mental model for how Optimizely CMS 12 (ASP.NET Core) resolves which controller and view renders a content item, and the conventions that make the setup predictable for the next developer - including Future You.

Template Selection Hierarchy

You do not need to memorize an internal algorithm. Use this order to predict outcomes and debug quickly.

  • 1. Start with the rendering context. Ask: are we rendering a full page request, or rendering content inside another view (for example, inside a ContentArea)? Context narrows candidate templates immediately.
  • 2. Match by content type. Optimizely looks for templates that can render the content type being requested. If your models use inheritance, you can reuse patterns without duplication.
  • 3. Refine with tags and TemplateDescriptor metadata. If more than one template can render the same type, tags let you steer which one wins - for example, full page vs. teaser, or different placements.
  • 4. Fall back to general templates. If a specialized template is missing, the system can fall back to a more general one. Make fallbacks intentional and consistent, not accidental.

Debug tip: If something renders unexpectedly, assume you have either (1) a different context than you think, or (2) multiple templates where the wrong one is winning due to tags or fallbacks. Those two causes cover the vast majority of template resolution issues.

Common Rendering Contexts

Select a context to expand and read the details.

Full page request

A URL routes to a page and the runtime renders it as a complete response. The page controller's Index action is invoked and returns the full page view.

Partial rendering

A page renders blocks inside a ContentArea or another container. Each block is resolved independently through its own DisplayTemplate or ViewComponent.

Display channel variation

Different templates may be chosen for different device or channel scenarios when display channels are configured. The same content type can render completely differently for web vs. mobile vs. email delivery.

Content Model Inheritance Conventions

A common approach is a base page type for shared fields (metadata, SEO, layout settings), then derived page types for specific page behavior. This avoids property duplication across content types.

C#
public abstract class SitePageData : PageData { public virtual string Heading { get; set; } } public class ArticlePage : SitePageData { public virtual XhtmlString MainBody { get; set; } }

Convention to adopt: Put shared page properties in one base type, then keep specialized page types small and focused. Resist the urge to add one-off properties to the base.

What to Put in a Base Page Type

Select a category to expand and read the details.

Shared heading and title conventions

Page naming and heading behavior should be consistent across the site. A shared Heading property on the base type ensures every page type follows the same convention without repetition.

SEO fields

Common meta title and meta description fields should live on the base type so every page type automatically exposes them to editors - no manual addition required for each derived type.

Layout settings

Theme flags, navigation options, and layout switches that should be available site-wide belong on the base type. This prevents derived page types from needing to redeclare structural settings.

Shared ownership and classification fields

Fields like content owner, category, or taxonomy classification that are used across many page types should be defined once in the base rather than duplicated - especially important when these fields drive search indexing or personalization.

MVC Conventions Developers Should Follow

Optimizely CMS 12 builds on standard ASP.NET Core MVC. Treat templates like normal MVC: controllers return views, views use view models, and shared UI moves into partial views.

Page Controller Pattern

Use one page controller per page type when it improves clarity. Use a base controller when many page types share logic - for example, building common layout data. Build view models in the controller action, not in the view.

C#
public class ArticlePageController : PageController<ArticlePage> { public IActionResult Index(ArticlePage currentPage) { var vm = new ArticlePageViewModel(currentPage); return View(vm); } }

Views Should Be Easy to Find

Follow standard MVC placement conventions so view discovery stays predictable. Avoid project-specific folder structures that force full-text search as the primary navigation strategy.

Use Partial Views for Reusable UI

Navigation, cards, teasers, and small components should typically be partial views. This reduces duplication and keeps UI consistent across templates.

A Simple Folder Convention

Select to expand and see the recommended structure.

Recommended folder structure
  • /Controllers for page controllers
  • /Views for page views
  • /Views/Shared for shared layouts and partials
  • /Views/Shared/DisplayTemplates for block and property templates

Use any convention your team prefers, but keep it consistent and document it - a short README in the solution beats tribal knowledge.

DisplayTemplates Conventions

When you render properties like ContentArea using @Html.PropertyFor(...), Optimizely resolves block and property rendering via DisplayTemplates.

HTML
@Html.PropertyFor(x => x.MainContentArea)

Two conventions to follow consistently:

  • Store block and property display templates under ~/Views/Shared/DisplayTemplates.
  • Name the template file after the type it renders - for example, ContactBlock.cshtml.
Fast troubleshooting checklist:
  1. Is the block type rendered via a DisplayTemplate with the expected filename and location?
  2. Is a different template winning due to a tag or fallback?
  3. Is the block being rendered in a different context than you expected?

TemplateDescriptor and Tags Conventions

Use tags and TemplateDescriptor metadata only when you truly need multiple templates for the same content type. This is common for full page vs. teaser rendering, or a block that needs different layouts in different placements.

Tag Usage Guidance

Select a topic to expand and read the details.

When tags are worth it
  • Teaser vs. full view: A compact card in a listing vs. the complete standalone page view of the same content type.
  • Placement-based rendering: The same block rendered differently in a header vs. the main content area.
  • Channel-based rendering: A different template selected for a specific configured display channel.
A safe minimum approach
  • Start with one template per content type. Keep it simple until you have a clear scenario that requires a second rendering option.
  • Add a second template only when the scenario has a name. "Teaser" is a scenario. "Different" is not.
  • Keep tag names simple and consistent. Define a set of allowed tags and document it - undocumented tag proliferation becomes a debugging hazard.

Developer Checklist

Use this ABCD map to keep rendering predictable across a growing project.

  • Align models and templates. Use inheritance for shared properties. Avoid copy/paste across page types.
  • Be consistent with MVC structure. Controllers and views should be locatable using standard MVC conventions without needing to search the codebase.
  • Control partial rendering intentionally. Use Views/Shared/DisplayTemplates for block and property templates. Add tags only when you have a clearly named scenario that requires a second variant.
  • Document your conventions. A short README in your solution beats tribal knowledge every time.

Quick Practice (Self-Check)

Practice Scenarios

Select a scenario to expand and see the answer.

ContentArea rendering debug: a block layout looks wrong

You render a ContentArea with @Html.PropertyFor(m => m.MainContentArea). A block layout looks wrong. Where do you look first?

  • Check DisplayTemplates - correct filename and location under Views/Shared/DisplayTemplates?
  • Check whether a tag-based template is winning unexpectedly.
  • Confirm you are in the context you think you are (full page vs. partial rendering inside a container).
Shared metadata fields across two page types

Your ArticlePage and LandingPage share metadata fields. What should you refactor?

  • Create or expand a base page type and move the shared properties there.
  • Consider a base controller if shared rendering behavior is also duplicated across the two page controllers.

Conclusion

Template hierarchies in CMS 12 are about predictable selection: understand context, keep type matching straightforward, use inheritance for reuse, rely on DisplayTemplates for block and property rendering, and introduce tags only when you have a clearly named scenario. If your structure is consistent, debugging becomes quick and boring - which is the best kind of debugging.