Working with Social Tags in SharePoint 2010

NOTE:  Please download the attachment to get a Word doc with this posting in human readable format.

I had an interesting request recently from someone that wanted help essentially being able to migrate social tags between two different farms.  There are some things out of the box that can help you do this, and some things that are tantalizing, but not quite sufficient.

I tested getting the social tags themselves without any kind of “real” impersonation.  It worked for me but I can’t guarantee that it will in all cases because I was logged in as UPA admin.  If it does not, then you could use the same impersonation technique I’ll describe down below.  To get the tags for a user, I just tried to create a SPSite context as that user, then create all the context objects needed to retrieve tags for that user, like this:

SPUserToken sut = null;

//get the user token for user first so we can

//use that to get a site context as that user

using (SPSite userSite = new SPSite(UrlTxt.Text))

{

   using (SPWeb rootWeb = userSite.RootWeb)

   {

       SPUser socialUser =

          rootWeb.EnsureUser(AccountTxt.Text);

       sut = socialUser.UserToken;

   }

}

UPDATE:  Make sure you read this post to see how you should format the user name for EnsureUser:  http://blogs.technet.com/b/speschka/archive/2013/02/03/what-you-need-to-know-about-using-ensureuser-in-claims-based-web-apps-in-sharepoint-2010-and-sharepoint-2013.aspx.                

//now get the site as that user – NOTE: these are

//all class scoped variables

using (SPSite newSite = new SPSite(UrlTxt.Text, sut))

{

   sc = SPServiceContext.GetContext(newSite);

   stm = new SocialTagManager(sc);

   upm = new UserProfileManager(sc);

   up = upm.GetUserProfile(AccountTxt.Text);

}

 

Once I have the contexts that were created as the current user, then it’s pretty easy to get the tags for that user:

SocialTag[] tags = stm.GetTags(up);

 

TagLst.Items.Clear();

foreach (SocialTag tag in tags)

{

   TagLst.Items.Add(tag.Term.Name + ” – ” + tag.Url.ToString());

}

 

This part was fairly straightforward and easy.  Writing a social tag for a different user was unfortunately not nearly as easy.  The SocialTagManager includes an AddTag method, but it doesn’t provide an overload that includes a UserProfile like the GetTags method does.  This is a tough oversight, and unfortunately using the context of the user that was passed into the new SPSite constructor does not help.  As a result, you need to use impersonation to do this.  In this case I just reused the approach I described in this posting – http://blogs.technet.com/b/speschka/archive/2011/08/07/using-saml-claims-sharepoint-wcf-claims-to-windows-token-service-and-constrained-delegation-to-access-sql-server.aspx.  I configured the CTWTS to allow me (since the my application was running in my user context) to impersonate users.  Specific details on how to do that are described here:  http://msdn.microsoft.com/en-us/library/ee517258.aspx.

So with that approach in mind, here’s how I did the impersonation first:

//start the impersonation

//create the WindowsIdentity for impersonation

WindowsIdentity wid = null;

 

try

{

   wid = S4UClient.UpnLogon(EmailTxt.Text);

}

catch (SecurityAccessDeniedException adEx)

{

   MessageBox.Show(“Could not map the Email to ” +

       “a valid windows identity: ” + adEx.Message);

}

 

//see if we were able to successfully login

if (wid != null)

{

   using (WindowsImpersonationContext ctx = wid.Impersonate())

   {

       //code goes here to add a new tag

   }

}

else

{

   MessageBox.Show(“Couldn’t impersonate user – can’t add tag.”);

}

 

The impersonation code itself is not particularly complex – you just need the user’s Email address (which you could probably get from their UserProfile, which this code retrieves).  You also need the CTWTS running, which installs on each SharePoint server, and this code needs to run on a SharePoint server since it uses the object model.  So again, this should not be a significant hurdle.

Finally, adding the new tag for the user does require jumping through a few hoops but is pretty manageable.  Here’s what that code looks like:

//this is the code that gets the SPSite, SPServiceContext, etc

GetServiceContext();

 

//work with the taxonomy classes so we

//can reuse any existing term, or create a

//new one if it doesn’t exist

TaxonomySession txs = stm.TaxonomySession;

TermStore ts = txs.DefaultKeywordsTermStore;

 

TermCollection terms =

   ts.KeywordsTermSet.GetTerms(TagTxt.Text,

   ts.DefaultLanguage, true);

 

Term t = null;

 

if (terms.Count == 0)

{

   t = ts.KeywordsTermSet.CreateTerm(TagTxt.Text,

       ts.DefaultLanguage);

       ts.CommitAll();

}

else

   t = terms[0];

 

//add the tag

stm.AddTag(new Uri(TagUrlTxt.Text), t);

 

So what happens here is we just look in the taxonomy store for the tag that is being added.  If we find it in there then we use that term, if not we create it and add it to the term store.  Then we add the term and associate it with a Url and it gets added to that user’s social tags.

Overall the code and approach is fairly straightforward.  The main problem here is just working around the inability to specify to which user a social tag should be added.  The impersonation code and CTWTS takes care of that part for us without requiring the password for each user.  The source code for this project is attached to the posting.

 

You can download the attachment here:

Advertisements

Find Out Who’s Following You with SharePoint 2010 Social Features

One of the things I’ve wondered about off and on is how to find out who is following a certain person using the new social features of SharePoint 2010.  For example, when I go to the My Site of somebody I can click on the Colleagues tab and I can see all of the people that person is following.  But what, for example, if I want to run a query and see who all is following me?  Or following Jimmy?  Or whomever.

UPDATE:  I got a great new way to do this from some other folks on the SharePoint team.  This post now uses that method.

To start out with, we’ll get the profile of the person we’re tracking – that is, we want to see who is following this person:

string acctName = “vbtoys\\sjackson”; //we want to see who is following this person

 

UserProfileManager upm = new UserProfileManager(ctx);

 

UserProfile up = upm.GetUserProfile(acctName);

 

 

Next we’re going to create a new instance of the ActivityManager class.  We’re going to pass in the UserProfile and SPServiceContext that we used with the UserProfileManager class to create this instance:

 

ActivityManager am = new ActivityManager(up, ctx);

 

 

Now we’re going to create three variables – one to hold a list of people we want to track (just one person in this case), and two to hold the return values that were found.  In my limited testing I’ve found that the owners variable contains only the UserProfile person.  It’s the colleaguesOfOwners variable that’s really interesting:

 

List<long> bulkRecordIds = new List<long>();

Dictionary<long, MinimalPerson> owners = new Dictionary<long, MinimalPerson>();

Dictionary<long, List<MinimalPerson>> colleaguesOfOwners =

new Dictionary<long,List<MinimalPerson>>();

 

//add the ID of the person I’m tracking

bulkRecordIds.Add(up.RecordId);

 

 

Now we have all the data points we need to make our query.  We do that with the static method GetUsersColleaguesAndRights method on the ActivityFeedGatherer class:

 

ActivityFeedGatherer.GetUsersColleaguesAndRights(am, bulkRecordIds,

out owners, out colleaguesOfOwners);

 

 

Now that we have the results, we can enumerate through the list of colleagues that it found.  These are all people that are following the account we specified in the acctName string var earlier:

 

foreach (long key in colleaguesOfOwners.Keys)

{

List<MinimalPerson> l = colleaguesOfOwners[key];

 

       foreach (MinimalPerson mp in l)

       {

       Debug.WriteLine(mp.AccountName);

       }

}

 

And there you have it – all the people who are following a particular person.

 

Using Audiences with Claims Auth Sites in SharePoint 2010

Something you may not have thought of around using SAML claims is the impact on the Audiences feature in SharePoint 2010.  By default we will only import users from directories like Active Directory and a few LDAP sources.  The problem is that the account name for most SAML claims users is something like i:05:t|adfs with roles|fred@foo.com.  So can you use audiences with these claims users?  The answer is yes, fortunately, but you need to do some work.

The first and most important thing is you’ll need to create profiles for these people.  You can do it manually or you can write some code to do it.  But you need to create these profiles and use the funky i:05:t|adfs with roles|fred@foo.com string as the Account Name.  Then populate the other fields with data that you want to use in your audiences.

Next, go ahead and create new audiences.  You won’t be able to use a user-based for the audience, like member of a group (at least not without writing more code, which is beyond what I’m going to discuss in this posting).  Instead you’ll use the property-based audience.  In my scenario I used the Office field from the profile as the basis for my audience.  I created two profiles for two different claims users and gave one an Office of Clackamas and one Goodyear.  So in my new audience, I created a rule where Office = Clackamas and called it Clackamas Employees.  After I compiled my audience I could see that its membership included my claims user.

To further validate it, I then went into my claims site and targeted a web part at an audience.  The only thing that was a little unexpected is that the picker was not properly populated with a list of all the audiences.  However, when I searched for Clackamas Employees it did find the audience I had created.  I selected that audience for the web part targeting and saved my changes.  Finally I navigated to the site as my two different claims users.  The one that was part of the Clackamas Employees audience saw the part, while the other did not.