Anders Hattestad
Mar 18, 2014
(1 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.

        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


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


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 class="col-md-9">
        <div class="row">
            <div class="col-md-4">
                <div class="hilightSelected">
                    @Html.RenderObjectMvcToString(Model.CurrentObject, "Col4","Default")
            <div class="col-md-4">
                <div class="hilight">

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


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

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


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">
    <div style="clear:both"></div>


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


valdis Mar 19, 2014 10:00 PM

Idea is great, but eventually I ended up with single responsive view for the same block. Reduces a lot of duplicates, cost for maintenance and upgrade. I ended up with custom content area render that takes care of proper display options to add necessary classes for Bootstrap v3 fluid grid system, aka EPiBootstrapArea (

Please login to comment.
Latest blogs
Content Delivery API – The Case of the Duplicate API Refresh Token

Creating a custom refresh provider to resolve the issues with duplicate tokens in the DXC The post Content Delivery API – The Case of the Duplicate...

David Lewis | Sep 29, 2022 | Syndicated blog

New Optimizely certifications - register for beta testing before November 1st

In January 2023, Optimizely is making updates to the current versions of our certification exams to make sure that each exam covers the necessary...

Jamilia Buzurukova | Sep 28, 2022

Optimizely community meetup - Sept 29 (virtual + Melbourne)

Super excited to be presenting this Thursday the 29th of September at the Optimizely community meetup. For the full details and RSVP's see the...

Ynze | Sep 27, 2022 | Syndicated blog

Preview multiple Visitor Groups directly while browsing your Optimizely site

Visitor groups are great - it's an easy way to add personalization towards market segments to your site. But it does come with it's own set of...

Allan Thraen | Sep 26, 2022 | Syndicated blog

The Report Center is finally back in Optimizely CMS 12

With Episerver.CMS.UI 12.12.0 the Report Center is finally re-introduced in the core product.

Tomas Hensrud Gulla | Sep 26, 2022 | Syndicated blog

Dynamic Route in ASP.NET Core When MapDynamicControllerRoute Does Not Work

Background Creating one of the add-on for Optimizely I had to deal with challenge to register dynamically route for the API controller. Dynamic rou...

valdis | Sep 25, 2022 | Syndicated blog