SharePoint客户端开发:增加用户信息到用户信息列表
用户信息列表(User Information List),这个隐藏的SharePoint列表,主要用来储存一些用户Profile的基本数据。这个列表不能使用普通的方式去添加或者删除数据。
首先,为什么要在这个列表中增加数据?
这个列表是由SharePoint进行管理的,在使用PeoplePicker控件的时候,点时候就会自动把用户添加进用户信息列表。对于列表中的用户类型的字段来说,这个会利用ID关联到用户信息列表。
我碰到过2个典型场景:
- 实际用户操作中,对于批量数据导入的时候,一般使用的是Datasheet View,通过Excel导入。如果张三从来没有访问过网站,导入的时候有张三这个人,这个时候Datasheet View就会报错。无法保存。
- 开发跨站点集的数据迁移同步,张三是A网站用户,却不是B网站用户,需要把涉及到张三的数据迁移到B网站。
对于第一个场景,可以让用户随便找个PeoplePicker控件,输入缺失的人信息,然后点,就可以补充回来。
对于开发者,一般情况下,服务端开发,可以用SPWeb.EnsureUser来添加用户到这个列表中。
对应客户端开发:
- SharePoint 2010以后的版本,Client OM也有与之对应的Web.EnsureUser方法。
- 传统的Web Service来做,可以用People.ResolvePrincipals方法。
大多数情况下,前面一种Client OM可以满足需要了。
不过Web.EnsureUser有个问题就是一次只能添加一个用户,并且这个是一个完整的服务请求,大数据量的情况下效率很成问题。
为了解决这个问题,需要利用到多线程。
这里我有一段我实际使用的代码作为参考:
实际使用多线程的过程中,我遇到了一个问题,参考了这篇讨论中给出的方法:Workaround for the WaitHandle.WaitAll 64 handle limit?
UserInfoList targetList = new UserInfoList(Parameters.TargetUrl, Parameters.ListNames.UserInformation);
IList<User> resolvedUsers = new List<User>();
logger.Info("Sync User Information from {0} to {1} started...", Parameters.SourceUrl, Parameters.TargetUrl);
int threadCount = 0;
ManualResetEvent finished = new ManualResetEvent(false);
foreach (var u in Users)
{
Interlocked.Increment(ref threadCount);
ThreadPool.QueueUserWorkItem(delegate
{
try
{
logger.Info("Resolving user in target web site:", u.SipAddress, u.Account);
if (!String.IsNullOrEmpty(u.Account) && targetList.GetUserInfoByAccount(u.Account) == null)
{
User targetUser = targetList.Web.EnsureUser(u.Account);
targetList.Context.Load(targetUser);
targetList.Context.ExecuteQuery();
resolvedUsers.Add(targetUser);
logger.Info("User resolved:", u.SipAddress, u.Account);
}
else
{
logger.Info("User exists or user is not valid:", u.SipAddress, u.Account);
}
}
catch (Exception)
{
logger.Info("Cannot update user information for {0} ({1})", u.SipAddress, u.Account);
}
finally
{
if (Interlocked.Decrement(ref threadCount) == 0)
{
finished.Set();
}
}
});
}
finished.WaitOne();
logger.Info("Sync User Information from {0} to {1} completed...", Parameters.SourceUrl, Parameters.TargetUrl);
如果不熟悉多线程,为了提高性能,只能用传统的Web Service来做,用People.ResolvePrincipals方法,因为这个方法的参数允许数组。在一次HTTP请求中可以传递多个用户,大大节约了HTTP往返时间。
同样,我也有一段我实际使用的代码作为参考:
值得注意的问题时,使用的时候,不能一次性的添加过多的用户,这样会导致Web Service超时失败。
public UserInfo[] EnsureUsers(string[] accounts, int batch = 200, string url = default(string))
{
if (url == default(string)) { url = WebUrl; }
logger.Info("Ensuring user information in {0} web site", url);
List<PrincipalInfo> pi = new List<PrincipalInfo>();
for (int i = 0; i < Math.Ceiling((double)accounts.Length / batch); i++)
{
string[] batchAccounts = accounts.Skip(i * batch).Take(batch).ToArray();
People peopleService = new People();
peopleService.Url = url + "/_vti_bin/people.asmx";
peopleService.Credentials = System.Net.CredentialCache.DefaultCredentials;
pi.AddRange(peopleService.ResolvePrincipals(batchAccounts, SPPrincipalType.User, true));
}
List<UserInfo> users = Convert(pi);
logger.Info("Found {0} user information items and updated to user information store", users.Count);
return users.ToArray();
}