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