In a previous post I talked about how to create an object to store ASP.NET profile information for Forms Based Authenticated users in a SharePoint solution. Today I’m going to talk about how we can import user profile data stored in our Membership Provider database into SharePoint.

Since our code is most likely running in a process and not a current Http context, it’s necessary to create a new Context object. In the code below, we’re using the first SPSite object in our SPWebApplication. We’ll create a ServerContext object, which will then be used to create a UserProfileManager object that we can use to access SharePont Profiles.

SPSite site = this.WebApplication.Sites[0];
ServerContext context = ServerContext.GetContext(site);
UserProfileManager profileManager = new UserProfileManager(context);

Now, whereas Eric Kraus has shown us great code for looping through each Profile in the SharePoint database, so we can update each SharePoint user’s data with their Membership Profile data, I decided to go in the opposite direction and loop through each user in my Membership database, and create or update a SharePoint profile for each Membership user I came across. It’s six one/half a dozen the other, but I wanted a good way of knowing which users to *add* and *delete* to SharePoint, in addition to updating existing user data.

In order to loop through all the users in the database, we have to use the MembershipProvider.GetAllUsers() method that requires us to process users in blocks of users.

int pageIndex = 0;
int totalRecords = 0;
int pageSize = 1000;
do
  {
  MembershipProvider provider = System.Web.Security.Membership.Providers["FBAMember"];
  MembershipUserCollection membershipUsers = provider.GetAllUsers(pageIndex, pageSize, out totalRecords);
  ProcessMember(profileManager, membershipUsers);
  pageIndex++;
}
while ((totalRecords - (pageIndex * pageSize)) > 0); 

In this example, we’re processing 1,000 users at a time, using a “while” loop to process each user. The first thing we do is get a hold of the MembershipProvider object stored in our config file. (If your code is running as a Console app, this will most likely be stored in your app.config. If your code is running as a SharePoint timer job, you’ll need to add this information to the owstimer.exe.config file in the BIN folder in the 12 hive.) We’re calling our own ProcessMembers() method for each user, then incrementing the pageIndex number in order to advanced to the next user.

The next thing we’re going to do is write out our ProcessMember() method, which will be used to add, update, or delete the Membership user in the SharePoint Profile store.

public static void ProcessMember(UserProfileManager profileManager, MembershipUserCollection membershipUsers)
{
  ArrayList memberUsers = new ArrayList();
  foreach (MembershipUser user in membershipUsers)
  {
    string accountName = user.ProviderName + ":" + user.UserName;
    memberUsers.Add(accountName);
    Microsoft.Office.Server.UserProfiles.UserProfile spProfile = null;
    bool valueChanged = false;
    try
    {
    if (!profileManager.UserExists(accountName))
    {
      spProfile = profileManager.CreateUserProfile(accountName);
      valueChanged = true;
    }
    else
    {
      spProfile = profileManager.GetUserProfile(accountName);
    }

    if (spProfile == null)
    {
      continue;
    }
  }
  catch (Exception ex)
  {
    continue;
  }
 }
}

The first thing we’re doing is creating an ArrayList that we’re going to use to store a record of each user we’ve encountered that either already has a SharePoint profile, or is going to have one created. (We’ll use this later on to loop through all the SharePoint profiles and delete those users in SharePoint who no longer have Membership accounts.) Each Membership user has an account name in SharePoint that has the format “fbamember:username”, so we’re going to construct this account name by appending the role provider name with the user name. Once we have an account name, we’re going to add it to our array of existing users.

We don’t want to update every single user in the SharePoint profile store; we only want to update those users who are new or have had some property in their profile changed. To keep track of this, we’re going to set a Boolean flag called valueChanged. The next thing we’re going to do is to create a new Profile in SharePoint if the user is new, or grab an existing Profile if the user is already in SharePoint. If after those two choices, a UserProfile object doesn’t exist, you don’t want the whole code to blow up, because you probably want the rest of the users to get process. In this code I left logging out, but I’d probably log the issue to the SharePoint log and keep the loop going. Same thing goes for an error that gets thrown.

Now we’re going to loop through the User Profile Properties we’ve set up already in the SharePoint Profile store, and set those properties equal to the properties that are stored in the ASP.NET Membership/Profile database. (If you refer to the previous post, you’ll see we stored those values in an object called “Person”.) This code will be added below the previous code snippet in the ProcessMember() method.

BB.FBA.UserProfile sqlProfile = BB.FBA.UserProfile.GetProfile(username);
Person person = sqlProfile.Person;
try
{
  foreach (Property prop in spProfile.ProfileManager.Properties)
  {
    bool returnValue = false;
    switch (prop.Name)
    {
      case "Fax":
      {
        returnValue = UpdateProfileProperty(spProfile, "Fax", person.Fax);
        break;
      }
      case "Phone":
      {
        returnValue = UpdateProfileProperty(spProfile, "Phone", person.Phone);
        break;
      }
    }
    if (returnValue == true)
    {
      valueChanged = true;
    }
  }
  if (valueChanged == true)
  {
    spProfile.Commit();
  }
}
catch
{
  continue;
}

In this code block we’re going to first grab the ASP.NET user profile object, from which we’ll grab our Person object (which contains profile information for this Membership user.) We’ll loop through each of the SharePoint User Profile Properties until we find one we’re looking for. In this case, we’re going to set the “Fax” and “Phone” properties of our user. We’ll decide whether we need to set the SharePoint User Profile Property in a method we’ll define later, called UpdateProfileProperty().

If this process runs on a regular basis, such as every 5 or 10 minutes, it’s quite possible that people have not changed any of their Profile properties since the last time the process ran. That being the case, every time we compare the SharePoint User Profile Property and the accompanying ASP.NET profile property, we need to know if the properties are no longer the same, i.e. the user has changed one of their Profile proerties, so we can update the SharePoint Profile with the new information. We’re going to set a local Boolean flag called returnValue for each property. If by the time all the properties have been processed, if at least one of those properties have changed, it will set that flag to true. Once all the user’s properties have been processed, if a value has changed, this will in turn set the “valueChanged” flag to true, which we’ve already used to determine if we should update the SharePoint user.

Now let’s look at the UpdateProfileProperty() we’ll use to check if a SharePoint User Profile property needs to be updated.

private static bool UpdateProfileProperty(Microsoft.Office.Server.UserProfiles.UserProfile profile, string propertyName, string propertyValue)
{
  bool returnValue = false;
  if ((profile[propertyName].Value == null && propertyValue != null & propertyValue != string.Empty) || (profile[propertyName].Value != null & profile[propertyName].Value.ToString() != propertyValue))
  {
    returnValue = true;
    if (propertyValue == string.Empty)
    {
      profile[propertyName].Value = null;
    }
    else
    {
      profile[propertyName].Value = propertyValue;
    }
  }
  return returnValue;
} 

Although the logic looks pretty overwhelming, we have a couple choices:

  • The SharePoint User Profile Property is null and the ASP.NET Profile property we’re passing in isn’t null o an empty string, in which case we need to update the SharePoint property.
  • The SharePoint Property is not null, but it’s not the same as the ASP.NET property that was passed in, in which case the SharePoint property needs to be updated.
  • The SharePoint property is not null and the ASP.NET property is null or an empty string, in which case the SharePoint property needs to be set back to null.

If any of these cases are true, the SharePoint property must changed, and the User’s Profile in SharePoint needs to be updated.

Last but not least, at the end of our ProcessMember() method, once we have looped through all the users in the ASP.NET database and all their profiles, we need to loop once more through the SharePoint profiles and see if each user was found when we were looping through the ASP.NET membership database. If not, we must delete that user’s SharePoint profile, because their account has been deleted.

foreach (Microsoft.Office.Server.UserProfiles.UserProfile profile in profileManager)
{
  string account = profile["AccountName"].Value.ToString();
  if (memberUsers.IndexOf(account) == -1)
  {
    profileManager.RemoveUserProfile(profile.ID);
  }
} 

I turned this code into a SharePoint Timer Job that’s executed every 5 minutes. (Note: when I was debugging the code, I had the timer job execute every minute, which brought the server to its knees because the Timer Process (OWSTIMER.EXE) grew to take up about 3.5 GB of memory. Bad Idea. I don’t recommend setting the schedule to execute every minute!)