A critical vulnerability was discovered in React Server Components (Next.js). Our systems remain protected but we advise to update packages to newest version. Learn More

Jonas Bergqvist
Jun 3, 2013
  8729
(1 votes)

Hidden template functionality in the MVC implementation

There is an alternative way of rendering content when using MVC. By implementing IRenderTemplate<T> and IView, the class will be registered as a render template for type (T).

Short before the RTM release of EPiServer 7, we found out that a block controller is very slow, and should be used carefully. We have, because of this, recommended partner developers to only use a view without a controller for blocks on their sites. The sad part of this is that it’s not possible to create view models of the blocks.

When you have patch 3 on your site, you can implement IView on a class, which automatically will register it as a render template, if the generic marker interface “IRenderTemplate” gets implemented by the same class. This makes it possible to create view models and use those instead of the actual “BlockData”. Because of a bug, the “IView” rendering template hasn’t worked before patch 3, so make sure to download patch 3 to your site.

Ok, so how does the “IVew” interface look like now again. Well, you get two parameters, “ViewContext” and a “TextWriter”. The view context should be used to render data on the text writer. Hm, not much help from EPiServer here it looks like.

I have created a base class, which looks pretty similar to a controller, that can be used. The class looks like this:

using EPiServer.Core;
using EPiServer.Web;
using EPiServer.Web.Routing;
using System;
using System.Globalization;
using System.IO;
using System.Web.Mvc;

namespace EPiServer.Samples.MvcTemplates.Controllers
{
    /// <summary>
    /// Base class for render template of type <see cref="IView"/>.
    /// </summary>
    /// <typeparam name="T">The <see cref="IContentData"/> this renderer should support.</typeparam>
    public abstract class ViewRenderTemplate<T> : IView, IRenderTemplate<T> where T : class, IContentData
    {
        /// <summary>
        /// Gets the default view name. "index" will be used when not overridden.
        /// </summary>
        protected virtual string DefaultViewName
        {
            get { return "index"; }
        }

        /// <summary>
        /// Gets the current view context
        /// </summary>
        protected ViewContext ViewContext { get; private set; }

        /// <summary>
        /// Gets the current writer
        /// </summary>
        protected TextWriter Writer { get; private set; }

        /// <summary>
        /// Called when the template will be executed
        /// </summary>
        /// <param name="viewContext">The current view context</param>
        /// <param name="writer">The current writer</param>
        public virtual void Render(ViewContext viewContext, TextWriter writer)
        {
            ViewContext = viewContext;
            Writer = writer;

            var currentContent = ViewContext.RequestContext.GetRoutedData<T>();
            if (currentContent == default(T))
            {
                currentContent = viewContext.RouteData.Values["currentContent"] as T;
            }

            ViewContext.RequestContext.SetController(currentContent.GetOriginalType().Name);
            Render(currentContent);
        }

        /// <summary>
        /// Called when the template will be executed
        /// </summary>
        /// <param name="currentContent">The current <see cref="IContentData"/></param>
        protected abstract void Render(T currentContent);

        /// <summary>
        /// Calls the default view with the <param name="model">view model</param>.
        /// </summary>
        /// <param name="model">The view model</param>
        protected virtual void RenderView(object model)
        {
            RenderView(DefaultViewName, model);
        }

        /// <summary>
        /// Calls the <param name="viewName">view</param> view with the <param name="model">view model</param>.
        /// </summary>
        /// <param name="viewName">The view name</param>
        /// <param name="model">The view model</param>
        protected virtual void RenderView(string viewName, object model)
        {
            RenderView(viewName, null, model);
        }

        /// <summary>
        /// Calls the <param name="viewName">view</param> view with the <param name="model">view model</param>.
        /// </summary>
        /// <param name="viewName">The view name</param>
        /// <param name="masterName">The master name. Default master will be used when set to 'null'.</param>
        /// <param name="model">The view model</param>
        protected virtual void RenderView(string viewName, string masterName, object model)
        {
            var result = GetViewEngineResult(viewName, masterName);
            RenderView(model, result, viewName);
        }

        /// <summary>
        /// Calls the default partial view with the <param name="model">view model</param>.
        /// </summary>
        /// <param name="model">The view model</param>
        protected virtual void RenderPartialView(object model)
        {
            RenderPartialView(DefaultViewName, model);
        }

        /// <summary>
        /// Calls the <param name="viewName">partial view</param> view with the <param name="model">view model</param>.
        /// </summary>
        /// <param name="viewName">The view name</param>
        /// <param name="model">The view model</param>
        protected virtual void RenderPartialView(string viewName, object model)
        {
            var result = GetPartialViewEngineResult(viewName);
            RenderView(model, result, viewName);
        }

        private void RenderView(object model, ViewEngineResult result, string viewName)
        {
            if (result.View == null)
            {
                throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, "The view '{0}' or its master was not found or no view engine supports the searched locations.", viewName));
            }

            ViewContext.ViewData.Model = model;
            result.View.Render(ViewContext, Writer);
        }

        private ViewEngineResult GetPartialViewEngineResult(string viewName)
        {
            return ViewEngines.Engines.FindPartialView(ViewContext, viewName);
        }

        private ViewEngineResult GetViewEngineResult(string viewName, string masterName)
        {
            return ViewEngines.Engines.FindView(ViewContext, viewName, masterName);
        }
    }
}

Now, we can simple use this base class like this:

using EPiServer.Framework.DataAnnotations;
using EPiServer.Templates.Alloy.Models.Blocks;

namespace EPiServer.Samples
{
    [TemplateDescriptor(Default = true, Tags = new[]{"myTag"}, AvailableWithoutTag = true)]
    public class TestBlockDemoController : ViewRenderTemplate<TestBlock>
    {
        protected override void Render(TestBlock currentContent)
        {
            RenderPartialView(currentContent);
        }
    }
}

Jun 03, 2013

Comments

Erik Täck
Erik Täck Jun 3, 2013 02:50 PM

Your versions and different types of updates are very confusing. If patch 3 is in the nuget feed, what version number is it?

Jun 3, 2013 10:34 PM

Erik, I should have written that. It's the version 7.0.586.16.

Antti Alasvuo
Antti Alasvuo Apr 4, 2014 11:25 AM

Just adding here a note to others who might stumble on issue that the above code like that doesn't work in EPiServer 7.5 (or at least versions 7.6) and above versions if used for blocks. I just had an email conversation with Jonas and Johan Björnfot who confirmed the changes in the EPiServer routing.

So the code in the Render method " var currentContent = ViewContext.RequestContext.GetRoutedData(); " will throw an exception of InvalidCastException when using this for blocks as the method will now return the page data of the "routed page" and not the content.

I changed my code to first see if there is the "currentContent" key in the viewContext.RouteData.Values and if it exists I try to use that as the T block data and if it is null then I try to get it with the " viewContext.RequestContext.GetRoutedData(); ". Works for blocks ;-) not tested with pages.

Janne Kuusela Kuusela
Janne Kuusela Kuusela Jul 31, 2014 09:27 AM

Thanks Antti, you saved me the trouble of debugging this after 7.5 upgrade! :-)

Please login to comment.
Latest blogs
A day in the life of an Optimizely OMVP - OptiGraphExtensions v2.0: Enhanced Search Control with Language Support, Synonym Slots, and Stop Words

Supercharge your Optimizely Graph search experience with powerful new features for multilingual sites and fine-grained search tuning. As search...

Graham Carr | Dec 16, 2025

A day in the life of an Optimizely OMVP - Optimizely Opal: Specialized Agents, Workflows, and Tools Explained

The AI landscape in digital experience platforms has shifted dramatically. At Opticon 2025, Optimizely unveiled the next evolution of Optimizely Op...

Graham Carr | Dec 16, 2025

Optimizely CMS - Learning by Doing: EP09 - Create Hero, Breadcrumb's and Integrate SEO : Demo

  Episode 9  is Live!! The latest installment of my  Learning by Doing: Build Series  on  Optimizely Episode 9 CMS 12  is now available on YouTube!...

Ratish | Dec 15, 2025 |

Building simple Opal tools for product search and content creation

Optimizely Opal tools make it easy for AI agents to call your APIs – in this post we’ll build a small ASP.NET host that exposes two of them: one fo...

Pär Wissmark | Dec 13, 2025 |