Monday, April 16, 2012

Access SocialTerms or Tags in custom timer job

Share/Save/Bookmark


Though we thought this could make implementing the functionality very easy, it went through with a lot of research and lessons learnt.

So, what’s the issue?

When accessing the tags/terms(GetTerms) from SocialTagManager it always throw an error “Microsoft.Office.Server.UserProfiles.UserNotFoundException: An error was encountered while retrieving the user profile”. With a common sense we can see that timer jobs run in a different contextual process(owstimer.exe) which is by default configured to be run by networkservice account. So I thought maybe service accounts did not have user profiles and that’s why it threw error.
Now, I navigated to CA and tried to add user profile for networkservice account which threw another error. The whole point that I understand here is service accounts(system,networkservice) cannot have user profiles but only for domain accounts. [Lesson 0]

My common sense strikes again and asks why does it need a userprofile? I just asked to get all tags for a url on which service account cannot be used to Tag.
It’s time to review what MS has done. So I pulled the source revealer, Reflector from the ware house and found the below code in SocialTagManager class


public SocialTerm[] GetTerms(Uri url, int maximumItemsToReturn, SocialItemPrivacy socialItemPrivacy)

{
SocialTerm[] termArray2;
if (null == url)
{
throw new ArgumentNullException("url");
}

if ((maximumItemsToReturn < 0) || (maximumItemsToReturn > 0x3e8))
{
throw new ArgumentOutOfRangeException("maximumItemsToReturn");
}

bool flag = socialItemPrivacy == SocialItemPrivacy.IncludeAllPrivateData;

bool flag2 = socialItemPrivacy == SocialItemPrivacy.IncludeMyPrivateData;

if (flag && !base.IsSocialAdmin)
{
throw new UnauthorizedAccessException();
}

url = AlternateAccessMapping.GetSerializedUrl(url);
using (SqlCommand command = new SqlCommand("dbo.proc_SocialTags_GetTermsForUrl")) // Procedure to get all terms for url from DB
{

command.CommandType = CommandType.StoredProcedure;
command.Parameters.Add("@includePrivate", SqlDbType.Bit).Value = flag;
if (flag2)
{
command.Parameters.Add("@viewer_user_recordID", SqlDbType.BigInt).Value = base.GetCurrentUserProfileRecordId(); // Incudes current user private data
}

…..

….

}

What we can infer from the code is that, when a user attempts to read social data his/her private data(tags/notes) is included the reason why GetTerms(url) is looking for userprofile of the user(networkservice) running the code - [Lesson1]. Found the root cause but what is the solution?

Quickly impersonation came to the mind, and I passed the token of my domain account while instantiating SPSIte object, still no luck but the same error. Again reflecting the DLL’ s I found the below from UserProfiles.UserProfileGlobal class.

[SecurityPermission(SecurityAction.Assert, Flags=SecurityPermissionFlag.ControlPrincipal
internal static string GetCurrentUserName()
{

string user = string.Empty;
HttpContext current = HttpContext.Current; //GOTCHA 1

if (current != null)
{
SPUtility.EnsureAuthentication(SPControl.GetContextWeb(current));

user = current.User.Identity.Name;
if (!current.User.Identity.IsAuthenticated)
{
throw new UnauthorizedAccessException(StringResourceManager.GetString("Exception_UserProfileGlobal_cs35"));
}
}

if ((user == null) || (user.Trim().Length == 0))
{
user = WindowsIdentity.GetCurrent().Name.ToLower(CultureInfo.InvariantCulture); //GOTCHA 2
}

if (!IsWindowsAuth())
{
if (!IsClaimsAuth())
{
user = SPUtility.FormatAccountName(user);
}

else
{
if (!SPClaimProviderManager.IsEncodedClaim(user))
{
user = SPUtility.FormatAccountName(ClaimsProviderConstants.strClaimsAuthMembershipProviderName, user);
}
user = EnsureWindowsLegacyAndClaimsEquivalence(user);
}
}

if (string.IsNullOrEmpty(user))
{
throw new UserProfileException(StringResourceManager.GetString("Exception_UserProfileGlobal_cs36"));
}
return user;
}

Ah, so that is how it’s getting the current user, which has always been the windows identity(code line labeled, GOTCHA 2) responsible for running the OWSTIMER process because HttpContext is always null in this case. As HttPContext is null, the impersonation code introduced by us will never gonna work. – [Lesson 2]
Since then I hated networkService and the object model for SocialData, being said that I could not wind this up concluding it’s not possible.

With no other option left, I have configured(SharePoint 2010 Timer->properties->logon Tab) my domain account(layman terms, account that has sharepoint userprofile) to run the OWSTIMER.EXE under services.msc. Restart timer service and IIS.

With no surprise, an error again but a new one, this time it threw the below error while instantiating SocailTagManager itself.

“Object reference could not be found at - at Microsoft.Office.Server.Administration.UserProfileApplicationProxy.get_ApplicationProperties().”

Even after using reflector, I could not find a clue but after gooogling, I came to know that I missed restarting the application service. [Lesson 3]

Now the error makes some sense for me. With an excitement I restarted User Profile Application service(CA->Manage Services on Server) and BRAVO, I nailed all the errors and it’s working. If you still did not get it working, you may need to try changing the account(in inetmgr) that runs the application pool responsible for User profile service application.

Though I found it working, I’m not sure on the best practice or the impact of configuring a non-service account to run the services.

UPDATE : Lately i learnt that socialterms can be accessed from timer job/console application(where httpcontext is null by default) by populating the context explicitly as below.

HttpRequest request = new HttpRequest("", http://teamsite, "");

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, user.LoginName);
HttpContext.Current.User = new GenericPrincipal(wi, new string[0]);
WindowsIdentity wi2 = WindowsIdentity.GetCurrent();

SocialCommentManager socialCommentManager = new SocialCommentManager(
SPServiceContext.GetContext(HttpContext.Current));

Though i have not tried hands on, i see the above solution promising. Actual reference can be found here.
I appreciate MS for leaving such puzzles in the code ;) …LOL


Subscribe

Sunday, April 8, 2012

SharePoint 2010 certification Topics

Share/Save/Bookmark

The other day i had cleared the Microsoft SharePoint Application Development 2010 (070-673) exam like a practice test. Never expected it to be so intermediate level exam. I have listed down some of the topics that were covered in the exam for some guidance


  • connected webparts(IWebpartfield/Row/Table)
  • SP.js
  • commonModalDialogPopup
  • client object model
  • sandbox solution(writing to logs, using spsite/spweb objects)
  • workflow-task related
  • Events(ListItem/Field)
  • SPwebProvisionProvider
  • Adding fields to existing content type using elements file
  • VS2010 - predeployment, active configuration
  • SiteDefinitions
  • Strongly typed objects(spmetal)
I'll keep this post updated timely as in when i recollect any more topics

If you have worked on atleast 2 different projects on sharepoint 2010, its far enough to clear the exam but if you need 1000 marks then you need to get some practice.


Subscribe

Monday, April 2, 2012

Close Shared WebPart Programmatically

Share/Save/Bookmark


These days i have been banging my head between personalized and shared mode of the publishing page.
Well, every publishing page has a shared mode(PageView=Shared) and personalize mode(PageView=Personal). Deleting a webpart from the mode in which it is added is possible but cross deletion is not possible. What i mean is deleting a webpart, added in shared mode from personalized mode .
SharePoint only allows to close such webparts, so how do we that operation programmatic?
Somehow SPLimitedWebPartManager did not work for me in doing this but definitely SPWebPartManager does the trick.

SPWebPartManager spwebPartManager = SPWebPartManager.GetCurrentWebPartManager(this.Page);
SPContext.Current.Web.AllowUnsafeUpdates = true;
               
if (currentWebPart.IsShared == false)
      spwebPartManager.DeleteWebPart(currentWebPart);
else if (currentWebPart.IsShared == true)
      spwebPartManager.CloseWebPart(currentWebPart);


SPContext.Current.Web.AllowUnsafeUpdates = false;


Subscribe

Sunday, April 1, 2012

Overriding context of SocialComments Control/WebPart

Share/Save/Bookmark


I have to override the current context of social comment control, in detail i need to have the control point to a different url from the current url. After exploring and digging through reflector i see a property Uri(internal) for SocialComment control but im not sure if there is a direct way of setting this. Using Reflection is worth a try.
I explored a little more and luckily found the property, WebPartPropertySpecifiedAddress for the webpart but not the control.

this.socialCommentsWebPart.WebPartPropertySpecifiedAddress = urlToCommentOn;
Above line of code need to be written OnInit.

 Subscribe

Overriding context of AverageRating Control of SharePoint 2010

Share/Save/Bookmark

SharePoint uses AverageRatingControl to display the rating(5 star) for any item. So i have used the same control to display for all the list items on a home page, but the tricky thing is that the control takes the context of the current page but not the list item it is supposed to represent. To do that i have to follow the below code to set or override the context of the item.


HttpContext context = HttpContext.Current;
SPServiceContext serviceContext = SPServiceContext.GetContext(SPServiceApplicationProxyGroup.Default, SPSiteSubscriptionIdentifier.Default);
averageRatingControl.ItemContext = = SPContext.GetContext(context, report.ItemId, listID, SPContext.Current.Web);

  Subscribe