沒有持久化的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方式的持久化操作。
源碼在下一篇中。
