Wednesday, April 24, 2013

Read Multiple Values from SharePoint Person or Group Field


When upgrading from SharePoint 2007 to 2010, one of the areas you need to look at is any custom code that was written for the 2007 environment.  Depending on what that code is doing, the effort to upgrade it may be larger than initially thought.  I recently ran into such a situation, and discovered some oddities I hadn’t run into before regarding Person or Group fields and how the values from those fields are submitted from a datasheet view.

First, the basics of the relevant code for the issue.  There is an event receiver that handles the ItemUpdating event and it needs to read the values a user enters into a People or Group column that allows multiple selections.

The 2007 code used to read the new values entered was:


object peopleAfterProp = 
        properties.AfterProperties[listItem.Fields[“FieldName”].InternalName];
SPFieldUserValueCollection newUsers 
     = new SPFieldUserValueCollection(listItem.Web, peopleAfterProp.ToString());



This code read the new values entered by the user and created a new SPFieldUserValueCollection object containing an SPFieldUserValue for each person or group entered.  It worked fine in 2007 for both standard list views and datasheet view.  Didn’t work quite as well in 2010. :)

In 2010 for standard list views, properties.AfterProperties for the field returns values such as "9;#BLACKDOG\\normaluser1;#2;#BLACKDOG\\ryan", which is perfect and creates a valid SPFieldUserValueCollection.  In 2010 datasheet view, the same field and the same users returns a value of "9;#;#2;#".  When the SPFIeldUserValueCollection is created using the code shown above, only a single SPFieldUserValue is created for the first user (ID = 9), and it’s invalid.  So basically, the datasheet view in 2010 for People or Group columns has what I would call a bug (MS might disagree) and only returns data containing the user IDs from AfterProperties.  It’s thus impossible to create a valid SPFieldUserValueCollection object.

There’s the problem, now how to fix it.  Since datasheet only returns the IDs, and the string format (using the “;#” delimiter) is the same as when submitted from a list view, we can key off the IDs.  If we can extract the IDs of the users, we can then quite easily go on to create SPUser objects, which is really where we need to get.  The goal is to find some code that we can use to create user objects regardless of which view the user is submitting data through.

My thought was to get a Dictionary object populated with key value pairs when a standard list view is used, and the same Dictionary object populated with just keys when datasheet view is used.

After some work with the regular expression (which is not my strong suit, by the way!), I ended up with the method:


public static Dictionary<int, string> GetDelimitedLookupValuesDictionary
        (string sFieldValue)
    {
        Dictionary<int, string> results = new Dictionary<int, string>();
     
        System.Text.RegularExpressions.Regex regexMultiValueField 
       = new System.Text.RegularExpressions.Regex(@"(?<id>(-?\d+));#(?<value>
       (\w+\\+\w+)?)(;#|$)", 
       System.Text.RegularExpressions.RegexOptions.Compiled);
       System.Text.RegularExpressions.Match choice 
                      = regexMultiValueField.Match(sFieldValue);
     
        while (choice.Success)
       {
           results.Add(Convert.ToInt32(choice.Groups["id"].Value), 
                                   choice.Groups["value"].Value);
           choice = choice.NextMatch();
       }
    
       return results;
}


Feel free to critique my regex, but it works.  Regardless of which view the user submits data through, I get a valid Dictionary object back.  Now we can create List<> objects containing our SPUsers by looking up each user in the Dictionary.

Dictionary<int, string> peopleAfterDic = GetDelimitedLookupValuesDictionary(peopleAfterProp.ToString());
    List<int> peopleIdsAfter = new List<int>();
    List<SPUser> peopleAfter = new List<SPUser>();
     
    foreach (KeyValuePair<int, string> personAfter in peopleAfterDic)
    {
     // Ensure the user exists in the site collection (only possible to 
     select users that aren't through list view, not through datasheet view)
       if (personAfter.Key == -1)
       {
           SPUser tempUser = listItem.Web.EnsureUser(personAfter.Value);
    
           peopleIdsAfter.Add(tempUser.ID);
           peopleAfter.Add(tempUser);
       }
       else
       {
           peopleIdsAfter.Add(personAfter.Key);
           peopleAfter.Add(listItem.Web.SiteUsers.GetByID(personAfter.Key));
       }
    }

Problem solved.

Hopefully this will be fixed in an update from Microsoft.  If / when it is, this code will still work, but going back to the expected SPFieldUserValueCollection method would probably be best.

P.S. Notice the check in the code above for a Key value of –1.  That means the SPUser that the end user chose through the standard list view isn’t yet a user listed in the site collection.  So we need to call EnsureUser, which adds the user and returns the valid SPUser object.  This can’t happen with the datasheet view as the UI only allows selecting existing site collection users.

No comments:

Post a Comment