Beginning WF4读书笔计 - 第二章 基于后台代码的工作流
在上一章中,我们通过工作流引擎设计器成功的实现了一个简单的工作流示例。接下来我们将采用后台代码的方式来实现同样的一个流程。
控制台程序
首先创建一个控制台程序
添加对“Systm.Activties”的引用(注:这个库在进行工作流开发时必须引用的)
同时更改Program.cs中的命名空间如下:
using System; using System.Activities; using System.Activities.Statements; using System.Activities.Expressions;
并在main()函数中添加如下代码:
WorkflowInvoker.Invoke(CreateWorkflow()); Console.WriteLine("Press ENTER to exit"); Console.ReadLine();
此时,你会发现与第一章中的代码已经是大同小异了,只不过用CreateWorkflow函数代替了原来的new Workflow1()而已。不同的是Workflow1函数是由工作流设计器引擎自动生成的,而CreateWorkflow则要我们手工打造。
设计工作流
在上一章中,我们用工作流设计器设计时,我们可以简单的认为“workflow”其实是一个很很多的内嵌的“类”及期“属性”构成的集合。
接下来,我们要用后台代码来实现一个简单的实例。在Program.cs文件中添加对CreateWorkflow函数的实现,代码如下:
static Activity CreateWorkflow() { Variable<int> numberBells = new Variable<int>() { Name = "numberBells", Default = DateTime.Now.Hour }; Variable<int> counter = new Variable<int>() { Name = "counter", Default = 1 }; return new Sequence() { }; }
在此函数中,我们首先定义了两个基于“Variable<T>”的“int”类型的工作流变量,分别叫做“numberBells”和“counter”,他们将在后续的活动中被使用。(作用第一单中已介绍)
函数CreateWorkflow最终要求返回一个Activity类型的实例,并在WorkflowInvoker中被执行。其实通过代码不难发现,CreateWorkflow实际生成的是一个Sequance对象,这是因为Sequance是从Activity中继承而来的。(注:关于多态的说明,读者可以参考面向对象相关内容)
第一层
至此,我们已经定义了一个基本Sequance类型的空活动,接下来我们将对其进行充实。第一层代码如下:
return new Sequence() { DisplayName = "Main Sequence", Variables = {numberBells, counter }, Activities = { new WriteLine() { DisplayName = "Hello", Text = "Hello, World!" }, new If() { DisplayName = "Adjust for PM" // Code to be added here in Level 2 }, new While() { DisplayName = "Sound Bells" // Code to be added here in Level 2 }, new WriteLine() { DisplayName = "Display Time", Text = "The time is: " + DateTime.Now.ToString() }, new If() { DisplayName = "Greeting" // Code to be added here in Level 2 } } };
在代码中,首先是设置了“DisplayName”及活动中所关联的变量,同时初始化了一系列活动,统计如下:
活动 | 名称 |
WriteLine | Hello |
If | Adjust for PM |
While | Sound Bells |
WriteLine | Display Time |
If | Greeting |
其中“WriteLine”活动的内容已经设置,而其他主要的活动我们将在“第二层”中提供。
第二层
为了实现第一个“If”活动,我们输入以下代码:
DisplayName = "Adjust for PM", // Code to be added here in Level 2 Condition = ExpressionServices.Convert<bool> (env => numberBells.Get(env) > 12), Then = new Assign<int>() { DisplayName = "Adjust Bells" // Code to be added here in Level 3 }
在代码中我们设置了“Condition”和“Then”属性,(此处并没对“Else”分支的要求)。不过对“Assign”活动的实现我们将留在下一层中介绍。
表达式
ExpressionServices的Convert<T>静态函数,可以用来创建一个“InArgument<T>”类型的临时变量,而这个类型正是“Condition”属性所需要的。同时,由于这些类和方法都是基于泛型,所以可以应用于所有类型。在本示例中我们将需要生成一个应用于“Condition”属性的“bool”类型。
在WF环境中所有的表达式都是基于lambda表达式的。(省略一些原文中关于lambda表达式语法细节),在此处的表达式将为运行时的“Condition”进行求值。
由于工作流实际上是状态无关的,所以它不存储任务元素的数据。这就需要通过“Variable”类型来定义一些变量来存储一些普通数据,并且可能通过其Get()方法来获取此类型的真实值。当然这都必须依赖于活动的上下文。ActivityContext。在实际应用中众多的“Variable”将在特定的工作流实例中用来区分彼此,在本处将用来判断取值是否大于12.
现在,在“While”活动中输入如下代码:
DisplayName = "Sound Bells", // Code to be added here in Level 2 Condition = ExpressionServices.Convert<bool> (env => counter.Get(env) <= numberBells.Get(env)), Body = new Sequence() { DisplayName = "Sound Bell" // Code to be added here in Level 3 }
大家注意到其实“While”活动的“Condition”属性是依赖于一个判断值的,同样是一个通过“ExpressionService”来创建的“InArgument<T>”类型的“bool”变量。在此处将通过“count <= numberBells”来进行判断的。这时你会发现,其实两个变量都是能过其Get()方法来取得真实值的。
第二个“If”活动,我们取名为“Greeting”,并输入如下代码:
DisplayName = "Greeting", // Code to be added here in Level 2 Condition = ExpressionServices.Convert<bool> (env => DateTime.Now.Hour >= 18), Then = new WriteLine() { Text = "Good Evening" }, Else = new WriteLine() { Text = "Good Day" }
在代码中,会出现“Condition”中的输入参数“env”并没有真实使用,但在代码中还是被定义,并把当前时间的赋值给它,用来判断其值是否已过下午6点。在“Then”和“Else”属性中,分别创建了一个“WriteLine”活动,用来输出“Good Evening”和“Good Day”。
第三层
在第一个“If”活动(那个叫“Adjust for PM”的活动)中,我们创建了一个空的“Assign”活动在它的“Then”属性中。为了完善它,我们输入如下代码:
DisplayName = "Adjust Bells", // Code to be added here in Level 3 To = new OutArgument<int>(numberBells), Value = new InArgument<int>(env => numberBells.Get(env) - 12)
Assign活动
“Assign”类型是支持泛型的。在此处我们要操作的是一个整型值,所以可以定义为“Assign<int>”。在本活动的“TO”属性是一个“OutArgument”类型,这个将通过原先定定义好的一个变量来构造,而“Value”属性则使用了一个“InArgument”类型。本活动操作的结果,将在后继的“If”和“While”活动的“Condition”属性中用,所以必须在提前完成。
Sequence
在“While”活动中,我们为“Body”提供的是一个空“Sequence”。它定义的一系列活动将在循环体中不断的被执行。代码如下:
DisplayName = "Sound Bells", // Code to be added here in Level 2 Condition = ExpressionServices.Convert<bool> (env => counter.Get(env) <= numberBells.Get(env)), Body = new Sequence() { DisplayName = "Sound Bell", // Code to be added here in Level 3 Activities = { new WriteLine() { Text = new InArgument<string>(env => counter.Get(env).ToString()) }, new Assign<int>() { DisplayName = "Increment Counter", To = new OutArgument<int>(counter), Value = new InArgument<int>(env => counter.Get(env) + 1) }, new Delay() { Duration = TimeSpan.FromSeconds(1) } } }
以上代码为“Sequcence”添加了三个活动:
一个用于显示“counter”值的“WriteLine”活动。
一个用于增加“counter”值的“Assign”活动。
一个用于延时的“Delay”活动。
在本例中,“WriteLine”活动并不是每次都输出统一的文本,而是显示一个表达式的值。由于它的“Text”属性需要的是一个“string”类型,所以我们定义了一个“InArgument<string>”类型来提供支持。现在,我们可以使用这些lambda表达了,通过其Get()来获取变量的当前值,同时通过“ToString”把“int”转换成了“string”。
当然在“Delay”活动中,我们是通过“TimeSpan”类型的“FromSeconds”静态方法来实现延时效果的。
运行程序(F5)
源代码:Chapter02
与本系列相关的所有文档及代码索引请参考: