Exam Area: Content Area 5 – Content Models (25%)
Reference: https://docs.developers.optimizely.com/content-management-system/docs/content-types
using EPiServer.Core;
using EPiServer.DataAbstraction;
using EPiServer.DataAnnotations;
using System.ComponentModel.DataAnnotations;
[ContentType(
DisplayName = "Article Page",
GUID = "b8fe8485-587d-4880-b485-a52430ea55de",
Description = "Page type for articles",
GroupName = "Standard")]
[ImageUrl("/icons/article.png")] // Icon in UI
public class ArticlePage : PageData
{
[CultureSpecific]
[Display(
Name = "Heading",
Description = "The article heading",
GroupName = SystemTabNames.Content,
Order = 10)]
public virtual string Heading { get; set; }
[CultureSpecific]
[Display(
Name = "Lead text",
GroupName = SystemTabNames.Content,
Order = 20)]
public virtual XhtmlString LeadText { get; set; }
[CultureSpecific]
[Display(
Name = "Main body",
GroupName = SystemTabNames.Content,
Order = 30)]
public virtual XhtmlString MainBody { get; set; }
[Display(
Name = "Published date",
GroupName = SystemTabNames.Content,
Order = 40)]
public virtual DateTime? PublishedDate { get; set; }
[Display(
Name = "Author",
GroupName = SystemTabNames.Content,
Order = 50)]
public virtual string Author { get; set; }
}
[ContentType(
// Required
GUID = "unique-guid", // Never change this!
// Optional
DisplayName = "My Page", // Name in UI
Description = "...", // Description in UI
GroupName = "Blog", // Group in the list
Order = 100, // Order in the list
// Availability
AvailableInEditMode = true, // Editor can create
)]
// Base class for all pages
public abstract class SitePageData : PageData
{
[Display(Name = "Meta keywords", GroupName = "SEO", Order = 200)]
public virtual string MetaKeywords { get; set; }
[Display(Name = "Meta description", GroupName = "SEO", Order = 210)]
[StringLength(160)]
public virtual string MetaDescription { get; set; }
[Display(Name = "Disable indexing", GroupName = "SEO", Order = 220)]
public virtual bool DisableIndexing { get; set; }
}
// Page inherits base
[ContentType(DisplayName = "Article Page", GUID = "...")]
public class ArticlePage : SitePageData
{
// Article-specific properties
}
[ContentType(
DisplayName = "Hero Block",
GUID = "c8bc5025-abcd-1234-efgh-000000000002")]
public class HeroBlock : BlockData
{
[CultureSpecific]
[Display(Name = "Heading", Order = 1, GroupName = SystemTabNames.Content)]
[Required]
public virtual string Heading { get; set; }
[CultureSpecific]
[Display(Name = "Sub heading", Order = 2)]
public virtual string SubHeading { get; set; }
[UIHint(UIHint.Image)]
[Display(Name = "Background image", Order = 3)]
public virtual ContentReference BackgroundImage { get; set; }
[Display(Name = "CTA Text", Order = 4)]
public virtual string CTAText { get; set; }
[Display(Name = "CTA Link", Order = 5)]
public virtual Url CTALink { get; set; }
}
// Image type
[ContentType(DisplayName = "Image File", GUID = "...")]
[MediaDescriptor(ExtensionString = "jpg,jpeg,png,gif,webp,svg")]
public class ImageFile : ImageData
{
[Display(Name = "Alt text", Order = 1)]
public virtual string AltText { get; set; }
[Display(Name = "Caption", Order = 2)]
public virtual string Caption { get; set; }
}
// Video type
[ContentType(DisplayName = "Video File", GUID = "...")]
[MediaDescriptor(ExtensionString = "mp4,webm,ogg")]
public class VideoFile : VideoData
{
[Display(Name = "Description", Order = 1)]
public virtual string Description { get; set; }
}
// ✅ Correct - virtual
public virtual string Heading { get; set; }
// ❌ Wrong - non-virtual will not work correctly
public string Heading { get; set; }
Reason: The CMS creates a proxy class that inherits from the content type. The proxy overrides properties to read/write from the internal property collection. If the property is not virtual, the proxy cannot override it.
[ContentType(DisplayName = "Article Page", GUID = "...", GroupName = "Articles")]
public class ArticlePage : SitePageData { }
[ContentType(DisplayName = "News Page", GUID = "...", GroupName = "Articles")]
public class NewsPage : SitePageData { }
[ContentType(DisplayName = "Product Page", GUID = "...", GroupName = "Products")]
public class ProductPage : SitePageData { }
// Container Page - no template, used for organization only
[ContentType(
DisplayName = "Articles Container",
GUID = "...",
AvailableInEditMode = true)]
[AvailableContentTypes(
Availability = Availability.Specific,
Include = new[] { typeof(ArticlePage), typeof(NewsPage) })]
public class ArticlesContainerPage : PageData
{
// No specific properties
}
// Rename class but keep GUID
// OLD: class BlogPost : PageData
[ContentType(DisplayName = "Blog Post", GUID = "aaa-bbb-ccc")] // Keep GUID
public class ArticlePage : PageData // New name
{ }
[ContentType])virtual? (Allows the CMS proxy class to override them to read/write from the property collection)[AvailableContentTypes] do? (Controls which content types can be created inside)PageData), for blocks? (BlockData)