向提供程序托管的加载项添加首次运行逻辑

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

备注

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

本文将介绍如何向连锁店 SharePoint 加载项的起始页添加代码,用于检查加载项的当前实例是否是首次运行。 如果是首次运行,代码会部署“本地员工”列表和自定义功能区按钮。

创建用于部署 SharePoint 组件的基本类

备注

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

  1. 右键单击“解决方案资源管理器”最上面的解决方案节点,再选择“设置启动项目”。
  2. 确保三个项目都在“操作”列中设置为“启动”。
  1. 在“解决方案资源管理器”中的“ChainStoreWeb”项目内,右键单击“实用工具”文件夹,再依次选择“添加” > “现有项”。

  2. 在“文件资源管理器”中,依次转到解决方案文件夹和“ChainStoreWeb”文件夹,再打开“实用工具”文件夹。

  3. 依次选择“SharePointComponentDeployer.cs”和“添加”。

  4. 打开文件 SharePointComponentDeployer.cs。该文件有一个静态类和两个静态方法,静态方法可在公司数据库的"租户"表中获取并设置外接程序的版本。我们不讨论这些方法,因为本系列文章的目的不是教您进行 ASP.NET 或 SQL Server/Azure 编程。

  5. 将以下“using”语句添加到文件最上面。

      using System.Web;
      using System.Linq;
      using System.Collections.Generic;
      using Microsoft.SharePoint.Client;
    
  6. SharePointComponentDeployer 类的最上面,添加以下两个静态字段。 这两个字段均在加载项起始页的 Page_Load 方法中初始化(将在后续步骤中添加此代码)。

      internal static SharePointContext sPContext;
      internal static Version localVersion;
    

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

    • 第一个字段保留对 SharePoint 执行 CRUD 操作所需的 SharePointContext 对象。

    • 第二个字段保留在主机 Web 中安装的加载项的版本号。 此值最初不同于在安装处理程序注册租户时企业“租户”表中记录的默认值 (0000.0000.0000.0000)。 例如,首版加载项的版本号为 1.0.0.0

  7. 创建以下静态属性,以保存当前记录在公司"租户"表中的外接程序版本。它使用文件中已有的两个方法来获取和设置该值。

      internal static Version RemoteTenantVersion
    {
        get
        {
            return GetTenantVersion();
        }
        set
        {
            SetTenantVersion(value);
        }
    }
    
  8. 现在,创建以下 IsDeployed 属性。

      public static bool IsDeployed
    {
        get
        {
            if (RemoteTenantVersion < localVersion)
                return false; 
            else
                return true; 
        }
    }
    

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

    • 加载项起始页的 Page_Load 方法使用此属性的值来确定加载项是否是首次运行。 如果值为 false,表明加载项之前尚未在当前主机 Web 上运行,因此需要部署它的组件。

    • 条件是“租户”表中注册的版本号是否低于实际安装的版本。 如果加载项是首次运行,则低于实际安装的版本。 在后续步骤中,将代码编写为,把“租户”表中的版本设置为与实际安装的版本相同。这样,当加载项再次运行时,IsDeployed 就会返回“true”,且部署逻辑也不会再次执行。

  9. 将下列方法添加到 SharePointComponentDeployer 类。 请注意,此方法最后会将企业数据库中的注册租户版本 (0000.0000.0000.0000) 更新为,与主机 Web 上的加载项实际版本 (1.0.0.0) 一致。 将在后续步骤中完成此方法。

      internal static void DeployChainStoreComponentsToHostWeb(HttpRequest request)
    {
        // TODO4: Deployment code goes here.
    
        RemoteTenantVersion = localVersion;
    }
    

备注

现在,大家可能会想知道,为什么加载项使用版本号和“低于”测试来确定下列简单的“是/否”问题的答案:“加载项是否是首次运行?”。 在安装处理程序中,也可以将“租户”表中的简单字符串字段设置为“not-yet-run”,然后在 SharePoint 组件部署后再通过首次运行逻辑将它更改为“already-run-once”。

对于连锁店加载项,简单测试就可以发挥作用。 不过,最佳做法通常是使用版本号,因为生产加载项今后可能会发生就地更新;也就是说,在已安装后更新。 届时,加载项逻辑需要留意“not-yet-run”和“already-run-once”这两种可取值之外的情况。

例如,假设要将其他列表添加到从版本 1.0.0.0 升级到 2.0.0.0 的主机 Web 中。 可以在更新事件处理程序中执行此操作,也可以在更新后首次运行逻辑中执行此操作。 无论采用上述哪种方式,部署逻辑都需要部署新组件,但还需要避免尝试重新部署已在旧版加载项中部署过的组件。 版本号 1.0.0.0 表明已部署版本 1.0.0.0 的组件,但更新后首次运行逻辑尚未运行。

添加基本启动逻辑

SharePoint 主机 Web 需要向远程 Web 应用告知已安装的加载项版本。 为此,请使用查询参数。

  1. 打开“ChainStore”项目中的 AppManifest.xml 文件。 在设计器中,将看到“查询字符串”框的值为占位符“{StandardTokens}”。 在结尾处添加字符串“"&amp;SPAddInVersion=1.0.0.0"”。

    清单设计器应如下所示。 请注意,在查询字符串中传递的版本号应与设计器的“版本”框中的值一致。 如果曾更新加载项,其中一项任务就是递增这两个值,并确保两值一致。

    图 1:清单设计器的“常规”选项卡

    清单设计器的“常规”选项卡,其中“版本”框的值为“1.0.0.0”。“查询字符串”框的值为“{StandardTokens}&SPAddInVersion=1.0.0.0”

  2. 打开 CorporateDataViewer.aspx.cs 文件,并将以下代码添加到“Page_Load”方法中,位置为初始化 spContext 对象的代码行的正下方。

     SharePointComponentDeployer.sPContext = spContext;
     SharePointComponentDeployer.localVersion = new Version(Request.QueryString["SPAddInVersion"]);
    
     if (!SharePointComponentDeployer.IsDeployed)
     {
         SharePointComponentDeployer.DeployChainStoreComponentsToHostWeb(Request);
     }
    

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

    • 它先在静态 SharePointComponentDeployer 类中设置两个静态字段。 它会传递 SharePointContext 对象(因为 SharePointComponentDeployer 中的代码会调用 SharePoint),并使用已添加的查询参数来设置 localVersion 属性。

    • 如果 IsDeployed 为 true(即首次运行逻辑已运行),则不会执行任何操作。 否则,它会调用部署方法,并传递 ASP.NET Request 对象。

以编程方式部署 SharePoint 列表

  1. 在 SharePointComponentDeployer.cs 文件中,将 TODO4 替换为以下代码行(将在下一步中创建此方法)。

      CreateLocalEmployeesList();
    
  2. 将下列方法添加到 SharePointComponentDeployer 类。

      private static void CreateLocalEmployeesList()
    {
        using (var clientContext = sPContext.CreateUserClientContextForSPHost())
        {
            var query = from list in clientContext.Web.Lists
                        where list.Title == "Local Employees"
                        select list;
            IEnumerable<List> matchingLists = clientContext.LoadQuery(query);
            clientContext.ExecuteQuery();
    
            if (matchingLists.Count() == 0)
            {
               // TODO5: Create the list 
    
               // TODO6: Rename the Title field on the list 
    
               // TODO7: Add "Added to Corporate DB" field to the list 
    
               clientContext.ExecuteQuery();
            }
        }
    }
    

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

    • 它具有 ExecuteQuery 的两个调用。第一个调用确定列表是否已存在,第二个调用执行创建列表的操作。

    • ClientContext.LoadQuery 方法类似于 ClientContext.Load 方法,不同之处在于前者不是将列表等实体向下传递到客户端,而是将查询的可枚举结果向下传递到客户端。

  3. TODO5 替换为以下代码。

      ListCreationInformation listInfo = new ListCreationInformation();
      listInfo.Title = "Local Employees";
      listInfo.TemplateType = (int)ListTemplateType.GenericList;
      listInfo.Url = "Lists/Local Employees";
      List localEmployeesList = clientContext.Web.Lists.Add(listInfo);
    

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

    • ListCreationInformation 类类似于本系列前面一篇文章中提及的 ListItemCreationInformation 类。 不同之处在于,前者是一个轻型类,与完整 List 类相比,更适合将 Web 应用中的信息发送到 SharePoint。

    • 有很多类型的列表模板,例如"待办事项"列表的"任务"类型和日历的"事件"类型。"本地员工"列表基于最简单的"通用"类型。

    • ListCreationInformation.Url 属性保留 相对于 主机 Web 的列表 URL。 通过指定 "Lists/LocalEmployees",代码将列表的完整 URL 设置为 https://{SharePointDomain}/hongkong/_layouts/15/start.aspx#/Lists/Local%20Employees

  4. TODO6 替换为以下代码,即将“称呼”字段(列)的公共名称从“称呼”更改为“姓名”。 这是在手动创建列表时对“列表设置”页执行的操作。

      Field field = localEmployeesList.Fields.GetByInternalNameOrTitle("Title");
      field.Title = "Name";
      field.Update();
    
  5. 还手动创建了“添加到企业 DB”字段。 若要以编程方式执行此操作,请将 TODO7 替换为以下代码。

          localEmployeesList.Fields.AddFieldAsXml("<Field DisplayName='Added to Corporate DB'"
                                                 +"Type='Boolean'>"
                                                 + "<Default>FALSE</Default></Field>",
                                                 true,
                                                 AddFieldOptions.DefaultValue);
    

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

    • 字段的关键属性是使用 XML blob 进行指定。 这是旧版 SharePoint 体系结构,其中网站、列表、字段、内容类型和其他大部分类型的 SharePoint 组件都被定义为 XML。 此示例指定字段的显示名称、数据类型和默认值。

    • 第二个参数确定字段在列表的默认视图中是否可见。 将它设置为 true

    • 第三个参数确定要将字段添加到哪些内容类型。 传递 DefaultValue 意味着,仅将字段添加到列表的默认内容类型。

  6. 回顾一下,虽然“添加到企业 DB”默认设置为“否”(即 false),但加载项中的自定义功能区按钮在向企业数据库添加员工后将它设置为“是”。 仅当用户无法手动更改字段值时,此机制的效果才最好。 为了确保用户无法手动更改,请确保字段在用于创建并编辑“本地员工”列表项的表单中不可见。 为此,请向第一个参数添加另外两个属性,如下面的代码所示。

      localEmployeesList.Fields.AddFieldAsXml("<Field DisplayName='Added to Corporate DB'" 
                                             + " Type='Boolean'"  
                                             + " ShowInEditForm='FALSE' "
                                             + " ShowInNewForm='FALSE'>"
                                             + "<Default>FALSE</Default></Field>",
                                             true,
                                             AddFieldOptions.DefaultValue);
    
  7. 此时,整个 CreateLocalEmployeesList 应如下所示。

           private static void CreateLocalEmployeesList()
         {
             using (var clientContext = sPContext.CreateUserClientContextForSPHost())
             {
                 var query = from list in clientContext.Web.Lists
                             where list.Title == "Local Employees"
                             select list;
                 IEnumerable<List> matchingLists = clientContext.LoadQuery(query);
                 clientContext.ExecuteQuery();
    
                 if (matchingLists.Count() == 0)
                 {
                     ListCreationInformation listInfo = new ListCreationInformation();
                     listInfo.Title = "Local Employees";
                     listInfo.TemplateType = (int)ListTemplateType.GenericList;
                     listInfo.Url = "LocalEmployees";
                     List localEmployeesList = clientContext.Web.Lists.Add(listInfo);
    
                     Field field = localEmployeesList.Fields.GetByInternalNameOrTitle("Title");
                     field.Title = "Name";
                     field.Update();
    
                     localEmployeesList.Fields.AddFieldAsXml("<Field DisplayName='Added to Corporate DB'" 
                                                             + " Type='Boolean'"  
                                                            + " ShowInEditForm='FALSE' "
                                                            + " ShowInNewForm='FALSE'>"
                                                            + "<Default>FALSE</Default></Field>",
                                                             true,
                                                             AddFieldOptions.DefaultValue);
                     clientContext.ExecuteQuery();
                 }
             }
         }
    

从项目中暂时删除自定义按钮

由于技术原因(将在下一篇文章中进行解释),如果创建的自定义按钮位于以编程方式部署的列表的功能区上,必须经过修改才能安装。 将把它从项目中暂时删除,以便可以测试首次运行逻辑。 在下一篇文章中,将重新添加此按钮。

  • 在“解决方案资源管理器”中的“ChainStore”项目内,右键单击“AddEmployeeToCorpDB”节点,再选择“从项目中排除”。

请求获取对主机 Web 列表的管理权限

由于加载项现在向主机 Web 添加列表,而不是向现有列表直接添加项,因此需要将加载项请求获取的权限从“写入”提升到“管理”:

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

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

  3. 保存文件。

运行加载项并测试首次运行逻辑

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

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

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

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

    备注

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

  5. 打开列表并添加项目。请注意,在新项目表单上,"已添加到公司数据库"字段不再存在,因此无法手动设置。这也适用于编辑项目表单。

    图 2:“本地员工”列表的“新建项”表单

    “本地员工”列表的“新建项”表单。 表单上不再显示“添加到企业 DB”字段,只显示名称字段以及“确定”和“取消”按钮。

  6. 使用浏览器的“后退”按钮返回到加载项的起始页。

  7. 依次选择最上面部件版式控制中的齿轮图标和“帐户设置”。

  8. 在“帐户设置”页上,选择“显示加载项版本”按钮。 版本显示为“1.0.0.0”,因为首次运行逻辑更改了它。

    图 3:“帐户设置”页

    包含 1.0.0.0 版本号的“帐户设置”页。

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

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

后续步骤

下一篇文章以编程方式在提供商托管加载项中部署自定义按钮将介绍如何把“本地员工”功能区的自定义按钮重新添加到至此已以编程方式部署了列表的加载项中。