Beginning WF4读书笔记(二):编码实现工作流

简介:

前一篇中,使用工作流设计器设计一个相当简单的工作流。现在,我们将使用编码的方式实现与前一篇相同的工作流。任何一个流程既可以用代码实现也可以用工作流设计器去实现。选择哪种方式是仁者见仁智者见智的问题。但是使用编码的方式去实现有利于更好的了解流程是如何运作的。

创建一个控制台应用程序

    启动VS,创建一个简单的控制台应用程序(请注意这里没有使用workflow的模板),如图2-1所示。

Figure 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。
 

table2-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! 







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,如需转载请自行联系原作者





相关文章
|
XML JSON 数据格式
编码与模式------《Designing Data-Intensive Applications》读书笔记5
进入到第四章了,本篇主要聊的点是编码(也就是序列化)与代码升级的一些场景,来梳理存储之中涉及到的编解码的流程。目前主流的编解码便是来自Apache的Avro,来自Facebook的Thrift与Google的Protocolbuf,在本篇之中,我们也会一一梳理各种编码的优点与痛点。
1311 0