WF 从入门到精通(第十二章):策略和规则

学习完本章,你将掌握:

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 规则关系运算符

image.png

表12-2 规则算术运算符

image.png

表12-3 规则逻辑运算符

image.png


表12-4 规则关键字

image.png

对于策略,你可使用一个专门的编辑器来编辑你的规则集(如图12-1 所示)。在这里,

你能批量地编辑并组成规则集。你可指定规则的优先 级,假如条件改变时怎样对规则进行重

新计算,以及指定你想运用的前向链接(forward chaining)机制。当你通过Visual Studio

的规则集编辑器用户界面来创建规则时,你可指定规则优先接收的值以及它的前向链接行为。

图12-1 规则集编辑器用户界面

image.png

规则属性

当通过规则调用你工作流的方法时,或许会有规则引擎不知道的依赖关系。当它们共享

工作流字段或属性时,规则就变成依赖的了。有时依赖关系并不明 显,但有时却不。例如,

设想一个顾客购买了一定数量的东西后可允许免费送货,但仍然要对手续费进行确定。这可

考虑这些规则:

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 基于规则的特性

image.png

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。

image.png

4.Visual Studio 会创建该AskQuestion 方法并为你自动切换到代码视图界面下。在该

AskQuestion 方法中输入下面的代码:

AskQuestion

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”)的条件。

image.png

9.在工作流视图设计器中,选择左边的ifElseBranchActivity1 分支,在Visual Studio

的属性面板中将显示该活动的属性。

10.选中Condition 属性,点击下拉箭头,这将显示可供使用的条件处理选项的选择列

表。选择声明性规则条件选项。

image.png

11.通过点击加号(+)展开Condition 属性,点击ConditionName 属性,这将激活浏览

(...)按钮,点击它。

image.png

12.这将打开“选择条件”对话框,点击新建按钮。

image.png


13.这会打开“规则条件编辑器”对话框。在“条件”域中输入

System.DateTime.Now.DayOfWeek == System.DayOfWeek.Tuesday,然后点击确定。

image.png

14.注意在“选择条件”对话框的条件列表中就有了一个名称为条件1 的条件。

image.png

15.在这里,IfElse 活动就有了一个条件去进行处理,但是它并没有执行任何代码!因

此,拖拽一个Code 活动到设计器界面上并把它放进左边的分支中。在它的ExecuteCode 属性

中输入ShowTuesday。

image.png

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。

image.png

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 特性。则这两条规则就

会和下面的这些看起来相像。

IF this.OrderQuantity > 500 THEN this.SetDiscount(0.1)

Update(this.Discount)

And

IF this.Discount > 0 && this.Customer == "Contoso"

THEN this.ShippingCost = 0

有了这些信息,规则引擎就知道了Discount 属性已经本更新了,并且也因此将对规则

的适用范围进行重新判定。

控制前向链接

你可能会认为一旦你开始了基于规则的工作流的执行后,你就失去了对它的控制并允许

规则引擎能进行所有的判定。尽管大多数情况下这正是你想做的,但在处理规则依赖和前向

链接上也有一些控制权。

表12-6 列出了你所具有的对前向链接进行控制的三种类型。

表12-6 前向链接控制行为

image.png

完全链接(full chaining)行为能让规则引擎根据需要去对包括隐式的和特性的在内的规则进行重新判定。

仅显式更新链接(explicit chaining)行为会使隐式的和特性标记的前向链接无效,并且它应用在使用了

显式前向链接,需要由你直接负责去通知规则引擎存在依赖关系的地方。在使用了Update 语句的地方,

你在规则依赖和重新判定上有总的控制权。在省略了Update 语句的地方,规则引擎不会做任何确定是否

存在依赖的尝试,因此即使实际上存在依赖关系,规则也将不会被重新进行判定。它的作用是在你的规则

中增加了Update 语句后,你就在前向链接上掌握了完全的控制权。你可能会这样去做以提高性能(因为

规则引擎就不再对所有那些非必要的规则进行重新判定),你也可能必须去这样做以便消除你的规则中的

依赖循环。

顺序的链接(sequential chaining)行为实际上是把所有的前向链接关闭。规则从顶到底地在单一的通道

上被判定。假如存在依赖关系,这些依赖会被完全忽略。

提示:优先级的正确使用通常也能高效地对前向链接进行控制。高优先级的规则首先执行,因此,高

优先级的规则会在低优先级的规则执行以前对低优先级将使用的字段和属性的值进行更新和确定。就像

你想起的,在同一个Visual Studio 用户界面中要根据你要使用的优先级去创建规则。

控制规则的重新判定

在怎样对规则重新判定上你也有控制权。表12-7 列出了这些方法。一个需要牢记的事

情是规则的重新判定模式只在个别规则等级上应用。在一个接一个的规则基础上,你能为特

定的规则指定重新判定的模式。

表12-7 规则重新判定模式

image.png

image.png

通过总是(Always 模式)使规则进行重新判定,规则引擎作出的判定可能会改变那些根据临时状态变化

进行对应处理的规则的最终结果。在依赖字段或属性值被修改时,规则引擎能在必要时重新执行规则以把

这些修改考虑在内。

但是,有时你可能不想这样,这时你可选择Never 作为你的规则重新判定模式。你为什么会选择这种重

新判定模式呢?哦,其中一个例子可能包括以下内容:

IF this.Handling < 5.0 && this.OrderQuantity > 500 THEN this.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 活动到设计器界

image.png

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)的方法下添加下面这些代码:

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 类的

代码编辑界面下。

image.png

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 属性,这将激活浏览

(...)按钮。

image.png

11.点击该浏览按钮,这将打开“选择规则集”对话框。一旦“选择规则集”对话框打

开后,点击新建按钮。

image.png

12.点击新建按钮将打开“规则集编辑器”对话框,然后点击添加规则。

image.png


13.你将添加三条规则中的第一条。你添加的每个规则都由三个部分组成:条件、Then

操作和Else 操作(最后一个是可选的)。在条件部分中输入this.CheckPlasticizer()。(注

意它调用的是一个方法,因此括号是必须的。)在Then 操作部分,输入

this.ProcessFullOrder()。最后在Else 操作部分中输入this.ProcessPartialOrder()。

image.png


14.再次点击添加规则,把第二条规则添加到规则集中。在本条规则的条件部分中输入

this.CheckActualPlasticizer()。在Then 操作部分输入this.ProcessFullOrder()。在Else

操作部分,输入this.ProcessPartialOrder()。

image.png


15.再次点击添加规则以便插入第三条规则。在第三条规则的条件部分,输入

this._shipping[this._customer] == PlasticFlow.Workflow1.Shipping.Hold &&

this._shipQty != this.OrderQuantity。在Then 操作部分,输入this._shipPartial = False。

在Else 操作部分输入this._shipPartial = True。

image.png

16.点击确定关闭“规则集编辑器”对话框。注意现在在规则列表中多了一个名称为规

则集1 的规则。再点击确定关闭“选择规则集”对话框。

image.png

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)

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-2 Tailspin Toys 的部分出货订单

image.png

但是假如Tailspin 预订的数量是200 个的话,该工作流还会正确地执行吗?我们就来

看看。再次在命令提示符下运行该程序:PlasticPolicy.exe /c:"Tailspin Toys" /q:200。

运行结果如下图12-2 所示:

图12-3 Tailspin 的完整出货订单

image.png

Tailspin 注册为能接受部分出货的类型。但是Wingtip Toys 则希望订单继续有效,直

到整个订单都能满足时为止。该工作流也处理Wingtip 吗?而且,假如Wingtip 的订单处在

这样一个范围:我们认为 我们有足够的增塑剂但实际上没有呢?为找出答案,试试这个命令:

PlasticPolicy.exe /c:"Wingtip Toys" /q:500。就像如图12-4 中显示的,我们发现我们只

能部分完成Wingtip 的订单。最重要的是,当我们访问需要优先进行处理的顾客的记录时,

选出被压下的Wingtip 的订单是当务之急。

图12-4 Wingtip Toys 的部分出货订单

image.png


为对最后一个场景进行测试,我们要能满足Wingtip 的需求,而不用考虑增塑剂的真实

的量,因此在命令提示符下输入下面的命令:PlasticPolicy.exe /c:"Wingtip Toys" /q:200。

Wingtip Toys 现在已经定了200 个成品,事实上,图12-5 也指明了我们能完整地履行Wingtip

的订单。

图12-5 Wingtip Toys 的完整出货订单

image.png

基于规则方式的强大在于处理的处理方式。想像这个塑胶策略的例子,假设它被创建成

使用了几个嵌套的IfElse 活动组成,或许还要一个ConditionedActivityGroup 活动,它们

通过使用工作流视图设计器以面向过程的方式创建。(当我们对储备罐中的增塑剂进行检查

时,ConditionedActivityGroup 活动也要对规则重新判定进行说明)。在这种情况下,面向

过程的模式不是一种好的工作方式,尤其对于要考虑使用许多嵌套的IfElse 活动和优先级来

说更是如此。

但是,以规则为基础的方式使处理模式简化。许多嵌套的活动合而为一。而且,因为规

则是一种资源,你能很方便地把它们抽取出来,然后用不同的规则 对它们进行替换,这比你

通常地去部署一个新的程序集来说要更加容易。你可能会发现真实世界的工作流由过程化的

方式和基于规则的方式二者合并组成。正确的做 法是保证你的工作流一定能工作的前提下,

根据实际情况的限制条件选择最合适的方式。

源码下载 http://files.cnblogs.com/gyche/WF%20Step%20by%20Step/Chapter12.rar


--------------------- 

作者:hackpig

来源:www.skcircle.com

版权声明:本文为博主原创文章,转载请附上博文链接!


本文出自勇哥的网站《少有人走的路》wwww.skcircle.com,转载请注明出处!讨论可扫码加群:

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

会员中心
搜索
«    2024年4月    »
1234567
891011121314
15161718192021
22232425262728
2930
网站分类
标签列表
最新留言
    热门文章 | 热评文章 | 随机文章
文章归档
友情链接
  • 订阅本站的 RSS 2.0 新闻聚合
  • 扫描加本站机器视觉QQ群,验证答案为:halcon勇哥的机器视觉
  • 点击查阅微信群二维码
  • 扫描加勇哥的非标自动化群,验证答案:C#/C++/VB勇哥的非标自动化群
  • 扫描加站长微信:站长微信:abc496103864
  • 扫描加站长QQ:
  • 扫描赞赏本站:
  • 留言板:

Powered By Z-BlogPHP 1.7.2

Copyright Your skcircle.com Rights Reserved.

鄂ICP备18008319号


站长QQ:496103864 微信:abc496103864