这个类从SPFieldLookup继承,并重写了Update方法以便在安全列被创建或编辑的任何时候,背后的数据存储列表都能被创建并以适当的权限进行设置。
public override void Update()
{
SPSecurity.RunWithElevatedPrivileges(EnsureSecureFieldStorageListExists);
SPWeb web = SPContext.Current.Site.RootWeb;
this.LookupWebId = web.ID;
this.LookupField = secureFieldStorageFieldName;
this.LookupList = web.Lists[secureFieldStorageListName].ID.ToString();
RetrieveCustomProperties();
SPSecurity.RunWithElevatedPrivileges(ApplyPermissions);
base.Update();
}
所有这些动作都在严格权限的级别中执行。这就保证标准用户不会创建一个具有错误的新安全列。
另外,为了达到这种定制的效果,我们也需要重写FieldRenderingControl属性以便我们的自定义字段控件能被使用。
public override BaseFieldControl FieldRenderingControl
{
get
{
BaseFieldControl control = new SecuredFieldControl();
control.FieldName = this.InternalName;
return control;
}
}
最后需要定制的是重写OnDeleting方法以保证我们能在列被删除的时候清楚任何相关的数据。
public override void OnDeleting()
{
base.OnDeleting();
SPSecurity.RunWithElevatedPrivileges(removeFieldFolder);
} 自定义字段编辑器控件
除了核心的字段类型类之外,还需要一个自定义字段编辑器控件,才能让用户设置安全列上的权限。权限不仅能在列被添加到列表的时候设置,也可以之后随时更新。这个功能是用一个包含了SharePoint PeopleEditor控件的用户控件来实现的。这个控件能让用户搜索和选择安全主体。
<sharepoint:PeopleEditor ID="AllowedPrincipalsPeoplePicker" runat="server"
AutoPostBack="false" PlaceButtonsUnderEntityEditor="true" SelectionSet="SPGroup"
MultiSelect="true" />
为了设置并获取安全字段类型的信息,用户控件的后置代码类实现了IFieldEditor接口。特别地,InitializeWithField方法用于从字段中获取任何现有的安全设置。
if (Page.IsPostBack)
return;
// Initialize the people picker control using comma separated account list from the secure field
SecureField secureField = (SecureField)field;
if (secureField != null && secureField.AllowedPrincipals != null)
{
StringBuilder accounts = new StringBuilder();
foreach (object entity in secureField.AllowedPrincipals)
{
accounts.Append((entity as PickerEntity).Key);
accounts.Append(',');
}
this.AllowedPrincipalsPeoplePicker.CommaSeparatedAccounts = accounts.ToString();
this.AllowedPrincipalsPeoplePicker.Validate();
}
此外,OnSaveChange方法会用于更新字段安全设置。
AllowedPrincipalsPeoplePicker.Validate();
SecureField secureField = (SecureField)field;
secureField.AllowedPrincipals = AllowedPrincipalsPeoplePicker.ResolvedEntities;
secureField.SaveCustomProperties(); 自定义字段控件
自定义字段类型的最后一部分是自定义字段控件,它实现了创建和维护存储在数据存储列表中的数据的逻辑。为了达到这个目的,且依旧保持现存查询字段的功能,我们让自定义类从LookupField类继承。在字段处于显示模式的时候,这个基类处理所有的功能。然而,在新建和编辑模式下,我们要修改一些地方让这个控件符合我们的需要。目前,我们使用基类的ControlMode属性确定字段控件处于什么模式下。
当字段控件处于新建或编辑模式下,需要对默认的功能进行几个改变以实现如下行为:
如果用户不具备这个列的权限,那么隐藏控件。
显示文本框,并用任何存储在后台列表条目中的当前值来填充它。
当值被更改时,创建和更新后台列表条目的值。
为了控制控件的可见性,最好的方法是重写Visible属性。在属性getter的实现中,我们要检查用户是否有权对数据进行访问,并在不能访问的时候返回False。
为了给可以输入或编辑数据的用户显示文本框,我们可以使用现存具有id为TextField的SharePoint模板。这个模板同样也被标准文本字段使用。为了实现这个功能,我们只需简单地重写DefaultTemplateName属性的Get方法,实现代码如下:
// If the mode is Display default to Lookup Field functionality
if (ControlMode == SPControlMode.Display || ControlMode == SPControlMode.Invalid)
{
return base.DefaultTemplateName;
}
return @"TextField";
为了实现创建或更新数据存储列表的余下功能,我们需要重写Value属性的Get和Set方法。Get方法将被SharePoint框架用于更新字段值。我们对于这个逻辑的自定义,是基于用户输入的值创建或编辑位于数据存储列表中的后台条目。为了避免没有权限的用户编辑列表,在必要时,该功能将运行在比较严格的安全级别下。我们也实现了保证用户不会对不能编辑的字段进行编辑的逻辑。在代码中,我们引用了多个辅助方法。这些方法的代码在本节的最后也能找到。
// If the mode is Display default to Lookup Field functionality
if (ControlMode == SPControlMode.Display || ControlMode == SPControlMode.Invalid)
{
return base.Value;
}
this.EnsureChildControls();
// Validate the current users permissions.
if (!DoesUserHavePermissions())
{
return lookupListItemId;
}
// Check for an existing value to determine if we create new or edit.
if (lookupListItemId == null)
{
SPSecurity.RunWithElevatedPrivileges(createLookupListItem);
}
else
{
SPSecurity.RunWithElevatedPrivileges(updateLookupListItem);
}
return lookupListItemId;
Value属性的Set方法用于为当前列表条目设置字段的值。我们针对这个功能的定制需要从数据存储列表中获取当前值,并显示到文本框中。它也实现了保证数据安全性的功能。
// If the mode is Display default to Lookup Field functionality
if (ControlMode == SPControlMode.Display || ControlMode == SPControlMode.Invalid)
{
base.Value = value;
return;
}
this.EnsureChildControls();
// Validate the current users permissions.
if (!DoesUserHavePermissions())
{
return;
}
if( value != null)
{
if (value is SPFieldLookupValue)
{
SPFieldLookupValue fullValue = value as SPFieldLookupValue;
lookupListItemId = fullValue.LookupId;
this.TextBoxValue.Text = fullValue.LookupValue;
}
else
{
if (!(value is string))
{
throw new ArgumentException();
}
try
{
SPFieldLookupValue fullValue = new SPFieldLookupValue(value as string);
lookupListItemId = fullValue.LookupId;
this.TextBoxValue.Text = fullValue.LookupValue;
}
catch (ArgumentException ex)
{
this.TextBoxValue.Text = string.Empty;
}
}
接下来的方法是我们之前用到的一些辅助方法。
private bool DoesUserHavePermissions()
{
bool doesUserHavePermissions = false;
SPSecurity.RunWithElevatedPrivileges(delegate()
{
SPFieldLookup lookupField = this.Field as SPFieldLookup;
using (SPSite site = new SPSite(SPContext.Current.Site.ID))
{
using (SPWeb web = site.OpenWeb(lookupField.LookupWebId))
{
SPList list = web.Lists[new Guid(lookupField.LookupList)];
SPListItem subFolderItem = SecureField.GetOrCreateSubFolderItem(web, list, ListId, Field, false);
if (subFolderItem == null)
{
throw new Exception("Cannot find the List folder or Field folder.");
}
doesUserHavePermissions = subFolderItem.DoesUserHavePermissions(SPContext.Current.Web.CurrentUser, SPBasePermissions.ViewListItems);
}
}
});
return doesUserHavePermissions;
}
private void createLookupListItem()
{
SPFieldLookup lookupField = this.Field as SPFieldLookup;
using (SPSite site = new SPSite(SPContext.Current.Site.ID))
{
using (SPWeb web = site.OpenWeb(lookupField.LookupWebId))
{
SPList list = web.Lists[new Guid(lookupField.LookupList)];
SPListItem subFolderItem = SecureField.GetOrCreateSubFolderItem(web, list, ListId, Field, false);
if (subFolderItem == null)
{
throw new Exception("Cannot find the List folder or Field folder.");
}
// Create the list item.
SPListItem listItem = list.Items.Add(subFolderItem.Folder.ServerRelativeUrl, SPFileSystemObjectType.File, this.TextBoxValue.Text);
listItem[SecureField.secureFieldStorageFieldName] = this.TextBoxValue.Text;
web.AllowUnsafeUpdates = true;
listItem.Update();
lookupListItemId = listItem.ID;
}
}
}
private void updateLookupListItem()
{
if (lookupListItemId == null)
{
return;
}
SPFieldLookup lookupField = this.Field as SPFieldLookup;
using (SPSite site = new SPSite(SPContext.Current.Site.ID))
{
using (SPWeb web = site.OpenWeb(lookupField.LookupWebId))
{
SPList list = web.Lists[new Guid(lookupField.LookupList)];
SPListItem listItem = list.GetItemById((int)lookupListItemId);
listItem[SecureField.secureFieldStorageFieldName] = this.TextBoxValue.Text;
web.AllowUnsafeUpdates = true;
listItem.Update();
}
}
} 部署
对于扩展SharePoint核心功能的大部分自定义开发,SharePoint Solution包是最佳的部署工具。这个框架允许我们创建部署安装包,以中心的、一致的和可检测的方式部署到服务器群集中的所有服务器上。我们所实现的自定义安全功能包含了一个程序集,一个XML配置文件和一个控件模板文件,以及一个大小适中的Solution包。