📋 Content Models
Rendering Adaptive
📖 Docs

Rendering and Adaptive Websites - Optimizely CMS 12

Exam Area: Content Area 5 – Content Models (25%)
Reference: https://docs.developers.optimizely.com/content-management-system/docs/rendering


1. Rendering Pipeline

Request URL
    │
    ▼
Content Routing (MapContent)
    │
    ▼
Template Resolver → Find Controller/View
    │
    ▼
Controller.Index(currentPage) → Model
    │
    ▼
View Rendering (Razor)
    │
    ▼
HTML Response

2. Page Controller

using EPiServer.Web.Mvc;

public class ArticlePageController : PageController<ArticlePage>
{
    private readonly IContentLoader _contentLoader;

    public ArticlePageController(IContentLoader contentLoader)
    {
        _contentLoader = contentLoader;
    }

    public ActionResult Index(ArticlePage currentPage)
    {
        // currentPage is the current page (injected by CMS)
        var model = new ArticleViewModel(currentPage);
        return View(model);
    }
}

3. Block Controller (ViewComponent)

// Block Controller
public class TeaserBlockController : BlockController<TeaserBlock>
{
    public override ActionResult Index(TeaserBlock currentBlock)
    {
        // currentBlock is the block instance
        return PartialView(currentBlock);
    }
}

4. View Models

// ContentViewModel base class
public class ArticleViewModel : ContentViewModel<ArticlePage>
{
    public ArticleViewModel(ArticlePage page) : base(page)
    {
        // CurrentPage is accessible
    }

    // Computed properties
    public string FormattedDate => 
        CurrentPage.PublishedDate?.ToString("dd MMMM yyyy") ?? string.Empty;
    
    public bool HasImage => CurrentPage.Image != null;
}

// Used in controller
public ActionResult Index(ArticlePage currentPage)
{
    var model = new ArticleViewModel(currentPage);
    // Add data from repository
    model.RelatedArticles = GetRelated(currentPage);
    return View(model);
}

5. Razor Views

@* Views/ArticlePage/Index.cshtml *@
@model ArticleViewModel

<article class="article-page">
    <h1>@Html.PropertyFor(m => m.CurrentPage.Heading)</h1>
    
    @if (Model.HasImage)
    {
        <img src="@Url.ContentUrl(Model.CurrentPage.Image)" 
             alt="@Model.CurrentPage.Heading" />
    }
    
    <div class="article-body">
        @Html.PropertyFor(m => m.CurrentPage.MainBody)
    </div>
    
    @if (Model.RelatedArticles.Any())
    {
        <div class="related">
            <h2>Related Articles</h2>
            @foreach (var article in Model.RelatedArticles)
            {
                <a href="@Url.ContentUrl(article.ContentLink)">@article.Heading</a>
            }
        </div>
    }
</article>

6. PropertyFor and Tag Helpers

@* Html Helper (traditional) *@
@Html.PropertyFor(m => m.CurrentPage.Heading)

@* Tag Helper (CMS 12 recommended) *@
<epi-property name="Heading" model="@Model.CurrentPage"></epi-property>

@* For ContentArea *@
@Html.PropertyFor(m => m.CurrentPage.MainContentArea)

7. TemplateDescriptor

// Register multiple templates for the same type
[TemplateDescriptor(
    Inherited = true,                    // Applies to subclasses
    Tags = new[] { "Wide" },             // Tag for specific rendering
    TemplateTypeCategory = TemplateTypeCategories.MvcController)]
public class ArticlePageWideController : PageController<ArticlePage>
{
    public ActionResult Index(ArticlePage currentPage)
    {
        return View("Wide", currentPage);
    }
}

8. Display Options

// Display Options for blocks in ContentArea
services.Configure<DisplayOptions>(options =>
{
    options
        .Add("full", "Full Width", "u-full", string.Empty, "epi-icon__layout--full")
        .Add("half", "Half Width", "u-half", string.Empty, "epi-icon__layout--two-columns")
        .Add("one-third", "One Third", "u-one-third", string.Empty, "epi-icon__layout--three-columns");
});
@* In View, render with display option *@
@Html.PropertyFor(m => m.CurrentPage.MainContentArea, 
    new { CssClass = "content-area" })

9. Adaptive Websites / Display Channels

// Display channels for responsive/adaptive rendering
[DisplayChannel(displayName: "Mobile", 
    channelName: "mobile",
    isPrimary: false)]
public class MobileDisplayChannel : DisplayChannel
{
    public override string ChannelName => "mobile";
    public override bool IsActive(HttpContextBase context)
    {
        var userAgent = context.Request.UserAgent?.ToLower() ?? string.Empty;
        return userAgent.Contains("mobile") || userAgent.Contains("android");
    }
}

10. Client Resources

// Register client resources (CSS, JS)
[InitializableModule]
public class ClientResourcesInit : IInitializableModule
{
    public void Initialize(InitializationEngine context)
    {
        var resourceManager = context.Locate.Advanced
            .GetInstance<IRequiredClientResourceList>();
        
        resourceManager.RequireScript("~/Static/js/site.js")
                       .AtFoot();
        
        resourceManager.RequireStyle("~/Static/css/site.css");
    }
}
@* In View *@
@Html.RequiredClientResources("head")  @* CSS *@
@Html.RequiredClientResources("foot")  @* JS *@

11. Partial Views for Blocks

// No controller needed for simple rendering
// Views/Shared/Blocks/_TeaserBlock.cshtml - found automatically
@* Views/Blocks/TeaserBlock.cshtml *@
@model TeaserBlock

<div class="teaser">
    @Html.PropertyFor(m => m.Heading)
    @Html.PropertyFor(m => m.Body)
</div>

Review Questions

  1. What is the method name in PageController to handle a request? (Index(T currentPage))
  2. What does @Html.PropertyFor() do in Edit Mode? (Enables on-page editing)
  3. What is [TemplateDescriptor(Tags = new[] { "Wide" })] used for? (Specifies a template for a specific tag/context)
  4. What are Display Channels used for? (Render different content for mobile/desktop)
  5. What does a block controller inherit from? (BlockController<T>)