Implementing Favorites in SharePoint 2010

As a SharePoint consultant, I’m often asked to provide features which “should” exist, but Microsoft just doesn’t provide. Often these are features which might be common in the Web 2.0 world, but – well, not here.

In one of my current projects (based on a Publishing Site), the paradigm of a “favorite button” is central to the desired site functionality. Users should be able to “favorite” any document or folder. These documents and folders are visible in a centrally located list, along with metadata items such as Modified Date and Author.

There are many possible ways to implement this, but initially I chose to use SharePoint 2010’s Social Tagging feature as the basic building block. It’s already set up to store link information by user, and integrates with the Keywords area of the Term Store. On the back end, this is the same mechanism used when you click “I like it” on any page.

This article is more concerned with the back end of things – the code listed below can be wrapped in web services, web parts or whatever you like, to provide functionality to the end user.

Tagging items

Social Tags are essentially key/value pairs, with the key being a Uri (e.g. the url of a web page), and the value being a description. In addition to the tag of “I like it”, other tags may be created in the Keywords area of the Term Store, and then used to tag documents. Here’s the code I’m using to tag a document:

 /// <summary>
/// Updates or adds a social tag for this user.
/// User info is derived from context.
/// Tag is added to term store if necessary.
/// </summary>
public static void UpdateSocialTag(SPSite site, string socialTagName, string title, string url)
{
    SPServiceContext context = SPServiceContext.GetContext(site);
    SocialTagManager mySocialTagManager = new SocialTagManager(context);
    //Retrieve the taxonomy session from the SocialTagManager.
    TaxonomySession taxSession = mySocialTagManager.TaxonomySession;
    TermStore termStore = taxSession.DefaultKeywordsTermStore;

    Term newTerm = termStore.FindOrCreateKeyword(socialTagName);
    Uri tagUri = ConvertToUri(site, url);
    mySocialTagManager.AddTag(tagUri, newTerm, title);
}

/// <summary>
/// Create a uri from this url.  
/// If exception, (e.g. url is relative), use site url as base and try again.
/// </summary>
public Uri ConvertToUri(SPSite site, string url)
{
    Uri tagUri;
    if (Uri.IsWellFormedUriString(url, UriKind.Absolute))
    {
        tagUri = new Uri(url);
    }
    else
    {
        //try again by prepending the site uri 
        if (!url.StartsWith("/")) url = "/" + url;
        tagUri = new Uri(site.Url + url);
    }
    return tagUri;
}

The reason this method is called “Update” is that if this Uri has already been tagged, that tag will be replaced.

The FindOrCreateKeyword extension is defined here:

public static class KeywordExtensions
{
     //Find this keyword in the term store.  If it doesn't exist, create it.       
     public static Term FindOrCreateKeyword(this TermStore termStore, string socialTagName)
     {
         Term term = FindKeyword(termStore, socialTagName);
         if (term == null)
         {
             term = termStore.KeywordsTermSet.CreateTerm(socialTagName, termStore.DefaultLanguage);
             termStore.CommitAll();
         }
         return term;
     }

     public static Term FindKeyword(this TermStore termStore, string socialTagName)
     {
         Term term = termStore.KeywordsTermSet.Terms.FirstOrDefault(t => string.Compare(t.Name, socialTagName, true) == 0);
         return term;
     }

     public static Term KeywordItems(this TermStore termStore, string socialTagName)
     {
         Term term = termStore.KeywordsTermSet.Terms.FirstOrDefault(t => string.Compare(t.Name, socialTagName, true) == 0);
         return term;
     }
}

Retrieving a list of favorites

I’ll also want to display my list of favorites. Social tagging does not intrinsically lend itself to pulling out the list of items which have a particular tag, but we can add that capability using the following code:

/// <summary>
/// Get the items tagged with TermName for this user.
/// If empty, return an empty array
/// </summary>
internal static SocialTag[] GetUserSocialTags(SPSite site, string termName)
{
    List<SocialTag> socialTags = new List<SocialTag>();

    SPServiceContext serviceContext = SPServiceContext.GetContext(site);
    UserProfileManager mngr = new UserProfileManager(serviceContext);  // load the UserProfileManager
    UserProfile currentProfile = mngr.GetUserProfile(false);// Get the user’s profile

    if (currentProfile == null) return socialTags.ToArray();            // user must have profile
    SocialTagManager smngr = new SocialTagManager(serviceContext);

    // Get the SocialTerm corresponding to this term.
    SocialTerm favTerm = GetSocialTerm(termName, currentProfile, smngr);
    if (favTerm == null) return socialTags.ToArray();

    // Get the terms for the user.  Loop through them for conformity.
    SocialTag[] tags = smngr.GetTags(currentProfile);
    foreach (SocialTag tag in tags)
        if (tag.Term == favTerm.Term)
            socialTags.Add(tag);
    return socialTags.ToArray();
}

/// <summary>
/// retrieve a named social term.
/// </summary>
private static SocialTerm GetSocialTerm(string tag, UserProfile currentProfile, SocialTagManager smngr)
{
    // Get the terms for the user
    SocialTerm[] terms = smngr.GetTerms(currentProfile);
    SocialTerm favTerm = null;

    //Iterate through the terms and search for the passed tag
    foreach (SocialTerm t in terms)
    {
        if (string.Compare(t.Term.Name, tag, true) == 0)
        {
            favTerm = t;
            break;
        }
    }
    return favTerm;
}

This code forms the “core” of my favorites system. The ability to tag a document (or remove tag) is wrapped in a web service to allow us to provide provide AJAX functionality. I wrote a web part to display the favorites, with simple sorting and filtering.

Metadata features

One missing element is the metadata, a part of the requirement I mentioned above. For this, I wrote code (as part of my Favorites Web Part) to pull out the necessary metadata for each Uri given, provided it’s a reference to the current site.

4 comments