65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 SharePoint 2013 工作流服务 JS API

starIconstarIconstarIconstarIconstarIcon

5.00/5 (10投票s)

2013年6月19日

CPOL

6分钟阅读

viewsIcon

107767

使用 SharePoint 2013 工作流服务 API 的代码示例。

引言

SharePoint 2013 中包含的工作流服务 API 不仅允许您启动和停止工作流,还可以实际编辑和创建它们、在网站之间复制它们、导出到 WSP、与列表关联等等。此 API 是 SharePoint 客户端对象模型的一部分,因此不仅可以从 C# 访问,还可以从 JavaScript 访问。不幸的是,该 API 的文档并不完善,而且有点麻烦,因此需要一段时间才能弄清楚如何使用它,甚至更长的时间来编写可工作的代码。本文旨在提供 API 用法的现成代码示例(使用 JavaScript),以便开发人员可以专注于实际业务目标,而不是浪费时间在 API 的困难之处。

背景

SharePoint 2013 支持两种类型的工作流:旧的 WF3 工作流(也称为 SharePoint 2010 工作流)和新的 WF4 工作流(也称为 SharePoint 2013 工作流)。工作流服务 API 主要用于处理新的 WF4 工作流,但也包含少量用于处理旧 WF3 工作流的方法。

新的 WF4 工作流在很多方面都更好,但同时也要明白,它们也缺少一些旧工作流拥有的功能。至少有两个重要的限制与 WF4 工作流相关,您可能需要考虑:

  1. 某些工作流操作,例如“设置审批状态”,对于 WF4 工作流不支持,而在 SP2010 工作流中却工作得很好。请参阅 MSDN 上的完整列表
  2. SP2013 工作流无法像 SP2010 工作流那样全局发布。例如,如果您想创建一个工作流并在某个网站集的不同网站中使用它,您必须将其复制到每个网站并单独管理。下面的示例可以帮助自动化此过程。

还有其他区别(例如 SP2013 可重用工作流不能与内容类型关联),但它们不像我上面列出的两个那样关键。

API 本身由 4 个服务组成:

  1. WorkflowDeploymentService - 提供创建新工作流定义和管理现有工作流定义的方法,例如修改、删除、发布、标记为弃用、导出到 WSP 等。
  2. WorkflowSubscriptionService - 在这里您可以找到与创建和管理工作流关联相关的方法,即附加和分离工作流与列表。
  3. WorkflowInstanceService - 使用此服务,您可以启动、取消、暂停、恢复和终止工作流。
  4. InteropService - 提供调用旧 SP2010 工作流的方法。

示例

示例列表

  1. 枚举网站的工作流定义
  2. 在网站之间复制工作流
  3. 在网站之间复制工作流,并动态自定义
  4. 添加工作流关联(订阅)
  5. 启动工作流

示例 1:枚举网站的工作流定义

要枚举网站的工作流定义,可以使用 WorkflowDeploymentServiceenumerateDefinitions 方法。

完整代码

(function () {

    // Getting client context and loading the current web
    var context = SP.ClientContext.get_current();
    var web = context.get_web();
    context.load(web);

    // Workflow Services API entry point - WorkflowServiceManager object
    var servicesManager = SP.WorkflowServices.WorkflowServicesManager.newObject(context, web);
    context.load(servicesManager);

    var workflowDefinitions = servicesManager.getWorkflowDeploymentService().enumerateDefinitions(false);
    context.load(workflowDefinitions);

    context.executeQueryAsync(function (sender, args) {

        // enumerateDefinition returns ClientCollection object
        var definitionsEnum = workflowDefinitions.getEnumerator();

        var empty = true;

        console.log('Site ' + web.get_url() + ':');

        // Going through the definitions
        while (definitionsEnum.moveNext()) {

            var def = definitionsEnum.get_current();

            // Displaying information about this definition - DisplayName and Id
            console.log(def.get_displayName() + " (id: " + def.get_id() + ")");

            empty = false;

        }

        if (empty) {
           console.log('No 2013 workflows found.');
        }

    }, errFunc);


    // Error handling
    function errFunc(sender, args) {
        alert("Error occured! " + args.get_message());
    };

})();

注意:此代码将不会返回旧的 SharePoint 2010 工作流。

这是我在一个测试网站上执行上述代码的结果:

正如您所看到的,甚至可以直接从控制台运行此代码。在这种情况下,您必须确保页面中包含 "/layouts/15/SP.WorkflowServices.js"。

示例 2:在网站之间复制工作流

此示例展示了如何复制 SP2013 工作流到不同网站。由于 SP2013 可重用工作流无法全局发布(这只能为 SP2010 工作流完成),因此当您想在多个网站中使用相同的工作流时,此代码在许多场景中都可能有用。

以下代码将一个工作流从当前网站复制到其子网站(不是复制到网站集中的所有网站,而只是当前网站的下一级)。

(function () {

    // please, provide actual id of the workflow you're going to copy
    var myDefinitionId = 'PUT-YOUR-GUID-HERE';

    var context = SP.ClientContext.get_current();

    // get current web and it's subwebs
    var web = context.get_web();
    var webs = web.get_webs();
    context.load(web);
    context.load(webs);

    // get WorkflowServiceManager object for the root web
    var servicesManager = SP.WorkflowServices.WorkflowServicesManager.newObject(context, web);
    context.load(servicesManager);

    // load the workflow definition
    var workflowDefinition = servicesManager.getWorkflowDeploymentService().getDefinition(myDefinitionId);
    context.load(workflowDefinition);

    context.executeQueryAsync(function (sender, args) {


        // Looks meaningless, but without this "fix" you workflow
        // will not have the displayName and it's Id will appear
        // instead of the displayName
        workflowDefinition.set_displayName(workflowDefinition.get_displayName());

        // enumerate through subsites
        var websEnum = webs.getEnumerator();

        while (websEnum.moveNext()) {

            var subweb = websEnum.get_current();

            // save a copy of the workflow definition to the subsite
            saveWorkflowDefinition(context, subweb, workflowDefinition);

        }

    }, errFunc);


    function errFunc(sender, args) {
        alert("Error occured! " + args.get_message());
    };


    function saveWorkflowDefinition(context, web, myDefinition) {

        // we have to repeat the procedure of getting WorkflowServicesManager object because
        // it should be retrieved for each web separately
        var servicesManager = SP.WorkflowServices.WorkflowServicesManager.newObject(context, web);
        context.load(servicesManager);

        // Save and publish the definition
        servicesManager.getWorkflowDeploymentService().saveDefinition(myDefinition);
        servicesManager.getWorkflowDeploymentService().publishDefinition(myDefinition.get_id());

        context.executeQueryAsync(function (sender, args) {

            console.log('Definition saved to site ' + web.get_url());

        }, errFunc);

    }

})();

注意:此代码有一个陷阱:在检索到工作流定义并将其保存到其他网站时,必须将显示名称设置为相同的值,否则该定义将没有名称。

我运行了此代码,在当前网站上有一个名为“Reusable workflow test”的测试工作流。当前网站有两个子网站(project1 和 project2),其中没有这样的工作流。

执行结果

在此之后,工作流出现在子网站上。

我已仔细检查:工作流是正确的,并且可以与列表关联。

以防万一,我已将此工作流关联到列表并启动了它,它已成功完成。

示例 3:在网站之间复制工作流,并动态自定义

这基本上与上一个示例相同,唯一的区别是工作流不是“按原样”复制的,而是在过程中进行了修改。

在大多数情况下,这可以通过修改工作流的 XAML 代码来实现,XAML 代码本质上是一段 XML。因此,您可以进行简单的字符串替换即可。

假设您的工作流中有一个“启动任务进程”操作,并且您希望在部署工作流的不同网站上具有不同的参与者。

首先,您应该检索工作流的 XAML 定义(为此请使用 workflowDefinition.get_xaml() 属性),并确定需要更改的位置。在上面的示例中,您会在工作流中找到类似这样的内容:

<local:CompositeTask.AssignedTo>
  <InArgument x:TypeArguments="sco:Collection(x:String)">
    <local:ExpandInitFormUsers>
      <local:ExpandInitFormUsers.Users>
        <InArgument x:TypeArguments="sco:Collection(x:String)">
          <p:BuildCollection x:TypeArguments="x:String" 
               Collection="{x:Null}" Result="{x:Null}">
            <p:BuildCollection.Values>
              <InArgument x:TypeArguments="x:String">i:0#.f|membership
                                    |user@test.onmicrosoft.com</InArgument>
            </p:BuildCollection.Values>
          </p:BuildCollection>
        </InArgument>
      </local:ExpandInitFormUsers.Users>
    </local:ExpandInitFormUsers>
  </InArgument>
</local:CompositeTask.AssignedTo>

显然,您需要将 i:0#.f|membership|user@test.onmicrosoft.com 替换为其他内容,但仅限于此片段的替换范围。

现在,如果您对正则表达式有基本了解,可以使用这个简单的脚本来完成:

  var regex = /(<local:CompositeTask.*?<local:CompositeTask.AssignedTo>.*?<InArgument x:TypeArguments="x:String">)([^\<]+)/;
  definition.set_xaml(definition.get_xaml().replace(regex, "\\$1i:0#.f|membership|user2@test.onmicrosoft.com");

不可否认,正则表达式因其可读性和可维护性方面的问题而臭名昭著。因此,如果您打算对工作流 XAML 进行一些真正复杂的操作,请考虑使用托管客户端对象模型(即 C#)或至少使用类似 JsXML 这样的库。

示例 4:添加工作流关联(订阅)

您可以将此示例视为对 示例 2:在网站之间复制工作流 的补充,因为很明显,通常在复制工作流后,您很可能会立即将其附加到列表。

代码

(function() {

        // please, provide actual id of the workflow you're going to copy
        var workflowDefinitionId = 'PUT-YOUR-GUID-HERE';
        var listGuid = 'PUT-YOUR-GUID-HERE';

        var web = context.get_web();
        context.load(web);

        // Get WorkflowServicesManager object for the specified web
        var servicesManager = SP.WorkflowServices.WorkflowServicesManager.newObject(context, web);
        context.load(servicesManager);

        // Workflow History list
        var historyList = web.get_lists().getByTitle('Workflow History');
        context.load(historyList, 'Id');

        // Workflow Tasks list
        var tasksList = web.get_lists().getByTitle('Tasks');
        context.load(tasksList, 'Id');

        context.executeQueryAsync(function (sender, args) {

            // Creating the subscription
            var sub = new SP.WorkflowServices.WorkflowSubscription(context);

            sub.set_name('Test subscription');
            sub.set_enabled(true);
            sub.set_definitionId(workflowDefinitionId);
            sub.set_statusFieldName('WF status');
            sub.set_eventSourceId(listGuid);
            sub.set_eventTypes(["WorkflowStart"]);

            // These 3 are MANDATORY! Otherwise the workflow will fail to complete
            sub.setProperty("TaskListId", tasksList.get_id().toString());
            sub.setProperty("HistoryListId", historyList.get_id().toString());
            sub.setProperty("FormData", "");

            // Associate the workflow with the list
            servicesManager.getSubscriptionService().publishSubscriptionForList(sub, listGuid);

            context.executeQueryAsync(function (sender, args) {

                console.log('Workflow association has been created successfully. Web: ' + web.get_url());


            }, errFunc);

        }, errFunc);

})();

注意:此代码的丑陋之处在于,您应该在创建的工作流订阅中添加三个强制性的附加属性(“FormData”、“TaskListId”和“HistoryListId”),否则,即使订阅成功创建,工作流也无法工作(启动后会立即失败)。这些属性没有在任何地方记录,我花了相当长的时间才弄清楚这一点。

示例 5:启动工作流

要启动 SP2013 工作流,您应该使用 WorkflowInstanceService 对象的 startWorkflow 方法。

以下是操作方法(此代码在当前网站的指定列表的第一个项目上启动指定的工作流):

(function () {


    var definitionId = 'PUT-YOUR-GUID-HERE';
    var listGuid = 'PUT-YOUR-GUID-HERE';

    var context = SP.ClientContext.get_current();
    var web = context.get_web();
    context.load(web);

    var list = web.get_lists().getById(listGuid);
    context.load(list);

    var items = list.getItems(SP.CamlQuery.createAllItemsQuery());
    context.load(items);

    var servicesManager = SP.WorkflowServices.WorkflowServicesManager.newObject(context, web);
    context.load(servicesManager);

    var subs = 
     servicesManager.getWorkflowSubscriptionService().enumerateSubscriptionsByDefinition(definitionId);
    context.load(subs);

    context.executeQueryAsync(function (sender, args) {

            if (resultsElement.innerHTML.indexOf('Loading') !== -1)
                resultsElement.innerHTML = '';

            var subsEnum = subs.getEnumerator();

            while (subsEnum.moveNext()) {

                var sub = subsEnum.get_current();

                console.log('Web: ' + web.get_url() + ', Subscription: ' + 
                  sub.get_name() + ', id: ' + sub.get_id());

                var initiationParams = {};
                servicesManager.getWorkflowInstanceService().startWorkflowOnListItem(
                  sub, items.getItemAtIndex(0).get_id(), initiationParams);

                context.executeQueryAsync(function (sender, args) {
                    console.log('Workflow started.');
                }, errFunc);

            }

    }, errFunc);


    function (sender, args) {
        alert("Error occured! " + args.get_message() + 
                 '\r\nStack trace: ' + args.get_stackTrace());
    }

})();

注意:如果您计划在不同的网站中操作,请确保使用同步的 SP.ClientContext 和 SP.Web 对象,否则工作流将无法启动。

结论

SharePoint 2013 中的工作流服务 API 是一个强大的工具,它不仅可以用于启动/停止工作流等常规任务,还可以用于广泛的维护和自动化目的,包括解决方案升级场景、自动化测试、动态生成工作流等。考虑到 Apps 和客户端对象模型的日益重要性,它对于现代 SharePoint 开发尤其有价值。

请记住,该 API 并不完美,并且有其自身的陷阱!

祝您的解决方案好运!

© . All rights reserved.