Adventures with SharePoint 2010 ActivityManager, Newsfeed and other animals…

So this week i was tasked with creating a custom newsfeed and social wall (think Facebook) for a client using SharePoint 2010’s built in activity feed. One aspect of the project was to take the OOTB activities from the newsfeed and save them in an external database, so custom built solutions will be able to display them. While it may seem as a simple task at first, it turned out to be, what you may call, a learning experience.

In the following post I’ll walk you through all the steps and findings i got from this experience, so hopefully you will save precious time if you get to deal with the same things i had to.

Chapter 1: The case of the missing activities.

Before getting to the developing part I started with the basics. After creating a handful of users I created some activities for them (tagging, creating a new blog post etc.). Soon after, I realized no activities are shown on any of the user’s newsfeeds. First thing that came to my mind was:

Thankfully, the solution was quick and easy.

By default, SharePoint doesn’t enable the newsfeed, and as such, the timer job responsible for collection all the data for the newsfeed and recent activities is disabled.

To fix this unfortunate issue, simply follow these instructions:

1)  Head over to central admin, click on Manage service applications and then click on your User profile service application. Once there, click on Setup my sites under the My site settings section:

2) When the new page opens, scroll down and find the Newsfeed section. Make sure the Enable newsfeed on My sites checkbox is checked and click OK.

3) Click on Monitoring on the left side quick navigation menu, and then click on Review job definitions under the Timer Jobs section.

4) Find the User Profile Service Application – Activity Feed Job (first part of the job name might vary in your environment), click on it and then click on Enable.

5) Set the desired time frame for the job to run (usually set to run every hour) and click Run Now.

6) Once the job finishes its run (you can see it on the Job History link under the Timer Links section of the left menu) all the activities are shown on the user’s my site.

Chapter 2: Who are you ActivityManager, and what have you done with my activities??

So with the activities shown, and everything is sunny and bright, I moved forward to create my custom timer job that will copy the activities from the newsfeed to my own database.

Things seemed pretty straightforward from this point on. All I have to do now is create a timer job that will loop through all the users in the User Profile Service (UPS for short), initialize an ActivitiyManager object for each one of them and use the GetActivitiesByMe method to get all of the specified user activities.

Creating the timer job was a breeze. Just create a new class and inherit from SPJobDefinition base class. Inside the Execute method i get access to the UserProfileManager using the following code:

var currentContext = SPServiceContext.GetContext(webApp.ServiceApplicationProxyGroup, SPSiteSubscriptionIdentifier.Default);
UserProfileManager userProfMan = new UserProfileManager(currentContext);

So far so good.  Inside the foreach loop of the user profiles, it’s time to initialize the ActivityManager for each profile and get its activities. The ActivityManager object require the following arguments:


Pay special attention to the last line: “userProfile: The UserProfile object representing the user who will be treated as the current user for this…” We will get back to this line shortly.

So with this info in mind, I created my ActivityManager class instance as follows:

foreach (UserProfile profile in userProfMan)
{
    ActivityManager activityMan = new ActivityManager(profile, currentContext);
}

Now, to get all of the selected user activities, i make a call to the GetActivitiesByMe method of the ActivitiyManager as follows:

ActivityEventsCollection myActs =  activityMan.GetActivitiesByMe();

So all smiling and happy i deploy my solution, set a breakpoint right at the end of the foreach loop and run the timer job from SharePoint’s central admin page.

When the first user returned zero activities i thought “OK, i might have forgotten to add activities for that one”, when the second returned zero i started to get suspicious, when the third, fourth and fifth returned zero activities – i knew something went wrong.

After a few hours of debugging, googling and using reflector, I found this key piece of evidence:

public ActivityEventsCollection GetActivitiesForMe(DateTime minEventTime, int maxEvents)
{
    this.EnsureViewerInfo();
    return new ActivityEventsCollection(this, -1L, false, minEventTime, maxEvents);
}

This is the method that gets called once you try to get events for a user and can be found under Microsoft.Office.Server.UserProfile.dll.

When the method gets called,It calls the EnsureViewerInfo method which has the following code snippet:

this.GetViewerAndPublisherInfo(this.m_UserInfoRetrieved ? null : UserProfileGlobal.GetCurrentUserName(), null, out person, out person2);

The method checks if m_UserInfoRetrieved private member is true. if so it returns null but otherwise it calls the GetCurrentUserName method.

I couldn’t find ANYWHERE in the code that sets this member so from what i can understand it is always false and as such always calls the GetCurrentUserName method!

The GetCurrentUserName method uses the current http context and windows authentication methods to get the current user. It doesn’t care nor want to care about whatever UserProfile you passed to the ActivityManager constructor.

Trying to find confirmation to my theory i found the following thread on MSDN forums:
Sharepoint 2010 social network API Activity feeds
On this thread, a member named Daniel Larson wrote the following:

…You cannot impersonate the activity manager… not with elevated priveledge or other means! In fact, nowhere in the Social APIs do they support impersonation.

>> Do you know why ActivityManager needs user profile object to create a new object?

Just guessing, but when they wrote the code they probably thought they’d support impersonation, but then decided against it for security reasons.

This is the exact same conclusion as i got.

Seeing this, i realized i will have to find another way to get a user’s activity feed.

Chapter 3: The case of the missing ‘New blog post’ activity!

As I realized the GetAcitiviesByMe method isn’t going to cut it, I went ahead and used the GetActivitiesForUser method of the same said ActivitiyManager object. This method takes a UserProfile object (or a string representing the user’s login name) as a single  argument and return an ActivityEventsCollection object. After the change, my code looks as follows:

foreach (UserProfile profile in userProfMan)
{
     ActivityManager activityMan = new ActivityManager(profile, currentContext);
     var activities = activityMan.GetActivitiesByUser(profile);
}

Once the solution deployed and debugged a progress was made! Instead of getting zero activities for users I now get some of them. A bit more checks and I realize that some types of activities just refuse to show up. The one that caught my eye the most was the “New blog post” activity. I’ll spare you the description of the head banging against the wall/desk/bowl of fruits etc. and just show you how to fix this.

Under the Setup My Sites area of My Site Settings (if you need a reminder on where it is – check chapter 1 in this article) you’ll find a section called Security Trimming Options. This property allows you to set whether to checks each link in the user’s activity feed against the user who try to watch it and determine whether he has the sufficient permissions to watch the activity or not:

By default, Check all links for permissions is selected and as such if the user who try to get the activities (the system account in my timer job case) doesn’t have direct access permissions to the area where the activity happened (the user’s blog in this example) – any activity from that area won’t be returned. By changing the property to Show all links regardless of permissions, all of the user activities are now returned in my custom timer job and i can happily move forward to get the correct template of the activity and save it to my database!

Chapter 4: “I could template you all day if i wanted to!”

So now i have a collection of ActivityEvent objects and i wish to template them the same way SharePoint does before showing them on a user’s my site or newsfeed. Sadly, an ActivityEvent object doesn’t come with a built-in property or method that will template the object for us. Instead, it has several properties (keywords) that are used by the templating engine to build the activity text:

  • Name
  • Link
  • Link2
  • Value
  • Publisher
  • Owner

An example of activity templates is as follows:

{Publisher} published a new blog post.<br/>{Link}

{Publisher} rated {Link} as {Value} of {Name}.

{Publisher} tagged {Link} with your interest.<br/>{Link2}

All of the OOTB templates are stored in the osrvcore resource file located at: C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\Resources.

What I need is the full activity text in order to store it in a database. Therefore, my plan of action is to load the templates resource file, get the right template for my ActivityEvent activity and use a super-simple regular expression to replace the keyword (i.e: Publisher, Link etc.) with its value from the ActivityEvent object.

To load the templates resource file I used Neo Assyrian’s post Retrieve Activities from the SharePoint 2010 Activity Feeds as reference:

ActivityType activityType = activityMan.ActivityTypes[activity.ActivityTypeId];
ActivityTemplate activityTemplate = activityType.ActivityTemplates[bool.FalseString];
var templateStr = SPUtility.GetLocalizedString("$Resources:" + activityTemplate.TitleFormatLocStringName, activityTemplate.TitleFormatLocStringResourceFile, (uint)CultureInfo.CurrentUICulture.LCID);

By getting the activity type (based on the ActivityTypeId of the current activity) we can get the ActivityTemplate object which, among other things, holds the title of the template. This title is actually the key in the resource (resx) file and its value is the template text.

Now that we have templateStr which holds the template text, it’s time to use my little non-scalable helper method and build the complete html text for that template:

private string RenderEvent(string format, ActivityEvent actEvent)
{
    Regex regx = new Regex(@”\{([^}]*)\}”, RegexOptions.IgnoreCase);

    MatchCollection mactches = regx.Matches(format);

    foreach (Match match in mactches)
{
        switch (match.Value.ToLower())
{
             case “{publisher}”:
format = format.Replace(match.Value, BuildUrl(actEvent.Publisher.Href, actEvent.Publisher.Name));
                 break;
             case “{value}”:
format = format.Replace(match.Value, actEvent.Value);
                 break;
            case “{link}”:
format = format.Replace(match.Value, BuildUrl(actEvent.Link.Href, actEvent.Link.Name));
                 break;
           case “{link2}”:
format = format.Replace(match.Value, BuildUrl(actEvent.Link2.Href, actEvent.Link2.Name));
                 break;
           case “{owner}”:
format = format.Replace(match.Value, BuildUrl(actEvent.Owner.Href, actEvent.Owner.Name));
                 break;
           case “{name}”:
format = format.Replace(match.Value, actEvent.Name);
                 break;
}
}
return format;
}

The method accepts two variables: a template format and an ActivityEvent object that represent the activity, it than use a simple regular expression to extract all the template keywords (words between two curly brackets) and then using a simple switch/case replace the keyword with HTML content using the BuildUrl method:

private string BuildUrl(string href, string name)
{
return string.Format(“<a href='{0}’ title='{1}’>{1}</a>”, href, name);
}

And that wrap the entire thing up. I now have the template fully rendered as html, just like SharePoint shows it on the user’s my site and newsfeed!

Epilogue:

So after all of this, I finally got my timer job working. Every hour it takes all the new activities and save them in an external database that i use with my custom web parts to show these activities. This just shows that even though the social API is not the best or the most neat one out there, with a little bit of work, we can bend it to do as we wish.

If you want to read a bit more about the newsfeed, check out my earlier post on the subject Working with SharePoint 2010 User Activity NewsFeed.

Advertisements

Working with SharePoint 2010 User Activity NewsFeed

I was recently tasked with creating an application that once used will add an entree to the user’s activity feed.

The activity feed is a fun and social feature (one of many) of SharePoint 2010, and its purpose is to present a feed of actions that the user or his colleagues performed on the site.

You can see how the activity feed looks on the following screen shot, taken from my local SharePoint development environment:

Now hopefully that got you excited enough to read on 😉

We are going to build a simple web part that will add an entree to the user activity feed. The web part will contain a single text box and a button that once pressed will add the entree to the user’s feed.

1) Open Visual Studio 2010, create a new Empty SharePoint Project with the name “ActivityFeedInteraction” and choose to deploy it as farm solution.

2) Right click on the solution name, choose “Add” and then “New Item”. Click on “Web Part” and name it “ActivityWebpart”

3) Right click on “References” and choose “Add Reference”. Add the following 3 dlls to your solution:

  • Microsoft.Office.Server
  • Microsoft.Office.Server.UserProfiles
  • System.Web

4) The activity feed is based upon activities our user chooses to follow on his “Newsfeed Settings” screen:

As we can see there are plenty of default activities that the user follows, and if we wish to tap in the user’s news feed we have to build a new activity for him to follow first. In our solution we will create that activity once the feature that installs the web part is activated.

In visual studio, expend the Features node, right click on “Feature1” and choose “Add Event Receiver” as seen on the following screen shot:

5) Open Feature1.EventReceiver.cs and uncomment the FeatureActivated method.

6) Add the following code to the method:

UserProfileManager pm = new UserProfileManager(SPServiceContext.Current);
UserProfile
currentUserProfile = pm.GetUserProfile(SPContext.Current.Site.OpenWeb().CurrentUser.LoginName);
ActivityManager actMgr = new ActivityManager(currentUserProfile);
ActivityType customActivityType = null;
ActivityApplication actApplication = null;
ActivityTemplate
customActTemplate = null;

Let’s explain what we did here: First we created a UserProfileManager object and passed the current SPService context to it, then we created a UserProfile object of the currently logged in user, then we created an ActivityManager object for the current user and finally we created an empty ActivityType, ActivityApplication and ActivityTemplate objects. We will use all of these later.

7) Add the following code right below the code you previously pasted:


if (actMgr.PrepareToAllowSchemaChanges())

{

}

else
{

SPDiagnosticsService
.Local.WriteTrace(0, new SPDiagnosticsCategory(“Custom Activity Creator”, TraceSeverity.High, EventSeverity.Error),
        TraceSeverity.High, string.Format(“The user {0} does not have the administrator rights on the User Profile service”,   
SPContext
.Current.Site.OpenWeb().CurrentUser.LoginName), “”);

throw new Exception(“The user dosent have the required permissions for this action”);
}

Before we go on with the creation of the activity we need to check if the user who install the feature have the required rights to create it. If he does – we go on with the creation, if not we write a message to SharePoint’s log and quit.

8) Now it’s time to add the code for creating the activity. First we create the activity application using the following code (paste it inside the if statement):

if (actMgr.ActivityApplications[“CustomActivity”] == null)

{
actApplication = actMgr.ActivityApplications.Create(“CustomActivity”);

actApplication.Commit();

actApplication.Refresh(false);

}

else

{

actApplication = actMgr.ActivityApplications[“CustomActivity”];

}

First we check if an activity application named CustomActivity exsist, if it doesn’t – we create it, if it does – we initialize our actApplication object to it.

9) Now that we have our ActivityApplication object ready, it’s time to move on to the ActivityType. Paste the following code right below the previous code blocks:

customActivityType = actApplication.ActivityTypes[“MyCustomActivity”];

if (customActivityType == null)
{

customActivityType = actApplication.ActivityTypes.Create(“MyCustomActivity”);

    customActivityType.ActivityTypeNameLocStringResourceFile = “CustomActivityResource”;
customActivityType.ActivityTypeNameLocStringName = “ActivityName”;

    customActivityType.IsPublished = true;
customActivityType.IsConsolidated = true;

customActivityType.AllowRollup = true;

customActivityType.Commit();

customActivityType.Refresh(false);

}

Like before, we first try to get our activity type from the ActivityApplication activity types collection, if it comes back null that means it doesn’t exists and we move on to creating it.
There are 2 lines of code in this block that we need to draw attention to:

customActivityType.ActivityTypeNameLocStringResourceFile = “CustomActivityResource”;

customActivityType.ActivityTypeNameLocStringName = “ActivityName”;

The first lines set the activity type resource file name and the second set the name inside the resource file for the activity name.

You might be wondering what is this resource file we are talking about, so fear not! We are going to create it next.

10) Right click on the solution name and choose “Add” and then “SharePoint Mapped Folders”. You should see the following dialog box:

Click on “Resources” and then OK.

11) Right click on the newly created “Resources” node in our solution and choose “Add” -> “New Item”. Find “Resource File” in the list of available templates and name it CustomActivityResource (this is how we set ActivityTypeNameLocStringResourceFile in the previous step).

12) Once the file is created, you will automatically be brought to the file editing screen. Replace “String1” with “ActivityName” (You’ve probably guessed that’s the same name as ActivityTypeNameLocStringName setting from the previous step) and give it a value of “My New Sweet Custom Activity!” This is the name that will be shown on the newsfeed settings screen we saw in step 4. You can add additional resource files in different languages and depending on the user’s language settings SharePoint will use the appropriate resource file for the user’s language.

13) We are now done with the ActivityType object and we are moving on to creating the last object in our activity – the template. Paste the following code right below the closing bracket for the last if statement:

customActTemplate = customActivityType.ActivityTemplates[ActivityTemplatesCollection.CreateKey(false)];

if (customActTemplate == null)
{

customActTemplate = customActivityType.ActivityTemplates.Create(false);

customActTemplate.TitleFormatLocStringResourceFile = “CustomActivityResource”;

customActTemplate.TitleFormatLocStringName = “Activity_Created”;
customActTemplate.Commit();
customActTemplate.Refresh(false);

}

Just like before, we check if the template we need already exists, if it doesn’t we create it. We then need to provide a resource file that holds a string that will act as a template for the user’s news feed. (In our code this template is called Activity_Created).

14) Open the resource file we previously created (CustomActivityResource) and add a new row with the following info:

Name:
Activity_Created
Value: {Publisher} wrote {Value} on the wall using a custom activity!

Think of the value text as the template for the activity. This template is how the activity will show on the user’s newsfeed. The template can use template variables like {Publisher} and {Value} that will be replaced by data we provide to the activity at run time. Other variables like {Link} or {Size} can also be used if needed.

15) That’s it! We are done with creating the activity! At this point, your method should look like this:

public override void FeatureActivated(SPFeatureReceiverProperties properties)
{

UserProfileManager
pm = new UserProfileManager(SPServiceContext.Current);
    UserProfile currentUserProfile = pm.GetUserProfile(SPContext.Current.Site.OpenWeb().CurrentUser.LoginName);
ActivityManager
actMgr = new ActivityManager(currentUserProfile);
ActivityType customActivityType = null;
ActivityApplication
actApplication = null;

ActivityTemplate
customActTemplate = null;

if(actMgr.PrepareToAllowSchemaChanges())
{
if
(actMgr.ActivityApplications[“CustomActivity”] == null)
{
actApplication = actMgr.ActivityApplications.Create(“CustomActivity”);
actApplication.Commit();
actApplication.Refresh(false);
}
else

{
actApplication = actMgr.ActivityApplications[“CustomActivity”];
}

        customActivityType = actApplication.ActivityTypes[“MyCustomActivity”];


if (customActivityType == null)

{

customActivityType = actApplication.ActivityTypes.Create(“MyCustomActivity”);

customActivityType.ActivityTypeNameLocStringName = “ActivityName”;

customActivityType.ActivityTypeNameLocStringResourceFile = “CustomActivityResource”;

customActivityType.IsPublished = true;

customActivityType.IsConsolidated = true;

customActivityType.AllowRollup = true;

customActivityType.Commit();

customActivityType.Refresh(false);

}

customActTemplate = customActivityType.ActivityTemplates[ActivityTemplatesCollection.CreateKey(false)];

if(customActTemplate == null)
{
customActTemplate = customActivityType.ActivityTemplates.Create(false);
customActTemplate.TitleFormatLocStringResourceFile = “CustomActivityResource”;
customActTemplate.TitleFormatLocStringName = “Activity_Created”;
customActTemplate.Commit();
customActTemplate.Refresh(false);
}
}
else

{
SPDiagnosticsService
.Local.WriteTrace(0, new SPDiagnosticsCategory(“Custom Activity Creator”, TraceSeverity.High, EventSeverity.Error),            
TraceSeverity
.High, string.Format(“The user {0} does not have the administrator rights on the User Profile service”,
SPContext.Current.Site.OpenWeb().CurrentUser.LoginName), “”);

        throw new Exception(“The user dosent have the required permissions for this action”);
}
}

16) Now that we have an activity let’s make use of it! Open “ActivityWebPart” node and double click “ActivityWebpart.cs”.

17) Add the following private variables:

private TextBox _tbActivity;

private Label _lblActivity,_lblInfo;
private Button _btnActivity;

 18) Add the following code to the CreateChildControl method:

_lblActivity = new Label() { Text = “Activity Text “ };

this.Controls.Add(_lblActivity);
_tbActivity = new TextBox();

this
.Controls.Add(_tbActivity);

this
.Controls.Add(new Literal() { Text = “<br/>” });

_btnActivity = new Button();

_btnActivity.Text = “Send Activity”;
_btnActivity.Click += new EventHandler(_btnActivity_Click);
this
.Controls.Add(_btnActivity);

this
.Controls.Add(new Literal() { Text = “<br/>” });

_lblInfo = new Label() { Visible=false };

this
.Controls.Add(_lblInfo);

19) Add the following method below CreateChildControl:

void _btnActivity_Click(object sender, EventArgs e)

{
UserProfileManager
pm = new UserProfileManager(SPServiceContext.GetContext(SPContext.Current.Site));

UserProfile
currentUserProfile = pm.GetUserProfile(SPContext.Current.Web.CurrentUser.LoginName);

ActivityManager
actMgr = new ActivityManager(currentUserProfile);

try
{

long
aId = actMgr.ActivityApplications[“CustomActivity”].ActivityTypes[“MyCustomActivity”].ActivityTypeId;

        if (aId != 0)
{

            CreateEvent(_tbActivity.Text, aId, currentUserProfile, actMgr);
_lblInfo.Text = “Activity created successfully!”;

}

else

_lblInfo.Text = “No Activity Found!”;

_lblInfo.Visible = true;

    }
    catch (Exception ex)
{

SPDiagnosticsService
.Local.WriteTrace(0, new SPDiagnosticsCategory(“Custom Activity Creator”, TraceSeverity.High, EventSeverity.Error), TraceSeverity.High, ex.Message,ex.StackTrace);

    }

}

Once the button is clicked the method gets the current user profile from the UserProfileManager object, and then gets the activity id from the activity we have created in the feature activated method. If we get an id we move on to the CreateEvent method which we will build in the next step, but if no id is returned the method changes the text on the info label to “No Activity Found” and quits.

20) Now let’s add the final peace of the puzzle: the CreateEvent method. Add the following code right below the closing bracket for _btnActivity_Click method

private void CreateEvent(string text, long aId, UserProfile currentUserProfile, ActivityManager actMgr)

{
Entity
publisher = new MinimalPerson(currentUserProfile).CreateEntity(actMgr);

ActivityEvent
activityEvent = ActivityEvent.CreateActivityEvent(actMgr, aId, publisher, publisher);

activityEvent.Name = “MyCustomActivity”;

activityEvent.ItemPrivacy = (int)Privacy.Public;

activityEvent.Owner = publisher;

activityEvent.Publisher = publisher;

activityEvent.Value = text;

activityEvent.Commit();

}

This method sets the publisher for the method (the person who initiated it), then creates an ActivityEvent object which will hold all the information about the event, setting its privacy, owner and publisher (in our example the owner and publisher are the same user, but if you want to publish events to other users, such as the current user colleagues – you will set different users in these properties) and finally we set the value property (which is what the {Value} template placeholder will use for rendering). Once we execute the Commit method, the event will show in the newsfeed!

21) Time to test our solution. Click on the solution name and in the properties window change “Active Deployment” to “No Activation”. Right click on the solution name and click Deploy.

22) Go to the site you deployed the solution to; click “Site Actions” and “Site Settings”. Click on “Site Collection Features” and activate “ActivityFeedInteraction Feature1”

23) Go to your “My Site” and click “Newsfeed Settings”. You should see our new activity is there and waiting for action!

24) Go back to your site, and add the “ActivityWebPart” web part to a page of your choice.

25) Type something in the text box and click “Send Activity”.

26) If all went well, you should see on your “My Profile” page a new entree for your activity which was based on the template we supplied earlier! The following screenshot shows an example:

That’s it! We have created a new custom activity and a web part that accompany it in order to add events to a user’s newsfeed.

I hope this post helped you understand how to use the activity feed, one of the best social features of SharePoint 2010.

If you want to download the final project then go right ahead and click here.