📋 Content Models
Content Types Code
📖 Docs

Content Types in Code - Optimizely CMS 12

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


1. Basic Content Types

Page Type:

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; }
}

2. ContentType Attribute

[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
)]

3. SitePageData (Base Class Pattern)

// 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
}

4. Block Type

[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; }
}

5. Media Content Type

// 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; }
}

6. Why Must Properties Be Virtual?

// ✅ 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.


7. Content Type Grouping

[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 { }

8. Container Pages

// 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
}

9. Refactoring Content Types

Safe Changes:

Dangerous Changes:

// 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
{ }

Review Questions

  1. Which attribute marks a class as a Content Type? ([ContentType])
  2. Why must properties be virtual? (Allows the CMS proxy class to override them to read/write from the property collection)
  3. Why is the GUID important in ContentType? (Allows renaming the class without losing data)
  4. What does [AvailableContentTypes] do? (Controls which content types can be created inside)
  5. What is the base class for pages? (PageData), for blocks? (BlockData)