(long winded title I know – just trying to assist anyone who might find this article of any use)
Ok – so this post will hopefully outline my preferred approach in packaging and activating a custom master page and associated supporting files (.css, .js and images etc) without needing to activate the publishing features (and thus giving you the option to set through the UI)
It’s also the standard I’ve just implemented within my current employer - Myriad Technologies – a Brisbane SharePoint Consulting Firm here in Australia… click here for more info ..
There are a couple of other little tricks/patterns demonstrated in the article… namely:
- Creating / Ensuring Master Page Compatibility with a search centre / minimal.master
- Setting an icon of a feature
- Creating a feature with a feature receiver
- Feature Stapling
This is not an article on how to create a custom brand or style for SharePoint 2010, nor is it an article explaining the ins and outs of masterpage development. It’s simply a framework or pattern that can be used to ensure branding and/or style compliance for all new site’s and site collections created in your farm, web application or site collection irrespective of SKU (Foundation, Standard or Enterprise) and/or relying on the Publishing Features activation or user intervention.
A little visual studio knowledge is required as it will be utilized to create the wsp. Also note I’ve chosen to use wsp builder to do my packaging… Purely because it’s what I've been using for the last 4 or 5 years and is what I'm most comfortable with. The same pattern can be designed and built utilizing the Visual Studio 2010 SharePoint Project types if required.
(Note: You will need the WSPBuilder Extensions 2010 Beta 1.4 package for integration with Visual Studio 2010 and SharePoint 2010)
One final thing to note before I jump in - Having a development background, I refer to a site collection as a site and a sub site or root site as a web
Solution Overview
The end solution consists of three features as outlined in the table below.
Feature | Scope | Purpose |
CustomBrandingDeployment | Site | Contains the Master Page, CSS File(s), Images and other supporting files required by the custom brand/style. Activation of this feature will see the Master Pages deployed to the Master Page Catalogue in the root web of the site collection, and depending on how where you want your supporting files to live, the CSS, Javascript and/or Image files deployed to the Style Library and/or Layouts Directories |
CustomBrandingApplication | Web | This feature does not contain any resources, but is wired up to a block of code to execute at time of feature activation. It is responsible for configuring the current web to use the master page(s) deployed by the CustomBrandingDeployment feature. |
CustomBrandingApplicationStapler | Site* | This feature is responsible for stapling the ‘CustomBrandingApplication’ feature to site definitions. After a feature has been ‘stapled’ to an existing site definition, it’ll immediately be activated by the web provisioning process whenever a new web using that definition is being created. |
*Note:The Scope of the ‘CustomBrandingApplicationStapler’ can be either site, webapplication or farm. By making it scoped at the farm, all new webs created in the farm (irrespective of web application or site collection will be branded). If you scope it at a web application, all webs created in a site collection that exist in a web application that has the feature activated will be branded. A site collection scope will see all webs created in that site collection branded accordingly.
Depending on the individual requirements, any one of these scopes may be used. At minimum, it’s designed to work at the site collection level. If you do scope it above this (webapplication or farm) you will need to also use stapling to ensure the ‘CustomBrandDeployment’ feature is also automatically activated at the site collection level. If not, the masterpage resources won’t be deployed and your new sites will break!
Step by Step
Contents
Step 1. Create a New WSP Builder Project
Step 2. Create the CustomBrandingDeployment feature
Step 3. Create the Master Page
Step 4. Create the Minimalistic Master Page
Step 5. Create / Add any supporting files to the CustomBrandingDeployment feature
Step 6. Configure the CustomBrandingDeployment Feature to deploy the files
Step 6. Create the CustomBrandingApplication feature
Step 7. Create the Feature Receiver for the CustomBrandingApplication feature
Step 8. Create the CustomBrandingApplicationStapler Feature
Step 9. Package
Step 10. Deploy
Step 11. Verify Deployment
Step 12. Activate Features
(It looks like a bit to get through – but after a couple iterations and assuming styling/master page development is done, you’ll have these steps done within less than an hour)
Create a New WSP Builder Project
- Create a new project in visual studio 2010 choosing the WSPBuilder –> WSPBuilder Project Template. Note that by default, the name of the root folder your solution exists in will be the name of your WSP file once packaged.
- Your solution explorer should now look like this:
Note that WSP Builder Extensions 1.4 will see the creation of a ‘SharePointRoot’ folder instead of a ‘12’ folder.
Create the CustomBrandingDeployment feature
- Right Click your ‘CustomBrand’ Project and choose Add –> New Item
- Select WSPBuilder –> Blank Feature and give the feature a name ‘CustomBrandingDeployment’. click ‘Add’
- Provide appropriate details in the Feature Wizard Dialogue. Ensure the feature is scoped at the ‘Site’ level and the Event handler option is unchecked. Click ‘Finish’
- The wizard will modify your project folder adding the required folders and files.
Create and Add the Master Page
Note: the following steps are provided under the assumption you are going to develop your master page from the OOB v4.master in the layouts directory. If this is not the case, be sure to add your custom master page from where ever it might currently live.
- Create a new project folder under ‘CustomBrandingDeployment’ called ‘MasterPage’
- Right click new folder and ‘Add Existing Item’
- Add v4.master from the directory:
c:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS
Note: If you have a pre-developed master page sitting somewhere, obviously you would browse to that master page… for the purpose of this demonstration however, I’m using a copy of the OOB v4.master - Rename the Master Page (if you are developing from scratch, this step is required, otherwise skip it!)
- Create / Modify Master Page (if you are developing from scratch, this step is required, otherwise skip it!)
As I said earlier, I’m not about to dive in and give you a detailed set of instructions around master page development for SharePoint 2010. This article is focusing on the packaging, deployment and activation aspect of a custom brand solution. As a result, My Custom Master page is going to be a copy of the v4.master from the layouts directory with the addition of a simple image top left hand corner.
Step 4. Create the Minimalistic Master Page
Any custom branding solution that requires a custom master page generally also requires a minimalistic master page. Why? Web’s created from the Basic, Enterprise or Fast search centres will not work with a page derived from v4.master. It’s unfortunate, but true. If you don’t believe me, skip this step and come back to me when you have learnt the hard way :)
Technically, the search centre definitions define ‘Minimal.master’ as the master page for the provisioned webs. In turn, the pages created during the provisioning of a web deriving from a search site definition place controls in the place holders as required by the Minimal.master master page. Place holder locations between minimal.master and v4.master are not the same causing the main search textbox to be rendered in the dropdown breadcrumb control.
‘So what’ you might say? There’s no scope to include a search site in my deployment?… fair call – but what happens when your client happens to try to create a search site after you've gone and delivered your solution? FAIL!
Before I go any further: Full Credit for these steps to Randy Drisgill
So, assuming you developed your master page using v4.master as a base design, follow the steps below to convert that master page into one that can be used by the search site definitions. Even if you’ve some other method to get to where you are now, the guide below should provide you enough information to get you up and running.
- Make copy of your custom master page in the same directory calling it something appropriate
- Open the minimal master page for edit
- Locate Remove (actually ‘cut’ – you’ll need them for step 4) the <asp:ContentPlaceHolder id="PlaceHolderTitleBreadcrumb" runat="server"> tag and corresponding closing tag. (The tag envelopes a <SharePoint:ListSiteMapPath … tag…. ensure this remains!)
- Paste the placeholder tags removed in step 3 directly before the <asp:ContentPlaceHolder id=”PlaceHolderMain” tag
- Wrap the <asp:ContentPlaceHolder id=”PlaceHolderPageTitleInTitleArea” runat=”server”> tags (And any surrounding html) in a hidden div, remove the <SharePoint:ClusteredDirectionalSeparatorArrow runat=”server” /> directly before this.
- Wrap the <asp:ContentPlaceHolder id="PlaceHolderGlobalNavigation" runat="server"> tag and all it’s contents in a hidden div
- Add CSS Below to the head
<style type="text/css">
/* remove left margin */
.s4-ca {
margin-left: 0px;
}
/* remove gray background at top (optional) */
.srch-sb-results {
background:transparent none repeat scroll 0 0;
}
/* clean up top padding on 1st search page */
.srch-sb-main {
padding-top: 20px;
}
/* remove centering on 1st search page (optional) */
.srch-sb-results4 {
margin: inherit;
padding-left: 20px;
}
/* remove background color on 1st search page (useful for colored designs) */
.ms-bodyareaframe {
background-color: transparent;
}
/* ------------------------------------------ */
/* -- CSS that may be req. to reset the search styling -- */
/* ------------------------------------------ */
/* fix height of area above search results */
td.ms-titleareaframe, div.ms-titleareaframe, .ms-pagetitleareaframe {
height: auto !important;
}
/* fix border color on search results */
.ms-main .ms-ptabrx, .ms-main .ms-sctabrx, .ms-main .ms-ptabcn, .ms-main .ms-sctabcn {
border-color: #eeeeee;
}
/* fix arrangement of body area on search results */
.srch-sb-results {
height: auto;
}
/* fix positioning of prefs and advanced link on results */
.ms-sblink {
display:block;
}
/* fix the color of the prefs and advanced link on results */
.ms-sblink a:link, .ms-sblink a:visited, .ms-sblink a:hover {
color:#0072BC;
}
</style>
- Remove the ‘<div id=”s4-brbboncont”> tag and all internal content
Step 5. Create / Add any supporting files to the CustomBrandingDeployment feature
This step will involve adding a custom css file and an image file to the CustomBrandingDeployment feature. The css file won’t actually be used by the master page for the purpose of this demo, it’s simply included to show how one would include a file that needed to be deployed to the style library.
- Create the directory structure as shown in the image below
- Add the files to the directories as below
Step 6. Configure the CustomBrandingDeployment Feature to deploy the files
- Double Click ‘feature.xml’ to edit it. Note the addition of the ‘ImageUrl’ attribute corresponding to the ‘MTFeatureLogo.png’ added to the solution. This will add an icon to the feature on the manage features interface.
- Double click ‘elements.xml’ to edit it
- Add a <module> tag for the master pages. This tells SharePoint to deploy the files listed to the library located at the url ~site/_catalogs/masterpage when the feature is activated.
- Add a <module> tag for the style sheet. This will see the style sheet deployed.
As far as what is required to package and deploy the master pages and associated files, we are done.
Note that we needed to define <Module> tags for the resources to be deployed to the content database (libraries in the site collection) – one module for the master pages for deployment to the master pages catalogue, and another for the style sheet deployment to the style library.
The two image files were placed in folders contained in the ‘IMAGES’ folder. The wsp build process will automatically detect these and have included in the wsp file for deployment to the location: <14 Hive>/Layouts/Images/<directoryname>. WSP Builder knows to include these and where to configure their deployment path because they have been created in the SharePointRoot/Template/Images folder.
Step 7. Create the CustomBrandingApplication feature
The CustomBrandingApplication feature will be responsible for programmatically configuring the web to use the master page. To achieve this we will write a custom feature receiver and bind it to our new feature.
- Right Click your ‘CustomBrand’ Project and choose Add –> New Item
- Select WSPBuilder –> Blank Feature and give the feature a name ‘CustomBrandingApplication’. click ‘Add’
- Provide appropriate details in the Feature Wizard Dialogue. Ensure the feature is scoped at the ‘Web’ level and the Event handler option is checked. Click ‘Finish’
- The wizard will modify your project folder adding the required folders and files.
- Delete the ‘elements.xml’ file and double click ‘feature.xml’ to edit it. Remove the <ElementManifests> tags.
Note that this feature contains no resources, and as a result, we don’t need the ‘elements.xml’ file or the reference to it in the feature.xml file.
Step 7. Create the Feature Receiver for the CustomBrandingApplication feature
I’m not going to go into any great detail about the code here, it’s fairly self explanatory.
- Double click the ‘CustomBrandingApplicationReciever.cs’ file in the CustomBrandingApplicationFeature folder to edit it
- Add the following code to the event receiver
using System;
using System.Collections.Generic;
using System.Xml;
using System.Text;
using System.Globalization;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;
namespace CustomBrand.EventHandlers.Features
{
public class CustomBrandingApplicationReceiver : SPFeatureReceiver
{
//we can declare these two constants as they are explicitly defined/known:
private const string MasterPageURL = "_catalogs/masterpage/MyCustomMasterPage.master";
private const string MinimalMasterPageURL = "_catalogs/masterpage/MyMinimalMasterPage.master";
private const string DefaultMasterPageURL = "_catalogs/masterpage/v4.master";
private string siteServerURL = string.Empty;
private string GetWebRelativeMasterPageURL(SPWeb web, string siteRelativeMasterPageURL)
{
//Check we have a value for siteServerURL:
if (string.IsNullOrEmpty(this.siteServerURL))
{
//if it is empty, we need to find it:
//(Remember: this feature will be scoped at the web (or sub-site) level
//and the master page themselves will have been deployed in the master page catalog
//located in the root web of the site collection)
using (SPSite site = web.Site)
{
this.siteServerURL = web.Site.ServerRelativeUrl.TrimEnd(char.Parse("/"));
}
}
return string.Format("{0}/{1}", this.siteServerURL, siteRelativeMasterPageURL);
}
public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
//We know that the feature is scoped at the web, so we can safely cast it!:
SPWeb web = (SPWeb)properties.Feature.Parent;
string mpURL = string.Empty;
if (web.CustomMasterUrl.Equals("minmal.master", StringComparison.CurrentCultureIgnoreCase))
{
//we need to use our minmal master page:
mpURL = MinimalMasterPageURL;
}
else
{
//we can use our standard master page:
mpURL = MasterPageURL;
}
//get server/host relative master page URL:
string serverRelativeMasterPageURL = GetWebRelativeMasterPageURL(web,mpURL);
//set master pages:
web.CustomMasterUrl = serverRelativeMasterPageURL;
web.MasterUrl = serverRelativeMasterPageURL;
//update:
web.Update();
}
public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
{
//We know that the feature is scoped at the web, so we can safely cast it!:
SPWeb web = (SPWeb)properties.Feature.Parent;
//get server/host relative master page URL:
string serverRelativeMasterPageURL = GetWebRelativeMasterPageURL(web, DefaultMasterPageURL);
//set master pages:
web.CustomMasterUrl = serverRelativeMasterPageURL;
web.MasterUrl = serverRelativeMasterPageURL;
//update:
web.Update();
}
}
}
Step 8. Create the CustomBrandingApplicationStapler Feature
As explained previously, the final feature of the three will be responsible for stapling the automatic branding feature to all existing site templates. By ‘Stapling’ a feature to a site template you are effectively instructing sharepoint that said feature is to be activated whenever a new site using that template is provisioned.
Being a branding solution, our requirement is to have each new site branded with our master page. There are two potential ways to achieve this.
We can explicitly staple our automatic branding feature to all site templates:
or, we can use the ‘GLOBAL’ option:
As one might might expect, using the ‘GLOBAL’ option will have the feature activated at all templates (well - nearly)… As you can also see, I’ve included an explicit FeatureSiteTemplateAssociation entry for STS#1 (Blank Site). If we take a look at the blank site template as declared in the 14 hive:
Notice the ‘AllowGlobgalFeatureAssociations=”False” ? – guess what that means?
(Check it out for yourself – file is located @ <14 Hive>\Template\1033\XML\webtemp.xml)
Out of the box, blank site is the only template that has this attribute set to false. It might be possible in the future to have some consultant or developer come along and develop a custom site template with that same flag, in which case your custom branding will not be applied.
One final thing to note here is that I’m creating the stapling feature scoped against a site collection. So that I will need to activate it at all new site collections created to ensure branding compliance. Do a little planning for yourself, it might be that this feature is better scoped at web application or even farm level level depending on your specific business requirements.
- Create the CustomBrandingApplicationStapler Feature
- Double click ‘elements.xml’ to open it
- Add the following tags
Note: I’ve blurred Feature GUIDs to avoid you blindly copying what i have… they need to relate to the features that have been created in your environment!!
Step 9. Package
This is a simple step, right click your project and choose ‘WSPBuilder –> Build WSP’.
If you ensure the visibility of your ‘Output’ window in visual studio, you should see WSPBuilder doing it’s thing followed up by a ‘Done!’ success message:
Hint: The completed package (.wsp file) can be inspected by opening in winrar or 7zip! This is true for all wsp files and can be an especially handy titbit if you want to pull apart someone else’s solution to see how it’s done…
Step 10. Deploy
This can again be potentially achieved through a ‘right click –> deploy’ approach… this of course will only deploy to a local farm, so assuming you copy the CustomBrand.wsp file to a path: c:\WSPs:
- Navigate to the bin directory within your 14 hives
cd c:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\Bin\
- Execute the following command to add the solution to the solution store
stsadm.exe –o addsolution –filename c:\WSPs\CustomBrand.wsp
- Execute the following command to deploy the solution
stsadm.exe –o deploysolution –name CustomBrand.wsp –allowGacDeployment –immediate
The result should be a timer job created to deploy your solution. Once this happens (timer job execution – give it a couple of minutes) your features will be available for activation.
Step 11. Verify Deployment
- Navigate to ‘Manage Farm Solutions’ (http://<YourCentralAdmin>/_admin/Solutions.aspx) and verify the solution has deployed successfully
Step 12. Activate Features
- Navigate to ‘Manage Site Collection Features’ and activate
- CustomBrandingDeployment
- CustomBrandingApplicationStapler
- Navigate to the ‘Manage Site Features’ of your root site and activate the ‘CustomBrandingApplication’ feature to brand your root site.
That should be it – all new webs created in your site collection should be branded with the single copy of the master page sitting in the masterpage catalog.
Note – I did hope to have some screen shots of the last couple of steps and the outcome, but somewhere along the line I’ve lost the VM i was working in to get this far?!
If I have made an errors, or you feel I’ve not explained everything, let me know – will be happy to help.