Try our conversational search powered by Generative AI!

Anders Hattestad
Mar 18, 2014
  7916
(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
Azure AI Language – Extractive Summarisation in Optimizely CMS

In this article, I demonstrate how extractive summarisation, provided by the Azure AI Language platform, can be leveraged to produce a set of summa...

Anil Patel | Apr 26, 2024 | Syndicated blog

Optimizely Unit Testing Using CmsContentScaffolding Package

Introduction Unit tests shouldn't be created just for business logic, but also for the content and rules defined for content creation (available...

MilosR | Apr 26, 2024

Solving the mystery of high memory usage

Sometimes, my work is easy, the problem could be resolved with one look (when I’m lucky enough to look at where it needs to be looked, just like th...

Quan Mai | Apr 22, 2024 | Syndicated blog

Search & Navigation reporting improvements

From version 16.1.0 there are some updates on the statistics pages: Add pagination to search phrase list Allows choosing a custom date range to get...

Phong | Apr 22, 2024