Hide menu Last updated: Jun 30 2016

TemplateDescriptor and tags

This topic describes how to work with the TemplateDescriptor attribute and tags in Episerver, to define which template should be selected for rendering in a specific context, when using multiple templates to render the same content. TemplateDescriptor lets you add meta data when registering templates for content types, and tags can be used for example to control the rendering of objects in a content area.

Note: The examples here are based on MVC. Refer to previous versions of this topic for Web Form-based examples.

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 you can use to register templates, and will be involved when the system decides which template to use when rendering a content instance in a specific context. See Rendering and  Selecting templates for more information on how templates are registered and selected. Use the attribute to add meta data such as template path, inheritance, content type model, and description. You can also define a default template to be used for all content types.  

Properties

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

Property name Description Deafult value
Path The path to the rendering template; only needs to be set if the folder structure does not follow the namespace structure. A namespace convention searches for the file in the path according to the namespace. null
ModelType The model for the content type. This can be set for untyped templates which derives from EPiServer.TemplatePage to set the model type, which is the type of the CurrentPage property. null
Default Defines the template as the default template for the model type. false
Description Contains a description of the template. null
Inherited When set to true, model types that inherit from the ModelType get the template as a supported template. false

Behavior

  • If TemplateDescriptor is not present, template inheritance will be true by default.
  • If TemplateDescriptor is present but without specified parameters, the default values specified above will apply.
  • If TemplateDescriptor is present with inherited set to true, the template will be inherited by all page types based on the associated page type. This is useful if you need a fallback template for content types without specific templates. 

Examples

Default template

The template (controller) for an Article page type, with the TemplateDescriptor attribute present. Inherited=false, meaning that this template will be used as the default template for the ArticlePage page type, and no inheritance will take place.

using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using EPiServer;
using EPiServer.Core;
using EPiServer.Framework.DataAnnotations;
using EPiServer.Web.Mvc;
using MyEpiserverSite.Models.Pages;
using System.Web.Security;

namespace MyEpiserverSite.Controllers
{
    [TemplateDescriptor(
        Inherited = false, 
        Default = true, 
        Description = "Default template to be used by Article pages")]
    
    public class ArticlePageController : PageControllerBase<ArticlePage>
    {

        public ActionResult Index(ArticlePage currentPage)
        {
            // Implementation of action view the page. 

            return View(currentPage);
        }
    }
}

Partial page rendering

The example below illustrates 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:

namespace MyEpiserverSite.Models.Pages
{
    public abstract class SitePageData : EPiServer.Core.PageData
    {
        [Display(GroupName = "SEO", Order = 10, Name = "Summary", Description = "Add a short content summary.")]
        public virtual String MetaKeywords { get; set; }

        [Display(
                   Name = "Image", Description = "Add an image representing the content (optional)",
                   GroupName = "SEO",
                   Order = 20)]
        public virtual ContentReference Image { get; set; } 
    }
}

We have a partial page controller 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.

namespace MyEpiserverSite.Controllers
{
    
    [TemplateDescriptor(Inherited=true)]
    public class PagePartialController : PartialContentController<SitePageData>
    {
        //
        // GET: /PagePartial/
        public override ActionResult Index(SitePageData currentContent)
        {
            return PartialView("/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 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 MyEpiserverSite.Models.Pages.SitePageData

<div class="block span2">
    <div class="border">
        @if (Model.HasTemplate())
        {
            <a href="@Url.PageUrl(Model.LinkURL)">
                <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 the Episerver CMS (Alloy) sample site for a fully working sample.

    [TemplateDescriptor(Inherited = true)]
    public class DefaultPageController : PageControllerBase<SitePageData>
    {
        public ViewResult Index(SitePageData currentPage)
        {
            var model = CreateModel(currentPage);
            return View(string.Format("~/Views/{0}/Index.cshtml", currentPage.GetOriginalType().Name), model);
        }

        /// <summary>
        /// Creates a PageViewModel where the type parameter is the type of the page.
        /// </summary>
        /// <remarks>
        /// Used to create models of a specific type without the calling method having to know that type.
        /// </remarks>
        private static IPageViewModel<SitePageData> CreateModel(SitePageData page)
        {
            var type = typeof(PageViewModel<>).MakeGenericType(page.GetOriginalType());
            return Activator.CreateInstance(type, page) as IPageViewModel<SitePageData>;
        }
    }

Using tags

You can apply tags to be used in the rendering selection. When a template is associated with a tag, then that template will only be used when the calling context, for example 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. 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 we have a model containing a Teaser block with a heading and an image as below.

namespace MyEpiserverSite.Models.Blocks
{
    [ContentType]
    public class TeaserBlock : BlockData
    {
                public virtual String Heading { get; set; }
                public virtual ContentReference Image { get; set; } 
    }
}

We want to register two templates for the Teaser block to display it differently depending on the context. We will add two sidebar templates (left and right) for displaying the block in the sidebar area of web pages that have this. We also want to add register a template for the Standard block, which is part of our content model. The blocks will be displayed in a content area of the start page for our website.

For performance reasons, it is recommended to use partial views directly, and not controllers, for block types. See Block types and templates. As we have partial views without controllers here, we will use EPiServer.Web.Mvc.IViewTemplateModelRegistrator, to register our templates. In the Business folder of our project, we create a ViewTemplateModelRegistrator class inheriting from IViewTemplateModelRegistrator, and add the desired templates and tags.

The template registration class using IViewTemplateModelRegistrator:

namespace MyEpiserverSite.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 SidebarTeaserBlockRight partial view for the Teaser Block (there is an identical SidebarTeaserBlockLeft for the left one):

@model MyEpiserverSite.Models.Blocks.TeaserBlock

<div>

    <h2>@Html.PropertyFor(x => x.Heading)</h2>
    
    <img src="@Url.ContentUrl(Model.Image)" />


</div>

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

@model MyEpiserverSite.Models.Pages.StartPage

<div>
  @Html.PropertyFor(m => m.MainBody)
</div>

<div>

    @Html.PropertyFor(m => m.GeneralContentArea, new { Tag = EPiServer.Framework.Web.RenderingTags.Sidebar })

</div>

 How tagging is used in template selection:

  • If the AvailableWithoutTag attribute for a template is set to true, it will be 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 will not be 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, and without the AvailableWithoutTag or where this set to false, will be applied. 
  • The SidebarTeaserLeft template has a matching tag and AvailableWithoutTag set, and will be 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 will be applied, even if the RenderingTags.Sidebar tag would be removed from the content area.

Related topics

Comments