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

[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

valdis
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 (https://github.com/valdisiljuconoks/EPiBootstrapArea).

Please login to comment.
Latest blogs
Creating an Optimizely CMS Addon - Adding an Editor Interface Gadget

In   Part One   of this series, I covered getting started with creating your own AddOn for Optimizely CMS 12. This covered what I consider to be an...

Mark Stott | Aug 30, 2024

Configure your own Search & Navigation timeouts

The main blog Configure your own Search & Navigation timeouts was posted for years but you need copy the code to your application. We now bring tho...

Manh Nguyen | Aug 30, 2024

Joining the Optimizely MVP Program

Andy Blyth has been honoured as an Optimizely MVP, recognising his contributions to the Optimizely community. Learn how this achievement will enhan...

Andy Blyth | Aug 29, 2024 | Syndicated blog

Welcome 2024 Summer OMVPs

Hello, Optimizely community! We are thrilled to announce and welcome the newest members to the Optimizely Most Valuable Professionals (OMVP) progra...

Patrick Lam | Aug 29, 2024

Create custom folder type in Edit Mode

Content Folders, which are located in assets pane gadget or multichannel content gadget, allow you to add any type of block or folders. But...

Grzegorz Wiecheć | Aug 28, 2024 | Syndicated blog

Creating an Optimizely AddOn - Getting Started

When Optimizely CMS 12 was launched in the summer of 2021, I created my first AddOn for Optimizely CMS and talked about some of the lessons I learn...

Mark Stott | Aug 28, 2024