以编程方式在提供程序托管的加载项中部署自定义按钮

这是关于开发 SharePoint 托管的 SharePoint 加载项的基础知识系列文章中的第 9 篇文章。你应该首先熟悉 SharePoint 加载项以及本系列中之前的文章(可在创建提供程序托管的 SharePoint 加载项入门中找到相关内容)。

备注

如果已完成有关提供商托管加载项的本系列文章之一,便已生成 Visual Studio 解决方案,可以在继续阅读本主题的过程中使用。 也可以从 SharePoint_Provider-hosted_Add-Ins_Tutorials 下载存储库,再打开 BeforeProgrammaticButton.sln 文件。

本文将介绍如何在自定义功能区按钮所属的列表本身也以编程方式部署到同一加载项中时,向 SharePoint 加载项添加自定义功能区按钮。

向项目重新添加自定义按钮

备注

只要重新打开解决方案,Visual Studio 中的“启动项目”设置往往会还原为默认值。 重新打开本系列文章中的示例解决方案后,请务必立即按照以下步骤操作:

  1. 右键单击“解决方案资源管理器”最上面的解决方案节点,再选择“设置启动项目”。
  2. 确保三个项目都在“操作”列中设置为“启动”。

上一篇文章从项目中删除了自定义功能区按钮“AddEmployeeToCorpDB”。 请按照以下步骤操作,重新添加此按钮。

  1. 在“解决方案资源管理器”最上面的工具栏中,选择“显示所有文件”按钮。

    图 1:“解决方案资源管理器”的工具栏

    “解决方案资源管理器”的工具栏,其中用一个框突出显示了“显示所有文件”按钮。

  2. 在“ChainStore”项目中,右键单击“AddEmployeeToCorpDB”,再选择“包括在项目中”。

  3. 再次选择“显示所有文件”按钮。

  4. 在“ChainStore”项目中,展开“AddEmployeeToCorpDB”,再打开 elements.xml 文件。

了解困境及其解决方案

在 elements.xml 文件中, CustomAction 元素的 RegistrationId 属性标识已在其功能区上添加按钮的列表: {$ListId:Lists/Local Employees;}。当列表已手动添加到主机 Web 时,这运行良好。但现在我们正在首次运行逻辑中以编程方式部署列表,当 SharePoint 安装外接程序并尝试部署按钮时,列表不存在。安装外接程序将抛出异常并失败。

在安装事件处理程序(而不是首次运行逻辑)中部署列表无法摆脱这一困境,因为 SharePoint 部署通过叙述性说明定义的自定义组件(如自定义按钮和“下订单”加载项部件),再运行自定义处理程序,所以在 SharePoint 尝试部署自定义按钮时列表尚不存在。

完全以编程方式创建自定义按钮并不可行,原因过于高深,本文未进行探究。 幸运的是,并不一定就需要这样做。 有一种相对轻松的方法,即以半编程方式创建自定义按钮,并将它分配到自定义列表。

基本步骤如下:

  1. 将描述性按钮保留在项目中,但将其分配到 SharePoint 网站上始终存在的功能区,而不是以编程方式部署在相同外接程序中的列表。

  2. 在首次运行逻辑中,以编程方式创建列表后,以编程方式将未定义的按钮添加到列表功能区。

  3. 用原始按钮的值初始化新按钮的属性。 此时,有两个完全相同的按钮。 第二个按钮是分配到“本地员工”列表的功能区。

  4. 以编程方式删除原始按钮。

以编程方式注册自定义按钮。

下面的过程展示了如何实现此策略。

  1. 在“ChainStore”项目中,展开“AddEmployeeToCorpDB”,打开 elements.xml 文件,再将“CustomAction”元素的“RegistrationId”属性值更改为“100”。 这是列表类型的 ID。 即使网站上没有此类型列表的任何实例,每个 SharePoint 网站上也都会有列表 类型。 此时,属性应如下所示。

      RegistrationId="100"
    
  2. 在文件 SharePointComponentDeployer.cs 中,将以下代码行添加到“DeployChainStoreComponentsToHostWeb”方法中,位置为调用 CreateLocalEmployeesList 的代码行的正下方(将在下一步中创建此方法)。

      ChangeCustomActionRegistration();
    
  3. 将下列方法添加到 SharePointComponentDeployer 类。

      private static void ChangeCustomActionRegistration()
    {
        using (var clientContext = sPContext.CreateUserClientContextForSPHost())
        {
         var query = from action in clientContext.Web.UserCustomActions
                 where action.Name == "{button_GUID} .AddEmployeeToCorpDB"
                 select action;
          IEnumerable<UserCustomAction> matchingActions = clientContext.LoadQuery(query);          
             clientContext.ExecuteQuery();
    
          UserCustomAction webScopedEmployeeAction = matchingActions.Single();
    
         // TODO8: Get a reference to the (empty) collection of custom actions 
         // that are registered with the custom list.
    
         // TODO9: Add a blank custom action to the list's collection.
    
         // TODO10: Copy property values from the descriptively deployed
         // custom action to the new custom action
    
        // TODO11: Delete the original custom action.         
    
          clientContext.ExecuteQuery();
        }
    }
    

    关于此代码,请注意以下几点:

    • 由于自定义操作(即自定义按钮)是向列表 类型 的功能区进行注册,因此它的作用范围是整个网站,并且位于网站的自定义操作集中。 所以,代码是从此集合中检索它。

    • action.Name 的值来自“AddEmployeeToCorpDB”中 element.xml 文件的“CustomAction”元素的“ID”属性。

    重要

    必须将代码中的 action.Name 值更改为与 elements.xml 文件中的值一致。 名称的 GUID 部分会有所不同。 请注意,GUID 和名称其余部分之间有一个 "." 字符。 下面的示例展示了此代码行:

    where action.Name == "4a926a42-3577-4e02-9d06-fef78586b1bc.AddEmployeeToCorpDB"

  4. TODO8 替换为以下代码。 请注意,撤回加载项时,并不会删除加载项创建的组件。 执行首次运行逻辑后,列表的“UserCustomActions”集合中便会有一个自定义操作,但此操作并不会在用户下次按 F5 时随加载项一起撤回。 为了避免造成混淆,此代码的最后一行 listActions.Clear(); 负责清空集合。

    var queryForList = from list in clientContext.Web.Lists
               where list.Title == "Local Employees"
               select list;
    IEnumerable<List> matchingLists = clientContext.LoadQuery(queryForList);
    clientContext.ExecuteQuery();
    
    List employeeList = matchingLists.First();
    var listActions = employeeList.UserCustomActions;
    clientContext.Load(listActions);
    listActions.Clear();
    
  5. TODO9 替换为以下代码行,向“本地员工”列表添加未定义的自定义操作。

      var listScopedEmployeeAction = listActions.Add();
    
  6. TODO10 替换为以下代码。

    listScopedEmployeeAction.Title = webScopedEmployeeAction.Title;
    listScopedEmployeeAction.Location = webScopedEmployeeAction.Location;
    listScopedEmployeeAction.Sequence = webScopedEmployeeAction.Sequence;
    listScopedEmployeeAction.CommandUIExtension = webScopedEmployeeAction.CommandUIExtension;
    listScopedEmployeeAction.Update();
    

    关于此代码,请注意以下几点:

    • 它将 Web 范围的按钮(使用描述性标记部署的按钮)的属性值分配到列表范围的按钮的相应属性,因此两个按钮除了作用域之外完全相同。

    • Sequence 属性指定了按钮在功能区区域中的相对显示顺序。 在此示例中,按钮位于功能区的“项”选项卡的“操作”部分中。 在描述性标记中,此值设置为“10001”。此值非常高,足以确保按钮位于 SharePoint 本身在功能区的“操作”部分中添加的任何现成按钮后面(即右侧)。

  7. TODO11 替换为以下代码,删除原始通过叙述性说明定义的按钮。 如果没有此代码行,网站上使用列表模板“100”的所有列表都会包含自定义按钮。 由于按钮功能与“本地员工”列表紧密相关,因此在其他任何列表上添加自定义按钮是毫无意义的做法。 此外,如果没有此代码行,自定义按钮会在“本地员工”列表上出现 两次,因为这个列表也使用模板“100”。

      webScopedEmployeeAction.DeleteObject();
    
  8. 此时,整个方法应如下所示(除了应将占位符替换为 GUID)。

      private static void ChangeCustomActionRegistration()
    {
        using (var clientContext = SPContext.CreateUserClientContextForSPHost())
        {
         var query = from action in clientContext.Web.UserCustomActions
                 where action.Name == "{button_GUID} .AddEmployeeToCorpDB"
                 select action;
          IEnumerable<UserCustomAction> matchingActions = clientContext.LoadQuery(query);          
             clientContext.ExecuteQuery();
    
          UserCustomAction webScopedEmployeeAction = matchingActions.Single();
    
         var queryForList = from list in clientContext.Web.Lists
                    where list.Title == "Local Employees"
                    select list;
         IEnumerable<List> matchingLists = clientContext.LoadQuery(queryForList);
         clientContext.ExecuteQuery();
    
        List employeeList = matchingLists.First();
        var listActions = employeeList.UserCustomActions;
        clientContext.Load(listActions);
        listActions.Clear();
    
        var listScopedEmployeeAction = listActions.Add();
    
        listScopedEmployeeAction.Title = webScopedEmployeeAction.Title;
        listScopedEmployeeAction.Location = webScopedEmployeeAction.Location;
        listScopedEmployeeAction.Sequence = webScopedEmployeeAction.Sequence;
        listScopedEmployeeAction.CommandUIExtension = webScopedEmployeeAction.CommandUIExtension;
        listScopedEmployeeAction.Update();
    
        webScopedEmployeeAction.DeleteObject();         
    
        clientContext.ExecuteQuery();
        }
    }
    

请求获取对主机 Web 的完全控制权

由于加载项现在添加和删除的是 Web 范围自定义操作,因此需要将加载项请求获取的权限从“管理”提升到“完全控制”:

  1. 在“解决方案资源管理器”中,打开“ChainStore”项目中的 AppManifest.xml 文件。

  2. 打开“权限”选项卡。将“范围”值保留为“Web”,但在“权限”字段中,从下拉列表中选择“完全控制”。

  3. 保存文件。

运行加载项并测试按钮部署

  1. 打开香港店网站的“网站内容”页,再删除“本地员工”列表。

    备注

    由于在 Visual Studio 中撤回加载项并不会删除加载项创建的列表,因此每次测试用于创建列表的代码时,都需要手动删除列表。

  2. 使用 F5 键部署并运行您的外接程序。Visual Studio 在 IIS Express 中托管远程 Web 应用程序,在 SQL Express 中托管 SQL 数据库。它还会在 SharePoint 测试网站上临时安装外接程序并立即运行它。在起始页打开之前,将提示您向外接程序授予权限。

  3. 在加载项的起始页打开后,选择最上面部件版式控制中的“返回到网站”。

  4. 转到“网站内容”页。 “本地员工”列表已存在,因为首次运行逻辑添加了它。

    备注

    如果列表不存在或有其他迹象表明首次运行代码未执行,可能是因为在按 F5 时“租户”表没有还原到空状态。 导致此问题发生的最常见原因是,在 Visual Studio 中,“ChainCorporateDB”项目不再设置为启动项目。 请参阅本文顶部附近的注意事项,了解如何解决此问题。 此外,还请确保已按照将 Visual Studio 配置为通过每个调试会话重建企业数据库中所述,配置待重建的数据库。

  5. 打开列表并添加项。

  6. 在列表视图中,选择此项,再打开功能区上的“项”选项卡。

  7. 在“项”选项卡上,选择“添加到企业 DB”按钮。 此时,员工添加到企业数据库,且“添加到企业 DB”字段变为“是”。

  8. 返回到“网站内容”页,并选择“添加加载项”。

  9. 添加一个新的 自定义列表。 默认情况下,类型为“常规”(“常规”是列表类型 100)。 创建列表后,打开功能区上的“项”选项卡。 请注意,功能区中 没有“添加到企业 DB”按钮。 这是因为代码已删除 Web 范围按钮。

  10. 若要结束调试会话,请关闭浏览器窗口或停止在 Visual Studio 中进行调试。每次按 F5,Visual Studio 都会取消旧版外接程序并安装最新版本。

  11. 您将在其他文章中使用此外接程序和 Visual Studio 解决方案,因此最好是当您使用一段时间后,最后一次撤回外接程序。在“解决方案资源管理器”中右键单击此项目,然后选择“撤回”。

后续步骤

在 SharePoint 中,列表和列表项事件还可以有自定义处理程序。 下一篇文章处理提供商托管加载项中的列表项事件将介绍如何创建自定义处理程序,并将它部署到首次运行逻辑中。