Applying Theme to an MCMS Site

From ThemesWiki

Jump to: navigation, search
Applying Theme to an MCMS Site
Official Page
Project Documentation
Download
Source Book
Enhancing Microsoft Content Management Server with ASP.NET 2.0
Enhancing Microsoft Content Management Server with ASP.NET 2.0
ISBN 978-1-904811-52-7
Publisher Packt Publishing
Author(s) Lim Mei Ying, Spencer Harbar, Stefan Goßner

Most websites are designed with a common look and feel in mind. After all, if you went to a site that had pages that are distinctively different from one another, you might start to wonder (probably after the second page) if you were still surfing the same website.

In this tutorial, we will see how a common look and feel can be applied efficiently to an MCMS site by using themes. We will create skins and cascading style sheets and demonstrate how they work together to define the appearance of a site. Finally, we will discuss an essential customization required for themes to work correctly in an MCMS site.

Contents

[edit] Creating a Theme

Let s start by creating a theme for the TropicalGreen site. TropicalGreen is the fictitious gardening society upon which the tutorial's sample website is based. We will name the theme Sunny and use it to apply a new design on our website. In ASP.NET 2.0, themes used by a single project are stored directly below the project s application folder in a special folder named App_Themes. You can create as many themes (or sub folders) as your site requires.

  1. First, let s create the App_Themes folder. In Solution Explorer, right-click the TropicalGreen project and select Add ASP.NET folder | Theme. A new folder named App_Themes appears.
  2. Within the App_Themes folder is a subfolder named Theme1. Rename the folder to Sunny.

[edit] Applying a Theme to the Entire Site

We have created a theme named Sunny. Now let s apply it to the TropicalGreen site.

In the web.config file, look for the <pages> tag and add an attribute named theme and set its value to Sunny.

 <pages validateRequest="false"
  theme="Sunny"/>

We have applied the Sunny theme to the TropicalGreen site. However, the theme doesn t define any styles or designs yet. In the next section, we will add skins and style sheets to define the look and feel of the site.

[edit] Skins for Server Controls

As the name suggests, skins serve as outer covers of controls. Most out-of-the box ASP.NET server controls support skins. Nevertheless, it s always a good idea to check if the control you plan to work with can be skinned. To do so, look for the SkinID property. If the control has a SkinID property, you can apply a skin to it.

There are two types of skins:

  • Default skins', which are applied to all controls that do not have a corresponding skin defined in the SkinID. For example, if you define a default skin for a TextBox control, all TextBox controls on pages that adopt the theme will follow the styles defined in the default skin.
  • Name-controlled skins', which target only controls that share the same SkinID. For instance, you may have a skin for a TextBox defined. Suppose the skin has the SkinID of MyTextBox. Only TextBox controls that have a matching SkinID of MyTextBox will have the skin applied.

[edit] Creating a Default Skin

Consider the case of the SiteMapPath control created earlier in our TropicalGreen.master template. We would like the control to use a colon character (:) as the node separator. The font type should be Verdana. All nodes will be in bold font and green color, with the exception of the current node, which will be colored orange. If we set these properties using code, here s how it would look:

  <asp:SiteMapPath ID="SiteMapPath1" runat="server" Font-Names="Verdana"
  PathSeparator=":">
  <PathSeparatorStyle Font-Bold="True" ForeColor="green"/>
  <CurrentNodeStyle ForeColor="orange"/>
  <NodeStyle Font-Bold="True" ForeColor="green"/>
  <RootNodeStyle Font-Bold="True" ForeColor="green"/>
  </asp:SiteMapPath>

While setting the controls of a property is easily done through code, we can do the same thing using a skin. Before doing so, remove all style properties from the SiteMapPath control such that the code becomes:

 <asp:SiteMapPath ID="SiteMapPath1" runat="server">
 </asp:SiteMapPath>

Now let s build a skin for the SiteMapPath control.

  1. In Solution Explorer, right-click the Sunny folder and select Add New Item
  2. In the Add New Item dialog, choose to create a new Skin File and give it the name Sunny.skin.
  3. The skin file contains sample code that shows how a skin template looks. We won't need the sample for our example. Delete all existing code.
  4. Now let s define a skin for the SiteMapPath control. For a dramatic effect so that we can see the difference in the screenshots later, we will set the font size to be extra large. Enter the following code in the skin file:
      <asp:SiteMapPath runat="server" Font-Names="Verdana" Font-Size= "X-Large" PathSeparator=":">
      <PathSeparatorStyle Font-Bold="True" ForeColor="green"/>
      <CurrentNodeStyle ForeColor="orange"/>
      <NodeStyle Font-Bold="True" ForeColor="green"/>
      <RootNodeStyle Font-Bold="True" ForeColor="green"/>
      </asp:SiteMapPath>
    

The code does not look very different from that in specified the original control. The difference is that the code now sits in a skin file instead of being embedded within the template file, providing a clean separation of design and application!

Since the skin that we have created is a default skin, we don t need to do anything more to see it in action. All SiteMapPath controls on the TropicalGreen site will immediately adopt the style provided by the skin. Navigate to any page on the website. The nodes of the breadcrumb trail now show up with bold green Verdana font, with the exception of the current node, which is orange, with extra large fonts as defined in the skin.

[edit] Creating a Name-Controlled Skin

What happens when there are controls on the page that should not adopt the default skin? For example, in the SiteMapPath control skinned earlier, we set the default skin to display extra-large sized fonts. If we didn t want to change the default skin, and yet apply a different font setting, we could use name-controlled skins.

A name-controlled skin makes use of the SkinID property. Here s how it works. First, we set the SkinID of the control defined in the skin file. Next, we identify the controls in the web form that will adopt that particular skin and set the SkinID of those controls to match the value of the skin. In this way, only controls with the same SkinID will adopt the look and feel of the skin.

Let s try giving the site map control a skin with normal sized fonts. Append a skin control in the Sunny.skin file as follows. Notice that we have set the font size to be Medium and the SkinID to Breadcrumb.

 <asp:SiteMapPath runat="server" Font-Names="Verdana" PathSeparator=":"
  Font-Size="Medium" SkinID="Breadcrumb">
  <PathSeparatorStyle Font-Bold="True" ForeColor="green"/>
  <CurrentNodeStyle ForeColor="orange"/>
  <NodeStyle Font-Bold="True" ForeColor="green"/>
  <RootNodeStyle Font-Bold="True" ForeColor="green"/>
  </asp:SiteMapPath>

In order for the site map control to pick up the name-controlled skin, we need to assign the SkinID of the control to Breadcrumb as well. In the tropicalgreen.master file, set the SkinID of the control as shown:

 <asp:SiteMapPath ID="SiteMapPath1" runat="server" SkinID="Breadcrumb">
 </asp:SiteMapPath>

Save the file. Now, navigate to any of the existing plant postings. The font-size is back to normal, showing that the control now adopts the name-controlled skin instead of the default skin.

[edit] Completing the Skin

Let s complete the skin file by defining skins for both the top horizontal menu and right vertical menu.

First, with Tropicalgreen.master open, remove some of the style elements added to the top menu earlier such that the code looks as follows:

 <asp:Menu ID="Menu1" runat="server" DataSourceID="SiteMapDataSourceTop" SkinID="TopMenu">
 </asp:Menu>

We will also remove any style elements that may have been added to the right menu. The code for the right menu should resemble the following:

<asp:Menu ID="Menu2" runat="server" DataSourceID= "SiteMapDataSourceRight" SkinID="RightMenu">
</asp:Menu>

Now let s add these style elements back using skins. Add the following skin definitions to the

Sunny.skin file.
 <asp:Menu SkinID="TopMenu" runat="server" Orientation="Horizontal"
  MaximumDynamicDisplayLevels="1" StaticDisplayLevels="2"
  StaticMenuItemStyle-CssClass="TopMenu"
  StaticEnableDefaultPopOutImage="False">
 </asp:Menu>
 
 <asp:Menu SkinID="RightMenu" runat="server" Orientation="Vertical"
  MaximumDynamicDisplayLevels="1" StaticDisplayLevels="1"
  StaticMenuItemStyle-CssClass="RightMenu"
  StaticEnableDefaultPopOutImage="False">
 </asp:Menu>

We have added skin definitions for both menus; now set the SkinID of the menus to match those of the skins:

Control ID Set the SkinID to
MenuTop TopMenu
MenuRight RightMenu

Notice that the skin definitions make use of cascading style sheets (see the CssClass property). We have not created any style sheets yet; so if you took a look at any themed page, you probably wouldn't see much difference after applying the new skin. Let's take a look at how skins and style sheets work together within a theme.

[edit] Using Style Sheets

We have seen how skins can be used to apply specific styles on ASP.NET server controls. They are really useful for styling elements such as navigation, form, and other server-side-based controls. Without a doubt, they are pretty nifty and practical tools to have in our toolbox.

However, in the world of MCMS, an important aspect of ensuring that the site has a consistent look and feel throughout is to make sure that content entered by authors in placeholder controls, like the HtmlPlaceholderControl, also follows the style guidelines of the website. In order to enforce design rules of HTML content, skins do not help us very much. For HTML controls and code, we still have to make use of good old cascading style sheets.

Previously, to apply a CSS file to a site, we would add a <link> tag to all template files and web forms that required it:

<link type="text/css" href="UrlOfFile.css"/>

Or, we could embed styles directly within the page itself, using <style> tags:

<style>H1{color:#993300;font-weight:bold;font-size:14pt} </style>

Either technique will work, but still can be a messy affair especially when working with multiple style sheets and web forms.

The good news is: themes support CSS as well. Now, we can add the CSS file directly to the theme. Behind the scenes, ASP.NET will generate the <link> tag for us.

[edit] Creating a Style Sheet

The master template of the TropicalGreen site is made up mostly of tables. In addition, all custom content entered by authors is limited to regular HTML text. As these aren t ASP.NET server controls, they do not support skins. To ensure that the look and feel of such content is consistent with the rest of the site, we need to apply a style sheet.

Let s try adding styling to the rest of the TropicalGreen site by using style sheets.

  1. To add a style sheet to a theme, right-click on the Sunny theme folder in Solution Explorer and select Add New Item
  2. In the Add New Item dialog, choose the Style Sheet template and name the file Sunny.css.
  3. The style sheet that we will use in this example is a relatively simple one. We will specify styles for the body, anchor, and a couple of header levels, as well as a few named styles for the banner and menus. Enter the following code:
      Body {font-family:Verdana,Arial;font-size:11pt}
      A{color:#0000FF;font-size:10pt;font-weight:bold}
      H1{color:#993300;font-weight:bold;font-size:14pt}
      H2{color:#FF6600;font-weight:bold;font-size:12pt}
      .Banner{background-color:#ffcc00}
      .TopMenu{background-color:#66cc33;font-weight:bold;color:#CCFF99;
      text-decoration:none;padding:5px;}
      .RightMenu{background-color:#669900;color:#CCFF99; text-decoration: none;
      font-weight:bold;padding:5px;}
      
  4. Save Sunny.css.
(We won t go into the details of the syntax of CSS. For a primer on CSS, take a look at the following website:http://www.w3c.rl.ac.uk/primers/css/cssprimer.htm.)

Note- Visual Studio also ships with a Style Builder that provides a graphical interface for defining styles. To launch the Style Builder, toggle to Design view and select Format |Style from the menu.

Now, take a look at any existing plant posting on the TropicalGreen website. The styles have been successfully applied on the posting.

[edit] Why Themes with Style Sheets May Not Work on MCMS Sites

Look at the HTML source of the posting (right-click anywhere on the page and select View Source) and take a closer look at the <link> tag generated by the theme. It should resemble the one that follows:

 <link href="../App_Themes/Sunny/Sunny.css"
 type="text/css" rel="stylesheet"/>

The URL stored in the <link> tag is a path that is relative to that of the template file. In this example, the preceding dot-dot-slash (../) characters indicate that the App_Themes folder is stored in the same parent folder as the current page. In a regular web application, referring to the style sheet using its relative path works correctly. After all, if you looked at the file structure, you can indeed find the style sheet at the suggested path.

However, when viewing a posting, we aren t accessing the template file directly. For example, to view the Aloe Vera posting, we request the URL http://tropicalgreen/PlantCatalog/AloeVera.htm The browser, not knowing that the MCMS ISAPI filter has rewritten the URL, attempts to retrieve the file by converting the relative path to the absolute path http://tropicalgreen/PlantCatalog/../App_Themes/Sunny/Sunny.css This path will, however, not evaluate to the real location of the CSS file, as the folder that s one-level above the AloeVera.htm page is tropicalgreen. Also, because the App_Themes folder is stored in the Templates project directory (see the following screenshot), looking for it under tropicalgreen (or the root website) will result in an HTTP 404 error.

If using relative paths results in HTTP 404 errors when accessing the style sheet, how then, did the style get successfully applied to the posting? The secret lies in the RobotMetaTag. In MCMS websites, the RobotMetaTag has a RenderBaseHref property, which when set to true (its default value), will generate a <base> tag between the <head> tags of the page. Search the generated HTML source of the posting, and you will find the <base> tag that looks like the following:

 <base href="http://tropicalgreen/tropicalgreen/Templates/Plant.aspx">

When evaluating links on a page, the web browser automatically prefixes all relative links with the value stored in the href attribute of the <base> tag (instead of the requesting URL for the page) such that a relative path like ../App_Themes/Sunny/Sunny.css becomes http://tropicalgreen/tropicalgreen/Templates/../App_Themes/Sunny/Sunny.css

The converted URL does lead to the style sheet, so we don t get any HTTP 404 errors.

By default, the RenderBaseHref property of the RobotMetaTag control stores the Boolean true. So usually, the <base> tag is generated and you won t have to worry about paths to style sheets and other relative links being inaccessible. However, depending on the site setup, there may be special situations where generating a <base> is not desirable.

Consider the case of a hosting scenario where a firewall or proxy forwards the request to an internal web server. The trouble with the MCMS-generated <base> tag is that it contains the full URL, which may include the server s name, especially when the "map channels to host headers" option has been turned off. When the internal server returns the page back to the proxy, the <base> tag of the returned page will contain the name of the internal server instead of the site s domain name as entered in the browser.

To simulate this scenario, try viewing the postings again, but this time by using a URL like http://servername/tropicalgreen/plantcatalog/aloevera.htm . Notice that when you do so, the server s name is stored in the <base> tag as such: <base href="http://servername/tropicalgreen/tropicalgreen/Templates/Plant.aspx ">

(Of course, the usual URL omits the server's name and will just be http://tropicalgreen/plantcatalog/aloevera.htm. But since we are testing to see how the server name creeps into the base URL, we have added it in for this exercise.)

Unless the user s browser has access to the internal server, it will likely receive HTTP 404 errors when attempting to access resources like style sheets. In such cases, the RenderBaseHref property has to be disabled and an alternative solution found.

[edit] Applying Themes when the RenderBaseHref Property is Disabled

In case the <base> tag can t be used, we need to have a different way to ensure that the App_Themes folder is accessed with the correct URL. As ASP.NET has no way to configure the way it generates the URL to the theme s folder, we need to modify this URL after ASP.NET has generated it.

There are many ways to address the problem. We could manually add code to each and every template file that has the theme applied. However, not only would that be tedious, but repeating code in multiple places is also not good programming practice. We could create a server-side control to encapsulate the code and insert it into each template file that requires it. Again, this requires code for the control to be copied into every template file there s still the possibility that it may get forgotten, especially when new templates are created.

The solution is to build an HTTP module. In this way, we can guarantee that all pages will have the correction without having to touch any of the existing template files. Before the page is sent to the browser, the HTTP module will convert all relative paths stored in <link> tags to absolute paths that do not contain the name of the server. The module will look for <link> tags such as the one for the style sheet: <link href="../App_Themes/Sunny/Sunny.css" type="text/css" rel="stylesheet"/> After the conversion process, the <link> tag will be rendered as:

<link href="/tropicalgreen/App_Themes/Sunny/Sunny.css" type="text/css" rel="stylesheet"/>

[edit] Creating the CorrectThemes HTTP Module

Let s create the HTTP module that corrects the URL stored in the <link> tags.

  1. Add a new Visual C# Class Library project to the solution. Name the project TropicalGreenHttpModules.
  2. Delete the Class1.cs file created by the wizard. We won t need it.
  3. Add the following reference required by the project: Microsoft.ContentManagement.Publishing.dll (The library file is located in the <install directory>\Microsoft Content Management Server\Server\bin\ directory). As well as the following references from the System namespace:System.Web
  4. Add a class file to the TropicalGreenHttpModules project. Name the new class file CorrectLinksInThemes.cs.
  5. Add the following using statements above the namespace declaration:
      
    using System;
      . . . code continues . . .
      using System.Web;
    
      using System.Web.UI;
    
      using System.Web.UI.HtmlControls;
    
      using Microsoft.ContentManagement.Publishing;
     
      namespace TropicalGreenHttpModules
      {
      . . . code continues . . .
      }
    
  6. As we are creating an HTTP module, the class will implement the IHttpModule interface. Add a colon followed by the interface name as shown in the following code:
    public class CorrectLinksInThemes : IHttpModule
      {
      }
    
  7. The IHttpModule requires the implementation of both the Init() and Dispose() methods. Add the following code to the CorrectLinksInThemes class file:
      public class CorrectLinksInThemes : IHttpModule
      {
      public void Init(HttpApplication httpApp)
    
     {
    
     }
    
     public void Dispose()
    
     {
    
     }
      }
    
  8. As all <link> tags stored in the page are analyzed before the page is rendered, we will write the code in the OnPreRender() event handler of the page object. The code loops through each control found in the page header. If the control is a <link> tag, it checks if the link is relative, and converts it to an absolute link if necessary. Add the OnPreRender() event handler below the Dispose() method.
      public void OnPreRender(object sender, EventArgs eventArgs)
      {
      Page currentPage = sender as Page;
      HtmlHead pageHeader = currentPage.Header as HtmlHead;
      if (pageHeader != null)
      {
      foreach (Control control in pageHeader.Controls)
      {
      HtmlLink link = control as HtmlLink;
      if (link != null &&
      VirtualPathUtility.IsAppRelative(link.Href))
      {
      link.Href = VirtualPathUtility.ToAbsolute (link.Href);
      }
      }
      }
      }
    
  9. We need to register the OnPreRender() event handler that we have written earlier to the PreRender() event of the page. A good place to do so would be within the OnPreRequestHandlerExecute() event handler of the current HTTP application. The OnPreRender() event handler will only be registered if the request is for a ChannelItem (e.g. a channel-rendering script or a posting). We wrap the code around a try-catch block, as an exception may be raised if the page was requested when the user s form s login has expired. In such a case, we will ignore the error as the page will not be rendered.
      public void OnPreRequestHandlerExecute(object sender, EventArgs e)
      {
      HttpContext ctx = ((HttpApplication)sender).Context;
      IHttpHandler handler = ctx.Handler;
     
      // Only execute for ASP.NET page and master page
      // http handler not for custom
      // http handler or for the ASP.NET 2.0 handler to handle
      // embedded resource items.
      // Check handler to see if it starts with "ASP."
      if (handler.GetType().ToString().StartsWith("ASP."))
      {
      try
      {
      if (CmsHttpContext.Current.ChannelItem != null)
      {
      ((System.Web.UI.Page)handler).PreRender +=
      new EventHandler( this.OnPreRender);
      }
      }
      catch
      {
      // An exception may be raised if the request is made
      // in the middle of
      // an expired forms authentication login
      // Just ignore this.
      }
      }
      }
    
  10. Finally, we register the OnPreRequestHandlerExecute() event handler to the PreRequestHanderExecute event of the current HttpApplication within the Init() event of the HTTP module.
      public void Init(HttpApplication httpApp)
      {
    
      httpApp.PreRequestHandlerExecute += new
    
      EventHandler(this.OnPreRequestHandlerExecute);
      }
    

The HTTP module is complete. Save and build the project. To add the module to the TropicalGreen project, insert the following code in TropicalGreen s web.config file, between the <httpModules> tag.

 <add type="TropicalGreenHttpModules.CorrectLinksInThemes,
 TropicalGreenHttpModules" name="CorrectLinksInThemes"/>

In addition, add the TropicalGreenHttpModules project (or the TropicalGreenHttpModules.dll library) as a reference to the TropicalGreen project.

The next time you view a posting with both themes applied and the RenderBaseHref property of the RobotMetaTag disabled, all style sheets will be correctly applied. Take a look at the HTML source, and you will find that the HTTP module has corrected all relative links to absolute links.

[edit] Source

The source of this content is Chapter 5: Applying Themes of Enhancing Microsoft Content Management Server with ASP.NET 2.0 by Lim Mei Ying, Spencer Harbar, Stefan Goßner (Packt Publishing, 2006).