1 SharePoint Designer工作流的迁移
SharePoint Designer设计工作流的功能是提供给最终用户使用的,就如同用SPD定制站点页面一样,并没有提供专门的流程迁移工具。
最终用户可以直接在生产环境中设计流程。对开发人员来说,如果在开发环境中设计好了一个比较复杂的流程,需要迁移到生产环境,必须手工操作,可以参考如下步骤。
Step1 确保生产环境已经建好了相应的列表,并确保列表的配置跟开发环境完全一致,包括列表名称、列表的所有栏。
Step2 用SharePoint Designer打开生产环境的站点,新建一个与开发环境名称一样的流程,绑定到同名称的列表,直接保存。
Step3 SharePoint Designer打开开发环境的站点,将工作流的所有文件复制到生产环境。
Step4 打开所有工作流的文件,将其中所有的列表项ID替换成生产环境相应的列表项ID。
2 SharePoint Designer工作流的扩展
SharePoint Designer提供了常用的很多条件和活动,但是这些条件和活动并不能满足所有需要。本节就来讲述SPD动作和条件的扩展。
2.1 动作的扩展
SPD工作流的动作是一个普通的WF活动类。WF活动类开发好以后,部署到GAC中,然后修改SPD工作流的配置文件,注册新开发的活动。
SPD工作流配置文件路径为C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\2052\Workflow,2052表示中文语言,英文语言的配置文件路径为C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\1033\ Workflow,在这个路径下,有个WSS.ACTIONS文件,这是一个XML文件,这个文件里面配置了所有系统自带的动作和条件。
WSS.ACTIONS在Actions节点下声明了所有的动作。SDP工作流是基于规则来定义的。在ACTIONS配置文件中,将动作类的属性与各种规则设计器关联。以下是"从用户处收集数据"动作的声明。
<Action Name="从用户处收集数据"
ClassName="Microsoft.SharePoint.WorkflowActions.CollectDataTask"
Assembly="Microsoft.SharePoint.WorkflowActions, Version=12.0.0.0, Culture=
neutral, PublicKeyToken=71e9bce111e9429c"
AppliesTo="all"
CreatesTask="true"
Category="任务操作">
<RuleDesigner Sentence="从 %2 处收集 %1 (输出到 %3)">
<FieldBind Field="Title,ContentTypeId" DesignerType="Survey" Text=
"数据" Id="1"/>
<FieldBind Field="AssignedTo" DesignerType="SinglePerson" Text="此用户" Id=
"2"/>
<FieldBind Field="TaskId" DesignerType="ParameterNames" Text="collect" Id="3"/>
</RuleDesigner>
<Parameters>
<Parameter Name="__Context" Type="Microsoft.SharePoint.WorkflowActions.
WorkflowContext, Microsoft.SharePoint.WorkflowActions"
Direction="In" />
<Parameter Name="ContentTypeId" Type="System.String, mscorlib" Direction=
"In" />
<Parameter Name="AssignedTo" Type="System.String, mscorlib" Direction=
"In" />
<Parameter Name="Title" Type="System.String, mscorlib" Direction="In" />
<Parameter Name="TaskId" Type="System.Int32, mscorlib" Direction="Out" />
</Parameters>
</Action>
以下是对配置文件中主要元素的说明。
l ClassName和Assembly分别是动作类的全名和所在的程序集。
l AppliesTo指定动作是应用到文档库还是列表,允许的值为list和all。
l CreatesTask指定是否需要创建任务项。
l Parameters节点中声明了WF活动类需要跟上下文数据进行绑定或进行设置的属性。
在Parameter节点中,Name为WF活动类的属性名,Type为属性的类型,Direction声明属性是需要赋值的还是对外提供值,有3个可选值:In、Out、Optional,指定In时必须给属性赋值,指定Out时必须将属性跟某个工作流变量进行绑定,Optional表示可选项,指定Optional时可以不对属性进行操作。以下为3个特殊的Parameter。
l __Context:当Action类需要访问工作流数据时,必须声明此属性,类型是Microsoft.SharePoint.WorkflowActions.WorkflowContext。
l __ListId:当需要访问工作流关联的列表时,需要指定此属性。
l __ItemID:当需要访问工作流关联的列表项时,需要指定此属性。
以上的3个属性定义在Action类中,然后声明在配置文件中,工作流运行时引擎会自动为其赋值。
RuleDesigner节点声明了对Action类属性的设计规则。Sentence指定了设计时显示的提示信息,其中的占位符号%与RuleDesigner中的FieldBind的ID属性配置,FieldBind定义了对某个属性的设计规则。Field为属性的名字,Text为%占位符处显示的文本,ID跟占位符%前的数字对应。DesignerType指定了对属性应用的设计器,系统支持如下的设计器。
l Survey:生成收集数据的表单,表单对应的内容类型ID和标题需要分别映射到两个属性中。
l SinglePerson:单个用户或用户组。
l Person:多个用户或用户组。
l ParameterNames:工作流变量。
l StringBuilder:生成可以跟当前工作流数据混合的字符串。
l Operator:选项,用Option指定可选值。如:
<FieldBind Field="TaskMode" DesignerType="Operator" Text="此模式" Id="3">
<Option Name="RequireOne" Value="RequireOne"/>
<Option Name="RequireAll" Value="RequireAll"/>
</FieldBind>
l fieldNames:列表栏,采用此设计器将工作流关联列表的某个栏和Action类的属性绑定。
l E-mail:邮件设计器,设计邮件的标题、内容和收件人等,需要将收件人、抄送人、标题、内容4个属性分别绑定,如:
<RuleDesigner Sentence="电子邮件 %1">
<FieldBind Field="To,CC,Subject,Body" Text="此电子邮件" DesignerType=
"Email" Id="1"/>
</RuleDesigner>
<Parameters>
<Parameter Name="Body" Type="System.String, mscorlib" Direction="Optional" />
<Parameter Name="To" Type="System.Collections.ArrayList,
mscorlib" Direction="In" />
<Parameter Name="CC" Type="System.Collections.ArrayList, mscorlib"
Direction="Optional" />
<Parameter Name="Subject" Type="System.String, mscorlib" Direction="In" />
</Parameters>
2.2 动作扩展示例——自定义E-mail活动
系统默认的发送邮件动作只能发送简单的文本,很多时候,用户希望审批任务的提醒邮件中带一个链接,可以直接链接到任务操作页面。本节我开发一个可以发送任务链接的E-mail活动,这个E-mail活动具有系统发送邮件动作的所有功能,可以动态绑定收件人、指定主题等,如图1所示。唯一跟系统默认功能的区别就是:在发送出去的邮件内容中带有任务操作链接。
Step1 新建一个工作流活动项目,如图2所示。将默认产生的Activity1类改名为"MailWithTaskLinkActivity"。
图2 新建工作流活动项目
Step2 添加Microsoft.SharePoint.dll和microsoft.sharepoint.WorkflowActions.dll两个程序集的引用。这两个程序集位于服务器上的C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\ISAPI目录中。
Step3 切换到代码视图,将MailWithTaskLinkActivity的基类由SequenceActivity改为Activity。
复核活动可以从SequenceActivity类继承,但是MailWithTaskLinkActivity并不需要使用子活动(也就是不是一个复核活动),所有应该从基本的Activity类继承。
Step4 添加对SharePoint API主要名称空间的引用,代码如下所示。
using Microsoft.SharePoint;
using Microsoft.SharePoint.Utilities;
using Microsoft.SharePoint.Workflow;
using Microsoft.SharePoint.WorkflowActions;
namespace CodeArt.SharePoint.WorkflowActions
{
public class MailWithTaskLinkActivity : Activity
{
public MailWithTaskLinkActivity()
{
}
}
}
Step5 希望MailWithTaskLinkActivity部署成SharePoint Designer的动作之后,依然具有系统自带的发送邮件活动的功能—可以动态绑定到收件人、指定邮件主题等,所以需要给MailWithTaskLinkActivity添加相应的属性。添加一个__Context属性,代码如下所示。
public static DependencyProperty __ContextProperty =
DependencyProperty.Register("__Context", typeof(WorkflowContext),
typeof(SendMail));
[Browsable(true), ValidationOption(ValidationOption.Required),
DesignerSerializationVisibility(DesignerSerializationVisibility.
Visible), Description("Context")]
public WorkflowContext __Context
{
get
{
return (WorkflowContext)base.GetValue(__ContextProperty);
}
set
{
base.SetValue(__ContextProperty, value);
}
}
__Context属性用来绑定到工作流的上下文,以便获取工作流运行环境的网站集、网站等信息。这个属性的名称是不能变的,后面的步骤会讲述如何在配置文件中配置这个属性。添加一个__ListId属性,代码如下所示。
public static DependencyProperty __ListIdProperty = DependencyProperty.Register(
"__ListId", typeof(string), typeof(MailWithTaskLinkActivity));
[ValidationOption(ValidationOption.Required)]
public string __ListId
{
get
{
return (string)base.GetValue(__ListIdProperty);
}
set
{
base.SetValue(__ListIdProperty, value);
}
}
__ListId属性用来绑定到工作流的关联的列表ID。添加一个__ListItem属性,代码如下所示。
public static DependencyProperty __ListItemProperty = DependencyProperty.Register(
"__ListItem", typeof(int), typeof(MailWithTaskLinkActivity));
[ValidationOption(ValidationOption.Required)]
public int __ListItem
{
get
{
return (int)base.GetValue(__ListItemProperty);
}
set
{
base.SetValue(__ListItemProperty, value);
}
}
__ListItem属性用来绑定到工作流的关联的列表项ID。添加一个To属性,代码如下所示。
public static DependencyProperty ToProperty = DependencyProperty.Register("To",
typeof(ArrayList), typeof(MailWithTaskLinkActivity));
[ValidationOption(ValidationOption.Required)]
public ArrayList To
{
get
{
return (ArrayList)base.GetValue(ToProperty);
}
set
{
base.SetValue(ToProperty, value);
}
}
To属性是一个ArrayList类型的集合,在SharePoint Designer进行设计的时候可以直接绑定到用户。同样,添加邮件的抄送(CC)、暗送(BCC)、主题(Subject)、内容(Body)4个属性代码如下所示。
public static DependencyProperty CCProperty = DependencyProperty.Register("CC",
typeof(ArrayList), typeof(MailWithTaskLinkActivity));
//抄送
[ValidationOption(ValidationOption.Optional)]
public ArrayList CC
{
get
{
return (ArrayList)base.GetValue(CCProperty);
}
set
{
base.SetValue(CCProperty, value);
}
}
public static DependencyProperty BCCProperty = DependencyProperty.Register(
"BCC", typeof(ArrayList), typeof(MailWithTaskLinkActivity));
//暗送
[ValidationOption(ValidationOption.Optional)]
public ArrayList BCC
{
get
{
return (ArrayList)base.GetValue(BCCProperty);
}
set
{
base.SetValue(BCCProperty, value);
}
}
public static DependencyProperty SubjectProperty = DependencyProperty.
Register("Subject", typeof(String), typeof(MailWithTaskLinkActivity));
//主题
[ValidationOption(ValidationOption.Required)]
public string Subject
{
get
{
return (string)base.GetValue(SubjectProperty);
}
set
{
base.SetValue(SubjectProperty, value);
}
}
public static DependencyProperty BodyProperty = DependencyProperty.Register(
"Body", typeof(String), typeof(MailWithTaskLinkActivity));
//邮件内容
[ValidationOption(ValidationOption.Optional)]
public string Body
{
get
{
return (string)base.GetValue(BodyProperty);
}
set
{
base.SetValue(BodyProperty, value);
}
}
Step6 上一步把MailWithTaskLinkActivity需要的所有属性都添加好了,下面来添加发送邮件的处理代码。重载Execute方法,代码如下所示。
protected override ActivityExecutionStatus Execute(
ActivityExecutionContext provider)
{
//获取到工作流服务
ISharePointService service = (ISharePointService)provider.GetService(
typeof(ISharePointService));
if (service == null)
{
throw new InvalidOperationException();
}
try
{
//获取到列表
SPList list = __Context.Web.Lists[new Guid(__ListId)];
//获取到列表项
SPListItem item = list.GetItemById(Convert.ToInt32(__ListItem));
//计算任务查看URL
string url = this.__Context.Web.Url +
"_layouts/codeArt/SPTaskRedirect.aspx?ListId=" + item.ParentList.ID +
"&ItemId=" + item.ID;
//发送邮件参数
StringDictionary headers = new StringDictionary();
headers["to"] = this.ParseSendTo(this.To);
headers["subject"] = this.Subject;
if (null != this.CC)
{
headers["cc"] = this.ParseSendTo(this.CC);
}
if (null != this.BCC)
{
headers["bcc"] = this.ParseSendTo(this.BCC);
}
string body = null;
if (null != this.Body)
{
Activity parent = provider.Activity;
while (parent.Parent != null)
{
parent = parent.Parent;
}
//处理邮件内容中的属性绑定,Helper是系统自带的类
body = Helper.ProcessStringField(this.Body, parent, this.__Context);
}
body += "<br/><a href='" + url + "'><b>点击此处查看或处理任务</b></a>";
//发送邮件
service.SendEmail(base.WorkflowInstanceId, false, headers, body);
return ActivityExecutionStatus.Closed;
}
catch (Exception ex)
{
//将异常信息记录到日志列表
service.LogToHistoryList(base.WorkflowInstanceId,
SPWorkflowHistoryEventType.WorkflowError,
__Context.Web.CurrentUser.ID, TimeSpan.MinValue,
"MailWithTaskLinkActivity Error",
ex.Message + ex.StackTrace, "");
}
return ActivityExecutionStatus.Faulting;
}
发送邮件活动一般放置在从用户处收集数据活动之前,那么在发送邮件活动执行的时候审批任务是没有创建的,这时候无法获取任务的路径或ID,所以我们采用一个中转页面(SPTaskRedirect.aspx),将列表项的ID传给这个页面,SPTaskRedirect.aspx负责转向任务操作页面。ParseSendTo方法用来将存放邮件地址和账号的ArraryList转换成合法的收件人格式,这个函数反编译自系统自带的MailActivity。
Step7 编写SPTaskRedirect.aspx页面代码,并将其复制到12\TEMPLATE\LAYOUTS\CodeArt目录下。
<%@ Page Language="C#" %>
<%@ Assembly Name="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral,
PublicKeyToken=71e9bce111e9429c" %>
<%@ Import Namespace="Microsoft.SharePoint" %>
<script runat="server">
void Page_Load(object sender , EventArgs e)
{
//获取到列表
SPList list = SPContext.Current.Web.Lists[ new Guid(Request.QueryString
["ListId"]) ];
//获取到列表项
SPListItem item = list.GetItemById(Request.QueryString["ItemID"])
//当前用户ID
int useId = SPContext.Current.Web.CurrentUser.ID;
Microsoft.SharePoint.Workflow.SPWorkflowTask currentTask = null;
//查找当前用户的任务
foreach (Microsoft.SharePoint.Workflow.SPWorkflowTask task in item.Tasks)
{
if ("" + task["PercentComplete"] == "1")
continue;
string assignedTo = "" + task["AssignedTo"];
if (assignedTo == "")
continue;
SPFieldUserValue user = new SPFieldUserValue(SPContext.Current.Web,
assignedTo);
if (useId == user.LookupId)
{
currentTask = task;
break;
}
}
if (currentTask == null)
{
Response.Write("任务不存在或已被删除。");
return;
}
//转向任务编辑页面
Response.Redirect(currentTask.ContentType.EditFormUrl + "?List=" +
currentTask.ParentList.ID + "&ID=" + currentTask.ID);
}
</script>
Step8 将项目进行签名,利用reflector找到程序集的全名,代码如下所示。
CodeArt.SharePoint.WorkflowActions, Version=1.0.0.0, Culture=neutral, PublicKeyToken=8d0e2047bbdccb4d
Step9 创建一个XML文件,改名为CodeArt.Actions,并将其复制到12\TEMPLATE\2052\ Workflow目录下。
<?xml version="1.0" encoding="utf-8" ?>
<WorkflowInfo>
<Actions Sequential="then" Parallel="and">
<Action Name="CodeArt_发送电子邮件"
ClassName="CodeArt.SharePoint.WorkflowActions.MailWithTaskLinkActivity"
Assembly="CodeArt.SharePoint.WorkflowActions, Version=1.0.0.0, Culture=
neutral, PublicKeyToken=8d0e2047bbdccb4d"
Category="SmartForm"
AppliesTo="all"
UsesCurrentItem="true"
>
<RuleDesigner Sentence="电子邮件 %1">
<FieldBind Field="To,CC,Subject,Body" Text="此电子邮件" DesignerType=
"Email" Id="1"/>
</RuleDesigner>
<Parameters>
<Parameter Name="__Context" Type="Microsoft.SharePoint.WorkflowActions.
WorkflowContext, Microsoft.SharePoint.WorkflowActions" Direction=
"In"/>
<Parameter Name="__ListId" Type="System.String, mscorlib" Direction=
"In" />
<Parameter Name="__ListItem" Type="System.Int32, mscorlib" Direction=
"In" />
<Parameter Name="Body" Type="System.String, mscorlib" Direction=
"Optional" />
<Parameter Name="To" Type="System.Collections.ArrayList, mscorlib"
Direction="In" />
<Parameter Name="CC" Type="System.Collections.ArrayList, mscorlib"
Direction="Optional" />
<Parameter Name="Subject" Type="System.String, mscorlib" Direction=
"In" />
</Parameters>
</Action>
</Actions>
</WorkflowInfo>
RuleDesigner节点配置了采用E-mail设计器设计MailWithTaskLinkActivity 类的To、CC、Subject、Body 4个属性。在Parameters节点中,指定了相应属性的输入/输出类型。
Step10 将程序集(CodeArt.SharePoint.WorkflowActions.dll)部署到GAC中,配置文件(CodeArt.Actions)添加到12\TEMPLATE\2052\Workflow目录中后,还需要修改应用程序的web.config文件,在authorizedType节点下添加如下配置:
<authorizedType Assembly="CodeArt.SharePoint.WorkflowActions,Version=1.0.0.0,
Culture=neutral, PublicKeyToken=8d0e2047bbdccb4d" Namespace=
"CodeArt.SharePoint.WorkflowActions" TypeName="*" Authorized="True" />
配置完成后,在SharePoint Designer中设计流程的时候,就可以选择这个自定义的发送邮件动作了,如图3所示。
图3 选择自己开发的发送邮件动作
2.3 条件的扩展
SPD的条件是一个普通的类静态函数,系统默认的条件同样配置在WSS.ACTIONS文件中。以下是"标题域包含关键字"条件的配置。
<Condition Name="标题域包含关键字"
FunctionName="WordsInTitle"
ClassName="Microsoft.SharePoint.WorkflowActions.Helper"
Assembly="Microsoft.SharePoint.WorkflowActions, Version=12.0.0.0,
Culture=neutral, PublicKeyToken=71e9bce111e9429c"
AppliesTo ="list"
UsesCurrentItem="true">
<RuleDesigner Sentence="标题域包含 %1">
<FieldBind Id="1" Field="_1_" Text="关键字"/>
</RuleDesigner>
<Parameters>
<Parameter Name="_1_" Type="System.String, mscorlib" Direction="In" />
</Parameters>
</Condition>
与动作类的配置很类似,ClassName和Assembly分别是条件方法所在的类的全名和类所在的程序集,FunctionName是静态函数的函数名。实现条件的静态函数必须采用如下签名。
public static bool SomeFunction(WorkflowContext context, string listId,
int listItem, string var1,string var2)
{
//…
}
其中context、listId和listItem分别是工作流上下文数据、当前列表的ID和当前列表项的ID,这3个参数由工作流引擎来赋值。第4个以后的参数是额外的特殊参数,需要声明到配置文件的Parameters节点中。
Parameter节点的Name属性表示参数的顺序号,因为前3个参数是系统必需的,所以从第4个参数开始,顺序号为1,并且要用_1_的形式。
RuleDesigner中的FieldBind定义了参数的设计器,通过Field跟Parameter中声明的参数对应。FieldBind支持的其他属性(如DesignerType)和动作的配置完全一样。
2.4 条件扩展示例——自定义E-mail活动
采用SharePoint Designer设计审批流程的时候,很多情况下需要按照提交人的将审批任务分配给不同的审批人,比如,普通员工的请假需要部门经理审批,部门经理的请假需要公司总监来审批。本节我们就来开发这样一个条件,可以用于判断流程的启动用户是否属于某个网站组。
Step1 创建条件方法。新建一个类WorkflowConditions,添加一个静态函数CompareOriginatorGroup,完整的代码如下所示。
namespace CodeArt.SharePoint.WorkflowActions
{
///<summary>
///条件类
///</summary>
public class WorkflowConditions
{
///<summary>
///比较流程发起人所属组
///</summary>
///<param name="context">工作流上下文</param>
///<param name="listId">关联列表ID</param>
///<param name="itemId">关联列表项ID</param>
///<param name="groupName">组名</param>
///<returns></returns>
public static bool CompareOriginatorGroup(WorkflowContext context, string listId,
int itemId,
string groupName )
{
//SPList list = context.Web.Lists[new Guid(listId)]; //获取关联的列表
//获取关联的列表项
//SPListItem item = list.GetItemById(Convert.ToInt32(itemId));
//初始化工作的参数
SPWorkflowActivationProperties initProp = new SPWorkflowActivationProperties();
context.Initialize(initProp);
SPGroup group = context.Web.SiteGroups[groupName];
if (group == null)
throw new Exception(String.Format("组[{0}]不存在或被删除", groupName));
string userLoginName = initProp.Originator.ToLower(); //流程启动人
//判断流程启动人是否属于组
foreach (SPUser user in group.Users)
{
if (user.LoginName.ToLower() == userLoginName) //排除系统管理员
return true;
}
return false;
}
}
}
Step2 编写配置文件。在CodeArt.ACTIONS文件中添加如下配置。
<Conditions And="且" Or="或" Not="非" When="如果" Else="否则">
<Condition Name="CodeArt_比较提交人所属组"
FunctionName="CompareOriginatorGroup"
ClassName="CodeArt.SharePoint.WorkflowActions. WorkflowConditions"
Assembly="CodeArt.SharePoint.WorkflowActions, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=8d0e2047bbdccb4d"
AppliesTo="all"
UsesCurrentItem="true">
<RuleDesigner Sentence="若提交人属于 %1 网站组">
<FieldBind Id="1" Field="_1_" DesignerType="TextArea" Text="此"/>
</RuleDesigner>
<Parameters>
<Parameter Name="_1_" Type="System.String, mscorlib" Direction="In" />
</Parameters>
</Condition>
</Conditions>
以上配置制定了条件名称,以及条件对象的方法。指定了采用TextArea(文本框)设计第一个额外参数(groupName)。将配置文件部署到12\TEMPLATE\2052\Workflow中,程序集部署到GAC后,在SharePoint Designer中就可以使用这个自定义的"比较提交人所属组"条件了,如图4所示。
图4 自定义的条件