Johan Björnfot
Sep 10, 2012
  27194
(10 votes)

EPiServer 7 – Rendering of content

In EPiServer 6 a PageType states which page template (a WebForm) that should render a request for the page. This is a one to one relationship between PageType and template.

In EPiServer7 it is possible to have several renderers/templates (both WebForm and MVC are supported) registered for a .NET type (typically pages or blocks even if TemplateRepository and ITemplateResolver handles any type) or having one renderer registered for several .NET types. The way it works is that is that each renderer states which model it can render. That is we have changed the dependency so instead that a PageType states which renderer that should be used each renderer states which model class it can render.

Which renderer that is used to render a content instance depends on the specific context (e.g. is the rendering a partial rendering e.g. inside a ContentArea, is any DisplayChannel active, is the request tagged with a certain tag etc.). In this post I will try to clarify how the render selection is done.

Note: DisplayChannel is a new concept in CMS7 and presented shortly later in this post.

For a renderer to be automatically registered it has to implement EPiServer.Web.IRenderTemplate<T> (where T states which model it can render). If you use base classes for your renderer like PageBase<T>, ContentControlBase<T>, BlockControlBase<T>, PageController<T>, PartialContentController<T> or BlockControllerBase<T> then you do not need to explicitly implement the interface since that is done by the base class. In addition the RenderDescriptorAttribute can be used to specify more details about the renderer, like e.g. tags or if the renderer should be available for subtypes as well (by default renderers are only available for specified type).

As an example say that there is a model that is defined as:

[ContentType]
public class MyPage : PageData
{
    public virtual string Heading { get; set; }
    public virtual string MainIntro { get; set; }
    public virtual XhtmlString MainBody { get; set; }
}

and given that there are renderers defined as below:

public partial class MyPageTemplate : TemplatePage<MyPage>
{}

[RenderDescriptor(Tags = new string[] { RenderingTags.Mobile })]
public partial class MyPageMobileTemplate : TemplatePage<MyPage>
{}

[RenderDescriptor(Inherited = true)]
public partial class MyFallbackTemplate : TemplatePage<PageData>
{}
public partial class MyPageTeaser : ContentControlBase<MyPage>
{}
[RenderDescriptor(Inherited = true)]
public partial class PageTeaser : ContentControlBase<PageData>
{}

All of the above renderers will be registered as possible renderers for instances of MyPage which one that actually gets selected is dependent on the request.

It is possible to register the renderer for a base type or interface (as MyFallbackTemplate and PageTeaser above which are registered for PageData). In the case you want the template to be available for all subtypes as well you need to mark it with Inherited=true on the RenderDescriptor attribute. This can be useful e.g. if you want to have a fallback renderer for instances that still not have a specific renderer.

The algorithm to select a template is handled by ITemplateResolver and the default implementation is something like:

  1. First event EPiServer.Web.ITemplateResolver.TemplateResolving is raised, if an event handler selects a template that template is used with no further handling.
  2. Else all the templates matching a type is filtered according to if the rendering is a page rendering (in that case suitable templates are WebForm or MVC controller) or a partial rendering (in that case a suitable template is a WebControl, UserControl, MVC partial controller or a MVC partial view). For partial renderers the list is filtered according to main renderer, that is if the main request is a e.g. handled by a WebForm then only partial web form renderers are taken into account for partial renderer and the same applies if main renderer is an MVC renderer then only partial MVC renderers are taken into account.
  3. If the template is requested with a specific tag the list is filtered on that tag (e.g. can rendering of a ContentArea be tagged with e.g. “SideBar” and then renderers with matching tag is preferred).
  4. If no template matched tag continue with all templates from point 2.
  5. If any DisplayChannel is active and there are templates with a Tag matching the active channel the templates are filtered to the ones matching the DisplayChannel.
  6. From the remaining templates select the “closest” TemplateModel that is marked as Default (can be set on RenderDescriptor attribute) and not inherited.
  7. If no match from 5 select “closest” TemplateModel that is marked as Default.
  8. If no match from 6 select “closest” TemplateModel.
  9. Event EPiServer.Web.ITemplateResolver.TemplateResolved is raised, giving chance to replace selected template.

With “closest” above means the template model with shortest “inheritance chain”. That means that a template that is registered direct for the model will be preferred before a template registered for a base class. It is possible to register templates for interfaces as well. For interfaces the length of the “inheritance chain” is defined by walking the inheritance chain upwards and see where the interface is implemented.

There is also a possibility in admin mode (on PageType/BlockType) to “override” which renderer that should be used by default. In that case the check for “closest” template will be skipped and instead that template model will be used.

So in the example above a request in a “ordinary web browser” for a page of type MyPage (assuming no DisplayChannel is active) the template MyPageTemplate will be selected since then no tag is active (disqualifies MyPageMobileTemplate)  and MyPageTemplate has shorter inheritance chain than MyFallbackTemplate (MyPageTeaser and PageTeaser is partial renderers and will be filtered away when selecting a page renderer).

And in case there is a DisplayChannel named “Mobile” active for a request, e.g. an implementation that check if request is from a mobile device (see SDK documentation for DisplayChannel) then the template MyPageMobileTemplate will be selected, since when selecting templates we will prefer templates that have a tag matching active channels.

Blocks

The selection of a renderer for a block is similar to the selection for a page renderer with the difference that since a block is rendered in the context of a page (either it is a property on a typed page or a shared block instance rendered in a content area) the selection of the block renderer is done in context of the page renderer. This means you can have both a WebForm renderer for your block (e.g. an user control) and a MVC renderer ( e.g. a partial view or a partial controller) then depending on if the block is rendered in the context of a WebForm page or a MVC page the suitable renderer will be selected. When rendering a ContentArea or a block property on a page it is also possible to specify a tag as example below for WebForms resp. MVC:

<EPiServer:Property runat="server" PropertyName="MyContentArea"  RenderSettings="{'Tag':'Sidebar'}"/>
<EPiServer:Property runat="server" PropertyName="MyBlockProperty" RenderSettings="{'Tag':'Sidebar'}"/> 
<%: Html.PropertyFor(m => m.MyBlockProperty, new { Tag = RenderingTags.Sidebar })%>
<%: Html.PropertyFor(m => m.MyContentArea, new { Tag = RenderingTags.Sidebar })%>
When specifying a tag then renderers with a matching tag will be preferred.

Preview of block during on page editing

Normally a block is displayed in the context of a page (e.g. rendered inside a ContentArea ). Therefore a block renderer is typically a partial renderer like a UserControl or Partial MVC controller/Partial View. However when a shared block instance is edited then we need to render the shared block instance with a page renderer. So for editing purposes it is useful to register a page renderer that can be used when editing shared block instances. Below is a WebForm example on such a renderer:

[RenderDescriptor(Inherited=true, Tags= new string[]{RenderingTags.Preview})]
public partial class PreviewBlock : SimplePage, IRenderTemplate<IContentData>
And in MVC an example of a page controller for editing of shared block could be defined as:
[RenderDescriptor(Inherited = true, Tags = new string[] { RenderingTags.Preview })]
public class PreviewBlockController : ActionControllerBase, IRenderTemplate<IContentData>

Note that the renderers used above are tagged with “Preview”, this is to ensure they only will be used during editing. The way it works is that when selecting a renderer in edit mode first an selection for a page renderer is selected as described above. If no template is found then we try to select a renderer with a tag RenderingTags.Preview.

IViewTemplateModelRegistrator

As mentioned before instances implementing EPiServer.Web.IRenderTemplate<T> will be automatically registered. The same goes for partial views that follows the “standard” ASP.NET MVC conventions. Say for example that there is a block type like:

[ContentType]
public class TeaserBlock : BlockData
{
    public virtual string Heading { get; set; }
    public virtual XhtmlString MainIntro { get; set; }
}

and then if there is a partial view as in /View/Shared/TeaserBlock.cshtml that has a model set to TeaserBlock, that partial view will also be automatically registered. However since it is not possible to set RenderDescriptor attribute on a partial view it is also possible to implement EPiServer.Web.Mvc.IViewTemplateModelRegitrator to register a partial view. An example on a registration of a partial view through that interface is like:

viewTemplateModelRegistrator.Add(typeof(TeaserBlock),
               new DataAbstraction.TemplateModel()
               {
                   Name = "SidebarTeaser",
                   Description = "Displays a teaser of a page.",
                   Path = "~/Views/Shared/SidebarTeaserBlock.cshtml",
                   Tags = new string[]{RenderingTags.Sidebar}               }
           );

Path Resolving

For WebForms and UserControls the virtual path to the aspx or ascx has to be known to be able to load the page/usercontrol. There is a possibility to specify the path on the RenderDescriptor attribute. If the path is not specified then we will try to resolve the path according to the namespace. That means that if your folder structure follows the namespace then it should not be needed to explicitly specify the path.

DisplayChannel

A DisplayChannel is an instance of a class that for each request is matched to see if it is considered active or not (similar to a Visitor Group), see SDK for DisplayChannel information. There is a similar concept called Display Mode introduced in MVC4. For MVC templates we rely on the MVC4 implementation. However the registration should be done against EPiServer and not directly to ASP.NET MVC (this is needed to make it possible for editors to be able to preview the site as if a Display Channel where active).

Whenever a display channel is active templates with a tag that matches the channel will be preferred. So for example say there is a display channel implementation that returns true for mobile devices (named “Mobile”) and then whenever that channel is active templates with tag “Mobile” will be preferred. That means you can choose to have duplicate templates for certain models, and then the one tagged with the name of a channel will be rendered when that channel is active.

Sep 10, 2012

Comments

Sep 10, 2012 08:50 AM

Thanks for this post! This was exactly what I was waiting for. Couldn't find all this in the SDK.

One question though... Is there a way to set RenderSettings on a webcontrol (Property) in a more typed manner?

Sep 10, 2012 05:32 PM

Great post! Hopefully something we add to the SDK also so good people like Johan can find it there. One small UNimportant note regarding "This is a one to one relationship between PageType and template" in 6. Technically you could associate multiple Page Types with one template. By design, general best practice, perhaps it's one-to-one. :) At least you know for sure I made it at least as far as the second sentence. ;) Anyway, again, nice post!

Stefan Forsberg
Stefan Forsberg Sep 11, 2012 06:28 AM

Great post Johan!

Will there be any diagnostics built into this so you, for instance when debugging, can see which templates where found for a type and why the selected template was selected?

Sep 11, 2012 09:51 AM

Johan: It would be nice to be able to set in a "typed manner", however we could not come up with a good way for this. The requirements where that it should be settable from markup and also that it should be exapandable. We use it for tag, but you could add your own settings to RenderSettings (this can be useful if you have a custom editor that needs custom values like e.g. Width, Height). The value will in edit mode be available as an attribute with a Json object.
But if you have an idea on how this could be done, we appreciate feedback.

Sep 11, 2012 10:02 AM

Stefan: There is a type EPiServer.DataAbstraction.TemplateModelRepsoitory where you can get all renderers that match a type. Instances of EPiServer.DataAbstraction.ContentType (including PageType, BlockType) also have a property SupportedTemplates that contains all matching templates for the ContentType.
Currently we log (using log4net on type EPiServer.Web.TemplateResolver) which template that was choosen for a certain instance/type. I will report a "bug" that we should log the reason as well (e.g. tag and/or active DisplayChannel), however I can not guarantee that it will make it into the release.

Mari Jørgensen
Mari Jørgensen Sep 12, 2012 02:19 PM

Is it possible to limit which block types that are allowed for a specific content area? I.e. if you use "Create new block" from "Forms editing" view, only the allowed would be listed..

Sep 12, 2012 07:45 PM

Mari: Currently it is not possible to specify which types that should be available in a specific content area. What you probably could do is write a validator for your page/block type (see SDK or http://www.david-tec.com/2012/06/EPiServer-7-Preview---IValidator-interface/) and in the validation implementatation iterate over all items in the area and then if some item is of a type that should not be available then return that it is not valid.

Mari Jørgensen
Mari Jørgensen Dec 6, 2012 09:24 AM

Lets say you register 3 tags for your controller. Is there a property or method that identifies the current rendering tag from inside the action method?

vincent.yang@orchardmarketing.com.au
vincent.yang@orchardmarketing.com.au Dec 19, 2012 01:19 PM

Hi Johan, thanks for your explanation, but where should I register the above code?

viewTemplateModelRegistrator.Add(typeof(TeaserBlock),
new DataAbstraction.TemplateModel()
{
Name = "SidebarTeaser",
Description = "Displays a teaser of a page.",
Path = "~/Views/Shared/SidebarTeaserBlock.cshtml",
Tags = new string[]{RenderingTags.Sidebar} }
);

David Sandeberg
David Sandeberg Jan 3, 2013 05:41 PM

Good post!
Is there a way to get the resolver to locate views put in /Views/Blocks/TeaserBlock/Index.cshtml instead? I think that would be cleaner.

I tried the way suggested in this blog post http://tedgustaf.com/blog/2012/11/conventions-for-episerver-7-mvc-views/ and it worked fine for pages but it doesn't seem to work for blocks.

Edit: The answer to the question was to do as the blog post above suggests and to add a route for the view without a controller parameter, like:
"~/Views/Blocks/{0}.cshtml"

David Sandeberg
David Sandeberg Jan 9, 2013 10:05 AM

Vincent: I belive you need to add a initialization module that implements IViewTemplateModelRegistrator and put the code in the Register method.

[InitializableModule]
public class SiteInitializer : IConfigurableModule, IViewTemplateModelRegistrator
{
public void Register(TemplateModelCollection viewTemplateModelRegistrator)
{
// code goes here
}
}

valdis
valdis Apr 13, 2013 07:17 PM

Mari: Is currently active / selected channels you are looking for? If so then you can access that info from controller:

ServiceLocator.Current.GetInstance().GetActiveChannels(HttpContext);

Feb 11, 2014 02:49 PM

If we want to completely skip the preview - how can we do that with MVC?

valdis
valdis Jun 19, 2014 09:44 PM

John, you can do similar stuff as I did to bring back container pages in 7.5: http://tech-fellow.net/2014/01/31/add-support-container-pages-episerver-7-5/

Jan 4, 2016 01:05 PM

Johan, why not take a look at how PropertyFor has the parameter object additionalViewData that is converted into a RouteValueDictionary? I'm really missing the feature of adding extra information when rendering a block or similar.

The case you mentioned is a good example "(this can be useful if you have a custom editor that needs custom values like e.g. Width, Height)"

Please login to comment.
Latest blogs
Block type selection doesn't work

Imagine you're trying to create a new block in a specific content area. You click the "Create" link, expecting to see a CMS modal with a list of...

Damian Smutek | Nov 4, 2024 | Syndicated blog

.NET 8 FAQ

I have previously written about .NET compatibility in general and .NET 8 in particular, see blog posts here , here and here . With the end of suppo...

Magnus Rahl | Nov 4, 2024

Dynamic packages in Commerce Connect

In Optimizely Commerce Connect, you can group different items using packages and bundles. Package: A package has one or more versions of a product...

K Khan | Nov 1, 2024

Efficient Catalog Metadata Management and Product Updates Using DTOs in Optimizely Commerce

This post explores ways to manage and update catalog metadata in Optimizely Commerce by utilizing Data Transfer Objects (DTOs). DTOs provide a...

Sujit Senapati | Oct 31, 2024

Effortlessly Resize Images with Cloudflare's On-the-Fly Solution

Resizing images in C# has traditionally been a complex and time-consuming task, often requiring intricate code and handling various image processin...

Manoj Kumawat | Oct 31, 2024 | Syndicated blog

XSS Vulnerabilities Patched with TinyMCE 6.8.4

Two different XSS vulnerabilities were fixed in the latest update of the NuGet package EPiServer.CMS.TinyMce. Update today!

Tomas Hensrud Gulla | Oct 30, 2024 | Syndicated blog