Exam Area: Content Area 3 – Website Implementation & Delivery (25%)
Reference: https://docs.developers.optimizely.com/content-management-system/docs/architecture
┌──────────────────────────────────────────────────┐
│ Presentation Layer │
│ (MVC Controllers, Razor Views, React) │
├──────────────────────────────────────────────────┤
│ Business Logic Layer │
│ (Services, ViewModels, Helpers) │
├──────────────────────────────────────────────────┤
│ Content API Layer │
│ (IContentRepository, IContentLoader) │
├──────────────────────────────────────────────────┤
│ Framework Layer │
│ (DI, Events, Caching, Globalization) │
├──────────────────────────────────────────────────┤
│ Data Layer │
│ (SQL Server, Blob Storage) │
└──────────────────────────────────────────────────┘
// Controller inherits from PageController<T>
public class ArticlePageController : PageController<ArticlePage>
{
private readonly IContentRepository _repo;
public ArticlePageController(IContentRepository repo)
{
_repo = repo;
}
public ActionResult Index(ArticlePage currentPage)
{
var model = new ArticleViewModel(currentPage);
return View(model);
}
}
app.MapContent() registers CMS routing// Register template for a content type
[TemplateDescriptor(
Inherited = true,
TemplateTypeCategory = TemplateTypeCategories.MvcController)]
public class ArticlePageController : PageController<ArticlePage> { }
// Multiple templates for the same type
[TemplateDescriptor(
Tags = new[] { "Wide", "Narrow" },
TemplateTypeCategory = TemplateTypeCategories.MvcPartialController)]
public class TeaserBlockNarrowController : BlockController<TeaserBlock> { }
// ViewModel pattern
public class ArticleViewModel : ContentViewModel<ArticlePage>
{
public ArticleViewModel(ArticlePage currentPage) : base(currentPage) { }
public string FormattedDate => CurrentPage.PublishDate?.ToString("dd MMM yyyy");
public IEnumerable<ArticlePage> RelatedArticles { get; set; }
}
// In the Controller
public ActionResult Index(ArticlePage currentPage)
{
var model = new ArticleViewModel(currentPage)
{
RelatedArticles = GetRelatedArticles(currentPage)
};
return View(model);
}
Models/
├── Pages/
│ ├── StartPage.cs
│ ├── ArticlePage.cs
│ └── ProductPage.cs
├── Blocks/
│ ├── TeaserBlock.cs
│ ├── HeroBlock.cs
│ └── AccordionBlock.cs
├── Media/
│ ├── ImageFile.cs
│ └── VideoFile.cs
└── ViewModels/
├── ArticleViewModel.cs
└── SearchViewModel.cs
Views/
├── ArticlePage/
│ └── Index.cshtml
├── Blocks/
│ ├── TeaserBlock.cshtml
│ └── HeroBlock.cshtml
└── Shared/
└── _Layout.cshtml
HTTP Request
│
▼
CMS Routing (ContentRoute)
│
▼
Template Resolver → Selects Controller/View
│
▼
Controller.Index(currentPage)
│
▼
View Model Created
│
▼
View Rendered (Razor)
│ ├── @Html.PropertyFor() → On-page editing enabled
│ └── @Html.FullRefreshPropertyFor() → Full refresh
▼
Response (HTML)
// Block controller
public class TeaserBlockController : BlockController<TeaserBlock>
{
public override ActionResult Index(TeaserBlock currentBlock)
{
return PartialView(currentBlock);
}
}
@* Views/Blocks/TeaserBlock.cshtml *@
@model TeaserBlock
<div class="teaser">
<h3>@Html.PropertyFor(m => m.Heading)</h3>
@Html.PropertyFor(m => m.Body)
</div>
Common integrations:
// Register integration
builder.Services.AddOptimizelyDataPlatform(options =>
{
options.ProjectId = "your-project-id";
});
// ServiceConfigurationContext in IConfigurableModule
public void ConfigureContainer(ServiceConfigurationContext context)
{
// Get IServiceCollection
var services = context.Services;
// Register
services.AddSingleton<IMyService, MyService>();
// Override
services.TryAddSingleton<ICacheService, RedisCacheService>();
// Decorator
services.Decorate<IContentLoader, LoggingContentLoader>();
// Named registrations
services.AddTransient<ISearchProvider, GoogleSearchProvider>(
serviceProvider => new GoogleSearchProvider("api-key"));
}
// Optimizely Content Delivery API (headless)
builder.Services.AddContentDeliveryApi();
// CMS events → Webhook
[InitializableModule]
public class WebhookModule : IInitializableModule
{
public void Initialize(InitializationEngine context)
{
var events = context.Locate.Advanced.GetInstance<IContentEvents>();
events.PublishedContent += async (sender, e) =>
{
await NotifyWebhook(e.ContentLink);
};
}
}
PageController<T> inherit from? (Controller in ASP.NET Core MVC)app.MapContent() used for? (Registers CMS content routing)[TemplateDescriptor])BlockController<T>)ContentViewModel<T>)