Editorial template control in Episerver? Also, what's `IDynamicTemplateContent`?

  • Page Owner: Not Set
  • Last Reviewed: 2021-05-21

How do I let editors in Episerver select the which template a block renders with? I want something like a dynamic template.


Answer

We use a system called IDynamicTemplateContent. It will provide editors with a list of possible templates (through a dropdown property that you create that lists all the registered views available).

Here's what you do:

  1. Add the IDynamicTemplateContent interface to your block model.
  2. Add a property for the template selection. For example:
        [CultureSpecific]
        [Display(Name = "Style", Order = 180, Description = "Optional multiple visual variations, adjusted by adding a CSS class to the object container.", GroupName = CmsGroupNames.Content)]
        [UIHint("TemplateModel")]
        public virtual string CTABlockStyle { get; set; }

  1. Implement IDynamicTemplateContent like so:
        public void SetDynamicTemplate(TemplateResolverEventArgs args)
        {
            if (!this.CTABlockStyle.HasValue() || SelectedTemplateIsPreviewController(args.SelectedTemplate))
            {
                return;
            }

            TemplateModel selectedTemplate = args.SupportedTemplates
                .FirstOrDefault(tmpl => tmpl.TemplateType.FullName.Equals(this.CTABlockStyle, StringComparison.InvariantCultureIgnoreCase));

            if (selectedTemplate != null)
            {
                args.SelectedTemplate = selectedTemplate;
            }
        }

        private bool SelectedTemplateIsPreviewController(TemplateModel selectedTemplate) =>
            selectedTemplate != null && selectedTemplate.Tags != null && selectedTemplate.Tags.Contains(RenderingTags.Preview);

You will need some supporting classes in your project.

The editor descriptor:

    [EditorDescriptorRegistration(TargetType = typeof(string), UIHint = "TemplateModel")]
    public class TemplateModelEditorDescriptor : EditorDescriptor
    {
        private readonly ITemplateRepository templateModelRepository;

        public TemplateModelEditorDescriptor()
            : this(ServiceLocator.Current.GetInstance<ITemplateRepository>())
        {
        }

        public TemplateModelEditorDescriptor(ITemplateRepository templateModelRepository)
        {
            this.templateModelRepository = templateModelRepository ?? throw new ArgumentNullException("templateModelRepository");
            ClientEditingClass = "epi-cms/contentediting/editors/SelectionEditor";
        }

        public override void ModifyMetadata(ExtendedMetadata metadata, IEnumerable<Attribute> attributes)
        {
            base.ModifyMetadata(metadata, attributes);

            metadata.CustomEditorSettings["uiType"] = metadata.ClientEditingClass;
            metadata.CustomEditorSettings["uiWrapperType"] = UiWrapperType.Floating;
            var contentType = metadata.FindOwnerContent()?.GetOriginalType();
            TemplateTypeCategories[] validTemplateTypeCategories;

            if (typeof(IRoutable).IsAssignableFrom(contentType))
            {
                validTemplateTypeCategories = new[] {
                    TemplateTypeCategories.MvcController,
                    TemplateTypeCategories.WebFormsPage,
                    TemplateTypeCategories.WebForms,
                    TemplateTypeCategories.MvcView,
                    TemplateTypeCategories.Page
                };
            }
            else
            {
                validTemplateTypeCategories = new[] {
                    TemplateTypeCategories.MvcPartialController,
                    TemplateTypeCategories.MvcPartialView,
                    TemplateTypeCategories.WebFormsPartial,
                    TemplateTypeCategories.ServerControl,
                    TemplateTypeCategories.UserControl
                };
            }
            var templates = this.templateModelRepository.List(contentType);

            var templateModels = templateModelRepository
                .List(contentType)
                .Where(x => Array.IndexOf(validTemplateTypeCategories, x.TemplateTypeCategory) > -1);

            metadata.EditorConfiguration["selections"] = templateModels
                .Select(x => new SelectItem
                {
                    Text = this.GetDisplayName(x.TemplateType, x.Name),
                    Value = x.TemplateType.FullName // Value stored in the database
                }).OrderBy(x => x.Text);
        }

        private string GetDisplayName(Type contentType, string name)
        {
            string displayName = name.Coalesce(contentType.Name);
            if (displayName.StartsWith("Default"))
            {
                displayName = "Default";
            }
            else if(displayName.Contains("-"))
            {
                displayName = displayName.Substring(displayName.LastIndexOf("-") + 1);
            }
            return displayName;
        }
    }

The initialization module:


    [ModuleDependency(typeof(EPiServer.Web.InitializationModule))]
    public class DynamicTemplateInitialization : IInitializableModule
    {
        public void Initialize(InitializationEngine context)
        {
            context.Locate.TemplateResolver().TemplateResolved += OnTemplateResolved;
        }

        public void Uninitialize(InitializationEngine context)
        {
            context.Locate.TemplateResolver().TemplateResolved -= OnTemplateResolved;
        }

        private static void OnTemplateResolved(object sender, TemplateResolverEventArgs args)
        {
            if (args.ItemToRender is IDynamicTemplateContent content)
            {
                content.SetDynamicTemplate(args);
            }
        }
    }

And of course the IDynamicTemplateContent interface:

    public interface IDynamicTemplateContent
    {
        void SetDynamicTemplate(TemplateResolverEventArgs args);
    }

All of this code was taken from the RW Baird project. For now, check there for the most up-to-date version of this code.