Hi, this is Steve Peschka from the SharePoint Rangers team again, and in this blog entry I’ll discuss customizing My Sites across an organization. There’s a good deal of confusion out there about how best to achieve this, which is partly caused by functional differences between SharePoint Portal Server 2003 and SharePoint Server 2007.
Before I get started, here’s a quick primer. My Sites in SharePoint have two sites, so to speak – a public site and private site. The same dynamic web page is used to generate everyone’s public site. You can see this when you go to look at an individual’s My Site, and the page you navigate to is called person.aspx. SharePoint appends information about the user whose details you want to see onto the query string portion of the URL. By default, this information is in the form of “accountname=domain\user”. So, if you were going to view the details for a user with a login name of “speschka” in the “steve” domain, you would navigate to http://mySiteHost/person.aspx?accountname=steve\speschka. Since that page is shared by all users, if a site designer makes changes to that page, then public information about all users will reflect those changes. In this respect, MOSS 2007 works the same as SPS 2003.
Modifying the private My Site is where things begin to work differently. In SPS 2003, a site administrator could go into his or her private site, edit the home page in Shared mode, and save their changes. This would update the layout and web parts for all My Site users, so everyone’s private site would have the same layout and web parts. In MOSS 2007, this is no longer possible – there are a number of more powerful customization tools than what SPS 2003 had, but some tasks such as customizing all private sites have unfortunately become a bit more difficult. So, how can you customize the private My Sites in MOSS 2007?
First, let’s start with how NOT to customize My Sites. As with SPS 2003, some people might think “Hey, I can modify things pretty quickly if I just go to the file system and change the template for My Sites there.” This is absolutely the wrong approach, and it will leave your site in an unsupported state. This means modifying any of the .aspx pages or onet.xml or any of the other out of the box templates files is off limits.
Instead, we’re going to take advantage of several components of the core SharePoint platform to solve this problem – features, feature site template associations (also known as “feature stapling”), master pages, and our old friend the ASP.NET web control. Before getting into the details, here are a few definitions to make sure that we’re on the same page:
· Feature: A feature is a package of SharePoint elements that can be activated for a specific scope (such as a site or web) and that helps users accomplish a particular goal or task. For example, a feature may deploy a list definition, populate it with data, and add a custom web part to work with the list data. Individually, those elements may not be particularly interesting, but when combined into a cohesive group as a feature they provide a mini-application or solution. For more information, go to the Working with Features section of the WSS 3.0 SDK.
· Feature site template association: Allows you to associate new features and functionality to existing templates such that when those sites are provisioned, the associated features automatically get added as well. To understand feature stapling, you need to understand that there are two features involved in the process:
o "Staplee" feature: This feature contains the functionality that you want to add to an existing site template.
o "Stapler" feature: This feature contains the "FeatureSiteTemplateAssociation" XML tags which bind the staplee feature to a particular site template. Basically, it’s a feature that says “for all Personal Sites provisioned henceforth, they shall have the staplee feature.”
· Master pages: A master page is an ASP.NET page that has the file name extension of .master and allows you to create a consistent appearance and layout for the pages in your SharePoint site.
· ASP.NET web control: For this solution we are talking about an ASP.NET server control, which consists of a .NET assembly and a set of tags that are added to a page to instantiate an instance of our control. Note that it is not a user control (.ascx file).
So, those are the key components of the solution. How do you put them all together?
A common set of requirements for customizing My Sites across an enterprise includes a) using a custom master page and b) adding, removing, and/or moving web parts around the page. Those are the only items that I will address in this blog entry, but the approach taken is flexible enough that you can do virtually anything else needed by just plugging your code into the appropriate location.
Here’s how the components described above can help you achieve this. The first feature, called “MySiteStaplee” is really where most of the work occurs.
The MySiteStaplee feature includes the following functionality:
· File upload – the feature is configured to automatically upload a custom master page into the master page gallery in the new My Site. We include this section in the feature.xml file:
So, here the feature is saying that it wants to include a file called “steve.master”, which is the custom master page. It’s also saying that there is additional configuration information in a file called “element.xml”. Now let’s look at a section of element.xml:
<Module Name="MPages" List="116" Url="_catalogs/masterpage">
<File Url="steve.master" Type="GhostableInLibrary" />
The Module and File elements are describing where the master page should be uploaded. In the Module element, the List attribute defines the type of list to which the item should be uploaded, and the Url attribute defines the list in which it will be placed. In the File element, the Url attribute defines where the file is that is going to be uploaded. GhostableinLibrary is a little more esoteric, but essentially when you are uploading a file that is going to land in a document library, you need to include this attribute in your File element because it tells SharePoint to create a list item to go with your file when it is added to the library. If you were instead provisioning a file outside a document library, you would specify Type=”Ghostable".
· Change the Master Page setting for the site – changing the master page setting for the site requires some code to be run. For this solution, you will use something often referred to as a “feature provisioning code callout.” All that really means is that when the feature gets activated, it will run some code. To do that, you’d have to write a new .NET assembly, using a class that inherits from SPFeatureReceiver. With that class, you get four events that you can override: FeatureActivated, FeatureDeactivating, FeatureInstalled, and FeatureUninstalling. For this solution, we will override the FeatureActivated event to change the master page.
Since we’re working with a fairly simplistic scenario here, you’re just going to look at the current master page and change it to use the one that will be uploaded in the feature. To do that, use the following code in the FeatureActivated event:
using (SPWeb curWeb = (SPWeb)properties.Feature.Parent)
//got the root web;now set the master Url to our
//master page that should have been uploaded as part
//of our feature
curWeb.MasterUrl = curWeb.MasterUrl.Replace(
The FeatureActivated event has a signature that looks like this:
public override void FeatureActivated(SPFeatureReceiverProperties properties)
The properties parameter provides access to a lot of useful information; in this case, you’re able to get a reference to the SPWeb associated with the My Site, so you can change the master page.
In order to get this code callout to execute, you need to configure the feature so that it uses this assembly. You’d do that in the feature.xml file for the staplee feature, by defining the assembly and class that are associated with it:
ReceiverAssembly="MySiteCreate, Version=184.108.40.206, Culture=neutral,
Some of you are now wondering why we didn’t make any changes to the home page for the site in the code callout, such as adding, deleting or moving web parts. The issue is that when you are provisioning a feature via the stapling mechanism, most of the document libraries and lists don’t exist at the time your provisioning code is executed. That includes the Pages library, where the default.aspx page lives that is used for the home page. Since it doesn’t exist yet, you can’t change it in the code callout, so you’ll need another way to do that.
It is also another important reason why you need to use a custom master page. This solution includes a custom ASP.NET server control that is going to be used to make changes to the home page. The way to get that control added and used in the site is to add it to the custom master page. When the custom master page is loaded, it contains an instance of the ASP.NET server control and that control can then finish off the customization work for us. You add one tag to the custom master page to register the control:
<%@ Register Tagprefix="IWPart" Namespace="Microsoft.IW" Assembly="MySiteCreatePart, Version=220.127.116.11, Culture=neutral, PublicKeyToken=cb1bdc5f7817b18b" %>
You need to add a second tag to instantiate an instance of the control when the page is loaded:
· Change web parts – the custom ASP.NET control is used to modify web parts and their layout on the page. Since you’d only want the provisioning code to run once, the first thing to do is check to see if the code has been run before by storing a value in the My Site’s SPWeb Property Bag and then checking it:
//get the current web; not using "using" because we don't want to
//kill the web context for other controls that need it
SPWeb curWeb = SPContext.Current.Web;
//look to see if our code has already run
if (! curWeb.Properties.ContainsKey(KEY_CHK))
The next thing is to get a reference to the home page in the site:
//look for the default page so we can mess with the web parts
SPFile thePage = curWeb.RootFolder.Files["default.aspx"];
With the home page, you can get the web part manager for it:
//get the web part manager
SPLimitedWebPartManager theMan = thePage.GetLimitedWebPartManager
Once you have the web part manager, you can work with the web parts on the page. The SPLimitedWebPartManager has a collection of all the web parts on the page as well as methods to add, close, delete and move web parts. One important note is that in most cases trying to change individual web parts as you enumerate through the web part collection will not be successful. Anything that changes the nature of the collection during enumeration causes problems, but you can normally work around this by copying the web parts you want to change into an array, hashtable, or some other kind of collection.
For this example, we are going to do three things:
· Close the Welcome web part
· Move the RSS Feeder web part to the bottom zone
· Add the This Week in Pictures web part to the middle right zone
The code will enumerate through the web part collection, find the parts you want to work with, and capture the part and operation you want to do with it (delete or move) to a hashtable. I chose a hashtable in this case because of personal preference, but you can use some other collection type as well. To determine whether the current part is one you need to do something with, we check the System.Type of the part. That’s a simple language-agnostic way of finding them:
//create a hashtable to store our web parts
hshWp = new Hashtable();
foreach (WebPart wp in theMan.WebParts)
//close the welcome part; WebPartAction is a custom class
//I wrote to keep track of web parts and their properties
You then create a new web part, set some properties, and also add it to the hashtable of web parts:
//add a new ThisWeekInPictures web part
ThisWeekInPicturesWebPart wpPix = new ThisWeekInPicturesWebPart();
wpPix.ImageLibrary = "Shared Pictures";
wpPix.Title = "My Pictures";
//add it to the hash so it gets put in the page
new WebPartAction(wpPix, WebPartAction.ActionType.Add,
Finally, the code enumerates through the hashtable and makes all of the web part changes:
foreach (string key in hshWp.Keys)
WebPartAction wpa = (WebPartAction)hshWp[key];
theMan.MoveWebPart(wpa.wp, wpa.zoneID, wpa.zoneIndex);
theMan.AddWebPart(wpa.wp, wpa.zoneID, wpa.zoneIndex);
All of the web part changes have been made now. Since you wouldn’t want to execute this code more than once, a flag is needed so we’ll know that this work has already been done. If you recall, the code checks this flag up above:
Now, we’re going to update the property bag with the flag, so when the page is loaded from this point forward, your code branch will not execute:
//add our key to the property bag so we don't run
//our provisioning code again
curWeb.AllowUnsafeUpdates = false;
Note as well that since the code that modifies the site has now completed, the AllowUnsafeUpdates property is also changed back to its default value of false.
The code is just about complete now, and there’s only one other thing to do. If you were to go directly to the page, you might think that the code didn’t work; the page would render exactly as it came out of the box, with all of the default web parts intact and in place. You need to refresh the page to get the changes to show up – to do that, we simply issue a redirect back to our page:
//force a page refresh to show the page with the updated layout
The MySiteStaplee feature has all this great functionality, but how do you get it to execute? This is where the feature stapler comes in. It does only one thing – it establishes an association between a site template and a feature. That means that whenever a new site is created based on a specific template, the staplee feature will get activated. When it does, your code callout will execute the FeatureActivated code.
Here is the feature.xml file for the stapler feature:
Title="My Site Creation Feature Stapler"
<ElementManifest Location="elements.xml" />
There are a couple of things to note:
· Id – this is a GUID just for this stapler feature; it is not related to the Id for the staplee feature
· Scope – the scope is Farm because you’d want to execute it anytime a My Site is created in the farm
The feature.xml file also references a second file called elements.xml; here are the contents of that file:
<Elements xmlns="http://schemas.microsoft.com/sharepoint/" >
This one is pretty simple to understand. The Id attribute is the GUID of the staplee feature; the TemplateName attribute makes a connection between the staplee feature and a site template called SPSPERS#0. To get the site template name you should use, look in the C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\1033\XML directory (assuming you installed to this path). In that directory, there are a number of xml files; when you install MOSS it adds one called webtempsps.xml. If you open that file up you will see an entry for a template with a name of SPSPERS; the default configuration for that template has an ID of 0. You combine the two and you get SPSPERS#0.
Now that you have all the code and features created, you’ll need to install and activate the features, then update the site’s configuration. Here are steps that need to be taken to get everything properly installed:
· Copy the MySiteStaplee and MySiteStapler folders to C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\FEATURES (assuming this is your installation directory); each feature folder contains the files that define that feature
· Add the two assemblies that were created (the feature activation assembly and the custom ASP.NET web part assembly) to the global assembly cache
· Install and activate the MySiteStaplee feature; when you activate it, do so to the web application that hosts My Sites. Use the installfeature and activatefeature switches with stsadm to do this
· Install the MySiteStapler feature; since its Scope is Farm it activates automatically. Use the installfeature switch with stsadm to do this
· Add the following SafeControl entry to the web.config for the web application that hosts My Sites:
<SafeControl Assembly="MySiteCreatePart, Version=18.104.22.168, Culture=neutral, PublicKeyToken=cb1bdc5f7817b18b" Namespace="Microsoft.IW" TypeName="*" Safe="True" AllowRemoteDesigner="True" />
This entry allows the custom ASP.NET control to be instantiated on the master page.
That’s it – you’re now ready to start creating My Sites with your customizations! One other thing worth noting – this ONLY applies to new My Sites. If you’ve already created My Sites then these features won’t be used.
In the next couple of months or so, I’m going to work on making the solution described here a little more generic. My goal is to make it more of an open framework for My Site customizations that can be reused without you having to rewrite code just for your implementation. This solution is now part of the larger Community Kit for SharePoint: Corporate Intranet Edition effort taking place on CodePlex, so the Visual Studio solution file containing all of the current source code is available for download there. [Update April 2, 2007: The production release of the MySiteCreate 1.0 solution file is now available here.]
Although this is likely the longest entry ever posted on this blog, I do hope that you’ll find this to be a useful solution for customizing My Sites in your MOSS environment. If you have questions, ideas, or suggestions, please leave a comment.