关于DateTime、RunWithElevatedPrivileges和SPRegionalSettings的一个小故事

在为SharePoint开发功能时,我们几乎不用专门考虑DateTime这种数据类型。虽然SharePoint的用户可以设置独立于网站的时区信息,但是无论这个用户设置了怎样的时区,在他尝试保存一个SPListItem时,SharePoint会将其中所有的DateTime都转换成UTC时间。当用户在浏览该SPListItem时,SharePoint又会根据该用户的时区信息向他呈现正确的时间。
十分美妙,对吧?

有一天,我们想要让用户可以往一个SPList里添加内容,但是又不愿意给这些用户充足的权限,听起来有些不可理喻,但这种事儿每天都在发生,我们甚至连该List的界面都不想让这些用户看到。
在SharePoint中,解决这件事情很简单,只需要调用SPSecurity.RunWithElevatedPrivileges方法,就可以用系统管理员的权限执行各种操作了,十分美妙,对吧?

我们编写了寥寥数行代码就实现了这个需求,现在用户可以往那个List里添加数据,却不需要具备权限。用户一定会很喜欢这一点的!

很快,我们收到了用户们雪片般的来函,“噢”,在点开其中一封邮件时我笑着说,“这是我们应该做的。”
可是这却不是一封表扬信,这位用户抱怨说,她输入的是早上9点,但添加完后却显示下午17点。
其他邮件也都是相似的抱怨。

我们迅速组织了人员来研究这个问题,很快就发现,用户所看到的时间和真实的时间之间的时间差,就是用户的时区时间与UTC时间的时间差,也就是说,在读取数据的时候,SharePoint按照惯例根据用户的时区转换了时间,但是在保存的时候却没有。所以罪魁祸首就是SPSecurity.RunWithElevatedPrivileges方法,在这个方法里边保存数据时,所有的时间都不会自动根据用户的时区进行转换,甚至也不会按照系统管理员的时区进行转换,而是会根据网站的时区进行转换(没错,我们的网站正好使用了UTC时区)。

这个可不太妙,不过没关系,我们的团队用了大概四分之一秒的时间就想到了解决方法,既然非用SPSecurity.RunWithElevatedPrivileges不可,那么就自己对时间进行转换吧。我们只需要从Web里拿到那个SPUser,然后去判断它是否有RegionalSettings,如果有,就利用其中的TimeZone.LocalTimeToUTC方法来转换时间。

var user = web.EnsureUser(loginName);
if(user.RegionalSettings!=null)
time = user.RegionalSettings.TimeZone.LocalTimeToUTC(time);

十分简单,对吧?

但是我们惊喜的发现,它没有用,我们取到的所有SPUser的RegionalSettings都是空的,后来微软羞涩地说:“其实RegionalSettings这个对象是给当前用户使用的”,如果你想获取其他用户的RegionalSettings的话,可以用这个用户的UserToken重新构造一个SPSite,然后再重新获取该用户……”
于是代码就变成了下面这个样子:

var user = topWeb.EnsureUser(loginName);
using (var userSite = new SPSite(this._topSite.ID, user.UserToken))
{
using (var userWeb = userSite.OpenWeb())
{
user = userWeb.EnsureUser(user.LoginName);
if (user.RegionalSettings != null)
time = user.RegionalSettings.TimeZone.LocalTimeToUTC(time);
}
}

这个故事告诉我们,团结就是力量,一个小bug毫无战斗力,但小bug们只要联合起来,总是能让程序员们忙活一阵子的。

One Comment

发表评论

电子邮件地址不会被公开。 必填项已用*标注