Root Templates - Sitefinity/PowerTools GitHub Wiki

This power tool can be used by Sitefinity 5.1 and higher versions.

Installation

The simplest way to install Sitefinity Power Tools is through a NuGet package.

Install-Package SitefinityPowerTools

Tool description

When Sitefinity renders pages, regardless of the mode (WebForms, Hybrid, MVC) it always uses embedded templates as root templates. The reason we call them root templates is because even Sitefinity templates (the ones created through user interface) are based on those templates.

To illustrate the concept, let's examine the default root templates Sitefinity uses:

Default front-end MVC root template

<!DOCTYPE html>
<head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
</head>
<body>
    <div class="sfPublicWrapper" id="PublicWrapper">
    </div>
</body>
</html>

Default front-end WebForms / Hybrid template

<%@ Page AutoEventWireup="true" Language="C#" %>
<%@ Register TagPrefix="sf" Namespace="Telerik.Sitefinity.Web.UI" Assembly="Telerik.Sitefinity" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
</head>
<body>
    <form id="aspnetForm" runat="server">
        <div class="sfPublicWrapper" id="PublicWrapper" runat="server">
            <sf:SitefinityPlaceHolder ID="Body" runat="server"></sf:SitefinityPlaceHolder>
        </div>
    </form>
</body>
</html>

Default back-end WebForms / Hybrid template

<%@ Master Language="C#" AutoEventWireup="true" %>
<%@ Import Namespace="Telerik.Sitefinity.Localization"%>

<%@ Register Assembly="Telerik.Sitefinity" Namespace="Telerik.Sitefinity.Web.UI" TagPrefix="sf" %>
<%@ Register Assembly="Telerik.Sitefinity" Namespace="Telerik.Sitefinity.Web.UI.Backend" TagPrefix="sf" %>
<%@ Register Assembly="Telerik.Web.UI" Namespace="Telerik.Web.UI" TagPrefix="telerik" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=8" />    
</head>
<body xmlns:dataview="javascript:Sys.UI.DataView" sys:activate="*" xmlns:code="http://schemas.microsoft.com/aspnet/code">
    <form id="aspnetForm" runat="server">
        <sf:ResourceLinks id="resourceLinks" runat="server">
            <sf:ResourceFile Name="CSS/Reset.css" />
            <sf:ResourceFile Name="CSS/Layout.css" />
            <sf:ResourceFile Name="CSS/Colors.css" />
            <sf:ResourceFile Name="CSS/MainMenu.css" />
        </sf:ResourceLinks>
        <a href="#sfToMainContent" id="sfSkip">
            <asp:Literal ID="skipToMainContentLiteral" runat="server" Text='<%$Resources:PageResources, SkipToTheMainContent %>' />
        </a>
        <asp:PlaceHolder ID="adminMessagesContainer" runat="server" Visible="false" />
        <div class="sfWrapper">
            <div class="sfHeader sfClearfix">
                <asp:ContentPlaceHolder ID="Header" runat="server" />
            </div>
            <asp:ContentPlaceHolder ID="Content" runat="server" />
        </div>
        <div class="sfFooter">
            <asp:ContentPlaceHolder ID="Footer" runat="server" />
        </div>
    </form>
</body>
</html>

The problem

Sometimes, a developer needs to modify the root templates in order to implement the required scenarios. Here are some scenarios which may require this modification:

  • Change the document type
  • Add META tags (especially in MVC mode)
  • Add styles and scripts to the HEAD tag (especially in MVC mode)

The solution

Sitefinity Power Tools provides a simple API for dynamically resolving root templates. All you need to do is implement a class which implements IRootTemplateResolver interface and register it through the PowerTools class during application startup.

Quick sample

Custom MVC template resolver that adds "keywords" meta tag for keywords "HTML, CSS, XML, XHTML, JavaScript"

CustomMvcTemplateResolver

using System.Web.Routing;
using Sitefinity.PowerTools.RootTemplates;

public class CustomMvcTemplateResolver : IRootTemplateResolver
{
   public RootTemplate ResolveTemplate(RootTemplate rootTemplate, RequestContext requestContext, string theme)
   {
      return rootTemplate.FromString(tmpl);
   }

   private readonly string tmpl =
      @"<!DOCTYPE html>
        <html>
        <head>
           <title></title>
           <meta http-equiv=""content-type"" content=""text/html; charset=utf-8"" />
           <meta name=""keywords"" content=""HTML, CSS, XML, XHTML, JavaScript"">
        </head>
        <html>                                
           <body>
              <div class=""sfPublicWrapper"" id=""PublicWrapper"">
              </div>
           </body>
        </html>";
}

In order for Sitefinity to use your custom root template resolver, you need to register it in Global.asax file during application start:

public class Global : System.Web.HttpApplication
{
   protected void Application_Start(object sender, EventArgs e)
   {
      PowerTools.Instance
                .RootTemplates
                .RegisterMvcTemplateResolver<CustomMvcTemplateResolver>();
   }
}

IRootTemplateResolver interface

When you implement a custom root template resolver, that class must implement IRootTemplateResolver interface, which is located under Sitefinity.PowerTools.RootTemplates namespace in the Sitefinity.PowerTools assembly.

The interface mandates only one method - ResolveTemplate and expects an instance of the RootTemplate type to be returned. Through the arguments, you will be passed an instance of RootTemplate which you can use to resolve a template, an instance of the current RequestContext which can help you to identify the page for which the template is to be resolved and finally the name of the theme that is to be applied on the template.

Using a string as a root template

using System.Web.Routing;
using Sitefinity.PowerTools.RootTemplates;

public RootTemplate ResolveTemplate(RootTemplate rootTemplate, RequestContext requestContext, string theme)
{
   return rootTemplate.FromString("<!DOCTYPE html>...</html>);
}

Using a file as a root template

using System.Web.Routing;
using Sitefinity.PowerTools.RootTemplates;

public RootTemplate ResolveTemplate(RootTemplate rootTemplate, RequestContext requestContext, string theme)
{
   var filePath = HostingEnvironment.MapPath("~/root-templates/mytemplate.html");
   return rootTemplate.FromFile(filePath);
}

Using an embedded file as a root template

using System.Web.Routing;
using Sitefinity.PowerTools.RootTemplates;

public RootTemplate ResolveTemplate(RootTemplate rootTemplate, RequestContext requestContext, string theme)
{
   return rootTemplate.FromEmbeddedFile("MyProject.MyFile.html", typeof(MyProject.Initializer));
}

When resolving template from the embedded file, make sure that the file Build Action is set to "Embedded Resource". The second parameter of the method, "assemblyInfo", is a reference to any type inside of the assembly where your embedded project is located.

Not resolving the root template - using the default Sitefinity root template

using System.Web.Routing;
using Sitefinity.PowerTools.RootTemplates;

public RootTemplate ResolveTemplate(RootTemplate rootTemplate, RequestContext requestContext, string theme)
{
   return rootTemplate;
}

Many times your logic will be resolving custom templates only for specific pages, that you can determine from the RequestContext. For all the other pages, you can just return the instance of RootTemplate that was passed to the method.

Resolving different templates for front-end and back-end pages

using System.Web.Routing;
using Sitefinity.PowerTools.RootTemplates;

public RootTemplate ResolveTemplate(RootTemplate rootTemplate, RequestContext requestContext, string theme)
{
   string filePath = "";
   if (rootTemplate.IsBackend)
   {
      filePath = HostingEnvironment.MapPath("~/RootTemplates/BackendRoot.Master");
   }
   else
   {
      filePath = HostingEnvironment.MapPath("~/RootTemplates/FrontendRoot.Master");
   }
            
   return rootTemplate.FromFile(filePath);
}

Generally, you want to have different templates for back-end pages and front-end pages. To simplify things, RootTemplate class implements a property IsBackend, which will be true if the template to be resolved is for a back-end page and false if it's for the front-end page.

Registering custom root template resolvers

In order for your custom root template resolvers to be called by Sitefinity, you need to register them. The best place to do this is in your Global.asax file in the Application_Start method.

Resolvers can be registered also in the Initialize methods of your custom modules and services.

Sitefinity has two basic root templates it uses:

  1. WebForms root template (used for WebForms and Hybrid mode)
  2. MVC root template (used for Pure MVC mode)

You can register a different resolver for those two modes, as in the code below:

public class Global : System.Web.HttpApplication
{
   protected void Application_Start(object sender, EventArgs e)
   {
      // register MVC resolver
      PowerTools.Instance
                .RootTemplates
                .RegisterMvcTemplateResolver<CustomMvcTemplateResolver>();
            
      // register web forms resolver
      PowerTools.Instance
                .RootTemplates
                .RegisterWebFormsTemplateResolver<CustomWebFormsTemplateResolver>();
   }
}

Examples

Change WebForms root template to HTML5 document type

In this example we will demonstrate how to use RootTemplates power tool to change the document type of our front-end pages that are running in WebFroms or Hybrid mode from XHTML to HTML5.

  1. Create a folder in your Sitefinity web application and call it "RootTemplates"
  2. Inside of the folder create a new file and call it WebFormsHtml5.html - this is going to be our new template with HTML5 document type. Paste the following in the file:
<%@ Page AutoEventWireup="true" Language="C#" %>
<%@ Register TagPrefix="sf" Namespace="Telerik.Sitefinity.Web.UI" Assembly="Telerik.Sitefinity" %>
<!DOCTYPE html>
<html>
<head runat="server">
    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
</head>
<body>
    <form id="aspnetForm" runat="server">
        <div class="sfPublicWrapper" id="PublicWrapper" runat="server">
            <sf:SitefinityPlaceHolder ID="Body" runat="server"></sf:SitefinityPlaceHolder>
        </div>
    </form>
</body>
</html>
  1. In the RootTemplates folder add a new class and call it CustomWebFormsResolver.cs (note that if you have opened Sitefinity as a website, you'll want to add this class to App_Code folder). Paste the following code in the class:
using System.Web.Routing;
using Sitefinity.PowerTools.RootTemplates;

public class CustomWebFormsResolver : IRootTemplateResolver
{
   public RootTemplate ResolveTemplate(RootTemplate rootTemplate, RequestContext requestContext, string theme)
   {
      if (!rootTemplate.IsBackend)
      {
         var templatePath = HostingEnvironment.MapPath("~/RootTemplates/WebFormsHtml5.html");
         return rootTemplate.FromFile(templatePath);
      }

      return rootTemplate;
   }
}

The code above will resolve our custom template only for frontend pages. 4. Finally, open your Global.asax file (add one if it's not present) and in the Application_Start method register your custom template resolver for WebForms based pages, like follows:

protected void Application_Start(object sender, EventArgs e)
{
   PowerTools.Instance
             .RootTemplates
             .RegisterWebFormsTemplateResolver<CustomWebFormsResolver>();
}

Use ASP.NET MVC Bundling and Minification in Pure MVC pages

In this example we will demonstrate how to use Bundling and Minification features of ASP.NET framework with Pure MVC front-end pages. To start, let's add some arbitrary scripts and styles to our web application.

  1. Create a folder in your web application root and name it "Scripts"
  2. Add a new javascript file to this folder and call it "myscript1.js", and paste following code inside
var Test = {
  value : "Hello world!"
};
  1. Add one more javascript file to the "Scripts" folder and call it "myscript2.js". Paste following code inside
alert(Test.value);
  1. Create a folder in your web application root and name it "Styles"
  2. Add a new css file to this folder and call it "mystyle1.css" and paste following into it:
body { background-color:green; }
  1. Add one more css file to the "Styles" folder and call it "mystyle2.css". Paste following inside:
body { color:#FFF; }
  1. Add App_Start folder to your web application and create a new class inside of it, which you should call "BundleConfig.cs". Register your bundles inside of this class as follows:
public class BundleConfig
{
   public static void RegisterBundles(BundleCollection bundles)
   {
      bundles.Add(new ScriptBundle("~/Scripts/myscripts").Include(
         "~/Scripts/myscript1.js",
         "~/Scripts/myscript2.js")
      );

      bundles.Add(new StyleBundle("~/Styles/mystyles").Include(
         "~/Styles/mystyle1.css",
         "~/Styles/mystyle2.css")
      );
   }
}
  1. Add a new folder to your web application and call it RootTemplates. Inside of it, create a new html file and call it MvcFrontendTemplate.html. Make the file look like this:
<!DOCTYPE html>
<html>
<head>
    <title></title>
    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
    <%: Styles.Render("~/Styles/mystyles") %>
</head>
<body>
    <div class="sfPublicWrapper" id="PublicWrapper">
    </div>
    <%: Scripts.Render("~/Scripts/myscripts") %>
</body>
</html>

Note that we are using server side code inside of this file, namely Styles.Render and Scripts.Render methods. Even though this is an HTML file, Sitefinity will use this only as a source and compile it when it's first needed. 9. In the RootTemplates folder, add another class and call it CustomMvcRootResolver.cs - this is going to be our custom root template resolver for front-end pages. Make the class look as follows:

using System.Web.Routing;
using Sitefinity.PowerTools.RootTemplates;

public class CustomMvcRootResolver : IRootTemplateResolver
{
   public RootTemplate ResolveTemplate(RootTemplate rootTemplate, RequestContext requestContext, string theme)
   {
      var filePath = HostingEnvironment.MapPath("~/RootTemplates/MvcFrontendTemplate.html");
      return rootTemplate.FromFile(filePath);
   }
}
  1. Finally, we need to register our custom root template resolver and the bundles. This we can do in Global.asax file in the Application_Start method, as follows:
protected void Application_Start(object sender, EventArgs e)
{
   PowerTools.Instance
             .RootTemplates
             .RegisterMvcTemplateResolver<CustomMvcRootResolver>();

   BundleConfig.RegisterBundles(BundleTable.Bundles);
}

To test this sample, create a new template in Sitefinity, set the web framework to MVC and then create one page based on this template. When you visit this page, you will see that the background is green as well as you will see a JavaScript alert saying "Hello World". If you examine the requests in the Firebug or similar tool, you will notice that there are only 1 request for JavaScript files and 1 request for CSS files, even though we are using 4 files.

Important

There are certain rules that need to be followed when designing custom root templates, as Sitefinity expects certain elements to be present in the root template.

MVC Root templates

  1. Sitefinity will expect that there is a DIV tag with class "sfPublicWrapper" and id "PublicWrapper" present inside of the body tag. This is the tag where users are able to drag initial layout elements to.
<div class="sfPublicWrapper" id="PublicWrapper">
</div>

WebForms templates

  1. The page must have a page declaration
<%@ Page AutoEventWireup="true" Language="C#" %>
  1. The head tag must have runat="server" attribute
<head runat="server">
  1. There can be only one form tag, it must be called aspnetForm, have runat="server" attribute and encompass all the content.
<form id="aspnetForm" runat="server">
  1. Inside of the form tag, there has to be a div tag with class set to "sfPublicWrapper" and id set to "PublicWrapper" as well as runat="server" tag. At least one SitefinityPlaceHolder control should be defined inside of the wrapper tag, though more can be defined.
<div class="sfPublicWrapper" id="PublicWrapper" runat="server">
   <sf:SitefinityPlaceHolder ID="Body" runat="server"></sf:SitefinityPlaceHolder>
</div>
⚠️ **GitHub.com Fallback** ⚠️