Take the community feedback survey now.

Anders Hattestad
Mar 18, 2014
  5934
(0 votes)

EPiServer 7 and MVC Views using Tags

The pattern I use for utilize the MVC framework and EPiServer blocks is based on inheritance and the use of tags. I have one Default block controller that handles all the blocks.

If a BlockTypeA that inherits from BlockData with tag=”Col4” if will look for

  • /Views/Partials/Col4/BlockTypeA.cshtml
  • /Views/Partials/Col4/BlockData.cshtml
  • /Views/Partials/Default/BlockTypeA.cshtml
  • /Views/Partials/Default/BlockData.cshtml

Then it will pick the first view that exists. This enables us to serve different result depending on the tag. However, it is optional so one could only add the view in the default tag folder. One could also make a BlockTypeB that inherits from BlockTypeA and only change the default values, and then we do not need to add a new view.

[TemplateDescriptor(Inherited = true)]
public class DefaultBlockController : BlockController<BlockData>
{
    public override ActionResult Index(BlockData currentBlock)
    {
        var tag = ControllerContext.ParentActionViewContext.ViewData["tag"] as string ?? "Default";
        ViewData["Tag"] = tag;
        var path = FindViewer.Current.FindPartialTemplate(currentBlock, tag, "Default");
        return PartialView(path, currentBlock);
    }
}

The same concept do I use for the page templates. Instead of tags I’m using the channels.

[TemplateDescriptor(
        Inherited = true,
        TemplateTypeCategory = TemplateTypeCategories.MvcController, 
        Tags = new[] { RenderingTags.Preview, RenderingTags.Edit}, Default = true,
        AvailableWithoutTag = true)]
public class DefaultPageController : PageController<PageData>
{
    public ActionResult Index(PageData currentPage)
    {
        var tags = new List<string> { "Default" };
        var test = ServiceLocator.Current.GetInstance<DisplayChannelService>().GetActiveChannels(this.ControllerContext.HttpContext).FirstOrDefault();
        if (test != null)
            tags.Insert(0, test.ChannelName);

        var type = typeof(PageViewModel<>).MakeGenericType(currentPage.GetOriginalType());
        var model = Activator.CreateInstance(type, currentPage) as IPageViewModel<PageData>;
        model.CurrentObject = currentPage;
        var path = FindViewer.Current.FindMainTemplate(currentPage, "Templates", tags.ToArray());
        return View(path, model);
    }

}

If I have a ContentPage that inherits from SitePageData and I have selected Mobile channel the Controller will look for the first view that exists from these.

  • /Views/Templates/Mobile/ContentPage.cshtml
  • /Views/Templates/Mobile/SitePageData.cshtml
  • /Views/Templates/Default/ContentPage.cshtml
  • /Views/Templates/ Default/SitePageData.cshtml

Therefore, when I add a new pagetype I do not need to add a view for it, if it inherits from an existing type. Not that I usually have specific content for the mobile channel, but I have added myself some channels

clip_image001[4]

There partial is a preview of this page in different kind of tags, and edit children will show an editable list of all the children for this page. The partial channel can so a page like this

image

With some of the code from /Views/Templates/Partial/SitePageData.cshtml like this

@model IPageViewModel<SitePageData>
@using Itera.Web

<div class="row addSpaceBellow">
    <div class="col-md-3">
        <h2>3 col</h2>
    </div>
    <div class="col-md-9">
        <div class="row">
            <div class="col-md-4">
                <div class="hilightSelected">
                    @Html.RenderObjectMvcToString(Model.CurrentObject, "Col4","Default")
                </div>
            </div>
            <div class="col-md-4">
                <div class="hilight">

                </div>
            </div>
            <div class="col-md-4">
                <div class="hilight">

                </div>
            </div>
        </div>
    </div>
</div>

I use the same functions for handling the edit/preview of blocks and blobs.

[TemplateDescriptor(
    Inherited = true,
    TemplateTypeCategory = TemplateTypeCategories.MvcController, //Required as controllers for blocks are registered as MvcPartialController by default
    Tags = new[] { RenderingTags.Preview, RenderingTags.Edit }, Default = true,
    AvailableWithoutTag = false)]
public class PreviewController : ActionControllerBase, IRenderTemplate<ContentData>
{
    public ActionResult Index(ContentData currentContent)
    {
        var tags = new List<string> { "Partial" };
        var startPage = _contentLoader.Get<PageData>(ContentReference.StartPage);
        var type = typeof(PageViewModel<>).MakeGenericType(startPage.GetOriginalType());
        var model = Activator.CreateInstance(type, startPage) as IPageViewModel<PageData>;
        model.CurrentObject = currentContent;
        var path = FindViewer.Current.FindMainTemplate(currentContent, "Templates", tags.ToArray());
        return View(path, model);
    }

image

When it comes to so pages or blocks from a content area I had to add some code in the ContentAreaRenderer.

Instead of adding classes to the render from code I add a wrapper class (PartialViewWithLayout) if the tag is set, and render that item. That view will then add the layout classes, and then add the EPiServer edit tag div, then render the content with the current tag.

@model Itera.Web.Models.PartialViewWithLayout
@using Itera.Web
<div class="col-md-4">
    @Html.Raw(Model.TagBuilder.ToString(TagRenderMode.StartTag)) 
    @Html.RenderObjectMvcToString(Model.Content,Model.Tag,"Default")
    <div style="clear:both"></div>
    @Html.Raw(Model.TagBuilder.ToString(TagRenderMode.EndTag))
</div>

clip_image004[4]

I also have made myself some easy to use extension methods like the RenderObjectMvcString that will render the object and find the view from different tags.

Have uploaded a sample prosject with these functions here

Mar 18, 2014

Comments

Please login to comment.
Latest blogs
A day in the life of an Optimizely OMVP - Opticon London 2025

This installment of a day in the life of an Optimizely OMVP gives an in-depth coverage of my trip down to London to attend Opticon London 2025 held...

Graham Carr | Oct 2, 2025

Optimizely Web Experimentation Using Real-Time Segments: A Step-by-Step Guide

  Introduction Personalization has become de facto standard for any digital channel to improve the user's engagement KPI’s.  Personalization uses...

Ratish | Oct 1, 2025 |

Trigger DXP Warmup Locally to Catch Bugs & Performance Issues Early

Here’s our documentation on warmup in DXP : 🔗 https://docs.developers.optimizely.com/digital-experience-platform/docs/warming-up-sites What I didn...

dada | Sep 29, 2025

Creating Opal Tools for Stott Robots Handler

This summer, the Netcel Development team and I took part in Optimizely’s Opal Hackathon. The challenge from Optimizely was to extend Opal’s abiliti...

Mark Stott | Sep 28, 2025

Integrating Commerce Search v3 (Vertex AI) with Optimizely Configured Commerce

Introduction This blog provides a technical guide for integrating Commerce Search v3, which leverages Google Cloud's Vertex AI Search, into an...

Vaibhav | Sep 27, 2025

A day in the life of an Optimizely MVP - Opti Graph Extensions add-on v1.0.0 released

I am pleased to announce that the official v1.0.0 of the Opti Graph Extensions add-on has now been released and is generally available. Refer to my...

Graham Carr | Sep 25, 2025