Exam Area: Content Area 5 – Content Models (25%)
Reference: https://docs.developers.optimizely.com/content-management-system/docs/rendering
Request URL
│
▼
Content Routing (MapContent)
│
▼
Template Resolver → Find Controller/View
│
▼
Controller.Index(currentPage) → Model
│
▼
View Rendering (Razor)
│
▼
HTML Response
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);
}
}
// Block Controller
public class TeaserBlockController : BlockController<TeaserBlock>
{
public override ActionResult Index(TeaserBlock currentBlock)
{
// currentBlock is the block instance
return PartialView(currentBlock);
}
}
// 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);
}
@* 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>
@* 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)
// 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);
}
}
// 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" })
// 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");
}
}
// 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 *@
// 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>
Index(T currentPage))@Html.PropertyFor() do in Edit Mode? (Enables on-page editing)[TemplateDescriptor(Tags = new[] { "Wide" })] used for? (Specifies a template for a specific tag/context)BlockController<T>)