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

SharePoint 2010 - 可自定义的顺序工作流

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0投票)

2015年5月10日

CPOL

18分钟阅读

viewsIcon

12563

downloadIcon

118

本文是对 VS2010 中 SharePoint 2010 顺序工作流项目的描述。生成的解决方案 .wsp 允许用户仅通过编写 XML 文件并将其上传到 SharePoint WFE 来快速创建 SP 2010 工作流。

引言

这个 SharePoint 2010 顺序工作流项目创建了一个非常通用的顺序工作流,可以通过 XML 进行自定义。作为 SP 管理员/开发人员/支持人员工作了将近一年,我得出结论,在我被指派的大部分项目中,关于工作流的要求几乎都是相同的。应该有 x 个任务需要完成,在完成一个或几个任务后,项目本身可能有一个状态字段更新为某个定义的值,并且在工作流完成后可以执行某种流程。每个任务通常至少应该有 3 个按钮 - 批准、拒绝、请求更改。实际上 SharePoint 已经有一个带有这些按钮的任务表单模板。因此,为每个工作流创建一个新项目,而这些项目 90% 以上都与我们已有的项目相似,至少对我来说不是一个合理的解决方案。新项目意味着增加代码维护、更大的知识转移、更大的文档、更大的 DRP,以及更多(易出错的)恢复或重新安装系统的步骤。拥有一个解决方案和一个由非技术人员编写的 XML 文件目录要容易得多。

为了使工作流保持简单和快速,在特定任务完成后应执行的大项更新和后台进程可以通过事件接收器来处理。工作流本身允许用户更新项目,但更新仅在用户执行操作后执行一次。这些操作之一是工作流激活,另一个是任务完成。因此,不应该与 ER 发生冲突。

我明白 SP2010 现在已经 pretty much 过时了,但我希望尽快将此项目更新到 SP2013。此外,我对用于指导工作流的 XML 的定义没有多想,因此欢迎所有改进它的建议。

XML 描述

因此,为了通过 XML 自动化我们的工作流,我们需要两件事:第一,一个通用的工作流,它只创建任务并等待用户操作来更改这些任务;第二,为该通用工作流提供 XML 来执行任务操作。由于我们正在处理文档管理系统,XML 可以并且将存储在同一 SP 站点中的单独列表中。该项目使用根 SharePoint 站点来创建具有预定义名称的列表。当您要以此 XML 工作流为模板创建新工作流时,首先要创建一个 XML 文件并将其上传到此列表。XML 文件将作为附件附加到与工作流同名的项目。因此,例如,如果您需要一个请假审批工作流,您将在该列表中创建一个标题为“请假审批”的项目,并将工作流“结构”的 XML 附加到该项目。然后,您在相应的列表中创建一个工作流,选择此项目作为模板,为其命名为“请假审批”,这样就完成了。

父节点是 '<SPSolution type="Workflow">'。目前 'type' 属性是可选的。内部是 '<Settings>' 节点,其中包含所有设置。内部有 3 个不同的节点,用于 3 种不同范围的配置:'<Modules>'、'<Global>' 和 '<Context>'。

Modules 存储工作流中使用的“模块”的设置。工作流现在唯一的模块是邮件功能,因此所有邮件设置都存储在那里。模块设置以 '<Module id="id_goes_here">' 节点开头。对于电子邮件是 '<Module id="Mail">'。例如,您可能添加一个函数来将项目或某些数据存储在外部数据存储中。因此,访问该存储的配置也可以存储在此节点中。也许是 '<Module id="MSSQL">'。在 '<Module>' 节点内部是 '<Option id="SETTING_ID">' 节点。在我实现的邮件功能的情况下,我只需要这 7 个节点,但您可以扩展它以获得更多设置。

Global 存储全局使用的设置。在 Global 节点内部是上面描述的 '<Option>' 节点。我使用这些选项来定义日志记录参数和与工作流本身相关的几个设置,但它们不是强制性的。日志记录选项也可以放在 'Modules' 中。

Context 节点存储工作流任务的设置。它的结构与 Modules 和 Global 不同。这是因为任务信息比模块配置复杂得多。首先是 'Workflow/onInitiate/Item' 节点,其中包含重复的 'Property' 节点,用于告诉工作流在工作流初始化时要更新哪些项目字段。Property 节点有一个 'Id' 属性来标识字段。然后是 Workflow/Terminate/Property 节点,用于告诉工作流检查哪个字段来完成工作流。该节点中的文本是字段可以具有以停止工作流的 ";" 分隔的值。我使用隐藏字段 hStatus 来完成工作流。

Context 节点下最重要的节点是 'Tasks/Task' 节点。这些节点中的每个节点定义一个任务。每个 'Task' 节点必须有一个 id 属性,其值为从 0 开始的整数,以定义任务的 id (TaskType)。在 0 之后,id 可以跳到 5、20 或任何其他正值,但必须存在 0 才能启动工作流。Task 节点有 3 个子节点:Properties、onUserAction 和 Next。Properties 包含重复的 Property 节点。每个 Property 节点都有一个必需的 id 属性以及其他一些非必需的属性,如 src、type 和 scope。“type”用作注释,告诉用户该节点的内容是否会被格式化。但工作流本身决定什么内容会被格式化,所以 type 可以移除。属性的描述如下。

id="TITLE" - 任务的标题。格式化值。

id="ASSIGNED_TO" - 任务的分配对象。";" 分隔的值。每个值可以是 SP 组或用户。格式化值。

id="ASSIGNED_TO_TYPE" - 分配对象的类型。";" 分隔的值。1 表示用户,2 表示组。

id="APPROVAL_TYPE" - 任务的类型。1 表示单任务,2 表示多任务 -> 由复制器创建。

id="ACTION_PRIORITY" - 任务操作的优先级。";" 分隔的值。多任务必需。如果您的任务表单有 3 个返回 1 为批准,2 为拒绝或 3 为更改请求结果的按钮,并且您将该任务分配给 3 个组或个人,并且他们都点击了不同的按钮,那么优先级将告诉应该遵循哪个操作。如果优先级是 2;3;1,那么第一个遵循的操作是拒绝。

id="RECON_ACT" - 协调操作。多任务必需。如果工作流无法决定选择了哪个操作,它将执行协调操作并使用其设置来完成任务并创建下一个任务。

id="WFH_COMMENT" - 任务工作流历史记录中的日志行。格式化值。

id="AUTH_SUBJECT" - 创建任务时发送给项目作者的电子邮件主题。格式化值。

id="AUTH_BODY" - 创建任务时发送给项目作者的电子邮件正文。格式化值。

id="ASSGTO_SUBJECT" - 发送给任务分配对象的电子邮件主题。格式化值。

id="ASSGTO_BODY" - 发送给任务分配对象的电子邮件正文。格式化值。

id="EMAIL_TO" - 电子邮件的附加收件人。";" 分隔的电子邮件地址。格式化值。

id="EMAIL_SUBJECT" - 创建任务时发送的附加电子邮件的主题。格式化值。

id="EMAIL_BODY" - 创建任务时发送的附加电子邮件的正文。格式化值。

此节点中其余的属性具有 scope="extended",这意味着这些属性将成为 Task.ExtendedProperties 的一部分,并将在任务 .xsn 表单中使用。

onUserAction 节点定义每个操作的属性和更新。在此项目中使用的任务表单中定义了 3 个结果/操作 - 1(批准)、2(拒绝)和 3(更改)。onUserAction 包含重复的 Action 节点,每个结果对应一个。Action 节点具有必需的 id 属性,该属性定义操作(在此项目中为 1、2、3),以及子节点 Item(任务完成后更新项目)、This(任务完成后更新任务数据)、Mail(任务完成后发送电子邮件)和 WF_HIST(任务完成后写入工作流历史记录)。前面描述的 4 个节点中的每个节点都有带有 id 属性和格式化内容的 Property 节点。

Next 节点定义要创建的下一个任务,或者工作流是否将终止。它具有一个名为 Action 的重复子节点,该节点带有一个 type 属性,该属性接受两个值 - "task" 或 "terminate"。在此 Action 节点内,有两个子节点 - If(用于操作值)和 Id(用于下一个任务 id)。Id 1000 用于 type="terminate" 的操作。

在项目 zip 文件中有一个示例 Test WF.xml。您可以检查它以更好地了解 XML 结构。

如果您检查了 Test WF.xml,您可能会看到一些值使用了 {object.property} 的表示法。此外,在上面的描述中,我指出一些或大部分值是格式化值。格式化值是我实现的一个功能,它允许您进一步扩展自定义。在 .cs 文件中有一个函数声明为 - private String sprintfSPItem(String templ, Hashtable ht, ref int err)。该函数以第一个参数 templ 作为格式来构建返回的 String。{object.property} 是一个占位符,将被相应的值替换。“object”部分可以是 task、item、xml、local、winreg 或 system,“property”部分可以是该对象的某个已定义属性或字段 (SPField)。如果 SPField 是 SPFieldType.User,则 {object.property} 变为 {object.user.property}。因此,对于 SPFieldType.User,您可以返回电子邮件、姓名或登录名。“local”表示从第二个参数(一个 Hashtable)获取值,“xml”表示从配置 XML 的 Global 节点获取值。“system”表示返回一些计算值,如日期时间、当前用户或换行符。在当前用户的情况下,您也可以返回 item.SPFieldType.User 属性中描述的 3 个属性中的任何一个。

创建项目

让我们开始打开 VS2010 并从以下模板创建新项目:Visual C#/SharePoint/2010/Sequential Workflow。

我们将项目命名为 WF_XMLBased,并将其存储在我们喜欢的 VS 项目位置。在第二个屏幕上,我们将指定用于调试的本地站点(尽管我们不会调试,而是直接将发布版本安装到场)。

在第三个屏幕上,工作流的名称也将是 WF_XMLBased,类型将是“List Workflow”。在第四个屏幕上,我们将取消选中自动关联并按“完成”。到目前为止一切顺利,您的项目结构应该如下所示。


现在我们将把我们的组件 Workflow1 重命名为 XMLBased,并将 Workflow1.cs 重命名为 XMLBased.cs。

重命名文件名后,我们将重构命名空间和类名。双击 XMLBased.cs 在设计器模式下打开它,然后按 F7 切换到代码视图。将“namespace WF_XMLBased.Workflow1”更改为“namespace WF_XMLBased”(在 XMLBased.designer.cs 中也执行相同的操作)。现在右键单击 Workflow1 并选择 Refactor/Rename。在“New name”字段中键入 XMLBased(在 XMLBased.designer.cs 中也执行相同的操作)。

重要提示 - 在 Elements.xml 的 XMLBased 下,将 CodeBesideClass 从 WF_XMLBased.Workflow1.Workflow1 更改为 WF_XMLBased.XMLBased。

创建自定义活动

由于我们的工作流必须支持多任务,我们需要创建一个自定义活动,当创建这些多任务时,该活动将被复制。让我们先创建这个自定义活动。在 Solution Explorer 中,右键单击项目名称 (WF_XMLBased),然后选择 Add/New Item... 选择模板 Visual C#/General/Component Class。将新组件命名为 ReplicatedTaskActivity。创建组件后,它会显示错误 -“Error loading workflow”,但这不用担心,我们会立即修复这个错误。在代码编辑器中打开 ReplicatedTaskActivity.Designer.cs,删除声明“private System.ComponentModel.IContainer components = null;”以及函数“protected override void Dispose(bool disposing)”及其注释。因此,现在“#region Component Designer generated code”是“partial class ReplicatedTaskActivity”的第一行。还要从“InitializeComponent”函数中删除“components = new System.ComponentModel.Container();”。

现在打开 ReplicatedTaskActivity.cs,即带有错误消息的那个,在代码编辑器中。删除复制构造函数“public ReplicatedTaskActivity(IContainer container)”并让 ReplicatedTaskActivity 继承自 SequenceActivity。

切换回设计器模式后,您会看到错误消息消失了。

将以下活动拖到 ReplicatedTaskActivity 区域(Drop Activities Here):CreateTask、While、onTaskChanged(在 While 中)和 UpdateTask。我不确定活动命名是否存在某种标准,但如果存在,我并没有遵循它们。因此,以下是我给活动及其属性的名称。
CreateTask - crtRepTask
    CorrelationToken - tokRepTask (owner activity ReplicatedTaskActivity)
    MethodInvoking - RepTask_Creating
    TaskId - RepTask_TI1
    TaskProperties - RepTask_TP1
While - whlRepTask
    Condition - Code Condition
        Method - RepTask_Continue
OnTaskChanged - otcRepTask
    CorrelationToken - tokRepTask (owner activity ReplicatedTaskActivity)
    Invoked - RepTask_Changed
    AfterProperties - RepTask_AP1
    BeforeProperties - RepTask_BP1
    TaskId - RepTask_TI1
UpdateTask - updRepTask
    CorrelationToken - tokRepTask (owner activity ReplicatedTaskActivity)
    MethodInvoking - RepTask_Updating
    TaskId - RepTask_TI1
    TaskProperties - RepTask_TP1

现在我们将添加更多活动属性,并将它们包含在“#region Activity Declarations”中。此外,之前定义的将包含在“#region Activity Implementations”中。此外,还有一些相关的日志记录、SP 和通用实用程序的辅助函数。我不会一一描述它们,只描述我发现对工作流功能特别重要的两三个函数。您可以在本文附带的源代码中找到所有函数。

 

String result = RepTask_AP1.ExtendedProperties["Result"].ToString();
if (TaskR2Os.ContainsKey(result))
{
	STOutcome = ((Hashtable)TaskR2Os[result])["Outcome"].ToString();
	String task_ids = getWFItemFieldData("hParallelTasks");
	String[] tids = task_ids.Split(';');
	for (int x = 0; x < tids.Length; x++)
	{
		if (tids[x] == RepTask_TI1.ToString())
		{
			tids[x] += ":" + result;
		}
	}
	task_ids = String.Join(";", tids);
	List f2v = new List();
	f2v.Add("hParallelTasks");
	f2v.Add("text");
	f2v.Add(task_ids);
	updateSPListItem(f2v);
	foreach (DictionaryEntry entry in (Hashtable)TaskR2Os[result])
	{
		if (!entry.Key.ToString().ToLower().Equals("outcome"))
		{
			RepTask_TP1.ExtendedProperties[entry.Key.ToString()] = entry.Value.ToString();
		}
	}
}

 

每个复制任务的结果存储在项目的 hParallelTasks 字段中。困难的部分在 Replicator Completed 方法中。下面我们将看到该函数的一部分。RepTask_Updating 的其余部分是工作流历史记录的写入。

在以编程方式开发工作流时,需要记住的一件重要事情是启用错误处理程序并记录错误。这将为您节省数小时的研究和调试时间。在设计模式下,右键单击 ReplicatedTaskActivity 并选择 View Fault Handlers。

将一个 Fault Handler 拖到 faultHandlersActivity1 中。对于 FaultType,选择 Referenced Assemblies/mscorelib/System/Exception (System.Exception 以捕获所有错误)。现在将 Code 活动拖到 faultHandlerActivity1 中,并将其命名为 errReportingActivity1。在 ExecuteCode 属性中键入 errReportingActivity_Log。

 

private void errReportingActivity_Log(object sender, EventArgs e)
{
    FaultHandlerActivity faultHandler = ((Activity)sender).Parent as FaultHandlerActivity;
    log_err("Fatal error caught in RTA - " + faultHandler.Fault.Message + "; stack trace is " + faultHandler.Fault.StackTrace);
}

 

在将所有函数和属性复制到 ReplicatedTaskActivity.cs 后,构建项目,将 bin/Release/WF_XMLBased.dll 安装到 GAC(将 bin/Release/WF_XMLBased.dll 拖到 Windows/assembly 文件夹),然后重新打开项目。

创建工作流

重新打开项目后,在设计模式下打开 XMLBased.cs。到目前为止,您在工作区中绘制的唯一活动应该是 onWorkflowActivated1。将以下活动拖到工作流区域:While、Sequence(在 While 中)、Code(在 Sequence 中)、If(在 Sequence 中的 Code 下)。在“true”/左侧分支下,拖入以下活动:Replicator、ReplicatedTaskActivity(在 Replicator 中)。您应该在 WF_XMLBased 组件类别下看到此自定义活动。在“false”/右侧分支下,拖入以下活动:Sequence、CreateTask(在 Sequence 中)、While(在 Sequence 中)、OnTaskChanged(在 While 中)、UpdateTask(在 Sequence 中)。这就是您的设计屏幕现在应该的样子。

这是我为所有活动及其属性和方法命名的。

OnWorkflowActivated - owaMain
    CorrelationToken - tokActivation (owner XMLBased)
    Invoked - WFMain_Activated
    WorkflowProperties - workflowProperties
While (在 owaMain 之后) - whlMain
    Condition - Code Condition
        Method - WF_Continue
Sequential (在 whlMain 之后) - seqMain
Code - codMain
    ExecuteCode - Prepare_Task
If (在 codMain 之后) - ifeMainFork
    Left path - ifeMainForkTrue
            Condition - Code Condition
                Method - Is_Multiple_Task
        Replicator - repMain
            ChildInitialized - ChildInitialized_Invoked
            Completed - Completed_Invoked
            Initialized - Initialized_Invoked
            ExecutionType - Parallel
            InitialChildData - assignees (public IList assignees = default(System.Collections.IList);)
       ReplicatedTaskActivity - replicatedTaskActivity
    Right path - ifeMainForkFalse
        Sequence - seqMainTask
        CreateTask - crtMainTask
            CorrelationToken - tokMainTask (owner activity seqMainTask)
            MethodInvoking - MainTask_Creating
            TaskId - MainTask_TI1
            TaskProperties - MainTask_TP1
        While - whlMainTask
            Condition - Code Condition
                Method - MainTask_Continue
        OnTaskChanged - otcRepTask
            CorrelationToken - tokMainTask (owner activity seqMainTask)
            Invoked - MainTask_Changed
            AfterProperties - MainTask_AP1
            BeforeProperties - MainTask_BP1
            TaskId - MainTask_TI1
       UpdateTask - updMainTask
            CorrelationToken - tokMainTask (owner activity seqMainTask)
            MethodInvoking - MainTask_updating
            TaskId - MainTask_TI1
            TaskProperties - MainTask_TP1

如果更改活动名称后出现有关丢失对象和错误引用的错误,您可以打开 XMLBased.designer.cs 并手动更改旧名称。例如,在我将 Workflow1 更改为 XMLBased 之后,OnWorkflowActivated 无法找到 Workflow1。更改 XMLBased.designer.cs 为我解决了这个问题。

现在让我们检查 repMain 的 Completed_Invoked 方法的一部分。

 

String task_ids = getWFItemFieldData("hParallelTasks");
if (task_ids.Equals("") || task_ids.Equals(String.Empty))
{
	log_errTerminateWF("Undefined parallel tasks");
	return;
}

String[] tids = task_ids.Split(';');
Hashtable ress = new Hashtable();
String uni_res = "";
for (int x = 0; x < tids.Length; x++)
{
	String[] task = tids[x].Split(':');
	String tid = task[0].ToString();
	String res = task[1].ToString();
	uni_res = res;
	if (!ress.ContainsKey(res))
	{
		ress.Add(res, 1);
	}
	else
	{
		ress[res] = Convert.ToInt32(ress[res].ToString()) + 1;
	}
}

String curr_task = getWFItemFieldData("hCAT");
if (curr_task.Equals(""))
{
	log_errTerminateWF("Undefined current task");
	return;
}

if (!ctxConfig.Contains(curr_task))
{
	log_errTerminateWF("Undefined config in task " + curr_task);
	return;
}

Hashtable ht = (Hashtable)ctxConfig[curr_task];
if (!ht.Contains("props"))
{
	log_errTerminateWF("Undefined properties in task " + curr_task);
	return;
}

if (!ht.Contains("onusract"))
{
	log_errTerminateWF("Undefined user actions in task " + curr_task);
	return;
}

if (!((Hashtable)ht["props"]).Contains("ACTION_PRIORITY"))
{
	log_errTerminateWF("'Action priority' is not defined in task " + curr_task);
	return;
}

if (!((Hashtable)ht["props"]).Contains("RECON_ACT"))
{
	log_errTerminateWF("'Reconciliation act' is not defined in task " + curr_task);
	return;
}

String[] act_pri = ((Hashtable)ht["props"])["ACTION_PRIORITY"].ToString().Split(';');
String rec_tsk = ((Hashtable)ht["props"])["RECON_ACT"].ToString();
String oua = "";
if (ress.Count == 1) // All approvers answered equally
{
	oua = uni_res;
}
else if (sameTaskResults(ress))
{
	for (int x = 0; x < act_pri.Length; x++)
	{
		if (ress.ContainsKey(act_pri[x]))
		{
			oua = act_pri[x];
			break;
		}
	}
}

Boolean send_email = false;
if (oua.Equals(""))
{
	oua = rec_tsk;
	send_email = true;
}

if (!((Hashtable)ht["onusract"]).Contains(oua))
{
	log_errTerminateWF("Undefined user action " + oua + " in task " + curr_task);
	return;
}

解析 hParallelTasks 的内容,并将 Hashtable ress 填充为以结果作为键,以结果数量作为值。选择下一个任务的逻辑很简单:如果 ress 只有一个键,则下一个任务对应于该结果;如果所有结果的数量都相同,则 ACTION_PRIORITY 决定下一个任务;如果前面的情况都不适用,则使用 RECON_ACT。

创建自定义任务表单并将其添加到项目中

我不会在本篇文章中涵盖 InfoPath Designer 这样众所周知且有 documented 的主题。我们假设您已经设计了表单,或者直接使用了项目中包含的表单。您很可能已经知道如何将 .xsn 添加到工作流,但我们还是检查一下。右键单击 XMLBased,选择 Add/New Item,然后选择模板 SharePoint/Module。将模块命名为 Forms,然后单击 Add。然后右键单击 Forms,选择 Add/Existing Item,导航到已发布的 .xsn 表单并添加它。在此项目中,我使用了名为“ApprRejChangeForm.xsn”的表单。现在打开 XMLBased/Forms/Elements.xml 并删除或注释掉这行“<File Path="Forms\Sample.txt" Url="Forms/Sample.txt" />”。现在打开 XMLBased/Elements.xml 并添加以下行:

将 TaskListContentTypeId="0x01080100C9C9515DE4E24001905074F980F93160" 添加为“<Workflow”的属性。

在“<StatusPageUrl>_layouts/WrkStat.aspx</StatusPageUrl>”之后添加

"<TaskN_FormURN>URN_OF_YOUR_XSN</TaskN_FormURN>",其中 N 从 0 到某个整数正数(如 40)不等。在此项目中,<TaskN_FormURN> 最大为 <Task39_FormURN>。

现在双击 Feature1,并将 Forms (WF_XMLBased) 移动到 Feature 中的 Items(右侧)。您的 Feature 窗口应该如下所示。

创建 Feature 事件接收器

一开始我提到定义工作流任务的 XML 文件将存储在同一站点或父站点中的列表中。因此,我们需要在 .wsp 安装期间发生的某个事件中创建该列表。要实现这一点,我们将需要一个 Feature 事件接收器。右键单击 Feature1 并选择 Add Event Receiver。自定义事件接收器将覆盖默认安装步骤,因此除了创建列表之外,我们还需要注册 .xsn 表单。要注册 .xsn 文件,您需要向项目添加引用。在 Solution Explorer 中,右键单击 References 并选择 Add Reference。在对话框中,选择 Browse 选项卡,然后转到 C:\Program Files\Microsoft Office Servers\14.0\Bin\。选择 Microsoft.Office.InfoPath.Server.dll 并单击 OK。如果您使用的是 .Net 3.5,它可能会显示有关兼容性问题的警告消息。您可以忽略它并单击 Yes。

在 Feature1.EventReceiver.cs 中取消注释 2 个函数:FeatureInstalled 和 FeatureUninstalling。

 

public override void FeatureInstalled(SPFeatureReceiverProperties properties)
{
	base.FeatureInstalled(properties);

	// Register .xsn files
	FormsService formsService = GetFormsService();
	if (formsService == null)
	{
		log_err("Unable to retrieve FormsService during installation of \"" + properties.Feature.Definition.Name + "\". Argument formsService was null.");
	}
	else
	{
		// Get list of form templates that are being deployed as part of this feature.
		List formTemplates = GetInfoPathFormTemplates(properties.Definition.Properties, properties.Definition.RootDirectory);
		foreach (string formTemplate in formTemplates)
		{
			if (formsService.FormTemplates.ItemFromFile(formTemplate) == null)
			{
				FormTemplateCollection.RegisterFormTemplate(formTemplate, properties.Definition, false);
			}
		}
	}

	// Install list to store XML files
	SPSite site = null;
	SPWeb web = null;
	int err = 0;

	try
	{
		site = properties.UserCodeSite != null ? properties.UserCodeSite : new SPSite(site_url);
	}
	catch (Exception ex)
	{
		log_err("Can't initiate the site from Feature ER; details - " + ex.Message + "\r\n" + ex.StackTrace);
		err = 1;
	}

	if (err == 0)
	{
		try
		{
			web = site.OpenWeb();
		}
		catch (Exception ex)
		{
			log_err("Can't open the web; details - " + ex.Message + "\r\n" + ex.StackTrace);
			err = 2;
		}
	}

	if (err == 0)
	{
		try
		{
			SPListCollection lists = web.Lists;
			Boolean found = false;
			for (int x = 0; x < lists.Count; x++)
			{
				if (lists[x].Title.Equals(cfg_list))
				{
					found = true;
				}
			}
			if (!found)
			{
				lists.Add(cfg_list, cfg_list_desc, SPListTemplateType.CustomGrid);
			}
		}
		catch (Exception ex)
		{
			log_err("Can't create the list; details - " + ex.Message + "\r\n" + ex.StackTrace);
			err = 2;
		}
	}
}

 

 

public override void FeatureUninstalling(SPFeatureReceiverProperties properties)
{
	base.FeatureUninstalling(properties);

	// Unregister .xsn files
	FormsService formsService = GetFormsService();
	if (formsService == null)
	{
		log_err("Unable to retrieve FormsService during installation of \"" + properties.Feature.Definition.Name + "\". Argument formsService was null.");
	}
	else
	{
		// Get list of form templates that were deployed as part of this feature.
		List formTemplates = GetInfoPathFormTemplates(properties.Definition.Properties, properties.Definition.RootDirectory);
		foreach (string formTemplate in formTemplates)
		{
			if (formsService.FormTemplates.ItemFromFile(formTemplate) != null)
			{
				formsService.FormTemplates.UnregisterFormTemplate(formTemplate, properties.Definition);
			}
		}
	}
}

 

这些方法的代码 pretty much 直截了当。在 FeatureInstalled 中,我们首先注册自定义任务表单,然后创建列表。在 FeatureUninstalling 中,我们只取消注册表单。我们不删除列表。此外,“Feature1EventReceiver”类中有一个名为“site_url”的属性。您必须将其设置为您要部署工作流的站点(父站点)。例如,对于 https;///legal/,site_url 将只是 https:///。

工作流对项目结构进行的修改

为了知道要创建的下一个任务、存储合并的注释以及运行此解决方案所需的其他数据,工作流会验证并(如果需要)在项目中创建各种隐藏字段。让我们快速回顾一下这些字段。

 

private Hashtable hdn_flds = new Hashtable()
{
	{"hCAT", "singleline_text"},
	{"hNXT", "singleline_text"},
	{"hPVT", "singleline_text"},
	{"hWFUPD", "singleline_text"},
	{"hStatus", "choice|In Progress|Completed|Canceled|Rejected"},
	{"hWFXMLContext", "multiline_text"},
	{"hWFXMLGlobal", "multiline_text"},
	{"hWFXMLModules", "multiline_text"},
	{"hXMLTskHist", "multiline_text"},
	{"hParallelTasks", "multiline_text"},
	{"hConsolidatedComments", "multiline_text"}
};

hCAT - 存储当前任务的 id (TaskType)。

hNXT - 存储下一个任务的 id。

hPVT - 存储上一个任务的 id(未在代码中使用)。

hWFUPD - 当项目被工作流更新时,此字段设置为 1。因此,如果列表也有侦听更新的事件接收器,它可以检查更新是否来自工作流。

hStatus - 用于检查工作流应继续还是完成的字段。

hWFXMLContext, hWFXMLGlobal, hWFXMLModules - XML 文件的内容被切分成 3 部分。每部分存储在其中一个字段中。

hXMLTskHist - 将存储任务的 GUID。尚未填充或使用。

hParallelTasks - 将存储多任务的 GUID 和结果。

hConsolidatedComments - 存储合并的注释。

使用代码

您可以创建自己的解决方案,然后从附加项目中添加文件或函数。或者,您可以简单地使用附加的解决方案。构建后,您可以使用 VS2010 或 SharePoint PowerShell 命令来部署解决方案。如果您使用的是附加解决方案,则必须设置要部署工作流的站点。

附加解决方案存在一些非关键问题,我希望尽快修复。这些问题不应妨碍工作流运行。

历史

版本 1.0。

 

© . All rights reserved.