没有持久化的WF 能称为一个完整的WF吗,答案是否定的;如果WF不能持久化,那么流程就需要一次就执行完毕,所有的操作就要一次走下去,可现实中的工作流是这样吗,答案同样是否定的。一个投票流程需要多个评委投票之后确定一个票数最高的组长才可以进入下一个流程,如果不能持久化,那么在此流程中每个评委使用的数据(在流程中需要处理的公共数据,可能为简单类型,也可能为复杂类型)则完全不同,甚至说每个人都是不一样的,我们要在每一个步骤记录下数据,在流程结束时候进行汇总分析,很明显流程没有根据原始的思路一步一步走下去,所以我们需要在所有投票完成之前都保持一个挂起状态,只有等所有的投票完成,流程才结束,此时我们想到了持久化。
持久化分析之BookMark:
在持久化之前,我提到了需要挂起状态,为什么要挂起,挂起是一个怎样的操作。在WF中一个挂起很简单,就是一个BookMark(书签),顾名思义,先存放一个书签,暂时结束,如果下次需要根据书签找到上次停止的地方,继续进行操作。
持久化分析之InstanceStore:
这个类为WF提供的,用于进行持久化的操作,WF中已经包含了一个实现它的SqlWorkflowInstanceStore,用于保存到数据库中,当然本文还会介绍另外一种是使用XML的方式。此类保存到的数据库有些要求,使用的sql脚本微软已经提供,下文将会一一说明。
持久化分析之PersistenceParticipant:
持久性参与者派生自 PersistenceParticipant 类或 PersistenceIOParticipant 类(PersistenceParticipant 类的派生类),实现抽象方法,然后添加类的实例作为工作流实例扩展。 WorkflowApplication 和 WorkflowServiceHost 在保留实例时查找此类扩展,并在适当的时间调用适当的方法。 根据MSDN的解释可以看出来此类是我们自定义持久化需要继承的类,那么在进行XML方式持久化的时候我们就会用到,Sql方式的时候无需自定义具体信息下一篇介绍。
到此,我们的关键技术分析已经完成,一个BookMark类和一个InstanceStore类以及自定义需要的PersistenceParticipant三个类来共同完成我们的持久化。
持久化实现之业务分析:
第一步,我们先来分析一个流程的,假设有若干个评委来投票,需要所有的评委投票完成之后流程才能进入到下一步,这里的投票可以认为是评委需要登录到系统进行投票,并且投票的先后顺序以及时间都不确定,
根据分析设计工作流如下:
一个包含了DoWhile和Foreach的流程,DoWhile控制是否进行多次审批,ForEach用于遍历所有的评委,同时创建多个书签,注意此处使用的Parallel循环,让创建书签的过程为同步,如果使用ForEach则只会创建一个书签;
在If中创建书签使流程同步,此时就是需要持久化的过程,同时每激活一个或多个也要再次持久化,直到流程结束。
持久化实现之BookMark类:
此类代码较简单直接贴出来
public sealed class WaitForVote<T>: NativeActivity<T> { public InArgument<string> UserId { get; set; } public InOutArgument<RequestForExpert> InOutRequestForExpert { get; set; } protected override void Execute(NativeActivityContext context) { string name = this.UserId.Get(context).ToString(); context.CreateBookmark(name, new BookmarkCallback(OnReadComplete)); } void OnReadComplete(NativeActivityContext context, Bookmark bookmark, object state) { string[] input = (string[])state; context.SetValue(this.Result, input); RequestForExpert requestForExpert = context.GetValue(InOutRequestForExpert); requestForExpert.ExpertList.Find((ExpertInfo expert) => { return expert.UserId == input[0]; }).IsConfirmed=true; context.SetValue(InOutRequestForExpert ,requestForExpert); } protected override bool CanInduceIdle { get { return true; } }
此类继承了NativeActivity,有关该类的具体说明可以查看MSDN。
该类主要为以下几个部分:
1.一个InArguement的参数,用于接收书签的名称
2.重写Execute方法,用于创建书签,通过传递的参数,作为书签的名称,同时指定激活回调方法
3.给BookMark指定激活回调方法,可以在激活书签的时候传递值,在此方法中可以处理值,并传递到页面去(InOutRequestForExpert就是页面传递到书签的值,在此方法中进行修改再返回到页面去)
4.设置属性CanInduceIdle属性为true,这样才可以进行持久化。
持久化实现之宿主类:
interface IExpertHost { /// <summary> /// 创建并运行WorkFlow实例 /// </summary> /// <param name="rfe"></param> /// <returns></returns> WorkflowApplication CreateAndRun(RequestForExpert rfe); /// <summary> /// 加载WorkFlow实例 /// </summary> /// <param name="instanceId"></param> /// <returns></returns> WorkflowApplication LoadInstance(Guid instanceId); /// <summary> /// 当前项目是否可以投票 /// </summary> /// <param name="instanceId">实例编号</param> /// <param name="userId">评委编号</param> /// <returns></returns> bool CanVoteToInstance(Guid instanceId, string userId); /// <summary> /// 投票 /// </summary> /// <param name="instanceId">WorkFlow实例编号</param> /// <param name="projectId">项目编号</param> /// <param name="userId">用户编号</param> /// <param name="selUserId">推荐评委编号</param> void Vote(Guid instanceId, string userId, string selUserId); }
首先来看CreateAndRun的实现写法:
这里定义了一个枚举类型,来表明持久化使用Sql方式还是XML的方式
public enum StoreType { Sql, Xml }
public WorkflowApplication CreateAndRun(RequestForExpert rfe) { IDictionary<string, object> inputs = new Dictionary<string, object>(); inputs.Add("InRequestForExpert", rfe); // 实例化工作流对象 Activity wf = new ExpertWorkFlow(); WorkflowApplication instance = new WorkflowApplication(wf, inputs); instance.PersistableIdle += OnIdleAndPersistable; instance.Completed += OnWorkflowCompleted; instance.Idle += OnIdle; //持久化设置 GetSqlInstanceStore(instance, StoreType.Sql); instance.Run(); return instance; } /// <summary> /// 设置持久化方式 /// </summary> /// <param name="instance"></param> private void GetSqlInstanceStore(WorkflowApplication instance, StoreType storeType) { switch (storeType) { case StoreType.Sql: SqlWorkflowInstanceStore sqlInstanceStore = new SqlWorkflowInstanceStore(connectionString); InstanceView view = sqlInstanceStore.Execute(sqlInstanceStore.CreateInstanceHandle(), new CreateWorkflowOwnerCommand(), TimeSpan.FromSeconds(30)); //设置默认实例的所有者 sqlInstanceStore.DefaultInstanceOwner = view.InstanceOwner; instance.InstanceStore = sqlInstanceStore; break; case StoreType.Xml: XmlWorkflowInstanceStore store = new XmlWorkflowInstanceStore(instance.Id); instance.InstanceStore = store; break; default: break; } }
// executed when instance goes idle public void OnIdle(WorkflowApplicationIdleEventArgs e) { } public PersistableIdleAction OnIdleAndPersistable(WorkflowApplicationIdleEventArgs e) { return PersistableIdleAction.Unload; } // executed when instance is persisted public void OnWorkflowCompleted(WorkflowApplicationCompletedEventArgs e) { }
在上述方法中创建了一个WorkFlowApplication工作流应用对象,同时绑定PersistableIdle ,此函数说明使用怎样的持久化方式,此处使用的为Unload即卸载并持久化,这样在Load的时候不会出现实例被锁定的错误。
设置持久化方式,此处使用的是Sql方式的存储,在存储的方法中主要是指定了要保存的数据库,此数据库可不是一般的数据库,需要特性的Sql脚本,别急此脚本已在你的电脑中,打开目录 "C:\Windows\Microsoft.NET\Framework\v4.0.30319\SQL\en"
创建一个数据库名字为WorkflowInstanceStore(可自定义),先执行第二个文件,再执行第一个文件。
下面再来看看其他的方法:
/// <summary> /// 加载工作流 /// </summary> /// <param name="instanceId"></param> /// <param name="userId"></param> /// <returns></returns> public WorkflowApplication LoadInstance(Guid instanceId) { WorkflowApplication instance = new WorkflowApplication(new ExpertWorkFlow()); instance.Completed += OnWorkflowCompleted; instance.PersistableIdle += OnIdleAndPersistable; instance.Idle += OnIdle; //持久化设置 GetSqlInstanceStore(instance, StoreType.Sql); instance.Load(instanceId); return instance; } /// <summary> /// 当前评委是否可以投票,判断其有没有投票 /// </summary> /// <param name="instanceId"></param> /// <param name="userId"></param> /// <returns></returns> public bool CanVoteToInstance(Guid instanceId, string userId) { WorkflowApplication instance = LoadInstance(instanceId); //除非存在当前的书签,则说明没有投票 foreach (BookmarkInfo item in instance.GetBookmarks()) { if (item.BookmarkName.Equals(userId)) { return true; } } return false; } /// <summary> /// 投票 /// </summary> /// <param name="instanceId">工作流实例Id</param> /// <param name="userId">用户Id</param> /// <param name="selUserId">推荐的用户Id</param> public void Vote(Guid instanceId, string userId, string selUserId) { WorkflowApplication instance = LoadInstance(instanceId); //除非存在当前的书签,则说明没有投票 foreach (BookmarkInfo item in instance.GetBookmarks()) { if (item.BookmarkName.Equals(userId)) { instance.ResumeBookmark(userId, new string[] { userId, selUserId }); } } instance.Unload(); }
一个用于加载工作流的方法,一个判断是否可以投票的方法(如果对应当前评委的书签还存在,则可以投票,注意这里书签的名称都为评委的Id,不过这个不重要,大家可以在自己的程序中自己决定使用什么,只是在激活
书签的时候需要使用一样的值,这样才可以找到书签),一个就是投票的方法(主要用于激活书签)。
持久化实现之WCF以及Web客户端:
到这里WorkFlow的工作已经完成了看看怎么调用,在这里我是通过WCF把WorkFlow和Web端结合起来,所以又添加了一个WCF的项目WFWCF(源码中都包含),添加WCF IExpertWCF
[ServiceContract] public interface IExpertWCF { /// <summary> /// 创建工作流 /// </summary> /// <param name="rfe"></param> [OperationContract] void CreateWorkFlow(RequestForExpert rfe); /// <summary> ///投票 /// </summary> /// <param name="instanceId"></param> /// <param name="userId"></param> /// <param name="selUserId"></param> [OperationContract] void Vote(Guid instanceId,string userId,string selUserId); } public class ExpertWCF : IExpertWCF { private string ConnectionString = ConfigurationManager.ConnectionStrings["ApplicationServices"].ConnectionString; /// <summary> /// 创建workFlow /// </summary> /// <param name="rfe"></param> public void CreateWorkFlow(RequestForExpert rfe) { ExpertProcessHost host = new ExpertProcessHost(); host.CreateAndRun(rfe); } /// <summary> /// 投票 /// </summary> /// <param name="instanceId"></param> /// <param name="userId"></param> /// <param name="selUserId"></param> public void Vote(Guid instanceId, string userId, string selUserId) { ExpertProcessHost host = new ExpertProcessHost(); host.Vote(instanceId, projectId, userId, selUserId); } }
方法都很简单,一个创建工作流的方法,一个投票的方法,实例化ExpertProcessHost 对象直接调用即可。
最后来看页面设计
首页,用于创建工作流
点击按钮会在数据库声称如下的一条数据(数据值不同),而Id列就是在投票页面要使用的InstanceId。
投票页面,输入InstanceId,点击投票即可投票,激活对应用户的书签(此处的用户为写固定,可以自行修改,同时InstanceId可以调试获得,或者从数据库的InstancesTable表中获得),当激活所有的书签之后流程将结束,同时数据库则会自动删除此条数据。至此,Sql方式的持久化就结束了,下一篇开始将介绍XML方式的持久化操作。
源码在下一篇中。