程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> WF從入門到精通(第十四章):基於狀態的工作流

WF從入門到精通(第十四章):基於狀態的工作流

編輯:關於.NET

學習完本章,你將掌握:

1.理解狀態機的概念以及它怎樣被模擬到工作流處理中的

2.創建基於狀態的工作流

3.運用初始(initial)和終止(terminal)狀態條件

4.使用代碼進行狀態的切換

在第四章“活動和工作流類型介紹”中,我闡述過你使用WF所能創建的工作流類型,在那裡我提到過基於狀態的工作流。基於狀態的工作流模型被認為是有限自動機(finite state machine)。基於狀態的工作流在工作流需要和外部事件進行許多交互的場合中大出風頭。在事件觸發並被工作流處理的時候,工作流能按要求進行狀態的切換。

WF為創建基於狀態的工作流提供了富余的開發體驗,你迄今為止在本書中看到的許多東西都適用於基於狀態的工作流。例如,當一個狀態切換過來的時候,假如你想的話,你能去執行幾個順序活動,進行條件判定(使用規則或者代碼),或者使用一個迭代活動結構來循環訪問一些數據點。唯一真正的區別是活動怎樣排隊執行。在順序或並行工作流中,它們以出現的順序進行排隊。但是在基於狀態的工作流中,活動以狀態切換進出來進行排隊。事件通常驅動這些切換過程,但是這條規則不是通用的。讓我們再看看狀態機的概念並把這些概念和你能使用的WF活動結合起來去構建你的工作流。

狀態機的概念

狀態機的目的是構建你業務流程中的離散點,切換通過事件來控制。例如,把你的洗衣機接通電源,然後關門並按下啟動按鈕。按下啟動按鈕時初始化了一個狀態機,它通過運行各種各樣的清潔周期來清洗你待洗的衣物直到這些周期全部完成。

狀態機有一個已知的起點和一個已知的終點。中間的狀態應能通過預期事件的觸發去進行控制,但機器總處於一個特定的狀態。有時事件把狀態機扔進無效的狀態中,這種情形和在你的應用程序中維持未處理的異常的情形來說並沒有什麼不同,整個過程不是忽然停止就是完全崩潰。無論哪種情況,切換到無效狀態都是要密切監視的,至少在數字電子系統(digital electronic systems)中是這樣。

總的來說,第4章涵蓋了涉及狀態機的基本概念。可看看“狀態活動”這一節快速復習一下。讓我們從怎樣設計活動轉到在基於狀態的工作流中怎樣使用活動去吧。

使用狀態活動

也許你不會太驚訝,在你的基於狀態的工作流中State活動構建了一個狀態。它是一個組合活動,但它局限於只接受特定類型的活動來作為它的子活動,它們是:EventDriven活動,StateInitialization活動,StateFinalization活動以及其它State活動。EventDriven活動等待(監聽)那些將導致切換到另一個狀態的事件,而在狀態被切換進來和切換出去的時候,StateInitialization和StateFinalization是保證能分別去執行相應處理的活動。對於能拖拽第二個State活動到一個已存在的State活動中去可能看起來有些古怪,但其意圖是提供一種在父狀態機中嵌入子狀態機的能力。

對於你的狀態能容納的那些有效活動的數目也有一個限制。只允許有唯一的一個StateInitialization和StateFinalization,你可以只有其中的一個,但每一個都不能超過一個。它們都不是必須的。

但是並沒有說你不能只有一個或者更多的子EventDriven和State活動。事實上,一般都能找到多個EventDriven活動,因為每一個事件可能會導致切換到一個不同的狀態。例如,一個“不批准(disapprove)”事件可能會切換到最終的狀態(結束狀態),而一個“批准(approve)”事件則可能切換到一個預定的狀態並要求進行更多的審批。至於State活動,假如你要創建嵌入的基於狀態的工作流的話,毫無疑問超過一個也應當是允許的。只有一個狀態(切換)的基於狀態的工作流構建成了一個簡單的順序工作流,因此在那種情況下你應當直接使用一個順序工作流。在任何情況下,使用State活動只需從工具箱中拖拽它的一個實例到工作流視圖設計器上,唯一的必要條件是工作流自身必須是基於狀態的工作流而不是順序工作流。然後確定你的狀態活動應容納些什麼子活動,並按需要把它們拖拽進去,牢記你只能插入四種類型的活動。

使用SetState活動

假如你回憶一下我在第四章中介紹過的狀態機示例的話,下圖14-1將看起來很眼熟。確實,它是一個(被簡化了的)自動售貨機狀態圖。我認為把這樣一個狀態圖制成一個真實的基於狀態的WF工作流並使用一個用戶界面來驅動它會是很有趣的一件事,考慮到我缺少藝術細胞,該用戶界面會被構成成一個簡陋的不含酒精飲料(“汽水”)的自動售貨機。

圖14-1飲料機的狀態圖

考慮到沒有用戶互動,該飲料售貨機應用程序的界面如圖14-2所示。一瓶飲料的價格是$1.25。當你投入硬幣的時候,左邊的飲料圖形按鈕都處於非激活狀態。但是,當你投入了足夠的金額後,這些飲料按鈕就能使用並且你可以做出選擇。這個簡化的模型不會處理如退款和更改之類的事情,但如果你願意的話,你可隨意修改該應用程序。

備注:為簡便起見,我並沒有使該示例應用程序國際化。它模擬的是只接受美國貨幣的自動售貨機。但是,請記住這裡的重點是工作流,而不是所使用的貨幣單位。

圖14-2飲料機處於初始狀態時的用戶界面

但是,你不能真正把硬幣投到一個Windows Forms應用程序中,因此我提供了5¢,10¢和25¢三個按鈕(注:符號¢代表美分)。很抱歉,只有這幾種硬幣。當你首次點擊其中一個硬幣按鈕的時候,一個新的基於狀態的工作流實例就被啟動了,執行該工作流的狀態圖如圖14-1所示。圖14-3為你展示了飲料機在投入了幾個硬幣後的情況。基於狀態的工作流隨時跟蹤接收到的硬幣並把金額總計反饋給應用程序,該應用程序在一個模擬的液晶二極管顯示屏上把它顯示出來。

圖14-3投入了硬幣的飲料機用戶界面

當投入了足夠的硬幣時,工作流就通知應用程序現在用戶可以選擇飲料了,如圖14-4所示。應用程序讓位於用戶界面左邊的各個飲料按鈕處於可用狀態(enable)。

圖14-4允許選擇飲料的飲料機用戶界面

當點擊了左邊的某個飲料按鈕後,即如圖14-4中顯示的變黑的按鈕,一個標簽(label)將呈現出來並顯示“Soda!”,這是我模擬一瓶客戶選中的飲料從機器中落出的一種方式。為重置整個過程,可點擊“Reset”按鈕。這不會影響到該工作流但是會重置用戶界面上的按鈕。圖14-2顯示了這種情形的用戶界面,你可再一次啟動所有的處理過程。

圖14-5選中了某瓶飲料後的飲料機用戶界面

已經為你創建了大量的應用程序代碼。假如你讀完該SodaMachine示例的代碼,你將發現我使用了CallExternalMethod活動(看看第8章中的“工作流數據傳送”)以及HandleExternalEvent活動(看看第10章“事件活動”)。有大量的工具來為你的工作流和你的應用程序之間進行交互。剩下的工作就是創建該工作流自身,下面就是具體的做法。

創建一個基於狀態的工作流

1.該SodaMachine應用程序再次為你提供了兩個版本:完整版本和非完整版本。你需要下載本章源代碼,打開SodaMachine文件夾中的解決方案。

2.當SodaMachine解決方案在Visual Studio中打開後,從Visual Studio的“生成”菜單中選擇“生成解決方案”。解決方案中的項目包含各種各樣的依賴,編譯該解決方案生成了那些關聯的項目所能引用的程序集。

3.在Visual Studio的解決方案資源管理器窗口中找到SodaFlow項目中的Workflow1.cs文件。然後在工作流視圖設計器中打開該工作流准備編輯。

備注:我已經創建了這個基本的工作流項目,因為該應用使用的CallExternalMethod和HandleExternalEvent活動的相關技術你在第8章和第10章中已經看過。重復這些必須的步驟來創建這些常規活動沒有任何必要,但是假如你從頭開始創建工作流項目的話,你需要去做這些工作。

4.該工作流現由唯一的一個State活動組成。選中該stateActivity1活動,把它重命名為“StartState”。

5.當創建工作流時,Visual Studio會為你自動添加一個原始的State活動。但它也把這個活動作為起始(開始)活動。當你在前一步驟中重命名該活動後,工作流就會丟失這個行為。為把這個活動重新設置為起始活動,需要在工作流視圖設計器的界面中點擊除了State活動之外的其它任何地方,以便激活整個工作流的屬性。在屬性面板中,你會看到一個InitialStateName屬性,把它從stateActivity1改為StartState。注意你可把這個值直接輸入到該屬性中也可從下拉列表框中選擇StartState。

6.我們現在要把剩下的State活動拖拽到工作流視圖設計器的界面上。和你記得的一樣,當SetState工作的時候,可方便地指定目標狀態。從Visual Studio的工具箱中拖拽一個State活動到設計器的界面上並把它放到StartState活動的旁邊。把它的名稱改為WaitCoinsState。

7.拖拽另一個State活動到工作流視圖設計器的界面上,把它的名稱改為WaitSelectionState。

8.拖拽最後的一個State活動到工作流視圖設計器的界面上,把它的名稱改為EndState。

9.就像你要重新指定開始狀態一樣,你也需要告知WF結束狀態是什麼。點擊任何State活動外面的工作流視圖設計器界面來激活該工作流的屬性。指定CompletedStateName屬性為EndState。然後Visual Studio會清空EndState的內容並改變它左上角的圖標。和前面一樣,你可直接輸入EndState也可從下拉列表框中選擇它。

10.放好了這些狀態活動,我們現在就來添加些細節。從StartState開始,從工具箱中拖拽一個StateInitialization活動並把它放到StartState中。

11.雙擊你剛剛添加的這個stateInitialization1活動,這將進入到順序工作流編輯器中。

12.從工具箱中拖拽一個Code活動到該StateInitialization活動中。指定它的ExecuteCode方法為ResetTotal。然後Visual Studio會為你添加對應的ResetTotal方法並為你切換到代碼編輯視圖下。此時我們不准備添加代碼,還是回到工作流視圖設計器上來吧。

13.接下來拖拽一個SetState活動到設計器界面上,把它放到你剛剛添加的Code活動的下面。

14.指定該SetState的TargetStateName屬性為WaitCoinsState。

15.回到工作流視圖設計器的狀態編輯器視圖中,點擊Workflow1左上角的超鏈接風格的按鈕。

狀態編輯器現在會顯示出StartState向WaitCoinsState的轉變。

16.StartState現在就完成了。下一步我們將轉到WaitCoinsState。首先拖拽一個EventDriven活動到設計器的界面上並把它放到WaitCoinsState中。在Visual Studio的屬性面板中把它的Name屬性修改為CoinInserted。

17.雙擊CoinInserted EventDriven活動使順序工作流編輯器呈現出來。

18.現在從工具箱中拖拽一個CoinInserted自定義活動到該EventDriven活動的表面上。注意,假如你還沒有編譯整個解決方案的話,該CoinInserted事件是不會在工具箱中顯示出來的。假如你漏過了第2步,你可能必須移除該EventDriven活動以便成功地進行編譯。

19.在工作流視圖設計器中選中該ExternalEventHandler coinInserted1活動,在屬性面板中點擊CoinValue屬性以便激活浏覽(...)按鈕,然後點擊該浏覽按鈕。這將打開“將‘CoinValue’綁定到活動的屬性”對話框。點擊“綁定到新成員”選項卡,在“新成員名稱”中輸入LastCoinDropped。此時選中的應該是“創建屬性”,假如不是的話選中它,以便你創建的是一個新的依賴屬性。然後點擊“確定”。

20.現在我們需要做一個判斷:用戶剛剛投入了足夠的金錢來使那些飲料按鈕處於可用(enable)狀態嗎?為此,拖拽一個IfElse活動到工作流視圖設計器界面上,把它放到CoinInserted EventDriven活動中,它的位置在coinInserted1的下面。

21.選中ifElseActivity1的左邊分支,以便在屬性面板中顯示它的屬性。對於它的Conditon屬性,選擇“代碼條件”。然後展開Condition節點,然後在Condition子屬性中輸入TestTotal。在Visual Studio添加一個新的方法並為你切換到代碼編輯視圖後,重新返回到工作流視圖設計器上來。

22.TestTotal將檢測你最終投入到飲料機中的金額總計。(我們將在添加代碼前完成該工作流在工作流視圖設計器中的設計工作,因為有一些我們需要的屬性還沒有創建。)假如投入了足夠的金錢,我們就需要轉換到WaitSelectionState。因此,拖拽一個SetState到該IfElse活動(ifElseBranchActivity1)的左邊分支上,指定它的TargetStateName為WaitSelectionState。

23.假如TestTotal判定了沒有足夠的金額來買飲料,該工作流需要傳達當前投入到飲料機中的錢的總計。為此,從工具箱中拖拽一個UpdateTotal並把它放到該IfElse活動的右邊分支中。UpdateTotal是我為本任務創建的一個自定義的CallExternalMethod活動。

24.UpdateTotal需要一個要去通信的總計值,因此選中它的total屬性並點擊浏覽(...)按鈕,這將再次打開一個綁定對話框。當綁定對話框打開後,選擇“綁定到新成員”選項卡並在“新成員名稱”中輸入Total並確認選中的是“創建屬性”選項。然後點擊“確定”。

25.點擊左上角的超文本風格的Workflow1按鈕回到狀態設計器視圖。拖拽一個StateFinalization到工作流視圖設計器的界面上,把它放到WaitCoinsState中。

26.雙擊你剛剛添加的stateFinalizationActivity1活動重新激活順序設計器視圖。

27。從工具箱中拖拽一個ReadyToDispense並把它放到stateFinalizationActivity1中。ReadyToDispense也是一個自定義的CallExternalMethod活動。

28.你剛剛添加的ReadyToDispense1活動將把最終的總計值返回給主應用程序。為做這些,它需要訪問你在第14步中添加的Total屬性。看看readyToDispense1的屬性,點擊finalTotal屬性,然後點擊在finalTotal中激活的浏覽(...)按鈕。點擊浏覽按鈕打開綁定對話框,但是這次是“綁定到現有成員”。從列表中選擇Total屬性然後點擊“確定”。

29.點擊超文本風格的Workflow1按鈕回到狀態設計器視圖上來。這裡,從工具箱中選擇EventDriven活動並把它放到設計器界面上的WaitSelectionState活動中。把它命名為ItemSelected。

30.雙擊ItemSelected EventDriven活動進入順序設計器視圖。

31.拖拽一個自定義ExternalEventHandler的活動ItemSelected,把它放進ItemSelected EventDriven活動中。

32.用戶挑選了飲料後,主應用程序觸發該ItemSelected事件。當該事件發生的時候,我們需要切換到EndState。為此,我們需要添加SetState活動。因此從工具箱中拖拽一個SetState並把它放到ItemSelected EventDriven活動中的itemSelected1的下面。指定它的TargetStateName為EndState。

33.點擊超文本風格按鈕Workflow1回到狀態設計器視圖上來。

34.從工作流視圖設計器的角度來看,該工作流就完成了,但我們還要寫一些代碼。在Visual Studio的解決方案管理器中選擇Workflow1.cs文件,然後在代碼編輯模式下打開該文件准備進行編輯。

35.查看Workflow1.cs源文件,找到你在第12步所添加的ResetTotal方法。把下面的代碼插入到ResetTotal方法中:

// Start with no total.
Total = 0.0m;

36.最後,找到你在第21步所添加的TestTotal方法。為該方法添加下面這些代碼:

// Add the last coin dropped to the total and check
// to see if the total exceeds 1.25.
Total += LastCoinDropped;
e.Result = Total >= 1.25m;

37.編譯整個解決方案。修正任何可能出現的編譯錯誤。

現在你可按下F5或Ctrl+F5運行該應用程序。點擊一個投幣按鈕,LCD上顯示的總金額更新了嗎?當你投入了足夠的金錢時,你能挑選飲料嗎?

備注:假如該應用程序由於InvalidOperationException異常崩潰的話,最可能的情況是由於引用在解決方案第一次完成編譯後沒有被完全更新。可簡單地重新編譯整個應用程序(重復第37步)並再次運行該應用程序,它應該能干淨利落地運行。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved