在前一篇中,使用工作流设计器设计一个相当简单的工作流。现在,我们将使用编码的方式实现与前一篇相同的工作流。任何一个流程既可以用代码实现也可以用工作流设计器去实现。选择哪种方式是仁者见仁智者见智的问题。但是使用编码的方式去实现有利于更好的了解流程是如何运作的。
创建一个控制台应用程序
启动VS,创建一个简单的控制台应用程序(请注意这里没有使用workflow的模板),如图2-1所示。
图2-1.创建一个控制台应用程序
添加System.Activities引用。这样你就可以在这个控制台应用程序中使用WF4中的活动了。然后使用下面代码代替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();
上面和前一篇的main()函数的实现是一样的。如果你愿意,你可以简单地复制并粘贴前一篇的应用程序的代码。尽管如此,它们之间还是有点区别。看下面代码调用的是CreateWorkflow() ,而前一篇调用的是Workflow1()。
WorkflowInvoker.Invoke(CreateWorkflow());
由工作流设计器生成的Workflow1被定义在Workflow1.xaml文件中。CreateWorkflow()是你即将实现的一个方法。
定义工作流
正如我在前一篇所说的,一个工作流程只是的嵌套属性的集合。更准确地说,工作流是嵌套类和它们的属性的集合。为了简化这个过程,我将一次一个层级地向你演示如何去实现它们。首先将下面的方法添加到的Program.cs文件中:
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() { }; }
CreateWorkflow()方法首先创建了两个int类型的Variable,分别是numberBells和counter。它们可以供各种活动使用。
CreateWorkflow()方法将返回一个Activity类型的活动。Activity类型是WorkflowInvoker类的Invoke方法需要参数类型。Activity是所有活动的基类,包括Sequence活动。
故返回Sequence实例,而CreateWorkflow()方法返回类型是Activity。
实现层级1
现在,您已经定义了好了一个空的Sequence活动。相当于创建了一个没有子活动的顺序工作流。现在,在Sequence上面添加几个子活动,使用清单2-1所示的代码代替 return new Sequence()
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 } } };
此代码首先定义了Sequence活动的DisplayName属性和与Sequence相关的Variable对象。然后初始化子活动成员的集合。具体来说,它创建的子活动如表2-1。
对于WriteLine活动,还定义Text属性。至于其余的活动具体的实现将被定义在下一层级中。
实现层级2
首先,对于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 }
这段代码定义了I活动的Condition属性和Then属性(没有定义Else 分支)。Assign活动具体在下一层级中实现。Condition属性的定义,需要解释一下。
Expressions
ExpressionServices 类的静态方法Convert<T>() 用来创建InArgument<T> 类。它是Condition属性需要的类型。这些类和方法使用了泛型(<T>)。所以他们可以使用任何一种数据类型。这里,我们使用bool类型。因为If活动的Condition属性的值不是true就是false。这种表达式是通过lambda表达式实现的(类似LINQ的语法),用来从工作流运行时环境中获取数据。在一个lambda表达式中,=>是lambda运算符。左边的是输入参数,实际的表达被放置在lambda 运算符的右边。运行时支持env的值,当判断Condition的时候.
工作流实际上是没有状态的,它不存储数据元素。Variable类是简单的数据类型的定义。若要从一个Variable 变量中获取实际数据,您将使用它的Get()方法。这还需要一个ActivityContext类对象。这是用来区分特定的工作流实例,因为当前会运行一些其它工作流实例。比较Get(env)的返回值是否大于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属性和If活动的Condition属性是相同的。它使用ExpressionServices类创建一个InArgument<T>类的对象,类型是bool。在这个例子中,用它来判断count <= numberBells是否成立。对于这两个变量,使用Get(env)方法来获取实际的值。在第二个 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:00。在Then和Else属性中,都添加一个WriteLine 活动。一个输出“Good Evening”,另一个输出“Good Day”。
实现层级3:
对第一个If活动(名为“Adjust for PM”),你已经在它的Then属性中添加了一个空的Assign活动。在Assign活动中输入下面的实现的代码:
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>类型的Assign活动。To和Value属性也使用该模版类,所以他们应该是相同的类型(<int>)。To属性的类型是一个OutArgument,在它构造函数中传递了一个Variable对象。Value属性需要一个InArgument类型的对象。在if和while的Condition属性之前,使用这个活动。和Condition属性一样,在它的构造函数中,使用了Lambda表达式,。
Sequence
在While活动中,您已经创建一个空的Sequence活动。while循环每迭代一次,sequence活动里面的子活动就将会被执行一次。输入以下代码来填充Activities属性:
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) } }
这段代码在Sequence中添加了三个活动:
WriteLine活动用于显示counter
Assign活动用于增加counter的数量
Delay活动用于在循环中暂停流程一段时间
这个WriteLine活动的Text属性与其他的WriteLine活动不同,不是一个纯字符串。这个WriteLine活动中Text属性被定义成一个表达式。由于Text属性需要一个字符串类型,所以我们给它创建了一个InArgument<string>类。现在,你可能已经习惯了这些Lambda表达式。Variable类的Get(env)方法返回一个int类型的值。ToString()方法将它转换成字符串。传递了一个由静态方法FromSeconds() 创建的TimeSpan对象给Delay活动的Duration属性。
运行应用程序
按F5运行该应用程序。结果取决于一天的时间,您看起来应该类似这样:
Hello, World!
1
2
3
4
5
6
7
The time is: 10/5/2009 7:02:41 PM
Good Evening
Press ENTER to exit
Program.cs完整实现代码如清单2-2。
清单2-2.Program.cs
using System; using System.Activities; using System.Activities.Statements; using System.Activities.Expressions; namespace Chapter02 { class Program { static void Main(string[] args) { WorkflowInvoker.Invoke(CreateWorkflow()); Console.WriteLine("Press ENTER to exit"); Console.ReadLine(); } 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() { DisplayName = "Main Sequence", Variables = { numberBells, counter }, Activities = { new WriteLine() { DisplayName = "Hello",}, new 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 To = new OutArgument<int>(numberBells), Value = new InArgument<int> (env => numberBells.Get(env) - 12) } }, new 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 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) } } } }, new WriteLine() { DisplayName = "Display Time", Text = "The time is: " + DateTime.Now.ToString() }, new If() { DisplayName = "Greeting", // Code to be added here in Level 2 Condition = ExpressionServices.Convert<bool> (env => DateTime.Now.Hour >= 16), Then = new WriteLine() { Text = "Good Evening" }, Else = new WriteLine() { Text = "Good Day" } } } }; } } }
回顾
本书有一些示例是使用设计器的方式实现的,其他的是使用编码的方式实现的。开始学习的时候,使用设计器的方式可能比使用编码更容易接受和理解工作流。但是,如果你对工作流程非常熟悉的时候,你可能会发现编码的方式写起来更快。两种方式的最终结果是一样的,两种方法都非常地好。
本文转自麒麟博客园博客,原文链接:http://www.cnblogs.com/zhuqil/archive/2010/05/19/Coded-Workflows.html,如需转载请自行联系原作者