So having got the user logged in (http://blog.njpenterprises.com/archive/2011/04/03/getting-claims-based-authentication-and-user-profiles-to-work-together-part-1.aspx) now it was time to time to get the user profile working.
I am indebted to Steve Curran (http://sharepointfieldnotes.blogspot.com/2010/02/creating-sp2010-social-comments.html) for putting me on the right track to fixing my issues. The problem was that when trying to create an user profile I was getting "Access Denied" or Only Administrators can create User Profile for other users. I tried a number of technics to get this to work including RunWithElevatedPrivileges but would never had found it in a million years without Steve's blog. Remember I was still running the application with anonymous access.
What Steve said was "Now most SharePoint developers would expect that by creating a SPSite with the user's SPUserToken and passing in this SPSite to the SPServiceContext.GetContext method that it would create an impersonated SPServiceContext that would generate a comment with that user's name and id. Unfortunately, this is not the case. It seems that the "User Profile Service Application" relies on the HttpContext object to get user profile information to determine who is generating tags and comments." I extrapolated this to most SharePoint developers would expect that by creating a SPSite with the user's SPUserToken and passing in this SPSite to the SPServiceContext.GetContext method that it would create an impersonated SPServiceContext that could be used to create a User Profile with that user's name and id. Unfortunately, this is not the case. It seems that the "User Profile Service Application" relies on the HttpContext object to get user profile information and hence allow Administrators (and as we shall see later) users to create and update User Profiles. So here we go…
string socialDataStatsSite = SPContext.Current.Site.Url;
SPSecurity.RunWithElevatedPrivileges(delegate()
{
using (
SPSite siteColl =
new SPSite(SPContext.Current.Site.ID))
{
using (SPWeb oWeb = siteColl.OpenWeb())
{
try
{
oWeb.AllowUnsafeUpdates = true;
// The CreateUser Profile checks the HTTPContext (NOT the SPContext) for some reason best known to itself, so we need to set it up
HttpRequest request = new HttpRequest("", socialDataStatsSite, "");
HttpContext.Current =
new HttpContext(request,
new HttpResponse(new StringWriter(CultureInfo.CurrentCulture)));
HttpContext.Current.Items["HttpHandlerSPWeb"] = oWeb;
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]);
WindowsIdentity wi2 = WindowsIdentity.GetCurrent();
SPContext adminContext = SPContext.GetContext(HttpContext.Current);
SPServiceContext context = SPServiceContext.GetContext(adminContext.Site);
ProfileSubtypeManager psm = ProfileSubtypeManager.Get(context);
// choose default user profile subtype as the subtype
string subtypeName = ProfileSubtypeManager.GetDefaultProfileName(ProfileType.User);
ProfileSubtype subType = psm.GetProfileSubtype(subtypeName);
UserProfileManager upm = new UserProfileManager(context);
// create a user profile and set properties
UserProfile newProfile = upm.CreateUserProfile(username);
As you can see we had to use reflection to change the WindowsIdentity's name property. It is this property that is used to create the user's profile. The username is of the same format as we discussed in the previous post ("i:0#.f|my_membership|email address"). We can no add in our entries thus :-
newProfile[PropertyConstants.FirstName].Add(
FirstName);
newProfile[PropertyConstants.LastName].Add(LastName);
newProfile[PropertyConstants.PreferredName ].Add(FirstName + " "+ LastName);
newProfile[PropertyConstants.WorkEmail].Add(email);
newProfile[PropertyConstants.WorkPhone].Add(
WorkPhone);
newProfile[PropertyConstants.DistinguishedName].Add
(AccountName);
newProfile.Commit();
So we now have our profile created. So you would think that updating that profile would be easy. WRONG ! we had to go through the whole rigmarole again when updating the profile. Reading the profile was easy as you would expect it to be.
So reading a profile (look no RunWithElevatedPrivileges) :-
SPServiceContext context = SPServiceContext.GetContext(site);
UserProfileManager profileManager = new UserProfileManager(context);
string sAccount = SPContext.Current.Web.CurrentUser.LoginName;
UserProfile u = profileManager.GetUserProfile(sAccount);
// Get the values from the user's profile
// Being careful to check for null values
ddlTitle.SelectedValue = (null == u[PropertyConstants.Title].Value)
? " "
: u[PropertyConstants.Title].Value.ToString();
tbFirstName.Text = (null == u[PropertyConstants.FirstName].Value)
? " "
: u[PropertyConstants.FirstName].ToString();
tbLastName.Text = (null == u[PropertyConstants.LastName].Value)
? " "
: u[PropertyConstants.LastName].ToString();
tbWorkEmail.Text = (null == u[PropertyConstants.WorkEmail].Value)
? " "
: u[PropertyConstants.WorkEmail].ToString();
tbJobRole.Text = (null == u[PropertyConstants.JobTitle].Value)
? " "
: u[PropertyConstants.JobTitle].ToString();
tbCity.Text = (null == u[PropertyConstants.Location].Value)
? " "
: u[PropertyConstants.Location].ToString();
tbPhone.Text = (null == u[PropertyConstants.WorkPhone].Value)
? " "
: u[PropertyConstants.WorkPhone].ToString();
if (null != u[PropertyConstants.WebSite].Value)
{
if ((u[PropertyConstants.WebSite].ToString().ToLower().StartsWith("http://")) ||
(u[PropertyConstants.WebSite].ToString().ToLower().StartsWith("https://")))
{
tbWebsite.Text = u[PropertyConstants.WebSite].ToString();
}
}
NB note how I checked for nulls everywhere and how to deal with urls (the other way did not work – I kept getting NullExceptions).
So Updating Profiles :-
SPSecurity.RunWithElevatedPrivileges(delegate()
{
using (
SPSite siteColl =
new SPSite(SPContext.Current.Site.ID))
{
using (SPWeb oWeb = siteColl.OpenWeb())
{
try
{
oWeb.AllowUnsafeUpdates = true;
// The CreateUser Profile checks the HTTPContext (NOT the SPContext) for some reason best known to itself, so we need to set it up
HttpRequest request = new HttpRequest("", socialDataStatsSite, "");
HttpContext.Current =
new HttpContext(request,
new HttpResponse(new StringWriter(CultureInfo.CurrentCulture)));
HttpContext.Current.Items["HttpHandlerSPWeb"] = oWeb;
WindowsIdentity wi = WindowsIdentity.GetCurrent();
typeof(WindowsIdentity).
GetField("m_name", BindingFlags.NonPublic | BindingFlags.Instance)
.SetValue(wi, sAccount);
HttpContext.Current.User = new GenericPrincipal(wi, new string[0]);
WindowsIdentity wi2 = WindowsIdentity.GetCurrent();
SPContext adminContext = SPContext.GetContext(HttpContext.Current);
SPServiceContext context = SPServiceContext.GetContext(adminContext.Site);
UserProfileManager upm = new UserProfileManager(context);
UserProfile u = upm.GetUserProfile(sAccount);
// Set the values for the user's profile
u[PropertyConstants.FirstName].Value = tbFirstName.Text;
u[PropertyConstants.LastName].Value = tbLastName.Text;
u[PropertyConstants.WorkEmail].Value = tbWorkEmail.Text;
u[PropertyConstants.JobTitle].Value = tbJobRole.Text;
u[PropertyConstants.Location].Value = tbCity.Text;
u[PropertyConstants.WorkPhone].Value = tbPhone.Text;
if ((tbWebsite.Text.ToLower().StartsWith("http://")) || (tbWebsite.Text.ToLower().StartsWith("https://")))
{
u[PropertyConstants.WebSite].Value = new Uri(tbWebsite.Text);
}
u.Commit();
}
}
}
}
Again sAccount is in the format described earlier ("i:0#.f|my_membership|email address").
I hope this has helped someone solve this particular annoying problem.