Saturday 19 January 2013

Creating Custom Activity Type - Part 2


​In the previous blog on creating custom activity type Part 1​ , I have shown how to add a custom activity type to appear in the Newsfeed Settings page. In this part I will show how to create activity events so that it starts  to appear in your activity feed. The most important object that you require in order to create an Activity Event is the ActivityManager. But it brings with it some problems. If you look at MSDN , ActivityManager has a constructor


public ActivityManager(
UserProfile userProfile,
SPServiceContext serviceContext
)


Parameters
userProfile : This value must be identical to the UserProfile object that represents the current user.
serviceContext : Default context for this ActivityManager

Here is the problem. The constructor says that the userProfile MUST represent the current user. Now, what will happen if the logged in user is not the administrator and is a normal user with limited permissions?
The user will get an error message stating that the user does not have the required permissions. As a SharePoint developer, we can think of elevating the SPSite object and passing the System Account user to create the ActivityManager object. But it is not simple as it seems to be. Even though you pass the elevated user, still it will pick user from the current context. So how to go about solving this problem. The solution that works over here is to modify the HTTPContext. It seems that the User Profile Service Application depends
upon the HTTPContext . So the workaround to solve this issue is that we need to store the current HTTPContext  in some temporary variable so that later you can set the context back to  the original, impersonate the context, perform your operations and finally set the context back to the original one. Below is the code snippet as to how you can do the same.


HttpContext httpContext = HttpContext.Current;
SPUser user = SPContext.Current.Web.CurrentUser;
string userName = user.LoginName;
try
{
    SPServiceContext currentContext = SPServiceContext.Current;
    HttpRequest request = new HttpRequest("", web.Url, "");
    HttpContext.Current = new HttpContext(request, new HttpResponse(new          StringWriter(CultureInfo.CurrentCulture)));
    HttpContext.Current.Items["HttpHandlerSPWeb"] = web;
             
    WindowsIdentity wi = WindowsIdentity.GetCurrent();
    typeof(WindowsIdentity).GetField("m_name", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(wi, adminUser);
    HttpContext.Current.User = new GenericPrincipal(wi, new string[0]);
             
    serviceContext = SPServiceContext.GetContext(HttpContext.Current);
    UserProfileManager pm = new UserProfileManager(serviceContext);
    UserProfile current = null;
             
    if (pm != null)
    {
        SPUser adminUser = web.EnsureUser(adminUser);
        if (user.IsSiteAdmin)
            current = upm.GetUserProfile(adminUser.LoginName);
        HttpContext.Current.Response.Write(current);

    }
    if (current != null)
        actMgr = new Microsoft.Office.Server.ActivityFeed.ActivityManager(current, serviceContext);
             
}
catch (Exception ex)
{
}
finally
{    
    //undo the impersonated httpcontext very much required
    HttpContext.Current = httpContext;
    WindowsIdentity wi = WindowsIdentity.GetCurrent();
    typeof(WindowsIdentity).GetField("m_name", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(wi, userName);
    HttpContext.Current.User = new GenericPrincipal(wi, new string[0]);
}

So, if you observe the above code snippet, First store the HTTPContext in a temporary variable httpContext. Second, generate a new context with impersonated user who has the permissions on the User Profile Service Application. Generate the ActivityManager instance using the elevated context. If you  observe now, you will not get any Permission related issue now. Please note that in the finally clause, we are setting the context back to the original one. It is IMPORTANT to set it back to original for obvious reasons. Having successfully generating the ActivityManager instance, you are very near to achieveing the actual motive and i.e. to have a custom feed in your Newsfeed settings page. Next step is to Create an event that will appear in your feed. Below is the sample code snippet to achieve this.


if (actMgr != null)
{
    long aId = actMgr.ActivityApplications["ActivityThemeApplication"].ActivityTypes["ActivityThemeType"].ActivityTypeId;
    if (aId != 0)
    {
        foreach (Colleague colleague in loggedInUser.Colleagues.GetItems())
        {
            CreateEvent(cssFilename, aId, colleague.Profile, actMgr, loggedInUser, themePageURL);
        }
    }
}
public void CreateEvent(string theme, long aId, UserProfile colleagueProfile, Microsoft.Office.Server.ActivityFeed.ActivityManager actMgr, UserProfile loggedInUser,string themePageURL)
{
    try
    {
             
      if (colleagueProfile != null)
      {
          Entity publisher = new MinimalPerson(loggedInUser).CreateEntity(actMgr);
          Entity owner = new MinimalPerson(colleagueProfile).CreateEntity(actMgr);
          ActivityEvent activityEvent = ActivityEvent.CreateActivityEvent(actMgr, aId, owner, publisher);
          activityEvent.Name = "ActivityThemeType";
          activityEvent.ItemPrivacy = (int)Privacy.Public;
          activityEvent.Owner = owner;
          activityEvent.Publisher = publisher;
          Link link = new Link();
          link.Name = "Click here to change your theme." ;
          link.Href = SPContext.Current.Site.Url + themePageURL;
          activityEvent.Link = link;
          activityEvent.Value = theme;
          activityEvent.Commit();
     
       }
     }
     catch (Exception ex)
     {
     }
}



loggedInUser : User Profile of the current logged in user
cssFileName  : It can be anything. In this case, this is the name of the theme that I want to set.
themePageURL : In this case this is the link for my custom page where users can change their themes. It appears in the newsfeed where colleagues can click to change their theme.


In the above code snippet, we are pushing this custom feed in the newsfeed page of all the colleagues of the current logged in user. Publisher is the current logged in user who is changing his theme and Owner is the colleague in whose newsfeed we want the notification to appear. Rest of the code is self-explanatory. The end result is that whenever a person changes his theme then all his colleagues will be notified of this change. In-case you want this to appear in the Recent Activities of the current logged in user who is changing the
theme, then there is a slight modification. In that case you simply need to sent the Owner and Publisher as the same and that is the current logged in user and that is it.



So we can see our custom type in the newsfeed. :)

In the next part I will explain what happens at the backend behind the scenes. Till then keep exploring. :)

- Geetanjali




No comments:

Post a Comment