Branding a MOSS Corporate Intranet Portal, Part 3: LAYOUTS Pages


Introduction


In order to get a consistent look and feel across all the pages, we had to brand the LAYOUTS pages as well. Since we needed webapp-specific branding and didn’t want to deal with the additional effort involved in branding & testing SSPAdmin pages, I started out following what eventually came to be known as Method 2 from KB944105. After a bit of prototyping, it looks like we’ll be going with the HttpModule-based approach, plus a few exceptions handled by Method 1 from KB944105. Performance testing is coming up in the next couple of weeks, so I’ll be sure to share how that shakes out (Method 2 is our fallback if performance is a big drag).


Thanks to Liam Cleary, whose code gave me a huge kickstart on this solution.


BrandingModule Project


The BrandingModule project consists of three components:


·         ResourceRedirect Class – HTTPModule used to execute master page and other resource redirects


·         RedirectSectionHandler Class – Configuration file section handler for processing ResourceRediect configuration settings


·         Redirect Structure – Structure used to capture individual configuration setting elements for ResourceRedirect


Redirect Structure


The Redirect Structure is a simple structure for capturing configuration settings for all five types of redirects. Usage by type is detailed in the description.


Public Members





























Member


Type


Inheritance


Description


pattern


Property


n/a


·         pageRedirects  -Captures the CONTAINS pattern-match filter


·         destinationRedirects – Captures the CONTAINS pattern-match filter


·         pathRedirects – Captures the STARTS WITH pattern-match filter


·         comboRedirects – Captures the STARTS WITH pattern-match filter


·         masterRedirects – not used


masterPageUrl


Property


n/a


·         pageRedirects  -identifies replacement master page


·         destinationRedirects – not used


·         pathRedirects – identifies replacement master page


·         comboRedirects – identifies replacement master page


·         masterRedirects – identifies replacement master page


originalMaster


Property


n/a


·         pageRedirects  – identifies original master page used to identify a class of pages


·         destinationRedirects – not used


·         pathRedirects – not used


·         comboRedirects – not used


·         masterRedirects – identifies original master page used to identify a class of pages


destinationPageUrl


Property


n/a


·         pageRedirects  – not used


·         destinationRedirects – identifies redirect page


·         pathRedirects – not used


·         comboRedirects – not used


·         masterRedirects – not used


RedirectSectionHandler class


The RedirectSectionHandler is a custom implementation of the System.Configuration.IConfigurationSectionHandler class.


Public Members














Member


Type


Inheritance


Description


Create


Object


IConfigurationSectionHandler


Returns a collection of Redirect objects and performs minimal validation on entries. Throws a ConfigurationErrorsException if either of the following conditions is true:


·         masterPageUrl and destinationPageUrl are blank/missing


·         pattern and originalMaster are blank/missing


ResourceRedirect Class


RecourceRedirect is a custom implementation of System.Web.IHttpModule.


Public Members





























Member


Type


Inheritance


Description


Init


Method


IHttpModule


Adds the context_PreRequestHandlerExecute event handler to the PreRequestHandlerExecute event handler.


Dispose


Method


IHttpModule


Non-implemented stub.


context_PreRequestHandlerExecute


Method


n/a


Adds the page_PreInit event handler to the PreInit event handler if the current handler is a page.


page_PreInit


Method


n/a


Processes redirect instructions as specified in the configuration file. Redirects are processed in the following order:


1.       Destination Redirects identified in the Branding/destinationRedirects configuration section.


2.       Combination Redirects identified in the Branding/comboRedirects configuration section.


3.       Path Redirects identified in the Branding/pathRedirects configuration section.


4.       PageRedirects identified in the Branding/pageRedirects configuration section.


5.       Master Page Redirects identified in the Branding/masterRedirects configuration section.


 


Private Members














Member


Type


Inheritance


Description


UpdateLog


Method


n/a


Writes event log entries. Not used – intended for diagnostic purposes only.


Configuration Settings


The ResourceRedirect requires three sets of configuration settings to be registered in a web application:


·         SectionGroup configuration


·         HttpModule configuration


·         Branding configuration


SectionGroup Configuration


The custom section group and sections for the Branding configuration require registration in the web.config file. The <configSection/> element REQUIRES the following:


<sectionGroup name=Branding>


      <section name=pageRedirects


      type=MOSS.Branding.RedirectSectionHandler,MOSS.Branding, Version=1.0.0.0, Culture=neutral, PublicKeyToken=bbbbccccddddeeee/>


      <section name=pathRedirects


      type=MOSS.Branding.RedirectSectionHandler,MOSS.Branding, Version=1.0.0.0, Culture=neutral, PublicKeyToken=bbbbccccddddeeee/>


      <section name=comboRedirects


      type=MOSS.Branding.RedirectSectionHandler,MOSS.Branding, Version=1.0.0.0, Culture=neutral, PublicKeyToken=bbbbccccddddeeee/>


      <section name=masterRedirects


      type=MOSS.Branding.RedirectSectionHandler,MOSS.Branding, Version=1.0.0.0, Culture=neutral, PublicKeyToken=bbbbccccddddeeee/>


      <section name=destinationRedirects


      type=MOSS.Branding.RedirectSectionHandler,MOSS.Branding, Version=1.0.0.0, Culture=neutral, PublicKeyToken=bbbbccccddddeeee/>


</sectionGroup>


HttpModule Configuration


The ResourceRedirect module must be added to the <httpModules/> element to be registered with the web application. This element must contain the following entry:


<add name=ResourceRedirect type=MOSS.Branding.ResourceRedirect,MOSS.Branding, Version=1.0.0.0, Culture=neutral, PublicKeyToken=bbbbccccddddeeee />


 Branding Configuration


The <Branding/> section group and all of its subsections are REQUIRED. Any or all of the subsections can be empty. A sample <Branding/> section group appears below:


<Branding>


      <pageRedirects>


            <redirect pattern=settings.aspx masterPageUrl=~/_layouts/customizations/newapplication.master />


            <redirect pattern=error.aspx masterPageUrl=~/_layouts/customizations/newsimple.master />


      </pageRedirects>


      <destinationRedirects>


            <redirect pattern=/_layouts/AccessDenied.aspx destinationPageUrl=/_layouts/customizations/AccessDenied.aspx />


      </destinationRedirects>


      <pathRedirects>


            <redirect pattern=/sites/branding/_layouts/create.aspx masterPageUrl=~/_layouts/customizations/newapplication2.master originalMaster=“”/>


      </pathRedirects>


      <comboRedirects>


            <redirect pattern=/sites/branding/_layouts/ masterPageUrl=~/_layouts/customizations/newsimple2.master originalMaster=simple.master/>


      </comboRedirects>


      <masterRedirects>


            <redirect masterPageUrl=~/_layouts/customizations/customsimple.master originalMaster=simple.master/>


            <redirect masterPageUrl=~/_layouts/customizations/customapplication.master originalMaster=application.master/>


      </masterRedirects>


</Branding>


“12” Customizations and Branding Redirects


We had three types of customizations to the “12” directory: modifications, new additions, and redirect additions.


Changes to the “12” Directory


The files listed below were actual customizations, subject to KB944105 Method 1.



















Component


Description


Location


Modification Purpose


SPTHEMES.XML


Defines the manifest of themes installed with SharePoint.


TEMPLATE\LAYOUTS\1033


Added a custom theme to the themes manifest.


NAVSHAPE.GIF
FORMTITLEGRAD.GIF
PAGETITLEBKGD.GIF


Background images typically appearing in the left and header areas of SharePoint content pages.


TEMPLATE\IMAGES


Replaced with images adhering to the branding look & feel guidelines to impose branding on non-customizable pages (specifically the “Operation in Progress” page).


ADDITION to this list as of 26JAN2008:











CORE.CSS


Core stylesheet for WSS 3.0.


LAYOUTS\1033\STYLES


Includes customizations to the search control styles, which cannot be overridden by a theme.

 


Take special note on the middle three files – you can’t directly modify the “Gears” page, so the only way of customizing it is indirectly via styles and changes to the graphics files it uses.


NEW Additions to the “12” Directory


These are the all-new files that had no corresponding file in the OOTB “12” directory.







































File name


Installed Location


Purpose


Notes


company.gif


IMAGES


Company standard logo (referenced in the custom default.master & MWSdefault.master).


Appears on all branded master pages.


themes.css


THEMES\CONTOSO


Defines the custom styles for the company theme.


Customized from the SharePoint standard “simple” theme.


mossExtension.css


THEMES\CONTOSO


???


Copied from the SharePoint standard “simple” theme.


CONTOSO.INF


THEMES\CONTOSO


Information file for the company standard theme.


Customized from the SharePoint standard “simple” theme.


navBullet_contoso.gif


THEMES\CONTOSO


Bullet icon for left navigation items.


Recolored version of navBullet_simple.gif from the SharePoint standard “simple” theme.


alldayOver_simple.gif


allday_simple.gif


ApplyFiltersActive.gif


ApplyFiltersHoverOver.gif


ApplyFiltersInactive.gif


formtitlegrad_simple.gif


linksectiongrad_simple.gif


listheadergrad_simple.gif


navBullet_simple.gif


pageTitleBKGD_simple.gif


partgrad_simple.gif


portaltabhover.gif


portaltabselected.gif


portraitbackground.gif


quickLaunchHeader_simple.gif


toolgrad_simple.gif


topnavhover_simple.gif


topnavselected_simple.gif


viewheadergrad_simple.gif


weekbox_simple.gif


THEMES\CONTOSO


Icons for the company theme.


Copied from the SharePoint standard “simple” theme.


ADDITION to this list as of 26JAN2008:











theme.css


LAYOUTS\customizations


Duplicate of themes.css from THEMES\CONTOSO


Used to apply theme styles to pages that can’t reference/access the theme.

 



Replacements/Redirects for the “12” Directory


These are all the files that required ResourceRedirect configurations.


Items in red font were added/updated 26JAN2008.














































File name


Original Location


Redirect Location


Purpose


Notes


CORE.CSS


LAYOUTS\1033\STYLES


LAYOUTS\1033\STYLES\Customizations


Core stylesheet for WSS 3.0.


Includes customizations to the search control styles, which cannot be overridden by a theme.


NEW_application.master


LAYOUTS


LAYOUTS\Customizations


Master page for most non-dialog system pages.


 


NEW_simple.master


LAYOUTS


LAYOUTS\Customizations


Non-themed master page for all nonsecured system pages.


 


theme.css


LAYOUTS


LAYOUTS\Customizations


Used to apply styles of company standard theme to simple.master, which cannot retrieve site theme due to security restrictions.


Duplicate of themes.css from THEMES\CONTOSO .


templatepick.aspx


LAYOUTS


LAYOUTS\Customizations


Site Template selection page


Added reference to LAYOUTS\Customizations\theme.css


SiteManager.aspx


LAYOUTS


LAYOUTS\Customizations


Site Content & Structure page


Added custom header and footer



The associated <Branding/> configuration for applying the appropriate redirects for these files appears below. We installed the ResourceRedirect on all of our end user-facing web applications, thus leaving Central Admin and SSP Admin as-is. This also gives us the flexibility to have different branding for our webapps down the road if necessary, since both the feature stapling and the HttpModule are configured at the WebApp level.


<Branding>


      <pageRedirects/>


      <destinationRedirects>


            <redirect pattern=/_layouts/AdminRecycleBin.aspx destinationPageUrl=/_layouts/customizations/AdminRecycleBin.aspx />


            <redirect pattern=/_layouts/osssearchresults.aspx destinationPageUrl=/_layouts/customizations/osssearchresults.aspx />


            <redirect pattern=/_layouts/SiteManager.aspx destinationPageUrl=/_layouts/customizations/SiteManager.aspx />


            <redirect pattern=/_layouts/templatepick.aspx destinationPageUrl=/_layouts/customizations/templatepick.aspx />


      </destinationRedirects>


      <pathRedirects/>


      <comboRedirects/>


      <masterRedirects>


            <redirect masterPageUrl=~/_layouts/customizations/customsimple.master originalMaster=simple.master/>


            <redirect masterPageUrl=~/_layouts/customizations/customapplication.master originalMaster=application.master/>


      </masterRedirects>


</Branding>


Code Snippets and Additional Comments

ResourceRedirect.cs


Updated 26JAN2006


The ResourceRedirect code appears below. Most of this is self-explanatory – especially if you read Liam Cleary’s article – but there are a few points worth calling out:


·         The HttpContext.Current.Request.RawUrl is used for pattern matches. This is the only property that includes the relative site URL for LAYOUTS files (i.e. /sites/test/_layouts/settings.aspx instead of /_layouts/settings.aspx). Without this, site & site collection-specific LAYOUTS customizations would be impossible.


·         The matchFound variable is essential for enforcing bailouts once a match is found, as per the “precedence” logic described earlier.


using System;


using System.Collections.Generic;


using System.Collections;


using System.Text;


using System.Web;


using System.Web.UI;


using System.Configuration;


using System.Diagnostics;


 


 


namespace MOSS.Branding


{


    public class ResourceRedirect : IHttpModule


    {


        public void Init(HttpApplication context)


        {


            context.PreRequestHandlerExecute += new EventHandler(context_PreRequestHandlerExecute);


        }


 


        void context_PreRequestHandlerExecute(object sender, EventArgs e)


        {


            HttpApplication httpApp = sender as HttpApplication;


 


            if (httpApp != null)


            {


                Page page = httpApp.Context.CurrentHandler as Page;


 


               


                if (page != null)


                {


                    page.PreInit += new EventHandler(page_PreInit);


                }


               


            }


        }


 


        void page_PreInit(object sender, EventArgs e)


        {


 


            Page page = sender as Page;


 


            string currentMaster = String.Empty;


           


            string currentPath = HttpContext.Current.Request.RawUrl.ToLower();


 


            ArrayList masterRedirects = null;


            ArrayList pageRedirects = null;


            ArrayList pathRedirects = null;


            ArrayList comboRedirects = null;


            ArrayList destinationRedirects = null;


 


            //retrieve remappings by type to enable enforcing precedence


           


            masterRedirects = (ArrayList)ConfigurationManager.GetSection(“Branding/masterRedirects”);


            pageRedirects = (ArrayList)ConfigurationManager.GetSection(“Branding/pageRedirects”);


            pathRedirects = (ArrayList)ConfigurationManager.GetSection(“Branding/pathRedirects”);


            comboRedirects = (ArrayList)ConfigurationManager.GetSection(“Branding/comboRedirects”);


            destinationRedirects = (ArrayList)ConfigurationManager.GetSection(“Branding/destinationRedirects”);


 


            bool matchFound = false;


 


            IEnumerator redirectEnum = null;


 


            if (page != null)


            {


                if (destinationRedirects.Count > 0) //check for full redirect


                {


                    redirectEnum = destinationRedirects.GetEnumerator();


                    while (redirectEnum.MoveNext())


                    {


                        Redirect destinationRedirect = (Redirect)redirectEnum.Current;


                        if (currentPath.Contains(destinationRedirect.pattern))


                        {


                           


                            HttpContext.Current.Response.Redirect(currentPath.Replace(destinationRedirect.pattern,


                                destinationRedirect.destinationPageUrl));


                            matchFound = true;


                        }


                    }


                }


                if (page.MasterPageFile != null)


                {


                    currentMaster = page.MasterPageFile.ToLower();


                    if ((comboRedirects.Count > 0) && (!matchFound))//check for combo remaps first


                    {


                        redirectEnum = comboRedirects.GetEnumerator();


                        while (redirectEnum.MoveNext())


                        {


                            Redirect comboRedirect = (Redirect)redirectEnum.Current;


                            if ((currentPath.StartsWith(comboRedirect.pattern)) && (currentMaster.Contains(comboRedirect.originalMaster)))


                            {


                                page.MasterPageFile = comboRedirect.masterPageUrl;


                                matchFound = true;


                            }


                        }


                    }


                    if ((pathRedirects.Count > 0) && (!matchFound)) //check for path-based remaps second


                    {


                        redirectEnum = pathRedirects.GetEnumerator();


                        while (redirectEnum.MoveNext())


                        {


                            Redirect pathRedirect = (Redirect)redirectEnum.Current;


                            if (currentPath.StartsWith(pathRedirect.pattern))


                            {


                                page.MasterPageFile = pathRedirect.masterPageUrl;


                                matchFound = true;


                            }


                        }


                    }


                    if ((pageRedirects.Count > 0) && (!matchFound)) //check for page-based remaps third


                    {


                        redirectEnum = pageRedirects.GetEnumerator();


                        while (redirectEnum.MoveNext())


                        {


                            Redirect pageRedirect = (Redirect)redirectEnum.Current;


                            if (currentPath.Contains(pageRedirect.pattern))


                            {


                                page.MasterPageFile = pageRedirect.masterPageUrl;


                                matchFound = true;


                            }


                        }


                    }


                    if ((masterRedirects.Count > 0) && (!matchFound)) //check for master page remaps last


                    {


                        redirectEnum = masterRedirects.GetEnumerator();


                        while (redirectEnum.MoveNext())


                        {


                            Redirect masterRedirect = (Redirect)redirectEnum.Current;


                            if (currentMaster.Contains(masterRedirect.originalMaster))


                            {


                                page.MasterPageFile = masterRedirect.masterPageUrl;


                            }


                        }


                    }


                }


            }


 


        }


 


        private void UpdateLog(string Message, EventLogEntryType msgType)


        {


            try


            {


                System.Diagnostics.EventLog.WriteEntry(“ResourceRedirect”, Message, msgType);


            }


            catch


            {


                //ignore


            }


        }


 


        public void Dispose()


        {


        }


 


    }


}


 


 


 

Comments (8)

  1. If you’re into MOSS customizations, check out Brett Geoffroy’s MSDN blog. He’s done a sweet job organizing

  2. If you’re into MOSS customizations, check out Brett Geoffroy’s MSDN blog . He’s done a sweet job organizing

  3. Ein sehr ausführliche Best-Practice Anleitung zum Theme SharePoint Anpassung mit vielen Hintergrundinformation

  4. Eine sehr ausführliche Best-Practice Anleitung zum Theme SharePoint Anpassung mit vielen Hintergrundinformationen

  5. aeberhar says:

    This is a great series of posts.

    I’ve implemented many of your suggestions on several installations, though not with quite the degree of code-level formality/reusability you’ve created here.  I found I had to create a timer job to recurse the sites in a site collection (or, in the case of My Sites, the site collections in a web application) to periodically ensure that admins seeking unsanctioned distinction for their site(s) hadn’t undone the branding customizations (typically they used a pieced together URL to _layouts/changesitemasterpage.aspx or _layouts/themeweb.aspx as I’ve hidden the links to the Themes and Master Page settings pages listed on the site settings page).

    I do think I noticed a couple of errors in this post, though they are minor:

    The second section discussing <Branding> nodes contains invalid XML

    One of your <sectionGroup> sections still contains a public key reference (mentioned only because you appear to have wanted to conceil that throughout these posts)

    Thanks again!

    Andrew Eberhard

  6. Eine sehr ausführliche Best-Practice Anleitung zum Theme SharePoint Anpassung mit vielen Hintergrundinformationen

  7. Several folks have asked for this, so here it is… the two other pieces of the ResourceRedirect solution

  8. Several folks have asked for this, so here it is… the two other pieces of the ResourceRedirect solution