Mar 24

This one took me a while to figure out so I'd better describe what I did so I don't forget it in a hurry.

You might think that the easiest way to do this is to modify the Person.aspx file, but that is not a good idea as it will void warranty and is not best practice. So I went about trying to figure out how this can be done without coding anything. After many hours, I realised that it would not be possible.

We need to write Features (available in MOSS 2007/WSS 3 and possibly in later versions). Before you go, oh no... it's going to be tough... well, that's what I thought too, until I started working on it. I won't lie, there is a quite a bit to do, so just bear with me as I try to explain it so that when I look back in a year or so, I'll still know what to do.

Step 1: Create your custom master page

Copy the default.master, which can be found in the TEMPLATEGLOBAL folder as seen in the screenshot below.

Masterpage Location

Create a folder somewhere and call it CustomMySite. Within this folder, create one called CustomMySiteStaplee.

Place the copy of the default.master into the CustomMySiteStaplee folder. Customize this master page in any way you like and save it as wei.master (or whatever name you choose). In my case, there was no need to make any changes to the master page since everything is going to be exactly where it is. You would only customize the master page if you want to add or remove some functionality. If you want to change the look and feel, you just need to edit the styles (which I will go through a little later).

Side note: Even if you are not going to edit your master page as in my case, you should make a copy of it anyway in case you decide to in the future.

Now, we're off to code...

Using Visual Studio 2005 or 2008

Start a new project, class library. Add a reference to Windows SharePoint Services (Microsoft.SharePoint.dll) and then just copy and paste the following code:

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;
using System.Diagnostics;

namespace CustomMySite
{
    class CustomMySite : SPFeatureReceiver
    {
        public override void FeatureInstalled(SPFeatureReceiverProperties properties)
        {
            //throw new Exception("The method or operation is not implemented.");
        }

        public override void FeatureUninstalling(SPFeatureReceiverProperties properties)
        {
            //throw new Exception("The method or operation is not implemented.");
        }

        public override void FeatureActivated(SPFeatureReceiverProperties properties)
        {
            SPSecurity.RunWithElevatedPrivileges(delegate()
{
    EventLog eventlog = new EventLog();
    eventlog.Source = "MySiteFeatureStaple";
    eventlog.WriteEntry("Starting Activation of the MySite Master Page Switcher Feature");
    try
    {
        SPSite mysite = properties.Feature.Parent as SPSite;
        SPWeb myweb = mysite.RootWeb;
        if (myweb.WebTemplate == "SPSPERS")
        {
            using (myweb)
            {
                myweb.Description = "MySite :: " + DateTime.Now.ToShortDateString();
                if (myweb.MasterUrl.Contains("default.master"))
                {
                    myweb.MasterUrl = myweb.MasterUrl.Replace("default.master", "wei.master");
                    myweb.ApplyTheme("WeiCustomTheme");
                }
                myweb.Update();
                eventlog.WriteEntry("Found site using 'SPSPERS' template & updated it with the custom master page");
            }
        }
        if (myweb.WebTemplate == "SPSMSITEHOST")
        {
            using (myweb)
            {
                myweb.Description = "MySite :: " + DateTime.Now.ToShortDateString();
                if (myweb.MasterUrl.Contains("default.master"))
                {
                    myweb.MasterUrl = myweb.MasterUrl.Replace("default.master", "wei.master");
                    myweb.ApplyTheme("WeiCustomTheme");
                }

                myweb.Update();
                eventlog.WriteEntry("Found site using 'SPSMSITEHOST' template & updated it with the custom master page");
            }
        }
    }
    catch (Exception e)
    {
        eventlog.WriteEntry(String.Format("Error activating MySite master page switcher feature {0} : ", e.Message));
    }
});
        }

        public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
        {
            SPSecurity.RunWithElevatedPrivileges(delegate()
{
    EventLog eventlog = new EventLog();
    eventlog.Source = "MySiteFeatureStaple";
    eventlog.WriteEntry("Starting De-activation of the MySite Master Page Switcher Feature");
    try
    {
        SPSite mysite = properties.Feature.Parent as SPSite;
        SPWeb myweb = mysite.RootWeb;
        if (myweb.WebTemplate == "SPSPERS")
        {
            using (myweb)
            {
                if (myweb.MasterUrl.Contains("wei.master"))
                {
                    myweb.MasterUrl = myweb.MasterUrl.Replace("wei.master", "default.master");
                    myweb.ApplyTheme("none");
                }
                myweb.Update();
                eventlog.WriteEntry("Removed custom master page from site using 'SPSPERS' template");
            }
        }

        if (myweb.WebTemplate == "SPSMSITEHOST")
        {
            using (myweb)
            {
                if (myweb.MasterUrl.Contains("wei.master"))
                {
                    myweb.MasterUrl = myweb.MasterUrl.Replace("wei.master", "default.master");
                    myweb.ApplyTheme("none");
                }
                myweb.Update();
                eventlog.WriteEntry("Removed custom master page from site using 'SPSMSITEHOST' template");
            }
        }
    }
    catch (Exception e)
    {
        eventlog.WriteEntry(String.Format("Error de-activating MySite master page switcher feature {0} : ", e.Message));
    }
});
        }
    }
}

Note where I set the theme in the code above myweb.ApplyTheme("WeiCustomTheme"); - this means that I chose to use my own custom theme for all the MySites that are created. This is important for styling purposes. You need to create a custom theme to reflect your own mysite if you want the right branding (logos, colours etc to appear on your MySite). Instructions to create your own custom theme can be found here. As a result, you will need to change that ApplyTheme value to your own custom theme.

Build the project (Ctrl+Shift+B) to get the DLL (don't forget to sign the assembly as I sometimes do). Drag and drop the DLL into the GAC (I find this is the fastest way to install DLLs into the GAC). FYI: GAC is located at c:windowsassembly.

Now the fun bit... creating the feature.

Creating the staplee feature.xml and the element.xml files

Open Notepad and copy the following code into it:

<?xml version="1.0" encoding="utf-8"?>
<Feature xmlns="http://schemas.microsoft.com/sharepoint/"
         Id="5E1DFC64-ECB7-4920-B55C-F8CE92FBD77D"
         Title="Custom MySite Feature Staplee"
         ReceiverAssembly="CustomMySite, Version=1.0.0.0, Culture=neutral, PublicKeyToken=48aa5af62c615cd0"
         ReceiverClass="CustomMySite.CustomMySite"
         Scope="Site"
         Version="1.0.0.0"
         DefaultResourceFile="core">
  <ElementManifests>
    <ElementManifest Location="CustomMySiteElement.xml" />
    <ElementFile Location="wei.master" />
  </ElementManifests>
</Feature>

Make sure you change the value for PublicKeyToken to your own PublicKeyToken value. You can get this value by navigating to the GAC folder (C:windowsassembly) and looking for the DLL that you dragged in there. The public key token appears next to the DLL Name. If you like, you can change the Guid, but it shouldn't cause a problem if you use the same one I did as this Guid identifies this feature and it is immensely unlikely that you are currently using this Guid to identify a different feature.

Save this file as feature.xml into the previously created CustomMySiteStaplee folder along with the wei.master file.

Open Notepad again and copy the following code into it:

<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <Module Name="CustomMySitePage" Path="" RootWebOnly="true" Url="_catalogs/masterpage">
    <File Url="wei.master" Type="GhostableInLibrary" />
  </Module>
</Elements>

Save this file as CustomMySiteElement into the CustomMySiteStaplee folder with the wei.master and element.xml files.

Creating the stapler feature.xml and the element.xml files

This bit needs to be done so that the public mysite pages also has the right branding applied to it (which is the main purpose of branding mysites in the first place). Create a new folder in the CustomMySite folder alongside the previously created CustomMySiteStaplee folder. Name this folder CustomMySiteStapler.

Open Notepad again and copy the following code into it:

<Elements xmlns="http://schemas.microsoft.com/sharepoint/" >
 <FeatureSiteTemplateAssociation Id="5E1DFC64-ECB7-4920-B55C-F8CE92FBD77D" TemplateName="SPSPERS#0"/>
 <FeatureSiteTemplateAssociation Id="5E1DFC64-ECB7-4920-B55C-F8CE92FBD77D" TemplateName="SPSMSITEHOST#0"/>
</Elements>

Save this file as elements.xml into the newly created CustomMySiteStapler folder. Note the Guids... look familiar? These Guids need to be identical to the ones in the Staplee feature.xml so the feature knows what to staple and where (SPSPERS and SPSMSITEHOST).

Open Notepad yet again (this is the last time) and copy the following code into it:

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

Save this file as feature.xml into the newly created CustomMySiteStapler folder.

Now, you're done... you have all the files you need to make MySite branding work.

Deploying MySite branding

Before we deploy, let's check that we have the right files and folders in the CustomMySite folder.

We have:

  1. CustomMySiteStaplee (which contains CustomMySiteElement.xml, feature.xml and wei.master)
  2. CustomMySiteStapler (which contains elements.xml and feature.xml)
  3. WeiCustomTheme (which contains the custom branding)

Copy the CustomMySiteStaplee folder to:
C:Program FilesCommon FilesMicrosoft Sharedweb server extensions12TEMPLATEFEATURESCustomMySiteStaplee

Copy the CustomMySiteStapler folder to:
C:Program FilesCommon FilesMicrosoft Sharedweb server extensions12TEMPLATEFEATURESCustomMySiteStapler

Copy the WeiCustomTheme folder to:
C:Program FilesCommon FilesMicrosoft Sharedweb server extensions12TEMPLATETHEMESWeiCustomTheme

If you haven't done so already, you need to add the theme template definition to SPTHEMES.XML, which is the file that determines which themes are available as options on the Site Theme page. This XML file is located in the C:Program FilesCommon FilesMicrosoft SharedWeb Server Extensions12TEMPLATELAYOUTS1033 directory
 <Templates>
  <TemplateID>WeiCustomTheme</TemplateID>
  <DisplayName>WeiCustomTheme</DisplayName>
  <Description>Wei's Custom MySite Theme.</Description>
  <Thumbnail>images/WeisCustomThemeThumb.gif</Thumbnail>
  <Preview>images/WeisCustomThemePreview.gif</Preview>
 </Templates>

The Thumbnail and Preview images are just image files that are used as previews when browsing available themes.

Run the following commands:

stsadm –o installfeature –name CustomMysiteStaplee
stsadm –o activatefeature –name CustomMysiteStaplee -url http://your_intranet/mysite
stsadm -o installfeature -name CustomMysiteStapler
iisreset

From here, every new MySite created will by default contain the custom branding you have created.

Updating existing MySite sites with the new branding

I think this only needs to be done once. Go to a previously created public MySite page as Administrator. Click Site Actions, then Site Settings. Click on Site theme and choose WeiCustomTheme from the list. Once you select this, all the other existing MySites will be updated to this custom theme.

If you did make changes to the master page, then you need to go into SharePoint Designer, open the MySite sites, navigate to _catalogs/masterpage, right click on the wei.master and select Set as Default Master Page.

Updating to new Custom Styles after deployment

You will notice that if you try to change the style after deploying the MySite branding, nothing appears to happen. What you need to do is firstly, uninstall the feature, change the styles and then reinstall the feature.

stsadm -o uninstallfeature -name CustomMysiteStapler
stsadm –o deactivatefeature –name CustomMysiteStaplee -url http://your_intranet/mysite
stsadm –o uninstallfeature –name CustomMysiteStaplee
iisreset

Make changes to the stylesheet here... then run:

stsadm –o installfeature –name CustomMysiteStaplee
stsadm –o activatefeature –name CustomMysiteStaplee -url http://your_intranet/mysite
stsadm -o installfeature -name CustomMysiteStapler
iisreset

I'm not 100% sure if we need to do the iisreset, so I'll let you experiment and see.

Once you have updated the styles and navigate back to the MySite, it will look as if the styles were not updated. I had to change the theme to something else, then change it back for it to pick up the changes. Oh well... as long as it works :)

Mar 24

In my last post, I discussed using static global variables so the variable maintains it's value even when a browser is closed. I will be using that here, referring to that global variable as a separate class (all code is provided there).

Don't forget that you will need to add a web service since the only way I know how to get all users is through this web service.

If you're using Visual Studio 2005, right click on References and click add Web Reference

If you're using Visual Studio 2008, right click on References, and click add Service Reference, then click Advanced, then click Add Web Reference.

The web service url is http://your_intranet_name/_vti_bin/UserProfileService.asmx

I usually set the Web reference name as "UserProfileService", though I'm not 100% sure if I should be doing that since it could get confusing.

First thing, set your variables:

 #region Variables
UserProfileService.UserProfileService UserProfileServiceWS = new UserProfileService.UserProfileService();
private const string UserProfileServiceURL = "/_vti_bin/UserProfileService.asmx";
#endregion

Now I check the static global variable to see if it has been set or if it has expired.

             if (GlobalVariable.ExpirationTime != null)
            {
                if (DateTime.Now.CompareTo(GlobalVariable.ExpirationTime) != -1) // if current time is after expiration time
                {
                    GlobalVariable.AllUsers = null; // reset user list so it is then re-populated
                }
            } 
That code above will set the AllUsers variable to null because the variable has expired. Then the function below (which includes the code above) will check if AllUsers is null (which is true if the variable is not populated) and then populate the variable accordingly.

         public string RetrieveUsers()
        {
            string userList = "";
            if (GlobalVariable.ExpirationTime != null)
            {
                if (DateTime.Now.CompareTo(GlobalVariable.ExpirationTime) != -1) // if current time is after expiration time
                {
                    GlobalVariable.AllUsers = null; // reset user list so it is then re-populated
                }
            }
            if (GlobalVariable.AllUsers == null)
            {
                // set the expiration time to be the next day
                GlobalVariable.ExpirationTime = DateTime.Today.AddDays(1);
                try
                {
                    //SPSite site = new SPSite(SiteURL);
                    SPWeb web = SPContext.Current.Web;
                    SPSecurity.RunWithElevatedPrivileges(delegate()
                    {
                        using (SPSite elevatedSite = new SPSite(web.Site.ID))
                        {
                            ServerContext context = ServerContext.GetContext(elevatedSite);
                            UserProfileManager profileManager = new UserProfileManager(context);
                            PeopleList pl = new PeopleList();
                            DateTime Today = DateTime.Now.Date;
                            UserProfileServiceWS.Credentials = CredentialCache.DefaultCredentials;
                            UserProfileServiceWS.Url = web.Url + UserProfileServiceURL;
                            foreach (UserProfile profile in profileManager) // cycle through all profiles
                            {
                                 userList += "|" + profile["AccountName"].ToString();
                            }
                            GlobalVariable.AllUsers = userList;
                        }
                    });
                }
                catch (Exception ex)
                {
                    throw ex; // or handle the exception any way you like.
                }
            }
            else
            {
                userList = GlobalVariable.AllUsers.ToString();
            }
            return userList;
        }
 

So you see, the code is fairly simple... if the global variable named AllUsers is null, then go and retrieve all users and place it in a single comma delimited string. If it is not null, then just return the userList since it already contains all the users in it.

Once you have the userList, you can turn this string into an array and handle it just like any other array.

string[] result = userList.Split(new Char[] { ',' });
foreach (string user in result)
{
    // you have the username, now you can get any of their properties (described here)
}

 

Mar 24

Don't know if you'll find this useful, but I did... especially when you're doing something that you don't want to do again everytime the web part loads. For example, when you want all the users in a drop down list, but don't want to keep querying the entire user list each time the web part opens.

This will definitely help with performance since the entire user list is in a single variable that can be queried across all sharepoint users after it loads just once by any single user.

The code below is for the entire class. I posted the whole thing because it was nice and short

using System;
using System.Collections.Generic;
using System.Text;

namespace UserList
{
public class GlobalVariable
{
public static string AllUsers;
public static DateTime ExpirationTime;
}
}

I placed a global expiration time above so I can check when to actually refresh the global variables list. I usually set it to expire at midnight, so the first person in the next day will be the one to populate this variable with all the users (for everyone's use) again.

Mar 24

This one I figured out recently. I have always used the UserProfileService.asmx to get the profile properties of users. Even wrote a little function to return the number based on a profile property (so it would work with custom properties). Now, I don't need to do that anymore.

SPWeb mySite = SPContext.Current.Web;
SPSite site = new SPSite(mySite.Url.ToString());
ServerContext context = ServerContext.GetContext(site);
UserProfileManager profileManager = new UserProfileManager(context);
UserProfile prof = profileManager.GetUserProfile(mySite.CurrentUser.ToString());

string PreferredName = (prof[PropertyConstants.PreferredName].Value == null) ? "" : prof[PropertyConstants.PreferredName].Value.ToString();
string PictureUrl = (prof[PropertyConstants.PictureUrl].Value == null) ? DefaultImageUrl : prof[PropertyConstants.PictureUrl].Value.ToString();

You can request all the properties you want using the PropertyConstants method which uses the Microsoft.Office.Server and Microsoft.Office.Server.UserProfiles class.

That is definitely handy... The question now is, is it possible to query all the sharepoint users and place them in a drop down list so you can retrieve details of a particular user (from the drop down list) without using a web service? If it's possible, I haven't figured out how. I only know how to do that by querying the UserProfileService.asmx web service, which I will discuss in my next post.

Mar 23
Web Part Properties
Posted by Wei in SharePoint Web Parts on 03 23rd, 2009| | No Comments »

Ok, this is really basic... I use this bit of code all the time. I don't know much about blogging, so this is going to serve as a bit of a lesson for me too.

First, declare the variable in camel case:

private string myString = "Hello World!";

Then underneath, you set the properties.

[WebBrowsable(true), WebPartStorage(Storage.Shared)]
[Personalizable(PersonalizationScope.Shared)]
[FriendlyNameAttribute("My String")]
public string MyString
{
get { return myString; }
set { myString = value; }
}

This all goes before you start coding your methods.

To see the string itself in the web part, add the following method into your code:

protected override void Render(HtmlTextWriter writer)
{
writer.Write(MyString);
}

Now, when you deploy the web part and edit the web part properties, you'll see a new category, Miscellaneous. There, you can edit the string and save it. When you save it, whatever you edited that property to should appear in the web part.

Next Entries »