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()

        {

        }

 

    }

}