Monthly Archives: October 2008

Update SharePoint User Information with enterprise data

SharePoint stores ‘user’ data in its “User Information List”. You can check it out here: http://YourSharePointServer/_layouts/people.aspx. It’s a lot like any other SharePoint list. You can add or remove fields to suit the needs of your organization.

When users first visit/authenticate to the site, or someone first adds a user via the people picker control, data from Active Directory is automatically copied into the “User Information List”. In my experience, this data is pretty minimal. Account (login), Name, and E-mail for example. SharePoint apps can really fall short when you need to provide more detailed, accurate, and up-to-date user information.

There’s probably plenty of excellent user data sitting in some other database, usually maintained by Human Resources, but leveraging it from within SharePoint can be a challenge. To help with this I wrote a console app that copies an organization’s “HR” data to SharePoint. Here’s what the main routine looks like:


private static void UpdateSharePointUsers() {
    //get disposable reference to SharePoint site
    using (SPSite site = new SPSite(Settings.Default.SharePointSiteUrl))
    {
        Console.WriteLine("Updating Users for {0}", site.Url);
        Console.WriteLine("Getting HR Data...");
        HrDataContext hrDb = new HrDataContext();

        foreach (Employee hrEmployee in hrDb.Employees)
        {
            Console.Write("Attempting to get or create SPUser for {0}:", hrEmployee.Login);
            SPUser user = site.GetOrCreateSPUser( hrEmployee );
            if (null == user)
            {
                Console.WriteLine(" Failed");
            }
            else
            {
                Console.WriteLine(" Got SPUser #{0}", user.ID);

                try
                {
                    Console.Write("Attempting to update User Information for SPUser #{0}:", user.ID);
                    user.UpdateUserInformation( hrEmployee );
                    Console.WriteLine(" Success");
                }
                catch (Exception ex) 
                {
                    Console.WriteLine(" Failed.rnError: {0}",
                        user.ID, ex.Message);
                }
            }
        }
    }
}

So the SPSite class has a GetOrCreateSPUser(SPSite), and the SPUser class has a handy UpdateUserInformation(Employee) method. What? Your SharePoint object model doesn’t have these methods? I’m afraid this article wasn’t too much help then.

Happy Coding! Cheers!!

Just kidding. My SharePoint object model doesn’t have those methods either. The magic of Extension Methods has allowed my console application to ‘velcro’ these methods onto SPSite and SPUser.

Here they are:


public static SPUser GetSPUser(this SPSite site, Employee hrEmployee) {

    try
    {
        return site.RootWeb.SiteUsers[hrEmployee.Login];
    }
    catch (Exception)
    {
        return null;
    }
}

public static SPUser CreateSPUser(this SPSite site, Employee hrEmployee)
{
    SPUser user = null;
    try
    {
        site.RootWeb.SiteUsers.Add(
        hrEmployee.Login,
        hrEmployee.Email,
        hrEmployee.FullnameLast,
        "User added via automated process.");

        user = site.RootWeb.SiteUsers[hrEmployee.Login];

    }
    catch (Exception) {

    }
    return user;
}

public static SPUser GetOrCreateSPUser(this SPSite site, Employee hrEmployee)
{
    SPUser user = site.GetSPUser(hrEmployee);
    if (null == user)
    {
        user = site.CreateSPUser(hrEmployee);
    }
    return user;
}

public static SPListItem GetUserInformation(this SPUser user) {
    return user.ParentWeb.SiteUserInfoList.GetItemById(user.ID);
}

public static void UpdateUserInformation( this SPUser user, Employee hrEmployee)
{
    var userInfo = user.GetUserInformation();

    userInfo["Title"] = hrEmployee.FullNameLast;
    userInfo["E-Mail"] = hrEmployee.Email;
    userInfo["Employee ID"] = hrEmployee.EmployeeNumber;
    userInfo["Department Number"] = hrEmployee.DepartmentNumber;
    userInfo["Supervisor Name"] = hrEmployee.Supervisor.FullnameLast;
    userInfo["Phone Extension"] = hrEmployee.Extension;
    userInfo["Cell"] = hrEmployee.CellPhone;

    userInfo.Update(); //write changes to SharePoint
}

NOTE: I used “Title” in place of the “Name” field in SharePoint’s User Information List, because even though SharePoint displays this field as “Name”, the “internal field name” is actually “Title”. Stuff like this tends to bite you a lot in SharePoint. There are ways to get around ‘internal’ vs. ‘display’ field names by taking extra steps. You can also refer to fields via GUID or index. Field name is easiest to read though, I think.

So, another thing that would be great… Doesn’t a SharePoint list support User fields? So rather than have a “Supervisor Name” Text field, why not instead have a “Supervisor” Person field? Here’s an example of the UpdateUserInformation() method with that bit added.


public static void UpdateUserInformation( this SPUser user, Employee hrEmployee)
{
    var userInfo = user.GetUserInformation();

    userInfo["Title"] = hrEmployee.FullNameLast;
    userInfo["E-Mail"] = hrEmployee.Email;
    userInfo["Employee ID"] = hrEmployee.EmployeeNumber;
    userInfo["Department Number"] = hrEmployee.DepartmentNumber;
    userInfo["Phone Extension"] = hrEmployee.Extension;
    userInfo["Cell"] = hrEmployee.CellPhone;

    //Instead of just a name,
    // add a 'user' as this person's supervisor
    //userInfo["Supervisor Name"] = hrEmployee.Supervisor.FullnameLast;
    var supervisorSPUser = user.ParentWeb.Site.GetSPUser(hrEmployee.Supervisor);
    userInfo["Supervisor"] = supervisorSPUser.ID;

    userInfo.Update(); //write changes to SharePoint
}
...

The UpdateUserInformation() method in the example above has a lot of hard-coded field mappings in it. Wouldn’t it be nice to add mappings or make other changes without having to re-compile and re-deploy the app?

Here’s an example that uses a collection of mapping objects that define how Employee properties map to SharePoint User Information fields. SharePoint field type is also indicated so that “User” fields are handled differently than “Text” fields.


public static void UpdateUserInformation(
    this SPUser user,
    Employee hrEmployee,
    List mappings)
{
    var userInfo = user.GetUserInformation();

    foreach (var mapping in mappings)
    {
        //handle user fields differently from text fields
        if (mapping.SPFieldType == SPFieldType.User)
        {
            try
            {
               //since the target is a SPFieldUser type,
                //we'll assume that the HrField is an Employee object
                var anotherHrEmployee = hrEmployee.GetPropertyValue(mapping.HrField) as Employee;
                var anotherUser = user.ParentWeb.Site.GetSPUser(anotherHrEmployee);
                //assign user id to this field
                userInfo[mapping.SPField] = anotherUser.ID;
            }
            catch (Exception)
            {
                //couldn't assign user field. Not a show stopper though
            }
        }
        else //probably just a text field. TODO: add other type conditions if needed
        {
            userInfo[mapping.SPField] = hrEmployee.GetPropertyValue(mapping.HrField);
        }
    }
    userInfo.Update(); //write changes to SharePoint
}

Here’s the HrSharePointMapping class:

public class HrSharePointMapping
{
    public string HrField { get; set; }
    public string SPField { get; set; }
    public SPFieldType SPFieldType { get; set; }
}

And this code builds the List from an XML file:

public List GetHrSharePointMappings() {
    XDocument mapXml = XDocument.Load(Settings.Default.HrSharePointMappingXmlFile);
    var mappings = from m in mapXml.Descendants("Mapping")
    select new HrSharePointMapping
    {
        HrField = m.Element("HrField").Value,
        SPField = m.Element("SPField").Value,
        SPFieldType = (SPFieldType)Enum.Parse(
            typeof(SPFieldType),
            m.Element("SPFieldType").Value,
            true )
    };
    return mappings.ToList();
}

Example mappings XML file, which allows mappings to be configured without recompiling and re-deploying the app:


<Mapping>
    <HrField>Email</HrField>
    <SPField>E-Mail</SPField>
    <SPFieldType>Text</SPFieldType>
</Mapping>
<Mapping>
    <HrField>EmployeeNumber</HrField>
    <SPField>Employee Number</SPField>
    <SPFieldType>Text</SPFieldType>
</Mapping>
<Mapping>
    <HrField>EmployeeNumber</HrField>
    <SPField>Employee Number</SPField>
    <SPFieldType>Text</SPFieldType>
</Mapping>
<Mapping>
    <HrField>FullnameLast</HrField>
    <SPField>Title</SPField>
    <SPFieldType>Text</SPFieldType>
</Mapping>
<Mapping>
    <HrField>Department</HrField>
    <SPField>Department Number</SPField>
    <SPFieldType>Text</SPFieldType>
</Mapping>
<Mapping>
    <HrField>Extension</HrField>
    <SPField>Phone Extension</SPField>
    <SPFieldType>Text</SPFieldType>
</Mapping>
<Mapping>
    <HrField>CellPhone</HrField>
    <SPField>Cell</SPField>
    <SPFieldType>Text</SPFieldType>
</Mapping>
<Mapping>
    <HrField>Supervisor</HrField>
    <SPField>Supervisor</SPField>
    <SPFieldType>User</SPFieldType>
</Mapping>

Perhaps ADO would have lent itself to this configurable mapping approach better than our Employee object. The DataRow object, for example, allows you to access its field values by column name string. But since we’re using an Employee object, I had to employ a bit of reflection to enable configuration. Here’s the GetPropertyValue() Extension Method that makes the Employee object a bit more ‘dynamic’. This could come in handy for any object, by the way.


public static object GetPropertyValue(this object obj, string propertyName)
{
    Type classType = obj.GetType();
    PropertyInfo info = classType.GetProperty(propertyName);
    return info.GetValue(obj, null);
}