HomeGuidesAPI Reference
Submit Documentation FeedbackJoin Developer CommunityOptimizely GitHubOptimizely NuGetLog In

TemplateDescriptor and tags

This topic describes how to work with the TemplateDescriptor attribute and tags in Optimizely Content Cloud (CMS), to define which template you should select for rendering in a specific context, when using multiple templates to render the same content.

TemplateDescriptor lets you add metadata when registering templates for content types, and you can use tags to control the rendering of objects in a content area.

ūüďė

Note

The examples are based on ASP.NET Core.

TemplateDescriptor attribute

A page or a block can have multiple associated templates, for example, one for a web channel and one for a mobile device. Pages can also have a partial template used; for example, when the page is displayed inside the content area of another page.

TemplateDescriptor is an optional attribute that registers templates, and is involved when the system determines which template to use when rendering a content instance in a specific context. See Rendering and  Selecting templates for information on how templates are registered and selected. Use the attribute to add metadata such as template path, inheritance, content type model, and description.

Properties

The attribute is found in the EPiServer.Framework.DataAnnotations namespace, and some of its properties are described below.

Property nameDescriptionDefault value
PathThe path to the rendering template; only needs to be set if the folder structure does not follow the standard MVC convention.null
ModelTypeThe model for which the template applies to. The model type can be set to an interface or abstract type if the template should be registered for multiple concrete types..null
DescriptionContains a description of the template.null
InheritedWhen set to true, model types that inherit from the ModelType get the template as a supported template.false
TagsAn optional list of tags that this template is registered for.
AvailableWithoutTagSpecifies if this template is available when tags defined in Tags is not present in context.
TemplateTypeCategoryOptional value to specify the type of template, if not specified type of templates is resolved from ModelType.

Behavior

If TemplateDescriptor is:

  • not¬†present, template inheritance is true¬†by default.
  • present but without specified parameters, the default values specified above apply.
  • present with inherited set to true, the template is inherited by types assignable to ModelType. This is useful if you need a fallback template for content types without specific templates.

Examples

Concrete type

Assume we have a ArticlePage content type :

using EPiServer.Core;

namespace MyOptimizelySite.Models.Pages
{
  [ContentType]
  public class ArticlePage : PageData
  {
    public virtual string Heading { get; set; }
  }
}

The template (controller) for the ArticlePage content type, with the TemplateDescriptor attribute present. Inherited is set to false, meaning that this template will be used as the default template for the ArticlePage content type, and no inheritance will take place.

using EPiServer;
using EPiServer.Core;
using EPiServer.Framework.DataAnnotations;
using EPiServer.Web.Mvc;
using MyOptimizelySite.Models.Pages;
    
namespace MyOptimizelySite.Controllers
{
  [TemplateDescriptor(
    Inherited = false, 
    Description = "Default template to be used by Article pages")]
  public class ArticlePageController : PageController<ArticlePage>
  {
    public ActionResult Index(ArticlePage currentPage)
    {
      // Implementation of action view the page. 
      return View(currentPage);
    }
  }
}

Partial page rendering

The following example shows how to use TemplateDescriptor for defining a partial page renderer to be used when rendering a page inside a content area.

Assume we have the following SitePageData base class with a summary (MetaKeywords string) and an image:

using EPiServer.Core;

namespace MyOptimizelySite.Models.Pages
{
  public abstract class SitePageData : PageData
  {
    public virtual string MetaKeywords { get; set; }
    public virtual ContentReference Image { get; set; } 
  }
}

We have a partial page component decorated with TemplateDescriptor with Inherited=true, to render page partials for pages inheriting from SitePageData. The controller selects a view located in a folder specified by the namespace convention.

using AlloyTemplates.Models.Pages;
using EPiServer.Framework.DataAnnotations;
using EPiServer.Web.Mvc;
using Microsoft.AspNetCore.Mvc;
    
namespace MyOptimizelySite.ViewComponents
{
  [TemplateDescriptor(Inherited = true)]
  public class PagePartialComponent : PartialContentComponent<SitePageData>
  {
    protected override IViewComponentResult InvokeComponent(SitePageData currentContent)
    {
      return View("/Views/Shared/PagePartials/PagePartial.cshtml", currentContent);
    }
  }
}

The partial view has an if-else construct checking if the model has a template for the content. Any page inheriting from our SitePageData, will now be rendered displaying the page name with a link, the MetaKeywords string, and an image, when added to the content area of another page.

@using EPiServer.Core
@model SitePageData
    
<div class="block span2">
  <div class="border">
    @if (Model.HasTemplate())
    {
      <a href="@Url.ContentUrl(Model.ContentLink)">
        <h3>@Model.PageName</h3>
        <p>@Html.PropertyFor(x => x.MetaKeywords)<p>
        <img src="@Url.ContentUrl(Model.Image)" />
      </a>
    }
    else
    {
      <h3>@Model.PageName</h3>
    }
  </div>
</div>

Pages without controllers

An example of how you can handles page types that do not have their own specific controllers by specifying Inherited=true, and dynamically selecting template. See Installing a sample site for a fully working sample.

[TemplateDescriptor(Inherited = true)]
public class DefaultPageController : PageController<SitePageData>
{
  public ViewResult Index(SitePageData currentPage)
  {
    return View($"~/Views/{currentPage.GetOriginalType().Name}/Index.cshtml", currentPage);
  }
}

Using tags

You can apply tags to be used in the rendering selection. When a template is associated with a tag, then that template is used only when the calling context, such as Property or PropertyFor in a view, has a matching tag. You can also have different content areas render the same content in different ways using tags/display options. If you have active display channels for your content types, the ChannelName will act as a tag in the rendering selection.

Examples

Registering multiple templates

Assume you have a model containing a teaser block with a heading and an image as follows.

using EPiServer.Core;

namespace MyOptimizelySite.Models.Blocks
{
  [ContentType]
  public class TeaserBlock : BlockData
  {
    public virtual string Heading { get; set; }
    public virtual ContentReference Image { get; set; } 
  }
}
  • Register two templates for the teaser block to display it differently depending on the context.
  • Add two sidebar templates (left and right) for displaying the block in the sidebar area of web pages that have this.
  • Add register a template for the standard block, which is part of our content model. The blocks are displayed in a content area of the start page for our website.

See Content templates for more information regarding blocks. In this case we will register partial views without controllers, so use EPiServer.Web.Mvc.IViewTemplateModelRegistrator to register your templates. In the Business folder of your project, create a ViewTemplateModelRegistrator class inheriting from IViewTemplateModelRegistrator, and add the desired templates and tags.

The following code shows the template registration class using IViewTemplateModelRegistrator:

namespace MyOptimizelySite.Business
{
  public class ViewTemplateModelRegistrator : IViewTemplateModelRegistrator
  {
    public void Register(TemplateModelCollection viewTemplateModelRegistrator)
    {
      viewTemplateModelRegistrator.Add(typeof(TeaserBlock),
        new EPiServer.DataAbstraction.TemplateModel()
        {
          Name = "SidebarTeaserRight",
          Description = "Displays a teaser for a page.",
          Path = "~/Views/Shared/SidebarTeaserBlockRight.cshtml",
          AvailableWithoutTag = true
        },
        new EPiServer.DataAbstraction.TemplateModel()
        {
          Name = "SidebarTeaserLeft",
          Description = "Displays a teaser for a page.",
          Path = "~/Views/Shared/SidebarTeaserBlockLeft.cshtml",
          Tags = new string[] { RenderingTags.Sidebar }
        });
    
      viewTemplateModelRegistrator.Add(typeof(StandardBlock),
        new EPiServer.DataAbstraction.TemplateModel()
        {
          Name = "SidebarTeaser",
          Description = "Displays a teaser of a page.",
          Path = "~/Views/Shared/StandardBlock.cshtml",
          Tags = new string[] { RenderingTags.Sidebar }
        });
      }
  }
}

The following code shows the SidebarTeaserBlockRight partial view for the teaser block (there is an identical SidebarTeaserBlockLeft for the left one):

@model MyOptimizelySite.Models.Blocks.TeaserBlock
    
<div>
  <h2>@Html.PropertyFor(x => x.Heading)</h2>
  <img src="@Url.ContentUrl(Model.Image)" />
</div>

The following code shows the rendering view for the start page, where the blocks are displayed:

@model MyOptimizelySite.Models.Pages.StartPage
    
<div>
  @Html.PropertyFor(m => m.MainBody)
</div>
    
<div>
  @Html.PropertyFor(m => m.GeneralContentArea, new { Tag = "Sidebar" })
</div>

How tagging is used in template selection:

  • If the¬†AvailableWithoutTag¬†attribute for a template is set to¬†true,¬†the template¬†is applied regardless of whether the rendering context has a corresponding tag or not.
  • If the¬†AvailableWithoutTag¬†is set to¬†false, or does not exist, the template is not applied unless the rendering context has a corresponding tag.

In this scenario, the following happens:

  • The start page GeneralContentArea has the RenderingTags.Sidebar¬†tag, which means that only templates with this tag are applied, and without the AvailableWithoutTag or where this set to false.
  • The¬†SidebarTeaserLeft template has a matching tag and AvailableWithoutTag set, and is applied.¬†This is also valid for the template used for the Standard block.
  • The SidebarTeaserRight template has a matching tag, and AvailableWithoutTag = true. This template is applied, even if the RenderingTags.Sidebar tag would be removed from the content area.

What’s Next