1.知道在工作流處理過程中怎樣進行策略和規則的處理
2.理解前向鏈接以及這是如何影響到基於規則的工作流處理過程的
3.為工作流處理過程創建規則
4.結合Policy活動來使用規則
我敢肯定,我們中的大多數人編寫面向過程的代碼(imperative code)都很輕松自在。過程式代碼指通過編程來實現業務處理過程的C#代碼,例如,讀取一個數據庫表,增加這個表中某些列的值,然後把它們統統都寫到另一個數據庫的表中。
但在本章,我們將深入規則,規則是對工作流的執行進行控制的一種機制,但它被看作是聲明性的(declarative)。通常,聲明性代碼並不會被編譯進程序集中,而是在應用程序執行時被解釋。ASP.NET 2.0中有許多新的特征就是聲明性的,這其中包括數據綁定和改進了的模板控件。它們能夠讓你在寫ASP.NET應用程序時不使用C#代碼就可去執行數據綁定或者其它復雜的控件呈現任務。
Windows Workflow Foundation(WF)也具有聲明性的能力,但它是對規則和策略進行綁定而不是數據。你不能使用HTML或者ASP.NET的構造來聲明你的規則,當然,涉及的概念都是相似的。
但是什麼是規則,什麼又是策略呢?
策略和規則
當我寫一個涉及到數據或業務過程的程序時,我都會對數據或業務過程進行理解並把它轉換成計算機去執行的代碼。例如,考慮這樣一個對帳目進行檢查的處理邏輯:“假如在AvailableBalance列中的值少於要求的值,將拋出一個OverdraftException異常。”這似乎很簡單...下面是表達這個行為的一些偽代碼:
IF (requestedValue > AvailableBalance) THEN
throw new OverdraftException("Insufficient funds.")
但是要是銀行客戶具有透支保障功能,假如主賬戶資金不足時能對次賬戶進行存取又會怎麼樣呢?要是客戶沒有透支保障功能但是可自動設置透支范圍的信貸業務又會怎麼樣呢?要是客戶兩樣都有呢……我們該使用哪一個呢?
就像你能預見到的,為了對各種情況都進行檢查,代碼就會變得既復雜又混亂。更糟糕的是,它不能很方便地移植到其它業務處理過程中,並且它維護起來可能也很困難。
更進一步,我們看到了這些不只是去進行數據處理而且還有數據之間的關系。在代碼中,我們運用過程化的處理方式來對關系進行處理,這些通常都會被翻譯成許多嵌套的if語句,swith語句和循環。假如以前你在處理過程中使用了大量的if語句去對所有可能的條件檢查,你或許應該問問自己是否已經沒有更好的方式了。
至少在WF中有更好的方式。我們可以創建聲明性規則然後使用規則引擎(rules engine)來處理它們。聲明性規則對關系進行描述說明,它也適合應用到潛在要進行判斷的地方。
WF承載了一個規則引擎(rules engine)。該規則引擎可使用XML格式編碼的規則,並且能把這些規則應用到你的工作流的方法和字段中。在WF中,你能把面向過程的代碼和聲明性規則兩者結合在一起形成一個總的解決辦法。
WF中主要有兩個地方會用到規則處理:條件處理和策略。你將發現條件處理是IfElse、While、Replicator以及ConditionedActivityGroup這些活動的一部分。假如你回顧一下第9章“邏輯流活動”和第11章“並行活動”的話,在那些地方介紹和示范的活動中,在每種情況下我都使用一個代碼條件來對處理流程進行判斷。當然,代碼條件的實現是你工作流處理類中的一個事件處理程序(它通過WF所提供的一個CodeCondition類被綁定)。但是,在本章中你將開始使用規則條件進行替換。直到目前在本書中還沒有體驗過策略的使用,但在本章中當我介紹Policy活動時將對策略進行演示。
備注:對於WF和基於規則的處理可以寫完整地一本甚至是一部系列叢書。我不可能在本章覆蓋到各個方方面面。但可以做到的是對幾個關鍵的概念進行介紹,這些概念對於你來說是全新的,並且也為你提供了一些基於WF的應用程序,它們用來對基於規則的處理過程的某個特定方面進行演示。假如你對這些話題感興趣,我強烈建議你花些寶貴時間到Google上(http://www.google.com/),大量的網站都有關於在基於工作流的系統中實現業務處理流程方面的論文和資料。
在WF中,規則(rule)通過條件來表示,它返回一個Boolean值,並伴隨著一個或多個操作。WF中規則風格的布局遵循if-then-else風格。規則引擎對條件進行判斷,然後指揮工作流按照條件處理的結果去執行。在一定程度上,規則類似於腳本代碼,與規則引擎一起充當腳本執行環境。在面向過程的代碼之上使用規則的優點是規則能很容易地進行修改,以讓你部分的業務處理過程更容易地適應易變的環境。
在WF術語中的策略是指規則的集合,它被包含到一個規則集(RuleSet)中。這使有些被稱作前向鏈接(forward chaining)的事情變得更方便,這個假想的術語指的是在當前處理規則發生改變導致狀態變化後,能對規則重新進行判定。
實現規則
規則基於XML,當在Microsoft Visual Studio中生成你的工作流時,這些XML被編譯成資源。許多基於WF的類都了解和規則相關的具體細節,它們都在System.Workflow.Activities.Rules中。這些類和XML協同工作去執行規則腳本,最終生成一個以true或者false為結果的條件語句,你的工作流邏輯使用它來指揮處理流程。在Visual Studio中通過兩個主要的用戶界面來讓規則協同工作。對於簡單的規則編輯過程,就像在基於流的活動(在第9章和第11章討論過)中的條件賦值一樣,你可使用一個用戶界面來對規則進行編輯,該用戶界面能讓你生成規則文本。在你的規則中,你可同時使用腳本化的關系運算符(如表12-1所示),算術運算符(如表12-2所示)、邏輯運算符(如表12-3所示)、關鍵字(如表12-4所示)以及字段、屬性和方法,以在你的工作流中為基於流的活動去判定該條件表達式。
表12-1 規則關系運算符
運算符 功能 ==或= 測試是否相等 >或>= 測試是否大於(>)或者是否大於等於(>=) <或<= 測試是否小於(<)或者是否小於等於(<=)
表12-2 規則算術運算符
運算符 功能 + 加 - 減 * 乘 / 除 MOD 模
表12-3 規則邏輯運算符
運算符 功能 AND或&& 邏輯與 OR或|| 邏輯或 NOT或! 邏輯非 & 位與 | 位或
表12-4 規則關鍵字
運算符 功能 IF 開始條件測試 THEN 假如條件測試值為true時所執行的流路徑 ELSE 假如條件測試值為false所執行的流路徑 HALT 終止規則處理過程,把控制權返回給使用該規則的活動,但是這和使用Terminate活動是不同的。工作流處理過程並沒有結束,僅僅是特定的條件停止處理。 Update 通知規則引擎有一個特定的字段或屬性值已經被修改了(這可方便地對前向鏈接進行依賴檢查,這將在本章的晚些時候進行論述)。
對於策略,你可使用一個專門的編輯器來編輯你的規則集(如圖12-1所示)。在這裡,你能批量地編輯並組成規則集。你可指定規則的優先級,假如條件改變時怎樣對規則進行重新計算,以及指定你想運用的前向鏈接(forward chaining)機制。當你通過Visual Studio的規則集編輯器用戶界面來創建規則時,你可指定規則優先接收的值以及它的前向鏈接行為。
圖12-1規則集編輯器用戶界面
規則屬性
當通過規則調用你工作流的方法時,或許會有規則引擎不知道的依賴關系。當它們共享工作流字段或屬性時,規則就變成依賴的了。有時依賴關系並不明顯,但有時卻不。例如,設想一個顧客購買了一定數量的東西後可允許免費送貨,但仍然要對手續費進行確定。這可考慮這些規則:
IF this.PurchasedQuantity >= this.DiscountQuantity THEN this.DiscountShipping(1.0)
AND
IF this.HandlingCost > 0 THEN
this.OrderCost = this.OrderCost + this.HandlingCost
第一條規則陳述了假如買的東西的數量超過了一個門檻值,就可不用收取運貨費用(運貨費用的折扣率是100%,注意我們調用了一個方法去設置這個值)。第二條規則在完全不同的工作流部分中執行,它把手續費加到訂單價格總額中。假如運費打了折扣,通常也就存在手續費用,但這兩條規則是獨立的。假如調用了DiscountShipping把一個值寫到HandlingCost屬性中,並且寫入後導致了第二條規則稍後會在處理中去執行(譯者注:因為此時handlingCost > 0,會執行第二條規則),你就應當讓規則引擎知道這裡存在依賴關系,方法是使用一個特殊的基於規則的工作流特性,它們都被列到了表12-5中。下面的代碼展示了其中的一個特性行為:
[RuleWrite("HandlingCost")]
public void DisountShipping(decimal percentageDiscount)
{
// 這裡是更新手續費的代碼
}
在處理前向鏈接(forward chaining)時這些特性將起作用。
表12-5基於規則的特性
特性 功能 RuleRead 這個特性是默認的。該特性通知規則引擎方法可讀出工作流實例的屬性和字段,但不能更新它們的值。 RuleWrite 該特性通知規則引擎工作流方法可更新該潛在依賴的字段或屬性的值。 RuleInvoke 該特性告知規則引擎被這個特性修飾的方法可調用一個或多個其它的方法,這些方法或許也會對潛在依賴的字段或屬性進行更新。
Update語句
表12-4中列出了你可自由使用的基於規則的關鍵字。它們相對都具有自解釋性,但Update例外。作為基於規則的特性,當我們接觸前向鏈接時將對Update進行更多的討論,但核心思想是要通知規則引擎你的規則是在明確地對一個字段或屬性進行更新,以便使其它相依賴的規則知道這個更改。Update實際上並不對字段或屬性進行修改——它只是通知規則引擎該字段或屬性被改變了。
Update需要一個單一的字符串,它表示字段或屬性的名稱,它的用途是通知規則引擎相依賴的規則可能需要重新判定。盡管最佳做法的原則是使用基於規則的特性作為首選,但有時使用Update更恰當。這有一個很好的例子:當你在一個工作流程序集上不能進行寫入操作但要修改某個屬性時(一是沒有基於規則的屬性,一是你不能更新源代碼以讓它包含必要的特性)。
可能對於理解在工作流處理過程中能怎樣去使用規則的最好方式是開始去寫一些代碼進行試驗。我們將從規則條件開始,它可和我們在第9章中使用過的代碼條件相對比。
規則條件
對條件表達式進行判定的WF活動有IfElse活動、While活動、Replicator活動和ConditionedActivityGroup活動。這些活動當中的每一個都會要求你做一個true/false的判斷。在第9章中,我們使用過“代碼條件”屬性設置,它使Visual Studio向我們的工作流代碼中添加進一個event handler,該事件參數的類型是ConditionalEventArgs,它包含了一個Result屬性,我們可設置它為true或者false,這取決於我們的決定。
但是,對於這些每一個條件的判定,我們也可使用“規則條件”來進行替換。規則條件是對true或者false進行判斷的一組規則,例如:購買的物品數目超過了免費送貨的界限值。為了清楚地說明這一點,這裡有一個使用了規則條件的示例應用程序。
創建一個使用了“規則條件”進行條件判定的新工作流應用程序
1.下載本章源代碼,打開RuleQuestioner目錄中的解決方案
2.創建一個順序工作流庫項目,步驟可參考第3章“工作流實例”中“向WorkflowHost解決方案中添加一個順序工作流項目”一節中的步驟。把該工作流庫的名稱命名為“RuleFlow”。
3.在Visual Studio添加了該RuleFlow項目後,打開工具箱,拖拽一個Code活動到設計器界面上,在它的ExecuteCode屬性值中輸入AskQuestion。
4.Visual Studio會創建該AskQuestion方法並為你自動切換到代碼視圖界面下。在該AskQuestion方法中輸入下面的代碼:
AskQuestion
// Ask a question
DialogResult res = MessageBox.Show("Is today Tuesday?", "RuleFlow",
MessageBoxButtons.YesNo, MessageBoxIcon.Question);
_bAnswer = res == DialogResult.Yes;
5.找到Workflow1的構造器,在該構造器下面添加這些代碼:
private bool _bAnswer = false;
6.添加如下的名稱空間:
using System.Windows.Forms;
7.因為MessageBox由System.Windows.Forms支持,當創建一個順序工作流項目時該程序集不會自動地被Visual Studio所引用,你需要添加該引用。因此在解決方案資源管理器中的RuleFlow項目內的引用樹狀結點上單擊右鍵,然後從右鍵快捷菜單中選擇“添加引用”,點擊.NET選項卡,在列表中找到System.Windows.Forms。選中它然後點擊確定。
8.然後切換到工作流視圖設計器界面上來。拖拽一個IfElse活動到工作流視圖設計器界面上,把它放到你剛剛所放入的Code活動的下面。紅色的感歎號標記表明還需要完成額外的工作,在本例中意味著我們還需要添加觸發工作流去選擇執行左邊的路徑(“true”)還是右邊的路徑(“false”)的條件。
9.在工作流視圖設計器中,選擇左邊的ifElseBranchActivity1分支,在Visual Studio的屬性面板中將顯示該活動的屬性。
10.選中Condition屬性,點擊下拉箭頭,這將顯示可供使用的條件處理選項的選擇列表。選擇聲明性規則條件選項。
11.通過點擊加號(+)展開Condition屬性,點擊ConditionName屬性,這將激活浏覽(...)按鈕,點擊它。
12.這將打開“選擇條件”對話框,點擊新建按鈕。
13.這會打開“規則條件編輯器”對話框。在“條件”域中輸入System.DateTime.Now.DayOfWeek == System.DayOfWeek.Tuesday,然後點擊確定。
14.注意在“選擇條件”對話框的條件列表中就有了一個名稱為條件1的條件。
15.在這裡,IfElse活動就有了一個條件去進行處理,但是它並沒有執行任何代碼!因此,拖拽一個Code活動到設計器界面上並把它放進左邊的分支中。在它的ExecuteCode屬性中輸入ShowTuesday。
16.Visual Studio會為你自動切換到代碼視圖下,在該ShowTuesday事件處理程序中輸入下面的代碼,然後重新切換到工作流視圖設計器界面上來。
string msg = _bAnswer ?
"The workflow agrees, it is Tuesday!" :
"Sorry, but today IS Tuesday!";
MessageBox.Show(msg);
17.拖拽第二個Code活動到IfElse活動的右邊分支中,在它的ExecuteCode屬性中輸入ShowNotTuesday。
18.在Visual Studio為你切換到代碼視圖後,在ShowNotTuesday事件處理程序中輸入下面的代碼後重新回到工作流視圖設計器界面上來:
string msg = !_bAnswer ?
"The workflow agrees, it is not Tuesday!" :
"Sorry, but today is NOT Tuesday!";
MessageBox.Show(msg);
19.工作流現在就設計完成了,現在從RuleQuestioner應用程序中添加對該工作流的項目級引用。
20.在RuleQuestioner項目中打開Program.cs文件,找到下面的代碼:
// Print banner.
Console.WriteLine("Waiting for workflow completion.");
21.在上面找到的代碼下面添加如下代碼,這將創建一個工作流實例。
// Create the workflow instance.
WorkflowInstance instance =
workflowRuntime.CreateWorkflow(typeof(RuleFlow.Workflow1));
// Start the workflow instance.
instance.Start();
22.編譯該解決方案,糾正任何可能出現的編譯錯誤。
23.按下F5(或者Ctrl+F5)執行該應用程序。
假如你仔細看看第13步,你會發現我們添加的規則和用戶是否通知了工作流今天是不是星期二完全無關。規則對屬於一周的哪一天進行了檢查,它也應該能把用戶的輸入考慮在內。(我也可以向規則中添加this._bAnswer去訪問該Boolean值。)
你可能也會對為什麼這樣做要比使用代碼條件好而感到疑惑。其實,並不能說一個要比另一個要好,效果都是一樣的。對於基於規則的條件的過程來說,對判定做出改變的東西是保存起來的規則,這可在運行的時候使用不同的規則去進行替換。這是一個很強大的概念。當涉及超過一個以上的規則的時候,它甚至會變得更加強大,這就是使用策略(policy)的情況。但在我們涉及策略之前,我們需要看看前向鏈接(forward chaining)。
前向鏈接
假如你曾經觀看過轎車的組裝過程的話,你絕對會感到驚奇。轎車本身實際上就非常復雜,組裝的過程甚至一定會更加復雜。組裝過程中隱藏的是一個選項的概念,轎車有一些可選的部件。一些或許有衛星收音機,其它或許要有GPS(全球定位系統)以便駕駛員絕不會迷路。並非組裝線上的所有轎車都有每個組裝選項。
因此當一輛轎車從線上下來的時侯,組裝過程通常會改變。一些組裝選項要求在組裝過程中很早的時候就布下不同的電氣配線、或者更持久的電池、或者不同的引擎部件。問題是組裝過程以每輛車作為基礎進行變化。在每個裝配站,線上的工人(或者機器人)都會被告知要組裝什麼部件。告知他們的過程可以容易地設想為一個使用了基於規則的方式的工作流過程。此外,早期作出的判定結果也會影響到後期將怎樣去進行判定。有些選項和其它選項不能同時存在,因此在轎車從線上下來時組裝過程必須進行改變。
這就是前向鏈接的本質。規則緊密地鏈接在一起,就像一個規則的判定結果會影響到接下來的規則會怎樣去進行判定。當我們有超過一個以上的規則要去處理時,就像是我們將使用的策略的過程,我們需要去關注規則依賴以及想怎樣去處理前向鏈接。
備注:術語“規則間的依賴關系”真正的意思是兩個或更多的規則共享了相同的工作流字段或屬性。假如沒有規則和其它的規則共享訪問相同的工作流字段或屬性,則這兩個規則之間也就沒有依賴關系。假如存在依賴關系,則這個問題將通知規則引擎存在著依賴關系,在有些情況下也有可能要掩蓋這些依賴關系的存在。(我們將在這節看到這些內容。)
正如我在本章前面提到過的,規則被聚集到一個規則集(RuleSet)中。在規則集中的規則能被指派優先級,在一個特定的時間點上你能指定它們是否處於激活狀態(和enabled屬性相似)。當正在處理一個以上的規則時,將以下面的方式來對這些規則進行處理。
1.派生出的處於激活狀態的規則列表。
2.所找到的最高優先級的規則(或者規則集)。
3.對規則(或多條規則)進行判定,然後必須執行它的then或者else內的操作步驟。
4.假如規則更新了前面所提到的規則列表中具有更高優先級的規則所使用過的工作流的字段或屬性的話,前面的規則會被重新判定並執行它所必須要去執行的步驟。
5.繼續進行處理過程,直到根據需要判定完(或者是重新判定)規則集中的所有規則。
通常在三種情況下規則可以是前向鏈接的:隱式鏈接(implicit chaining)、特性聲明鏈接(attributed chaining)和顯式鏈接(explicit chaining)。也就是說,規則能被進行鏈接並且共享依賴,因為工作流運行時能(在某些條件下)弄清是否有這個必要(這是隱式鏈接),你也可應用基於規則的特性中的某一個標記來聲明某個方法(這是特性聲明鏈接),或者使用一個Update語句(這是顯式鏈接)。我們就來對每一個進行簡要的看看。
隱式鏈接
當字段和屬性被一條規則進行了更新,而這些字段或屬性又顯而易見地被其它規則所讀出的時候,就會產生隱式鏈接。例如,考慮這些規則:
IF this.OrderQuantity > 500 THEN this.Discount = 0.1
And
IF this.Discount > 0 && this.Customer == "Contoso"
THEN this.ShippingCost = 0
第一條規則在訂單數量超過500單位時將進行打折。第二條規則陳述了假如公司是Contoso並且也進行了打折,則對運費免費。假如第一條規則起作用的話,第二條規則可能需要再次進行重新判定並執行。
特性聲明鏈接
因為在你的工作流中的方法能對字段和屬性進行修改,但是規則引擎可能對此卻一無所知,因此WF提供了我在本章前面提到過的基於規則的特性。結合先前的例子,對規則稍微進行改寫,特性聲明鏈接可能看起來就像下面這樣:
IF this.OrderQuantity > 500 THEN this.SetDiscount(0.1)
AND
IF this.Discount > 0 && this.Customer == "Contoso"
THEN this.ShippingCost = 0
這裡,第一條規則調用了工作流類中的一個方法:SetDiscunt,它對Discount屬性進行了更新。但規則引擎並不知道SetDiscount將改變Discount的值,因此當寫SetDiscount方法時,你應當使用RuleWrite(或者RuleInvoke)特性:
[RuleWrite("Discount")]
private void SetDiscount(decimal discountValue)
{
}
RuleWrite特性將通知規則引擎對SetDiscount的調用將導致對Discount屬性進行更新。因為這些形成了一個依賴關系,因此假如SetDiscount方法被調用的時候這些規則將會被重新進行判定。
顯式鏈接
最後一種前向鏈接是顯式的,也就是說你的規則中使用了Update語句來告知規則引擎有一個字段或屬性的值已經被修改了。Update的作用等同於使用RuleWrite特性。但是正如你所知道的,當調用一個工作流方法的時候,規則引擎並不知道該方法是否對某個規則依賴到的字段或屬性進行了更新。在這種情況下,你調用了工作流方法後接著通過使用一個Update語句來告知該規則引擎存在的依賴關系。
這些或許聽起來有些古怪,但它還是有使用價值的。假如你寫你自己的工作流的話,你應該使用基於規則的特性。但是,當基於工作流的軟件變得普遍,人們開始使用第三方的工作流的時候,他們可能會發現基於規則的特性並沒有應用到各個工作流方法中去。在這些情況中,他們就應當使用Update語句來保持正確的工作流狀態以及規則引擎的同步。基於規則的特性是以聲明的方式來指明更新,而Update語句則是在不可避免的時侯使用。當使用了已預編譯過的第三方軟件的時候,你就需要這種不可避免的方案。
回到先前的例子,假設SetDiscount方法並沒有應用RuleWrite特性。則這兩條規則就會和下面的這些看起來相像。IFthis.OrderQuantity>500THENthis.SetDiscount(0.1)Update(this.Discount)AndIFthis.Discount>0&&this.Customer=="Contoso"THENthis.ShippingCost=0 有了這些信息,規則引擎就知道了Discount屬性已經本更新了,並且也因此將對規則的適用范圍進行重新判定。
控制前向鏈接
你可能會認為一旦你開始了基於規則的工作流的執行後,你就失去了對它的控制並允許規則引擎能進行所有的判定。盡管大多數情況下這正是你想做的,但在處理規則依賴和前向鏈接上也有一些控制權。
表12-6列出了你所具有的對前向鏈接進行控制的三種類型。
12-6前向鏈接控制行為
行為 功能 Full Chaining 這是默認的,這個行為允許工作流引擎在它認為有必要的時候去對規則進行處理和重新判斷。 Explicit Chaining 當應用它時,該控制行為就把前向鏈接行為的應用范圍限定在包含了Update語句的規則上。 Sequential 這實質上是把前向鏈接關閉。(指明)沒有會被判定到的依賴關系,規則依次、按順序被使用。完全鏈接(full chaining)行為能讓規則引擎根據需要去對包括隱式的和特性的在內的規則進行重新判定。
僅顯式更新鏈接(explicit chaining)行為會使隱式的和特性標記的前向鏈接無效,並且它應用在使用了顯式前向鏈接,需要由你直接負責去通知規則引擎存在依賴關系的地方。在使用了Update語句的地方,你在規則依賴和重新判定上有總的控制權。在省略了Update語句的地方,規則引擎不會做任何確定是否存在依賴的嘗試,因此即使實際上存在依賴關系,規則也將不會被重新進行判定。它的作用是在你的規則中增加了Update語句後,你就在前向鏈接上掌握了完全的控制權。你可能會這樣去做以提高性能(因為規則引擎就不再對所有那些非必要的規則進行重新判定),你也可能必須去這樣做以便消除你的規則中的依賴循環。
順序的鏈接(sequential chaining)行為實際上是把所有的前向鏈接關閉。規則從頂到底地在單一的通道上被判定。假如存在依賴關系,這些依賴會被完全忽略。
提示:優先級的正確使用通常也能高效地對前向鏈接進行控制。高優先級的規則首先執行,因此,高優先級的規則會在低優先級的規則執行以前對低優先級將使用的字段和屬性的值進行更新和確定。就像你想起的,在同一個Visual Studio用戶界面中要根據你要使用的優先級去創建規則。
控制規則的重新判定
在怎樣對規則重新判定上你也有控制權。
表12-7列出了這些方法。一個需要牢記的事情是規則的重新判定模式只在個別規則等級上應用。在一個接一個的規則基礎上,你能為特定的規則指定重新判定的模式。
表12-7規則重新判定模式
模式 功能 Always 這是默認的。這個模式使工作引擎在必要時對規則進行重新判定。 Never 當應用該模式時,將指明規則只能被判定一次(絕不會被重新判定)。通過總是(Always模式)使規則進行重新判定,規則引擎作出的判定可能會改變那些根據臨時狀態變化進行對應處理的規則的最終結果。在依賴字段或屬性值被修改時,規則引擎能在必要時重新執行規則以把這些修改考慮在內。
但是,有時你可能不想這樣,這時你可選擇Never作為你的規則重新判定模式。你為什麼會選擇這種重新判定模式呢?哦,其中一個例子可能包括以下內容:
IFthis.Handling<5.0&&this.OrderQuantity>500THENthis.Handling=0
這條規則的意思是:“假如手續費低於$5.0並且訂單數量超過了500個單位的話,那麼就不收取任何的手續費。”但是當滿足該規則判定標准並且把手術費設置為0時會發生什麼呢?哦,依賴屬性Handling被更新了,因此該規則要重新判定!假如你猜到該規則會導致一個無限循環的話,你猜中了。因此,應用一個Never類型的重新判定模式很有意義:手續費用一旦為0,為什麼還需再次對規則進行判定呢?盡管在寫這個特定的規則時可能使用其它的方式來防止出現無限循環,但問題是在你的工作流創作工具包你有這樣一種重新判定模式來作為工具,你又為什麼不利用它呢?
使用策略活動
當超過一個以上的規則要被處理的時候,前向鏈接這種情形就出現了。對於規則條件(Rule Condition)的情形來說,這是不可能發生的情況:因為這種情況下只有一個規則。事實上,它甚至不是一個完整的規則而只是一個布爾表達式。但是,Policy活動改變了所有這些。有了Policy活動,你就有機會把多個復雜的規則組合到一起,並且你可能會看到(某些時候也可能看不到)前向鏈接的結果。
當你使用Policy活動的時候,規則被聚集進一個集合中,這個集合通過WF的規則集(RuleSet)對象來維護。當你把Policy活動拖拽進你的工作流中後,你需要創建一個規則集對象並插入到你的規則中,在必要時應用前向鏈接進行控制並使用規則重新判定模式。Visual Studio為幫助創建規則集合提供了一個用戶設計界面,就像有一個為添加單一的規則條件(Rule Condition)的用戶界面一樣。
為了演示Policy活動,讓我們重新回味一下我在第4章中“選擇一種工作流類型”這一節中所概述的情景。我不會實現前面提到過的所有規則,但我將實現足夠多的規則以便對Policy活動的功能進行演示。這些規則集如下所示:
1.當你收到一份訂單時,檢查你帳目上目前還有的增塑劑的合計數目。假如你認為數目足夠的話,就可嘗試填寫一份完整的訂單。否則的話,就准備填寫一份部分出貨類型的訂單。
2.假如你正填的是一份部分出貨類型的訂單,檢查看看訂貨的公司是接受這種部分出貨類型的訂單呢還是需要讓你先等等,直到你能提供一份完整的訂單時為止。
3.假如你正填的是一份完整的訂單,就檢查在儲備罐中增塑劑的實際的量(有些可能已經被蒸發了)。假如具有足夠的增塑劑來履行這份完整的訂單,就處理這份完整的訂單。
4.假如沒有足夠的增塑劑來履行這份訂單,就把它當部分出貨的訂單類型處理。(看看第二條規則。)
我也知道任何有競爭力的塑膠公司都會知道在儲備罐中儲存的增塑劑的真實的量,但這仍不失為一個好例子,因為這裡實際上包含了許多的條件。假如訂單一來並且我們知道我們沒有足夠的量來滿足它,我們就看看我們是否可以提供一份部分出貨類型的訂單(這些能夠根據和客戶達成的協議作出選擇)。我們也總是可以嘗試對我們知道能完全滿足的訂單進行處理,但當增塑劑的實際的量和我們先前認為的量有所區別的時候會發生什麼呢?,這會導致部分出貨嗎?這種情形我很有興趣進行演示,因為它顯示出了在操作過程中的規則判定處理。
假設我們是塑膠制造商,我們有兩個主要的客戶:Tailspin Toys和Wingtip Toys。Tailspin Toys已告知我們他們可以接受部分出貨,但Wingtip需要訂單要完整地發送。我們的工作流將使用一個Policy活動來把這些規則應用到我概述的這些客戶、他們的訂單以及我們手頭的原料數量當中,這可能(或不能)足夠完成他們的訂單。我們在操作中來看看這個活動。
創建一個使用了Policy活動的新工作流應用程序
1.該PlasticPolicy應用程序再次為你提供了兩個版本:完整版本和非完整版本。你需要下載本章源代碼,打開PlasticPolicy文件夾中的解決方案。
2.在Visual Studio加載了PlasticPolicy解決方案後,新創建一個順序工作流庫的項目,該工作流庫的名稱命名為PlasticFlow。
3.在Visual Studio添加了該PlasticFlow項目後,Visual Studio會打開Workflow1工作流以便在工作流視圖設計器中進行編輯。打開工具箱,拖拽一個Policy活動到設計器界面上。
4.在你真正創建規則以便把它們和你剛才插入進你的工作流中的Policy活動相配合前,你需要添加一些初始化代碼和方法。以代碼視圖的方式打開Workflow1.cs文件,在該類的構造器前添加這些代碼:
private enum Shipping { Hold, Partial };
private decimal _plasticizer = 14592.7m;
private decimal _plasticizerActual = 12879.2m;
private decimal _plasticizerRatio = 27.4m; // plasticizer for one item
private Dictionary<string, Shipping> _shipping = null;
// Results storage
private bool _shipPartial = false;
private Int32 _shipQty = 0;
// Order amount
private Int32 _orderQty = 0;
public Int32 OrderQuantity
{
get { return _orderQty; }
set
{
// Can't be less than zero
if (value < 0) _orderQty = 0;
else _orderQty = value;
}
}
// Customer
private string _customer = String.Empty;
public string Customer
{
get { return _customer; }
set { _customer = value; }
}
5.添加下面的名稱空間:
using System.Collections.Generic;
6.再次找到Workflow1的構造器,在該構造器內調用初始化組件(InistializeComponent)的方法下添加下面這些代碼:
// Establish shipping for known customers
this._shipping = new Dictionary<string, Shipping>();
this._shipping.Add("Tailspin", Shipping.Partial);
this._shipping.Add("Tailspin Toys", Shipping.Partial);
this._shipping.Add("Wingtip", Shipping.Hold);
this._shipping.Add("Wingtip Toys", Shipping.Hold);
7.在構造器下添加下面這些方法:
private bool CheckPlasticizer()
{
// Check to see that we have enough plasticizer
return _plasticizer - (OrderQuantity * _plasticizerRatio) > 0.0m;
}
private bool CheckActualPlasticizer()
{
// Check to see that we have enough plasticizer
return _plasticizerActual - (OrderQuantity * _plasticizerRatio) > 0.0m;
}
[RuleWrite("_shipQty")]
private void ProcessFullOrder()
{
// Set shipping quantity equal to the ordered quantity
_shipQty = OrderQuantity;
}
[RuleWrite("_shipQty")]
private void ProcessPartialOrder()
{
// We can ship only as much as we can make
_shipQty = (Int32)Math.Floor(_plasticizerActual / _plasticizerRatio);
}
8.為了使你能在規則處理時看到輸出結果,你需要激活屬性面板。在屬性面板中,點擊事件工具條按鈕,然後在Completed事件中輸入ProcessingComplete。這會在你的工作流代碼中為WorkflowComplete事件添加一個對應的event handler並為你切換到Workflow1類的代碼編輯界面下。
9.定位到Visual Studio剛剛添加的ProcessingComplete方法,插入下面這些代碼:
Console.WriteLine("Order for {0} {1} be completed.", _customer,
OrderQuantity == _shipQty ? "can" : "cannot");
Console.WriteLine("Order will be {0}", OrderQuantity == _shipQty ?
"processed and shipped" : _shipPartial ?
"partially shipped" : "held");
10.現在切換回工作流視圖設計器來,我們將添加一些規則。首先,選中policyActivity1,以便在屬性面板中激活它。點擊RuleSetReference屬性,這將激活浏覽(...)按鈕。
11.點擊該浏覽按鈕,這將打開“選擇規則集”對話框。一旦“選擇規則集”對話框打開後,點擊新建按鈕。
12.點擊新建按鈕將打開“規則集編輯器”對話框,然後點擊添加規則。
13.你將添加三條規則中的第一條。你添加的每個規則都由三個部分組成:條件、Then操作和Else操作(最後一個是可選的)。在條件部分中輸入this.CheckPlasticizer()。(注意它調用的是一個方法,因此括號是必須的。)在Then操作部分,輸入this.ProcessFullOrder()。最後在Else操作部分中輸入this.ProcessPartialOrder()。
14.再次點擊添加規則,把第二條規則添加到規則集中。在本條規則的條件部分中輸入this.CheckActualPlasticizer()。在Then操作部分輸入this.ProcessFullOrder()。在Else操作部分,輸入this.ProcessPartialOrder()。
15.再次點擊添加規則以便插入第三條規則。在第三條規則的條件部分,輸入this._shipping[this._customer] == PlasticFlow.Workflow1.Shipping.Hold && this._shipQty != this.OrderQuantity。在Then操作部分,輸入this._shipPartial = False。在Else操作部分輸入this._shipPartial = True。
16.點擊確定關閉“規則集編輯器”對話框。注意現在在規則列表中多了一個名稱為規則集1的規則。再點擊確定關閉“選擇規則集”對話框。
17.你的工作流現在就完成了,盡管它看起來有些古怪,因為整個工作流中就只有單一的一個活動。其實,你已經通過你提供的規則來通知你的工作流要做些什麼。還有就是需要在PlasticPolicy應用程序中添加對該工作流的項目級引用。
18.在PlasticPolicy項目中打開Program.cs文件,找到Main方法。在Main方法的左大括號下面添加下面這些代碼:
// Parse the command line arguments
string company = String.Empty;
Int32 quantity = -1;
try
{
// Try to parse the command line args
GetArgs(ref company, ref quantity, args);
}
catch
{
// Just exit
return;
}
19.然後在Main方法中找到下面這行代碼:
// Print banner.
Console.writeLine("Waiting for workflow completion.");
20.在上面的代碼下添加如下這些代碼:
// Create the argument.
Dictionary<string, object> parms = new Dictionary<string, object>();
parms.Add("Customer", company);
parms.Add("OrderQuantity", quantity);
// Create the workflow instance.
WorkflowInstance instance =
workflowRuntime.CreateWorkflow(typeof(PlasticFlow.Workflow1), parms);
// Start the workflow instance.
instance.Start();
21.在第18步,你添加的代碼調用了一個對命令行參數進行處理的方法。現在你需要添加該方法。在Program.cs源文件的底部添加這個方法:
static void GetArgs(ref string company, ref Int32 quantity, string[] args) { // Pre-set quantity quantity = -1; try { // Parse the argumentswe must have both a company // and a quantity. for (Int32 i = 0; i < args.Length; i++) { // Check this argumentmust have at least // two characters, "/c" or "/q" or even "/?". if (args[i].Length < 2) throw new Exception(); if (args[i].ToLower()[1] == 'c') { // Company The company name will be // located in character position 3 to // the end of the string. company = args[i].Substring(3); } // if else if (args[i].ToLower()[1] == 'q') { // Quantity The quantity will be // located in character position 3 to // the end of the string. Note Parse // will throw an exception if the user // didn't give us an integer. quantity = Int32.Parse(args[i].Substring(3)); } // else if else { // "/?" or unrecognized. throw new Exception(); } // else } // for // Make sure we have both a company and a // quantity value. if (String.IsNullOrEmpty(company) || quantity == -1) throw new Exception(); } // try catch { // Display usage Console.WriteLine("\nPlasticPolicy.exe -"); Console.WriteLine("\tTests Windows Workflow Foundation " + "rules-based processing\n"); Console.WriteLine("PlasticPolicy.exe /c: /q:\n"); Console.WriteLine("\t- Required Arguments -\n"); Console.WriteLine("/c:<company>\n\tCompany placing order\n"); Console.WriteLine("/q:<quantity>\n\tOrder quantity\n"); throw; } // catch }
22.編譯該解決方案,修正任何出現的編譯錯誤。
我們現在將使用這個示例應用程序來執行四個場景。第一個場景是規則引擎要處理的最棘手的場景之一:Tailspin Toys預訂的量是500單位。這是一個很有用意的數字,因為這樣要消耗的增塑劑總計為14,592.7(這是一個完全編造的數字),但是在儲備罐中的增塑劑的真實的量總共是12879.2(我總共能湊足的數字!)。
因為每生產一件成品需要消耗27.4個單位的增塑劑(這是我編造的另一個值,它通過Workfow1中的_plasticizerRatio來表示),該訂單正好處於這樣一個范圍:表面上該訂單能全部滿足,但實際上沒有足夠的增塑劑。也就是說,我們認為我們的全部增塑劑能加工出532個成品(14,592.7除以27.4),但是看到儲備罐中真實的量後,我們只能加工出470個成品(12,879.2除以27.4)。最後,我們只能進行部分出貨。
甚至,假如你運行該應用程序時,提供的公司名稱為“Tailspin Toys”,提供的數量為“500”(對應的命令行內容為:PlasticPolicy.exe /c:"Tailspin Toys" /q:500),你就可看到如圖12-2所示的輸出結果。此外,Tailspin眾所周知可接受部分出貨,工作流也指明了這一點。
備注:因為PlasticPolicy應用程序接受命令行參數,在調試模式下你需要使用Visual Studio項目設置來提供這些參數然後運行該應用程序,或者打開一個命令提示符窗口,浏覽並定位到包含PlasticPolicy.exe的目錄下,然後在命令提示符下執行該應用程序。
圖12-2Tailspin Toys的部分出貨訂單
但是假如Tailspin預訂的數量是200個的話,該工作流還會正確地執行嗎?我們就來看看。再次在命令提示符下運行該程序:PlasticPolicy.exe /c:"Tailspin Toys" /q:200。運行結果如下圖12-2所示:
圖12-3Tailspin的完整出貨訂單
Tailspin注冊為能接受部分出貨的類型。但是Wingtip Toys則希望訂單繼續有效,直到整個訂單都能滿足時為止。該工作流也處理Wingtip嗎?而且,假如Wingtip的訂單處在這樣一個范圍:我們認為我們有足夠的增塑劑但實際上沒有呢?為找出答案,試試這個命令:PlasticPolicy.exe /c:"Wingtip Toys" /q:500。就像如圖12-4中顯示的,我們發現我們只能部分完成Wingtip的訂單。最重要的是,當我們訪問需要優先進行處理的顧客的記錄時,選出被壓下的Wingtip的訂單是當務之急。
圖12-4Wingtip Toys的部分出貨訂單
為對最後一個場景進行測試,我們要能滿足Wingtip的需求,而不用考慮增塑劑的真實的量,因此在命令提示符下輸入下面的命令:PlasticPolicy.exe /c:"Wingtip Toys" /q:200。Wingtip Toys現在已經定了200個成品,事實上,圖12-5也指明了我們能完整地履行Wingtip的訂單。
圖12-5Wingtip Toys的完整出貨訂單
基於規則方式的強大在於處理的處理方式。想像這個塑膠策略的例子,假設它被創建成使用了幾個嵌套的IfElse活動組成,或許還要一個ConditionedActivityGroup活動,它們通過使用工作流視圖設計器以面向過程的方式創建。(當我們對儲備罐中的增塑劑進行檢查時,ConditionedActivityGroup活動也要對規則重新判定進行說明)。在這種情況下,面向過程的模式不是一種好的工作方式,尤其對於要考慮使用許多嵌套的IfElse活動和優先級來說更是如此。
但是,以規則為基礎的方式使處理模式簡化。許多嵌套的活動合而為一。而且,因為規則是一種資源,你能很方便地把它們抽取出來,然後用不同的規則對它們進行替換,這比你通常地去部署一個新的程序集來說要更加容易。你可能會發現真實世界的工作流由過程化的方式和基於規則的方式二者合並組成。正確的做法是保證你的工作流一定能工作的前提下,根據實際情況的限制條件選擇最合適的方式。
本文配套源碼