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

 

与本系列相关的所有文档及代码索引请参考:

《Beginning WF : Windows Workflow in .NET 4.0》读书笔记

posted @ 2013-04-09 10:06  家住腊树下  阅读(497)  评论(0编辑  收藏  举报