Try our conversational search powered by Generative AI!

Anders Hattestad
Mar 18, 2014
  7932
(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
Build a headless blog with Astro and Optimizely SaaS CMS

I’m a big fan of using the right tool for the right job. I’m also a big fan of Astro , for the right use case. Let's explore Astro to see what it's...

Jacob Pretorius | May 28, 2024

Microsoft announces Natural language to SQL

Finally, Microsoft launches "Natural language to SQL," after it has been available for several months in Optimizely CMS!

Tomas Hensrud Gulla | May 23, 2024 | Syndicated blog

Five easy ways to start personalizing your content right now

If you clicked on this article, you already know that getting the right message to the right person at the right time helps drive conversions and...

Kara Andersen | May 23, 2024

ExtendedCms.TinyMceEnhancements – serwer side webp support

Today I will introduce another small feature of TinyMceEnhancements plugin. The functionality is used to automatically detect whether a browser...

Grzegorz Wiecheć | May 22, 2024 | Syndicated blog

Azure AI Language– Detect Healthcare Content in Optimizely CMS

In this blog post, I showcase how the Azure AI Language service's Text Analytics for health feature can be used to detect healthcare content within...

Anil Patel | May 22, 2024 | Syndicated blog

Stott Security Version 2 So Far

In December 2023, I unveiled the initial version of Stott Security version 2. Although I typically announce each version I release on LinkedIn and...

Mark Stott | May 22, 2024