Welcome to MSDN Blogs Sign in | Join | Help

Customizing MOSS 2007 My Sites within the enterprise

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?

 

Customization Requirements

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.

 

MySiteStaplee Feature

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:

<ElementManifests>

<ElementFile Location="steve.master"/>

<ElementManifest Location="element.xml"/>

 

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" />

</Module>

 

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:

try

{

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

if (curWeb.MasterUrl.Contains("default.master"))

{

curWeb.MasterUrl = curWeb.MasterUrl.Replace(

"default.master", "steve.master");

curWeb.Update();

}

}

}

 

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=1.0.0.0, Culture=neutral,

PublicKeyToken=c726fa831b98198d"

ReceiverClass="Microsoft.IW.MySiteCreate" 

 

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=1.0.0.0, Culture=neutral, PublicKeyToken=cb1bdc5f7817b18b" %>

 

You need to add a second tag to instantiate an instance of the control when the page is loaded:

<IWPart:PartCheck runat="server"/>

 

·         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

(System.Web.UI.WebControls.WebParts.PersonalizationScope.Shared);

 

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

if (wp.GetType().Equals(typeof(PersonalWelcomeWebPart)))

hshWp.Add(wp.StorageKey.ToString(),

new WebPartAction(wp,

WebPartAction.ActionType.Delete));

//etc

}

 

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

hshWp.Add(Guid.NewGuid().ToString(),

new WebPartAction(wpPix, WebPartAction.ActionType.Add,

"MiddleRightZone", 10));

 

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];

 

switch (wpa.Action)

{

case WebPartAction.ActionType.Delete:

theMan.DeleteWebPart(wpa.wp);

break;

case WebPartAction.ActionType.Move:

theMan.MoveWebPart(wpa.wp, wpa.zoneID, wpa.zoneIndex);

theMan.SaveChanges(wpa.wp);

break;

case WebPartAction.ActionType.Add:

theMan.AddWebPart(wpa.wp, wpa.zoneID, wpa.zoneIndex);

break;

}

}

 

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:

//look to see if our code has already run

if (! curWeb.Properties.ContainsKey(KEY_CHK))

 

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.Properties.Add(KEY_CHK, "true");

curWeb.Properties.Update();

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

Context.Response.Redirect(thePage.Url);

MySiteStapler Feature

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:

<Feature  

Id="4457E66E-6FCD-4352-AD4D-B870600B4696"

Title="My Site Creation Feature Stapler"

Scope="Farm"

xmlns="http://schemas.microsoft.com/sharepoint/" >

<ElementManifests>

<ElementManifest Location="elements.xml" />

</ElementManifests>

</Feature>

 

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/" >

<FeatureSiteTemplateAssociation

Id="4DEFA336-EDC4-43cb-9560-FE2E27E76DFB"

TemplateName="SPSPERS#0"/>

</Elements>

 

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.

Installing

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=1.0.0.0, 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.

 

 

Steve Peschka

Published Thursday, March 22, 2007 11:16 PM by LLiu

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

# re: Customizing MOSS 2007 My Sites within the enterprise

Hi,

Can you tell me how I can change the name of the private My Site from My Home to something else (like My page)? I'm working with a Dutch version of MOSS and in the translation to Dutch My home has become a not-so-common word...

Thanks, Willem

Friday, March 23, 2007 8:33 AM by woosterhof

# re: Customizing MOSS 2007 My Sites within the enterprise

Hi Willem.  Unfortunately this will be difficult to do.  This string is not part of the standard navigation that is used in other sites, it is actually contained in a resource file.  I'm not sure if it's in a resx or actually embedded in Microsoft.Office.Server.Intl.dll.  In either case it is probably going to be more effective to modify it with some client-side script included on the master page.

Friday, March 23, 2007 11:43 AM by Steve

# re: Customizing MOSS 2007 My Sites within the enterprise

I am using Forms Based Authenticaion (FBA) with the AD Membership Provider.  I set the default zone to use FBA.  I CAN authenticate to my site, and CAN add site admins from the site admin page, but CANNOT get users added to the Shared Service Provider's security page that controls wether a user can have a personal site (ssp/admin/_layouts/ManageServicePermissions.aspx).  The user/group field only appears to be authenticating against the domain and NOT my AD Membership provider.

I have added the membership provider to the SSP site web.config.

Any Ideas?

Friday, March 23, 2007 1:03 PM by BobC

# re: Customizing MOSS 2007 My Sites within the enterprise

Hi Bob.  You need to add your FBA membership provider information to the web.config for the SSP's web application.  Then you need to go into the SSP, Personalization Services Permissions and add your FBA users and/or groups to have the Personal Features and Personal Site rights.  After you do that your FBA users should have the My Site link show up.

Sunday, March 25, 2007 10:49 AM by Steve

# re: Customizing MOSS 2007 My Sites within the enterprise

Hi Steve,

I looked in the resource files in C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\Resources. I couldn't find the text 'My Home' there... Is this the only place for resource files?

Greetings, Willem

Monday, March 26, 2007 3:21 AM by woosterhof

# re: Customizing MOSS 2007 My Sites within the enterprise

How can we apply new features and functionality to existing My Sites? One way could be to loop through all My Sites and i.e. add/remove a web part as in your blog. Is that the best approach to update existing sites?

Monday, March 26, 2007 5:43 AM by Wendy

# re: Customizing MOSS 2007 My Sites within the enterprise

Hi, do you have the code of both assemblies? I don't seem to be able to create them with your instructions.

Monday, March 26, 2007 9:08 AM by Wouter van Vegchel

# re: Customizing MOSS 2007 My Sites within the enterprise

Hi Willem.  We look at 12\RESOURCES\foobar.XYZ.resx for the strings we will use to replace $Resources tokens.  If DefaultResourceFile is not specified (or is set to _res), we will look at 12\TEMPLATE\Features\<featurename>\Resources\Resources.XYZ.resx for the resource file.(where XYZ is the culture, e.g., en-us.)  Our default resource file is at Resources\Resources.resx.

Monday, March 26, 2007 12:50 PM by Steve

# re: Customizing MOSS 2007 My Sites within the enterprise

Hi Wendy.  As far as modifying existing My Sites you are correct - you basically need to enumerate all of the sites and then apply your changes on each one.  There unfortunately isn't a real easy or sophisticated way to do it another way.

Tuesday, March 27, 2007 1:24 PM by Steve

# re: Customizing MOSS 2007 My Sites within the enterprise

Hi Wouter.  The source code for everything should be on the CodePlex site at the location referenced in the blog.  If you are not finding it there then let us know and we will check into it.

Tuesday, March 27, 2007 1:25 PM by Steve

# re: Customizing MOSS 2007 My Sites within the enterprise

Hi Steve. Is there a way to display the Top Navigation Tabs of the main portal page on all MySite pages? - Instead of the default My Home and My Profile tabs.

Thursday, March 29, 2007 3:37 PM by Jeff

# re: Customizing MOSS 2007 My Sites within the enterprise

Hi Jeff.  The navigation in the home page is hard-coded right now in the master page.  You would need to look at changing it there, and inserting one of the navigation controls.  However, more specific to your point, since the main portal page is in a separate site collection, there isn't a way to get the navigation from it and display it in another site collection, since each My Site is its own site collection.

Friday, March 30, 2007 10:25 AM by Steve

# re: Customizing MOSS 2007 My Sites within the enterprise

Steve, I have a few questions regarding this approach.

1. Where do we define the ContentPlace holders for the My Home and My Profile page

2. Would I also have to follow the same approach to modify the "My Profile" page.

3. If I do want to revert back to my original master page after the site creation how do I do that

4. How do I enable "Audit Logging" for all the My Sites that are created.

5. How do I add custom pages all the sites

Tuesday, April 03, 2007 2:07 AM by vijay

# re: Customizing MOSS 2007 My Sites within the enterprise

As I understand it, the webcontrol is loaded every time the user visits the MySite page. Are there any performance problems if we have 1000 or 10.000 users visiting the MySite? Since we always have to check if the code has already run.

Is it better (faster) to store a boolean in a session variable and only get the key property once?

Wednesday, April 04, 2007 7:08 AM by Martin

# re: Customizing MOSS 2007 My Sites within the enterprise

Hi Vijay, some answers are below.  I'm not sure what your exact question is around ContentPlaceholders, but if you are saying you are building a new master page and asking where you should put these controls, I would look at the default master page for My Sites to figure that out.  The My Profile page is the shared public page, so you don't need to necessarily go through these same gyrations.  You can change it once and it affects all users that "My Profile".  If you want to change to a different master page just go into Site Settings and select the one you want to use.  If you want to do it retroactively for every single My Site then you would need to write code that enumerates all personal sites and changes the master page programmatically, just as the code