Workflow 4.0 简介

概述

欢迎使用 Windows Workflow Foundation 4 (WF),这是 Microsoft 快速构建基于工作流的应用程序的编程模型、引擎和工具。.NET Framework 4 中这个 WF 版本更改了上一个版本中的几种开发范式:现在可以更加轻松地创建、执行、维护和实现众多新功能。

在本实验中,您将了解创建、承载和运行工作流的基本知识。本实验旨在介绍 .NET Framework 4 和 Visual Studio 2010 中新的工作流创作结构,包括新的 Workflow Designer、表达式、变量和参数。此外,您还将了解一些基本的内置活动的使用。

在第一个练习中,您将创建一个在控制台窗口中打印问候消息的工作流应用程序。该应用程序的工作流将使用设计器和 XAML 构建,还将使用 C# 或 Visual Basic 代码。

在第二个练习中,您将向工作流添加更多条件逻辑,使用 If 活动根据自定义条件打印不同的问候消息。最后,在第三个练习中,您将了解如何使用异常处理活动处理运行工作流中的错误。

此外,在本实验中,您将使用“首先编写测试(write the test first)”的方法,这种方法坚持首先对要添加的新功能编写测试,然后实现必要的代码让测试通过。

目标

在本次动手实验中,您将学习:

•              如何使用设计器和 XAML 或者使用纯 C# 或 Visual Basic 代码创建顺序工作流。

•              如何使用 WorkflowApplication 和 WorkflowInvoker 类运行和测试顺序工作流

•              如何传递 InArguments 并从工作流接收 OutArguments

•              如何使用 Expressions 和 Variables

•              如何使用 WriteLine、If、TryCatch、Catch<T> 和 Throw 活动

•              如何从 .xaml 文件加载和运行活动

•              如何创建活动设计器

•              如何在自己的应用程序中承载 WorkflowDesigner

               

系统要求

您必须拥有以下工具才能完成本实验:

•             Microsoft Visual Studio 2010 Beta 2

•             Microsoft .NET Framework 4 Beta 2

安装

使用 Dependency Checker 验证本实验的所有先决条件。要确保正确配置所有内容,请按照以下步骤进行:

注意: 要执行安装步骤,您需要使用管理员权限在命令行窗口中运行脚本。

1.         如果之前没有执行,运行 Training Kit 的 Dependency Checker。为此,运行位于 %TrainingKitInstallationFolder%\Labs\IntroToWF\Setup 文件夹下的 CheckDependencies.cmd 脚本。安装先决条件中没有安装的软件(如有必要请重新扫描),并完成向导。

注意: 为了方便,本实验中管理的许多代码都可用于 Visual Studio 代码片段。CheckDependencies.cmd 文件启动 Visual Studio 安装程序文件安装该代码片段。如果在编写解决方案时无法找到该片段,请确保在 Visual Studio 2010 Code Snippets Repository 中安装了该代码片段。

               

练习

本次动手实验由以下练习组成:

1.            Hello Workflow

2.            重构工作流

3.            CodeActivity

4.            XAML 动态工作流

5.            测试工作流

6.            WorkflowApplication

7.            添加 If/Else 逻辑

8.            错误处理

9.            活动设计器

10.          托管设计器

               

初始材料

这次动手实验包括以下初始材料。

•              Visual Studio 解决方案。您将发现可以用作练习起点的 C# 和 Visual Basic Visual Studio 解决方案,具体解决方案取决于练习。

如果我束手无策了怎么办?

该动手实验中的源代码包括一个最终文件夹,如果完成了每个练习的每一步,您可以在该文件夹中找到应该获取的 Visual Studio 解决方案。如果需要其他帮助来完成练习,您可以使用该解决方案作为指南。

在使用工作流设计器打开任何文件之前切记生成解决方案。

练习 1:Hello Workflow

工作流是执行业务流程的一种方式。流程中的每个步骤都由 活动 来实现。

在本练习中,您将使用 Windows Workflow Foundation 4 创建和测试简单的“Hello World”业务流程。

任务 1 –创建简单的 Hello Workflow 应用程序

在本任务中,您将创建一个非常简单的工作流,等效于以下代码:

C#

private static void SayHello()

{

Console.WriteLine("Hello Workflow 4");

}

Visual Basic

Private Shared Sub SayHello()

Console.WriteLine("Hello Workflow 4")

End Sub

1.            从 Start | All Programs | Microsoft Visual Studio 2010 启动 Microsoft Visual Studio 2010。

2.            在 Visual Studio 2010 中新建一个类型为 Workflow Console Application(位于 Workflow 项目类型下)的项目,使用 Visual C# 或 Visual Basic 项目模板列表。确保选择了 .NET Framework 4 作为目标运行时,并将其命名为 HelloWorkflow。将解决方案命名为 Begin,设置期望的位置并单击 OK。

 

图 1

新建一个工作流控制台应用程序 (C#)

 

图 2

新建一个工作流控制台应用程序 (Visual Basic)

3.            由于您的业务流程只需要一个步骤,您只需要添加一个 WriteLine 活动来实现该流程。从 Toolbox 拖动 WriteLine 活动并放入设计界面。

 

图 5

添加一个 WriteLine 活动

注意:如果 Toolbox 窗口不可见,请从 View 菜单选择 Toolbox。

4.            将 WriteLine Text 属性设置为 "Hello Workflow 4"。

 

图 6

设置 Text 属性

 WriteLine 活动

WriteLine 是一个简单的活动,用于在控制台显示一条消息。文本属性是一个表达式,可以是调用函数或对对象属性求值的结果。在本例中,表达式是文字字符串,因此您必须为字符串加上引号。

               

后续步骤

练习 1:验证

               

练习 1:验证

1.            按 CTRL+F5,在不调试的情况下编译并运行工作流。应用程序应该在控制台窗口中运行,并打印消息“Hello Workflow 4”。

 

图 3

完成的 HelloWorkflow 应用程序

               

后续步骤

练习 2:重构工作流

               

练习 2:重构工作流

注意:在本练习中,您将重构练习 1 中创建的解决方案。如果尚未完成练习 1,您可以使用本实验提供的解决方案。在 %TrainingKitInstallFolder%\Labs\IntroToWF\Source\Ex2-RefactoringWorkflow\Begin 文件夹中,您将发现 C# 和 Visual Basic 的初始解决方案。

即使您的应用程序能够生效,您也可能想做一些改进。在本练习中,Workflow1 不是一个表意很清晰的名称,将其更改为 SayHello。

任务 1 –将 Workflow1 重命名为 SayHello

1.            要更改 Workflow1.xaml 中定义的工作流名称,使用工作流设计器打开它并单击设计器界面的空白区域。这使您能更改工作流的属性。

 

图 4

单击设计器界面的空白区域,以设置工作流名称

2.            在 Properties 窗口中,将工作流的 Name 属性设置为 HelloWorkflow.SayHello (C#) 或 SayHello (VB)

 

图 5

设置工作流的 Name 属性 (C#)

 

图 6

设置工作流的 Name 属性 (VB)

3.            尽管不是必需的,但最好让 .xaml 文件名与所包含的活动名称匹配。在 Solution Explorer 中,右键单击 Workflow1.xaml,选择 Rename 并键入 SayHello.xaml。

  注意

重命名 C# 或 Visual Basic 文件时,Visual Studio 会询问您是否需要重构以同时更改类名称。在重命名 XAML 文件时不会遇到这种情况,因此您如果想更改工作流的名称,需要手动进行。

4.            按 CTRL+SHIFT+B 生成解决方案。编译将会失败,因为您更改了工作流名称。

 

图 7

工作流编译失败 (C#)

 

图 8

工作流编译失败 (VB)

   为什么在我更改工作流名称时应用程序编译失败?

您的工作流实际上是在 XAML 中声明的类,继承自 System.Activities.Activity。工作流的名称是必需创建来运行工作流的那个类的名称。当您设置 Name 属性时,您更改了该类的名称。

               

任务 2 –更新 Main 方法以使用新名称

WF4 使用工作流运行时运行工作流。调用运行时的最简单方式是使用 WorkflowInvoker 类,这也是 Workflow Console Application 模板使用的类。

1.            要修复生成中断,打开 Program.cs (C#) 或 Module1.vb (VB),并将其修改为使用新的 SayHello 类名。为此:

a.            找到 WorkflowInvoker.Invoke 方法调用。

b.            将其修改为使用 SayHello 而不是 Workflow1,如以下代码所示(粗体显示的部分)。

C#

static void Main(string[] args)

{

  WorkflowInvoker.Invoke(new SayHello());

}

Visual Basic

Shared Sub Main()

  WorkflowInvoker.Invoke(New SayHello())

End Sub

  为什么 Visual Studio 发出警告说没有定义 SayHello 类型?

如果在使用新的 SayHello 名称编译工作流之前更改 Main(),您可能会收到没有定义 SayHello 类型的警告。原因是生成解决方案之前 Visual Studio 无法感知 XAML 中声明的类型。

               

后续步骤

练习 2:验证

               

练习 2:验证

1.            按 CTRL+SHIFT+B 生成解决方案。现在应该能够正常生成解决方案了。

2.            按 CTRL+F5 运行解决方案。您应该看到与之前的“Hello Workflow 4”相同的消息。

 

图 9

Hello Workflow 4 消息

               

后续步骤

探索:XAML 和工作流

               

探索:XAML 和工作流

默认情况下,工作流存储在 .xaml 文件中。不用担心,由于有了工作流设计器,如果您不乐意也可以不读取、编写或部署 .xaml 文件。就像 .cs 或 .vb 文件一样,编译 .xaml 文件,其中声明的类型包含在项目创建的程序集中。

如果想要查看 XAML,右键单击 SayHello.xaml 文件并选择 View Code。Visual Studio 将警告您文件已在设计器中打开

 

图 10

文件已在设计器中打开的警告

单击 Yes 关闭设计器,您将看到 XAML 中的工作流定义

XAML

<Activity mc:Ignorable="sap"

x:Class="SayHello"

xmlns="http://schemas.microsoft.com/netfx/2009/xaml/activities"

     ...

<WriteLine ...Text="Hello Workflow 4" />

</Activity>

               

后续步骤

练习 3:CodeActivity

练习 3:CodeActivity

注意:如果还没有完成练习 2,您可以使用本实验提供的解决方案。在 %TrainingKitInstallFolder%\Labs\IntroToWF\Source\Ex3-CodeActivity\Begin 文件夹中,您将找到 C# 和 Visual Basic 的初始解决方案。

到目前为止您已经知道,WF4 包括一个设计器,用于编辑 .xaml 文件,还包含一个运行时,用于调用活动。编写工作流时您将创建一种新的活动,由于活动是继承自 System.Activities.Activity 或其子类的类,所以可能使用 C#、VB 或 XAML 声明工作流。在本练习中,您将使用 C# 或 VB 创建一个活动来实现您的“问候”业务流程。

活动实现业务流程。有些活动将通过调用其他活动来实现流程。例如,SayHello 活动实际上不向控制台写入文本,而是使用 WriteLine 活动完成工作。您还可以使用 C# 或 VB 实现相同的 SayHello 活动,只需继承 System.Activities.Activity 并重写 Implementation 属性即可,如下例所示:

C#

public class SayHelloActivity :Activity

{

public override Func<Activity> Implementation

  {

get

    {

// Return a Lambda expression

// that creates an implementation for

// the activity

return () =>

        {

return new WriteLine()

         {

Text = "Hello Workflow 4"

         };

        };

    }

  }

}

Visual Basic

Public Class SayHelloActivity

Inherits Activity

Public Overrides Property Implementation As System.Func(Of Activity)

Get

      ' Return a function that

      ' creates an implementation for

      ' the activity

Return Function()

Return New WriteLine() With {.Text = "Hello Workflow 4"}

End Function

End Get

Set(ByVal value As System.Func(Of Activity))

MyBase.Implementation = value

End Set

End Property

End Class

如果您通过其他活动创建流程,并像在练习 1 中一样使用 WriteLine 活动完成工作,那么该方法将非常有用。但是,有时需要创建自行实现业务逻辑或调用另一个不是活动的类来完成工作的活动。为此,您需要继承另一个基类 System.Activities.CodeActivity,并改写 Execute 方法。

任务 1 –创建 SayHelloInCode 活动

在本任务中,您将使用代码创建一个活动,并使用 Console.WriteLine 向控制台写入文本

1.            右键单击 HelloWorkflow 项目,指向 Add 并单击 Class。在 Name 框中输入 SayHelloInCode。

2.            将以下命名空间指令添加到文件中。

C#

using System.Activities;

Visual Basic

Imports System.Activities

3.            CodeActivity 是一个抽象类。这意味着当您从中继承时,您必须重写 Execute 方法。这就是您完成活动的工作的地方。将类的默认实现替换为以下代码:

(代码片段 - WF 简介实验 - SayHelloInCode 类 CSharp)

C#

public class SayHelloInCode :CodeActivity

{

  protected override void Execute(CodeActivityContext context)

  {

    Console.WriteLine("Hello Workflow 4 in code");

  }

}

  (代码片段 - WF 简介实验 - SayHelloInCode 类 VB)

Visual Basic

Public Class SayHelloInCode

  Inherits CodeActivity

  Protected Overrides Sub Execute(ByVal context As CodeActivityContext)

    Console.WriteLine("Hello Workflow 4 in code")

  End Sub

End Class

               

任务 2 –更新 Main 以调用 SayHelloInCode

1.            更改 Program.cs (C#) 或 Module1.vb (Visual Basic),使用新的 SayHelloInCode 类。为此,找到 WorkflowInvoker.Invoke 方法代码,并使用以下代码替代:

C#

static void Main(string[] args)

{

  WorkflowInvoker.Invoke(new SayHelloInCode());

}

Visual Basic

Shared Sub Main(ByVal args() As String)

  WorkflowInvoker.Invoke(New SayHelloInCode())

End Sub

               

后续步骤

练习 3:验证

               

练习 3:验证

1.            按 CTRL+F5,在不调试的情况下运行工作流。应用程序应该在控制台窗口中运行,并打印消息“Hello Workflow 4 in code”。

 

图 11

完成的 HelloWorkflow 应用程序

  我为什么要创建一个代码活动?

在代码中编写业务逻辑不是什么新鲜事物。为什么要在继承自 CodeActivity 的特殊类中编写代码呢?这样做的原因在于,现在让您的业务逻辑可以组合到使用工作流运行时的其他更大业务流程中。您将在本实验的后面部分看到,还可以从线程和数据管理模型中受益,可提供高度可扩展和长期运行的业务应用程序。

               

后续步骤

练习 4:XAML 动态工作流

               

练习 4:XAML 动态工作流

注意:如果还没有完成练习 3,您可以使用本实验提供的解决方案。在 %TrainingKitInstallFolder%\Labs\IntroToWF\Source\Ex4-DynamicWorkflows\Begin 文件夹中,您将发现 C# 和 Visual Basic 的初始解决方案。

到目前为止,您已经在 .xaml、.cs 或 .vb 文件编写了工作流。这些文件可以编译为项目程序集中包含的类型,并由工作流运行时运行。

虽然看起来源文件的格式没什么关系,但 .xaml 文件的优点远远超过使用 C# 或 VB 编写的工作流。

•              工作流设计器仅支持 .xaml 文件,使用 C# 或 VB 编写的工作流没有设计器支持。

•              XAML 可以动态加载和动态,无需编译到程序集中

动态工作流为希望生成业务逻辑,或者决定要加载或运行的哪个业务逻辑的程序提供了一些有趣的功能。

任务 1 –修改 SayHello.xaml 文件属性

在本任务中,您将修改 HellowWorkflow 程序以加载和运行 SayHello.xaml 文件。然后修改 SayHello.xaml 的文本,并在下次运行应用程序时观察消息中的变化。

1.            您需要告知 Visual Studio 将 SayHello.xaml 视为必须部署的内容,而不是必须编译的代码。为此

a.            在 Solution Explorer 中选择 SayHello.xaml

b.            在 Properties 窗口中,将构建操作设置为 Content

c.             将 SayHello.xaml 中的 Copy To Output 文件属性设置为 Copy if newer

d.            将 Custom Tool 设置为空

 

图 12

更改 SayHello.xaml 的属性,将其视为内容

               

任务 2 –修改 Main() 以加载 SayHello.xaml 文件

您的类在之前编译为类型。要从 .xaml 文件调用工作流,您将需要使用 ActivityXamlServices 将 .xaml 文件加载到内存中,并创建 WorkflowInvoker 可以调用的 System.Activities.DynamicActivity 实例。记住,在调用工作流时,.xaml 文件引用中的任何程序集都必须可用。

1.            将以下命名空间指令添加到文件中。

C#

using System.Activities.XamlIntegration;

Visual Basic

Imports System.Activities.XamlIntegration

2.            修改 program.cs 以使用 ActivityXamlServices,并向 Console.ReadKey 添加调用,这将使您能够更加轻松地从 Windows 资源管理器看到运行应用程序时发生的情况。为此,使用以下代码替换 Main 方法实现:

C#

static void Main(string[] args)

{

  WorkflowInvoker.Invoke(ActivityXamlServices.Load("SayHello.xaml"));

  Console.ReadKey(false);

}

Visual Basic

Shared Sub Main()

  WorkflowInvoker.Invoke(ActivityXamlServices.Load("SayHello.xaml"))

  Console.ReadKey(False)

End Sub

               

后续步骤

练习 4:验证

               

练习 4:验证

在本验证中,您将运行 HelloWorld 应用程序,然后修改 SayHello.xaml 文件并查看控制台窗口中是否出现了新文本。

1.            按 CTRL+F5,在不调试的情况下运行工作流。应用程序应该在控制台窗口中运行,并打印消息“Hello Workflow 4”。

2.            导航到项目文件夹下的 Bin\Debug 目录,找到 SayHello.xaml。

 

图 13

在项目的 Bin\Debug 目录中找到 SayHello.xaml 文件

3.            右键单击 SayHello.xaml 并选择 Edit 在记事本中打开。

 

图 14

右键单击并选择 Edit

4.            在记事本中,将 WriteLine 活动的 Text 属性更改更改为“Hello Workflow 4 XAML”,然后保存并关闭。

 

图 15

在记事本中更改 Text 属性

5.            在 Windows 资源管理器中,运行 HelloWorkflow.exe,它现在将显示“Hello Workflow 4 from XAML”。按任意键退出

 

图 16

HelloWorkflow.exe 显示了来自 .xaml 文件的新消息

               

后续步骤

练习 5:测试工作流

               

练习 5:测试工作流

到现在为止,应用程序还不是很有趣。它只能在控制台中工作,无法接受任何输入参数。大部分有实用价值的应用程序都应该处理输入和输出参数。此外,在当前窗体中不能轻松测试应用程序。

在本任务中,您将修改 SayHello 活动以使用参数,并准备在其他非控制台应用程序环境中使用它,使其返回一个问候语句而不是使用 WriteLine 活动直接写入控制台。您将通过“首先编写测试”方法实现这一点。首先创建一个失败的测试,然后添加必要的代码以通过测试。

得到的应用程序将等效于以下代码。

C#

private static string SayHello(string name)

{

return "Hello " + name + " from Workflow 4";

}

Visual Basic

Private Shared Function SayHello(ByVal name As String) As String

  Return "Hello " & name & " from Workflow 4"

End Function

任务 1 –创建单元测试项目

1.            打开 %TrainingKitInstallFolder%\Labs\IntroToWF\Source\Ex5-TestingWorkflows\Begin 文件夹下的 Begin 解决方案。您也可以继续使用练习 3 中得到的解决方案。

2.            首先为工作流创建一个单元测试,以确保它的行为正常。在 Solution Explorer 中,右键单击 Begin 解决方案并指向 Add,然后单击 New Project。在 Add New Project 对话框中,在 Visual C# 或 Visual Basic 模板列表的 Test 项目类型下选择 Test Project,然后输入 HelloWorkflow.Tests 作为项目 Name。保留默认的位置。

 

图 17

向解决方案添加新的 Test Project (C#)

 

图 18

向解决方案添加新的 Test Project (Visual Basic)

3.            右键单击 HelloWorkflow.Tests 项目并单击 Add Reference。使用 Projects 选项卡,添加对 HelloWorkflow 项目的项目引用。重复上述步骤,使用 .NET 选项卡添加对 System.Activities 库的引用。

4.            右键单击 UnitTest1.cs (C#) 或 UnitTest1.vb (VB),单击 Rename 并将其名称更改为 SayHelloFixture.cs (C#) 或 SayHelloFixture.vb (VB)。当提示重命名 UnitTest1 类时选择 Yes。

               

任务 2 –创建测试

在本任务中,在实际实现活动行为之前创建测试。

1.            将以下命名空间指令添加到 SayHelloFixture.cs (C#) 文件或 SayHelloFixture.vb (VB) 文件中:

C#

using System.Activities;

Visual Basic

Imports System.Activities

2.            创建一个测试,确保工作流正确地显示了问候信息。为此,打开 HelloWorkflow.Tests 项目中的 SayHelloFixture.cs (C#) 或 SayHelloFixture.vb (VB)。

3.            SayHello 活动还不能接受任何参数,但是可以编写代码来调用它,就像它能够接受参数。这可以让我们考虑使用它时活动界面的外观。如果 Visual Studio 警告您没有正确定义 UserName,不用担心。使用以下代码替换 TestMethod1:

(代码片段 - WF 简介实验 - ShouldReturnGreetingWithName TestMethod CSharp)

C#

[TestMethod]

public void ShouldReturnGreetingWithName()

{

  var output = WorkflowInvoker.Invoke(

    new SayHello()

    {

      UserName = "Test"

    });

  Assert.AreEqual("Hello Test from Workflow 4", output["Greeting"]);

}

(代码片段 - WF 简介实验 - ShouldReturnGreetingWithName TestMethod VB)

Visual Basic

<TestMethod()> Public Sub ShouldReturnGreetingWithName()

  Dim output = WorkflowInvoker.Invoke( _

    New SayHello() With {.UserName = "Test"})

  Assert.AreEqual("Hello Test from Workflow 4", output("Greeting"))

End Sub

   我如何将参数传递给活动?

您可以创建该活动并使用对象初始化程序初始化参数(公开的属性),也可以传递 Dictionary<string, object> (C#) 或 Dictionary(Of String, Object) (VB) 的输入参数,映射到活动输入参数的名称。

   我如何从输出获取数据?

输出变量是一个 IDictionary<string, object> (C#) 或 IDictionary(Of String, Object) (VB),包括使用变量名称作为键的输出变量映射。

               

任务 3 –编译应用程序

活动的界面(输入和输出参数方面)看起来不错,但是您的应用程序并没有编译。第一个目标是让应用程序进入编译状态。

1.            按 CTRL+SHIFT+B 编译应用程序,它应该失败并出现一个编译错误

 

图 19

UserName 尚未定义 (C#)

 

图 20

UserName 尚未定义 (VB)

2.            在设计器中打开 SayHello.xaml

3.            单击 Arguments 打开参数面板

 

图 21

单击 Arguments 打开参数面板

4.            添加 UserName 和 Greeting 参数,如以下屏幕截图所示:

 

图 22

向活动声明参数

   参数

在 Windows Workflow Foundation (WF) 中,参数表示进出活动的数据流.活动有一组参数,它们组成了活动的签名。每个参数都有特定的方向:输入,输出,或者输入/输出。

5.            按 CTRL+SHIFT+B 生成解决方案,现在应该能够正常生成。

               

任务 4 –查看测试失败

查看测试失败很重要,因为测试中可能存在让测试总是成功的 bug。在本任务中,您将确保测试一定会失败。

1.            打开 SayHelloTestFixture.cs (C#) 或 SayHelloTestFixture.vb (VB) 文件。

2.            按 CTRL+R, T 在当前环境中运行单元测试。测试将运行但是它将失败,因为活动没有在 Greeting 输出参数中返回任何内容。

 

图 23

ShouldReturnGreetingwithName 测试

               

任务 5 –让测试通过

知道测试能正常工作之后,您需要做一件最简单的事情让测试通过。

1.       使用 WriteLine 向控制台写入文本是无法让测试通过的,因此右键单击并选择 Delete ( ) 删除 WriteLine 活动。

2.            您需要设置 Greeting 输出参数的值。为此,从工具箱拖动一个 Assign 活动到设计器界面上。

 

图 24

将 Assign 活动拖到设计界面上

3.            将 To 属性设置为 Greeting

 

图 25

在左边的框中输入 Greeting

4.            您可以在设计界面中输入问候表达式,但由于表达式不长,可以使用属性窗口。单击 Value 属性右边的按钮打开表达式编辑器

 

图 26

单击 Value 属性右边的按钮

5.            在表达式编辑器中,您可以输入更长的表达式。将表达式设置为 "Hello " & UserName & " from Workflow 4"

 

图 27

设置 Greeting 参数的 Assign 活动

6.            按 CTRL+SHIFT+B 生成解决方案,它应该会成功编译。

               

后续步骤

练习 5:验证

               

练习 5:验证

现在可以运行测试并查看活动是否能通过测试了

1.            打开 Test View 窗口。为此,指向 Test 菜单上的 Windows 并单击 Test View ( )。

2.            在 Test View 中,选择 ShouldReturnGreetingWithName 测试并单击 Run Selection 按钮 ( )。

 

图 28

选择并运行 ShouldReturnGreetingWithName 测试

3.            验证测试运行是否通过。

 

图 29

测试通过了

               

后续步骤

练习 6:WorkflowApplication

               

练习 6:WorkflowApplication

注意:如果还没有完成练习 5,您可以使用本实验提供的解决方案。在 %TrainingKitInstallFolder%\Labs\IntroToWF\Source\Ex6-WorkflowApplication\Begin 文件夹中,您将发现 C# 和 Visual Basic 的初始解决方案。

到目前为止,您已经创建了一个活动,并通过 WorkflowInvoker 类使用最简单的方法调用了该活动。WorkflowInvoker.Invoke 方法很简单,因为它是同步的,可以在与调用者相同的线程上调用工作流。

另一种方法是使用 WorkflowApplication 类调用工作流。该类支持在分离的线程上运行工作流,并提供了可在工作流完成、空闲、终止或者存在未处理异常时调用的代理。与没有工作流时相比,您可以更加轻松地创建多线程服务器或客户端程序。

在本练习中,您将修改主机应用程序,使用 WorkflowApplication 运行 SayHello,并观察线程行为。因此,现在您的工作流有两个要求:

1.            返回个性的问候语句

2.            返回非零的 Int32 值,其中包含调用工作流时适用的托管线程 ID

使用“首先编写测试”方法,编写一个测试来验证工作流线程 ID 的新要求。

               

任务 1 –编写测试来验证工作流线程 ID 是否作为输出参数返回

在本任务中,您将创建一个测试来验证工作流是否在名为 WorkflowThread 的输出参数中返回非零整数。

1.            打开 SayHelloFixture.cs 并添加以下命名空间指令

C#

using System.Threading;

using System.Diagnostics;

Visual Basic

Imports System.Threading

2.            按照演示添加 ShouldReturnWorkflowThread 测试

(代码片段 - WF 简介实验 - ShouldReturnWorkflowThread TestMethod CSharp)

C#

/// <summary>

/// Verifies that the workflow returns an Out Argument

/// Name:WorkflowThread

/// Type:Int32

/// Value:Non-Zero

/// </summary>

[TestMethod]

public void ShouldReturnWorkflowThread()

{

  var output = WorkflowInvoker.Invoke(

   new SayHello()

   {

     UserName = "Test"

   });

  Assert.IsTrue(output.ContainsKey("WorkflowThread"),

    "SayHello must contain an OutArgument named WorkflowThread");

  // Don't know for sure what it is yet

  var outarg = output["WorkflowThread"];

  Assert.IsInstanceOfType(outarg, typeof(Int32),

    "WorkflowThread must be of type Int32");

  Assert..AreNotEqual(0, outarg,

    "WorkflowThread must not be zero");

  Debug..WriteLine("Test thread is " +

          Thread.CurrentThread.ManagedThreadId);

  Debug..WriteLine("Workflow thread is " + outarg.ToString());

}

(代码片段 - WF 简介实验 - ShouldReturnWorkflowThread TestMethod VB)

Visual Basic

''' <summary>

''' Verifies that the SayHello workflow contains an Out Argument

''' Name:WorkflowThread

''' Type:Int32

''' Value:Non-Zero

''' </summary>

''' <remarks></remarks>

<TestMethod()> Public Sub ShouldReturnWorkflowThread()

  Dim output = WorkflowInvoker.Invoke( _

  New SayHello() With {.UserName = "Test"})

  Assert.IsTrue(output.ContainsKey("WorkflowThread"),

   "SayHello must contain an OutArgument named WorkflowThread")

  ' Don't know for sure what it is yet

  Dim outarg = output("WorkflowThread")

  Assert.IsInstanceOfType(outarg, GetType(Int32),

    "WorkflowThread must be of type Int32")

  Assert..AreNotEqual(0, output("WorkflowThread"),

    "WorkflowThread must not be zero")

  Debug..WriteLine("Test thread is " & _

    Thread..CurrentThread.ManagedThreadId)

  Debug..WriteLine("Workflow thread is " & outarg.ToString())

End Sub

3.            按 CTRL + SHIFT + B 重新编译应用程序。

4.       按 CTRL+R, T 在当前环境中运行所有测试。您应该看到测试失败,因为还没有添加 WorkflowThread 输出参数。

 

图 30

一个测试通过了,一个测试失败了,因为您没有 WorkflowThread 参数

               

任务 2 –作为参数返回 WorkflowThread

有了验证行为的测试之后,您需要修改工作流让测试通过。

1.            添加一个名为 WorkflowThread,类型为 Int32 的 Out 参数

 

图 31

添加 WorkflowThread 输出参数

到目前为止,您的工作流只有一个活动。但现在您需要两个活动,一个用于分配问候语句,另一个用于分配工作流线程。您需要修改工作流,以使用包含两个分配活动的活动,有许多活动可以选择,但让我们从最简单的开始,即 Sequence 活动。

2.            必须首先移除现存的 Assign 活动,然后才能在设计器中放入序列。您将在序列中使用该分配活动,因此需要剪切、放置序列,然后将其粘贴回去。右键单击 Assign 活动并选择 Cut。

 

图 32

剪切 Activity

3.            将 Sequence 拖到设计界面上

 

图 33

将 Sequence 放到设计器上

4.            右键单击序列内部并选择 Paste 将 Assign 活动放入序列。

 

图 34

在序列中粘贴分配活动

5.            在工作流中导入 System.Threading 命名空间。单击 Imports 并添加 System.Threading

 

图 35

单击 Imports 并添加 System.Threading

   我必须将 System.Threading 添加到 Imports 吗?

不用,就像在任何 C# 或 VB 项目中一样,这是可选的。如果没有导入命名空间,您必须像在 System.Threading.Thread 中一样全面限定命名空间的类。

6.            现在需要将当前托管线程 ID 分配给 WorkflowThread 输出参数。将 Assign 活动放到第一个 Assign 活动之前的 Sequence 上,并按照演示设置属性

 

图 36

按照演示设置第二个 Assign 活动的属性

7.            按 CTRL + SHIFT + B 重新编译应用程序。

8.            按 CTRL+R, T 在当前环境中运行测试。测试现在能够通过。如果希望查看工作流返回的线程,双击 ShouldReturnWorkflowThread 测试。测试结果中将出现 Debug 输出。实际的线程 ID 将因计算机不同而不同,但是应该注意,测试的 ID 与工作流的线程 ID 应该是相同的,因为 WorkflowInvoker 在调用线程时会同步调用工作流。

 

图 37

测试结果中出现的调试跟踪输出

               

任务 3 –修改测试以使用 WorkflowApplication

您的测试效果不错,但是有一点不足。它验证返回的 WorkflowThread 是否非零,但是不验证它是否返回运行工作流的实际托管线程 ID。如果工作流始终返回 1,您的测试应该能够通过。

如果希望验证实际线程 ID,您需要使用 WorkflowApplication 调用它。在本任务中,您将修改测试以在调用 WorkflowApplication.Completed 操作过程中捕获工作流线程,然后比较该值与工作流返回的值。

1.            打开 SayHelloFixture.cs (C#) 或 SayHelloFixture.vb (VB),然后找到 ShouldReturnWorkflowThread 测试。使用以下代码替换测试,以使用 WorkflowApplication 类运行工作流。该代码将使用 WorkflowApplication.Completed 操作捕获输出参数和线程 ID。

(代码片段 - WF 简介实验– ShouldReturnWorkflowThread WorkflowApplication TestMethod CSharp)

C#

/// <summary>

/// Verifies that the workflow returns an Out Argument

/// Name:WorkflowThread

/// Type:Int32

/// Value:Non-Zero, matches thread used for Completed action

/// </summary>

[TestMethod]

public void ShouldReturnWorkflowThread()

{

  AutoResetEvent sync = new AutoResetEvent(false);

  Int32 actionThreadID = 0;

  IDictionary<string, object> output = null;

  WorkflowApplication workflowApp =

    new WorkflowApplication(

         new SayHello()

         {

     UserName = "Test"

         });

  // Create an Action<T> using a lambda expression

  // To be invoked when the workflow completes

  workflowApp.Completed = (e) =>

    {

      output = e.Outputs;

      actionThreadID = Thread.CurrentThread.ManagedThreadId;

      // Signal the test thread the workflow is done

      sync.Set();

    };

  workflowApp.Run();

  // Wait for the sync event for 1 second

  sync.WaitOne(TimeSpan.FromSeconds(1));

  Assert.IsNotNull(output,

    "output not set, workflow may have timed out");

  Assert.IsTrue(output.ContainsKey("WorkflowThread"),

    "SayHello must contain an OutArgument named WorkflowThread");

  // Don't know for sure what it is yet

  var outarg = output["WorkflowThread"];

  Assert.IsInstanceOfType(outarg, typeof(Int32),

    "WorkflowThread must be of type Int32");

  Assert..AreNotEqual(0, outarg,

    "WorkflowThread must not be zero");

  Debug..WriteLine("Test thread is " +

          Thread.CurrentThread.ManagedThreadId);

  Debug..WriteLine("Workflow thread is " + outarg.ToString());

}

(代码片段 - WF 简介实验– ShouldReturnWorkflowThread WorkflowApplication TestMethod VB)

Visual Basic

''' <summary>

''' Verifies that the SayHello workflow contains an Out Argument

''' Name:WorkflowThread

''' Type:Int32

''' Value:Non-Zero, matches thread used for Completed action

''' </summary>

''' <remarks></remarks>

<TestMethod()> Public Sub ShouldReturnWorkflowThread()

  Dim sync As New AutoResetEvent(False)

  Dim actionThreadID As Int32 = 0

  Dim output As IDictionary(Of String, Object) = Nothing

  Dim workflowApp As New WorkflowApplication( _

    New SayHello() With {.UserName = "Test"})

  workflowApp.Completed = _

    Function(e)

      output = e.Outputs

      actionThreadID = Thread.CurrentThread.ManagedThreadId

      ' Signal the test thread the workflow is done

      sync.Set()

      ' VB requires a lambda expression to return a value

      ' It is not used with Action(Of T)

      Return Nothing

    End Function

  workflowApp.Run()

  ' Wait for the sync event for 1 second

  sync.WaitOne(TimeSpan.FromSeconds(1))

  Assert.IsNotNull(output, "output not set, workflow may have timed out")

  Assert.IsTrue(output.ContainsKey("WorkflowThread"),

         "SayHello must contain an OutArgument named WorkflowThread")

  ' Don't know for sure what it is yet

  Dim outarg = output("WorkflowThread")

  Assert.IsInstanceOfType(outarg, GetType(Int32),

            "WorkflowThread must be of type Int32")

  Assert..AreNotEqual(0, output("WorkflowThread"),

            "WorkflowThread must not be zero")

  Debug..WriteLine("Test thread is " & _

        Thread..CurrentThread.ManagedThreadId)

  Debug..WriteLine("Workflow thread is " & outarg.ToString())

End Sub

   代理,不是事件

WorkflowApplication.Completed 和另一个 WorkflowApplication 属性不是事件,而是代理。处理它们需要提供一个方法,匿名代理或 lambda 表达式。

               

后续步骤

练习 6:验证

               

练习 6:验证

现在可以运行

测试并查看活动是否能在新要求下通过测试

1.            按 CTRL+R, T 在当前环境中运行测试

2.            验证测试运行是否通过。

 

图 38

测试通过了

3.            双击测试结果查看有关线程的调试消息

 

图 39

测试结果显示工作流在其他线程上运行

               

后续步骤

练习 7:添加 If/Else 逻辑

               

练习 7:添加 If/Else 逻辑

注意:如果您没有完成

练习 6,您可以使用本实验提供的解决方案。在 %TrainingKitInstallFolder%\Labs\IntroToWF\Source\Ex7-IfElse\Begin 文件夹中,您将发现 C# 和 Visual Basic 的初始解决方案。

在上一个练习中,您创建了一个增强的 Hello Workflow 应用程序,带有自定义的问候消息。在本练习中,您将向工作流添加 If/Else 逻辑,根据自定义情况显示不同的问候消息。

本练习将使用“首先编写测试”方法,即首先为新要求编写一个测试,然后实现必要的代码以通过测试。

任务 1 –针对新要求编写测试

现在对应用程序提出了一个新的要求。如果名称字母数为奇数,您希望问候语句的第一个单词为“Greetings”,否则为“Hello”。举例来说,现在工作流将等效于以下代码:

C#

private static string SayHello(string userName)

{

string FirstWord = null;

if (userName.Length % 2 == 0)

FirstWord = "Hello";

else

FirstWord = "Greetings";

return FirstWord + ", " + userName +" from Workflow 4";

}

Visual Basic

Private Shared Function SayHello(ByVal userName As String) As String

Dim FirstWord As String = Nothing

If userName.Length Mod 2 = 0 Then

FirstWord = "Hello"

Else

FirstWord = "Greetings"

End If

Return FirstWord & ", " & userName & " from Workflow 4"

End Function

1.            创建一个测试来验证新要求。为此,打开 HelloWorkflow.Test 项目下的 SayHelloFixture.cs 文件 (C#) 或 SayHelloFixture.vb (VB),并添加以下测试。

(代码片段 - WF 简介实验 - ShouldReturnGreetingWithOddLengthName 测试 CSharp)

C#

[TestMethod]

public void ShouldReturnGreetingWithOddLengthName()

{

  var output = WorkflowInvoker.Invoke(

    new SayHello() { UserName = "Odd" });

  string greeting = output["Greeting"].ToString();

  Assert.AreEqual("Greetings Odd from Workflow 4", greeting);

}

(代码片段 - WF 简介实验 - ShouldReturnGreetingWithOddLengthName 测试 VB)

Visual Basic

<TestMethod()>

Public Sub ShouldReturnGreetingWithOddLengthName()

  Dim output = WorkflowInvoker.Invoke( _

    New SayHello() With {.UserName = "Odd"})

  Assert.AreEqual("Greetings Odd from Workflow 4",

          output("Greeting").ToString())

End Sub

2.            右键单击测试方法并选择 Run Tests ( )。测试将失败,因为您尚未修改工作流以在每种情况下返回不同的问候消息。

 

图 40

ShouldReturnGreetingWithOddLengthName 测试失败

               

任务 2 –在工作流中实现新要求

1.            在工作流设计器中打开 SayHello.xaml。为此,在 Solution Explorer 中双击该文件。

2.            添加一个 FirstWord 变量来存储问候消息的第一个单词,无论是“Hello”还是“Greetings”。为此,执行以下步骤:

a.            单击形状界面选择 Sequence。

b.            单击 Variables 按钮。将出现一个显示 Sequence 活动的可用变量的面板。

c.             单击 <Create Variable>。

d.            在 Name 框中输入 FirstWord。

 

图 41

向 Sequence 添加类型为字符串的 FirstWord 变量

   变量

在 Windows Workflow Foundation (WF) 中,Variables 表示数据的存储器。另一方面,Arguments 表示进出活动的数据流。与 C# 或 Visual Basic 中一样,变量有一个作用域。如果在没有选择活动的情况下打开变量面板,您将无法添加变量。在设计器中选择的活动将提供变量的作用域。在本例中,FirstWord 属于 Sequence 作用域。

3.            现在您需要测试 UserName 参数,查看它的字符数量是奇数还是偶数。为此,从 Toolbox 中将 If 活动拖到 Assign 活动上方的 Sequence 活动中。

4.            将 If 活动 DisplayName 设置为 Set FirstWord value,选择形状上的文本并进行内联编辑,或者在恰当的属性网格中设置(选择活动并按 F4 显示该网格)。

注意:工作流设计器使您能设置 DisplayName 属性,给形状提供更可读的名称。

 

图 42

带有描述集的 If 活动

   注意

红色按钮 ( ) 警告您该流程当前无效。If 活动仍然需要您为 Condition 参数提供表达式。

5.            为 If 条件提供表达式。为此,双击 If 活动打开它,并在 Condition 框中键入以下表达式。这将查看名称长度是奇数还是偶数。

Visual Basic

UserName.Length Mod 2 = 0

   表达式

表达式是一种程序语句,可以是文字字符串、条件语句,也可以是连接几个字符串、调用方法或调用其他活动的表达式。表达式是使用 Visual Basic 语法编写的,即使在 C# 应用程序中也是如此。这意味着不区分大小写,使用一个等号而不是“==”来执行比较,Boolean 运算符是单词“And”和“Or”而不是符号“&&”和“||”。

6.            对长度为偶数的名称更新 FirstWord 变量值,应该使用“Hello”问候消息。从工具箱中将 Assign 活动拖到 Then 区域中。然后,在 To 框(左边)中键入 FirstWord,在 Value 框(右边)中键入 "Hello "(包括结尾的空格)。

7.            现在为长度为奇数的变量值更新 FirstWord 变量值,应该使用“Greetings”问候消息。从工具箱中将 Assign 活动拖到 Else 区域中。然后,在 To 框(左边)中键入 FirstWord,在 Value 框(右边)中键入 "Greetings "(包括结尾的空格)。

 

图 43

完整的 If 活动

8.            修改最终的 Assign 活动,自定义根据 FirstWord 值显示的问候消息。为此,在 If 活动下方的 Assign 活动中,使用以下表达式替换 Value 框(右边)的内容:

Visual Basic

FirstWord & UserName & " from Workflow 4"

 

图 44

完成的工作流

9.            按 CTRL+SHIFT+B 生成解决方案。

               

后续步骤

练习 7:验证

               

练习 7:验证

为了验证是否正确执行了所有的练习步骤,您应该运行解决方案的所有测试确保它们都通过。

1.            从菜单中选择 Test / Run / All Tests In Solution 或按 CTRL+R,A

2.            验证是否所有测试都已通过。

 

图 45

测试通过

               

后续步骤

练习 8:错误处理

               

练习 8:错误处理

注意:如果还没有完成练习 6,您可以使用本实验提供的解决方案。在 %TrainingKitInstallFolder%\Labs\IntroToWF\Source\Ex8-ErrorHandling\Begin 文件夹中,您将发现 C# 和 Visual Basic 的初始解决方案。

您可能注意到了,这个简单的应用程序中可能存在一个 bug。如果您没有向工作流传递 UserName 将发生什么?在本练习中,您将使用 Try/Catch、Catch<T> 和 Throw 内置活动向工作流添加一些错误处理功能。

任务 1 –编写测试来观察错误行为

注意,如果您传递了空字符串 (""),应用程序应该能够正常运行。获取传递给工作流的全名的唯一方式是,在创建它时不为 UserName 提供输入参数。

1.            创建一个测试来观察不向工作流传递名称参数时将出现什么情况。为此,使用 Solution Explorer,打开 HelloWorkflow.Tests 项目下的 SayHelloFixture.cs (C#) 或 SayHelloFixture.vb (Visual Basic),并添加以下测试。

(代码片段 - WF 简介实验 - ShouldHandleNullUserName 测试 CSharp)

C#

[TestMethod]

public void ShouldHandleNullUserName()

{

  // Invoking with no arguments

  WorkflowInvoker.Invoke(new SayHello());

}

(代码片段 - WF 简介实验 - ShouldHandleNullUserName 测试 VB)

Visual Basic

<TestMethod()>

Public Sub ShouldHandleNullUserName()

  ' Invoking with no arguments

  WorkflowInvoker.Invoke(New SayHello())

End Sub

2.            现在运行测试。右键单击测试方法名称并选择 Run Tests ( )。

 

图 46

运行测试

3.            结果是测试失败并抛出 NullReferenceException,因为有一个表达式使用了 If 活动中的 UserName.Length,而 UserName 为空。

 

图 47

ShouldHandleNullUserName 测试失败

测试结果

HelloWorkflow.Tests.SayHelloFixture.ShouldHandleNullUserName threw exception:System.NullReferenceException:Object reference not set to an instance of an object.

               

任务 2 –向工作流添加 Try/Catch 活动

为了处理错误,您应该在使用名称参数之前对其进行验证,或者捕获异常并进行处理。在本任务中,您将捕获异常。

1.            在设计器中打开 SayHello.xaml,并从工具箱中将 TryCatch 活动拖放到序列最上方。

 

图 48

工具箱中的 TryCatch 活动

 

图 49

添加一个 TryCatch 活动

   Try/Catch 活动

工作流可以使用 TryCatch 活动处理在工作流执行过程中出现的异常。这些异常可以处理,也可以使用 Throw 活动重新抛出。完成 Try 部分或者 Catches 部分时,将执行 Finally 部分的活动。

2.            现在需要将 If 活动移动到 Try 代码块中。

a.            折叠 If 活动 ( ) 让图表变得小一些

b.            将 if 活动拖到 Try 块中。

 

图 50

移动 If 活动

3.            现在需要捕获 NullReferenceException。为此,执行以下步骤:

a.            单击 Add new catch。

b.            从组合框中选择 System.NullReferenceExeption 并按回车键。

 

图 51

选择 NullReferenceExeption

4.            在活动的 Catch 部分,您需要决定如何处理错误。在本例中,您将将异常替换为 ArgumentNullException 并将其抛出。这将使调用者知道出现异常是因为他们没有提供 UserName 参数。从工具箱中将 Throw 活动拖到 Catches 区域中。

5.       设置 Throw 活动表达式。为此,在 Properties 窗口中选择 Throw 活动,并在 Exception 框中键入以下表达式。

Visual Basic

New ArgumentNullException("UserName")

 

图 52

添加一个 Throw 活动

6.            现在您需要修复 ShouldHandleNullUserName 测试,让它预期到该异常。为此,打开 SayHelloFixture.cs,并按照以下代码所示向测试方法添加ExpectedException 注释。

C#

[TestMethod]

[ExpectedException(typeof(ArgumentNullException))]

public void ShouldHandleNullUserName()

{

// Invoking with no arguments

WorkflowInvoker.Invoke(new SayHello());

}

Visual Basic

<TestMethod(), ExpectedException(GetType(ArgumentNullException))>

Public Sub ShouldHandleNullUserName()

' Invoking with no arguments

WorkflowInvoker.Invoke(New SayHello())

End Sub

7.            按 CTRL+SHIFT+B 生成解决方案。

               

后续步骤

练习 8:验证

练习 8:验证

1.            从菜单中选择 Test / Run / All Tests In Solution 或按 CTRL+R,A

2.            验证是否所有测试都已通过。

 

图 53

测试通过

               

后续步骤

练习 9:活动设计器

正如您已经看到的,Windows Workflow 允许您使用代码构建自定义活动。根据基类的不同,您可以构建几种不同的自定义活动。

基类 用于

Activity

活动由其他活动组成

CodeActivity

希望控制执行的活动

AsyncCodeActivity

希望在执行期间异步执行工作的活动

NativeActivity

包含其他活动或需要工作流运行时的高级服务的活动

任务 1- 创建自定义 NativeActivity

在本任务中,您将创建一个包含 Pre/Post 处理功能的简单自定义活动。

1.            从 Start | All Programs | Microsoft Visual Studio 2010 启动 Microsoft Visual Studio 2010。

2.            要开始该练习,您可以使用上一个练习中完成的解决方案。

如果您没有成功完成练习,您也可以在 %TrainingKitInstallFolder%\Labs\IntroToWF\Ex9-ActivityDesigner \Begin 文件夹中找到练习 3 的初始解决方案并打开,选择您所喜欢的语言(C# 或 VB),使用它作为本练习的起点。

3.            按 CTRL+SHIFT+B 生成解决方案。

4.            在 Solution Explorer 中,右键单击 HelloWorkflow 解决方案,然后选择 Add / New Project…

5.            选择 Workflow 模板并选择 Activity Library。将项目命名为 HelloWorkflow.Activities。

 

图 54

添加一个名为 HelloWorkflow.Activities 的新 Activity Library 项目

6.            删除 Activity1.xaml,本实验不再需要该文件。

7.            在 Solution Explorer 中,右键单击 HelloWorkflow.Activities 项目并选择 Add / NewItem (Ctrl+Shift+A)

8.            从 Workflow 模板中选择 Code Activity 并将其命名为 PrePostSequence

 

图 55

添加一个名为 PrePostSequence 的新 Code Activity

9.            PrePostSequence 类将充当其他活动的容器。您需要对模板提供的代码做一些更改。删除类的内容并使用以下代码替代:

(代码片段 - WF 简介实验– PrePostSequence CSharp)

C#

public sealed class PrePostSequence :NativeActivity

{

    public Activity Pre { get; set; }

    public Activity Post { get; set; }

    public List<Activity> Activities { get; set; }

    public PrePostSequence()

    {

        Activities = new List<Activity>();

    }

    protected override void Execute(NativeActivityContext context)

    {

        // Schedule the activities in order

        context.ScheduleActivity(Pre);

        Activities.ForEach((a) => { context.ScheduleActivity(a); });

        context.ScheduleActivity(Post);

    }

}

(代码片段 - WF 简介实验– PrePostSequence VB)

Visual Basic

Public NotInheritable Class PrePostSequence

    Inherits NativeActivity

    Public Property Pre() As Activity

    Public Property Post() As Activity

    Public Property Activities() As List(Of Activity)

    Public Sub New()

        Activities = New List(Of Activity)()

    End Sub

    Protected Overrides Sub Execute(ByVal context As System.Activities.NativeActivityContext)

        ' Schedule the activities in order

        context.ScheduleActivity(Pre)

        For Each Activity In Activities

            context.ScheduleActivity(Activity)

        Next

        context.ScheduleActivity(Post)

    End Sub

End Class

10.          在设计器中打开 SayHello.xaml。注意,工具箱现在包含我们的 PrePostSequence 活动。将其拖到界面上 Finally 块的下方。该活动也许能够运行,但是目前还没什么作用。您需要创建一个自定义设计器。

 

图 56

没有自定义设计器的 PrePostSequence 活动

11.          从设计界面中删除 PrePostSequence 活动。

12.          关闭 SayHello.xaml 以便 Visual Studio 关闭程序集

               

               

任务 2 –创建活动设计器

在本任务中,您将为 PrePostSequence 活动创建一个自定义活动设计器。

1.            在 Solution Explorer 中,右键单击 HelloWorkflow 解决方案,然后选择 Add / New Project

2.            选择 Workflow 模板并选择 Activity Designer Library。将项目命名为 HelloWorkflow.Activities.Designers

 

图 57

添加一个新的 Activity Designer Library

3.            删除 ActivityDesigner1.xaml,您现在不再需要该文件。

4.            在 Solution Explorer 中,右键单击 HelloWorkflow.Activities.Designers 项目并选择 Add / New Item

5.            从 Workflow 模板中选择 Activity Designer 并将其命名为 PrePostSequenceDesigner

 

图 58

添加 PrePostSequenceDesigner 活动设计器

6.            使用以下内容替换模板 XAML:

XAML (C#)

<sap:ActivityDesigner x:Class="HelloWorkflow.Activities.Designers.PrePostSequenceDesigner"

 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

 xmlns:sap="clr-namespace:System.Activities.Presentation;assembly=System.Activities.Presentation"

 xmlns:sapv="clr-namespace:System.Activities.Presentation.View;assembly=System.Activities.Presentation">

    <StackPanel>

        <Border BorderBrush="Green" BorderThickness="4" CornerRadius="5">

            <sap:WorkflowItemPresenter Item="{Binding Path=ModelItem.Pre, Mode=TwoWay}" HintText="Insert Pre Activities Here"/>

        </Border>

        <Border BorderBrush="Red" BorderThickness="4" CornerRadius="5">

            <StackPanel>

                <Border BorderBrush="Black" BorderThickness="2" CornerRadius="5">

                    <TextBlock HorizontalAlignment="Center">Activities</TextBlock>

                </Border>

                <sap:WorkflowItemsPresenter Items="{Binding Path=ModelItem.Activities}" HintText="Insert Activities Here">

                    <sap:WorkflowItemsPresenter.SpacerTemplate>

                        <DataTemplate>

                            <Ellipse Fill="Red" Width="30" Height="30" />

                        </DataTemplate>

                    </sap:WorkflowItemsPresenter.SpacerTemplate>

                    <sap:WorkflowItemsPresenter.ItemsPanel>

                        <ItemsPanelTemplate>

                            <StackPanel Orientation="Horizontal"/>

                        </ItemsPanelTemplate>

                    </sap:WorkflowItemsPresenter.ItemsPanel>

                </sap:WorkflowItemsPresenter>

            </StackPanel>

        </Border>

        <Border BorderBrush="Black" BorderThickness="4" CornerRadius="5">

            <sap:WorkflowItemPresenter Item="{Binding Path=ModelItem.Post, Mode=TwoWay}" HintText="Insert Post Activities Here"/>

        </Border>

    </StackPanel>

</sap:ActivityDesigner>

XAML (VB)

<sap:ActivityDesigner x:Class="PrePostSequenceDesigner"

 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

 xmlns:sap="clr-namespace:System.Activities.Presentation;assembly=System.Activities.Presentation"

 xmlns:sapv="clr-namespace:System.Activities.Presentation.View;assembly=System.Activities.Presentation">

    <StackPanel>

        <Border BorderBrush="Green" BorderThickness="4" CornerRadius="5">

            <sap:WorkflowItemPresenter

                Item="{Binding Path=ModelItem.Pre, Mode=TwoWay}"

                 HintText="Insert Pre Activities Here"/>

        </Border>

        <Border BorderBrush="Red" BorderThickness="4" CornerRadius="5">

            <StackPanel>

                <Border BorderBrush="Black"

                    BorderThickness="2" CornerRadius="5">

                    <TextBlock

                        HorizontalAlignment="Center">Activities</TextBlock>

                </Border>

                <sap:WorkflowItemsPresenter

                 Items="{Binding Path=ModelItem.Activities}"

                 HintText="Insert Activities Here">

                    <sap:WorkflowItemsPresenter.SpacerTemplate>

                        <DataTemplate>

                            <Ellipse Fill="Red" Width="30" Height="30" />

                        </DataTemplate>

                    </sap:WorkflowItemsPresenter.SpacerTemplate>

                    <sap:WorkflowItemsPresenter.ItemsPanel>

                        <ItemsPanelTemplate>

                            <StackPanel Orientation="Horizontal"/>

                        </ItemsPanelTemplate>

                    </sap:WorkflowItemsPresenter.ItemsPanel>

                </sap:WorkflowItemsPresenter>

            </StackPanel>

        </Border>

        <Border BorderBrush="Black" BorderThickness="4" CornerRadius="5">

            <sap:WorkflowItemPresenter Item="{Binding Path=ModelItem.Post, Mode=TwoWay}" HintText="Insert Post Activities Here"/>

        </Border>

    </StackPanel>

</sap:ActivityDesigner>

    WorkflowItemPresenter  / WorkflowItemsPresenter

我们的自定义设计器使用数据绑定来绑定 PrePostSequence 类的属性。Pre 和 Post 属性只是一个活动,因此设计器使用 WorkflowItemPresenter 来为它们实现一个设计界面。

Activities 集合使用 WorkflowItemsPresenter 创建保存活动集合的设计界面。

7.            按 CTRL+SHIFT+B 生成解决方案

               

任务 3 –将活动设计器链接到活动

现在有了用于类的设计器,但是首先必须将它链接到任务 1 中创建的自定义活动才能使用。

1.            在 Solution Explorer 中,右键单击 HelloWorkflow.Activities 项目并选择 Add Reference…

2.            从项目选项卡中,添加对 HelloWorkflow.Activities.Designers 的引用

3.            再次选择 Add Reference,在 .NET 选项卡中添加对以下程序集的引用

◦              System.Activities.Presentation

◦              PresentationFramework

◦              PresentationCore

◦              WindowsBase

4.            打开 PrePostSequence.cs (C#) 或 PrePostSequence.vb (VB).

5.            添加以下命名空间指令:

C#

using System.ComponentModel;

using HelloWorkflow.Activities.Designers;

Visual Basic

Imports System.ComponentModel

Imports HelloWorkflow.Activities.Designers

6.            向 PrePostSequence 类添加以下属性。

C#

[Designer(typeof(PrePostSequenceDesigner))]

public sealed class PrePostSequence :NativeActivity

Visual Basic

<Designer(GetType(PrePostSequenceDesigner))>

Public NotInheritable Class PrePostSequence

7.            HelloWorkflow 主机项目需要一个对自定义活动项目的引用。在 Solution Explorer 中,右键单击 HelloWorkflow 项目,然后选择 Add Reference。从项目选项卡中选择 HelloWorkflow.Activities。

后续步骤

练习 9:验证

               

练习 9:验证

在本验证中,您将创建一个测试来验证是否成功执行了练习中的所有步骤。

1.            按 CTRL+SHIFT+B 生成解决方案。

2.            HelloWorkflow.Tests 项目需要一个对自定义活动项目的引用。在 Solution Explorer 中,右键单击 HelloWorkflow 项目,然后选择 Add Reference。从项目选项卡中选择 HelloWorkflow.Activities。

3.            向 SayHelloTests.cs (C#) 或 SayHelloTests.vb (VB) 文件添加一个新的测试,如下所示。

(代码片段 - WF 简介实验–ShouldreturnPrePostMessages CSharp)

C#

[TestMethod]

public void ShouldReturnPrePostMessages()

{

    IDictionary<string, object> output;

    output = WorkflowInvoker.Invoke(new SayHello()

    {

     UserName = "Test"

    });

    Assert.AreEqual("This is Pre-Sequence", output["PreMessage"]);

    Assert.AreEqual("This is Post-Sequence", output["PostMessage"]);

}

(代码片段 - WF 简介实验–ShouldreturnPrePostMessages VB)

Visual Basic

<TestMethod()>

Public Sub ShouldReturnPrePostMessages()

    Dim output = WorkflowInvoker.Invoke(

        New SayHello() With {.UserName = "Test"})

    Assert.AreEqual("This is Pre-Sequence", output("PreMessage"))

    Assert.AreEqual("This is Post-Sequence", output("PostMessage"))

End Sub

4.            单击新测试名称 (ShouldReturnPostMessages) 然后单击 Run Tests 运行测试。测试将失败,因为您尚未实现 PrePostSequence。

5.            在 Solution Explorer 中,右键单击 SayHello.xaml 打开它。

6.            将 PrePostSequence 拖到 Finally 活动下方,您应该看到新的 PrePostSequence 活动设计器

7.            添加 2 个新的输出参数来保存 PrePostSequence 的 Pre 和 Post 区域的消息。

 

图 59

添加 PreMessage 和 PostMessage 输出参数

8.            从 Primitives 组中将 Assign 活动拖到 Pre Activities 区域上并设置属性

a.            To:PreMessage

b.            Value:"This is Pre-Sequence"

9.            将另一个 Assign 活动拖到 Post Activities 区域上并设置属性

a.            To:PostMessage

b.            Value:"This is Post-Sequence"

10.          将两个 Assign 活动拖到 PrePostSequence 下方并将其拖到活动区域中。为此,放置活动时将指针移动到红点上。

 

图 60

已完成的带有 PrePostSequence 的工作流。

11.          按 CTRL+SHIFT+B 生成解决方案

12.          按 Ctrl+R, A 运行所有测试。所有测试现在都应该能够通过。

后续步骤

练习 10:托管设计器

               

练习 10:托管设计器

如果您想创建一个类似 PrePostSequence 的自定义活动,而其他人没有能使用它的 Visual Studio 怎么办?许多产品都允许最终用户自定义工作流。Windows Workflow Foundation 4 允许您非常轻松地在应用程序中托管设计器。在本练习中,您将托管设计器并使用您的自定义活动。

任务 1 –添加新的 WPF 应用程序

1.            在 Solution Explorer 中,右键单击 HelloWorkflow 解决方案,然后选择 Add / New Project…

2.            从 Windows 模板中,添加名为 HelloDesigner 的新 WPF Application

3.            将 HelloDesigner 项目设置为 Startup 项目。为此,在 Solution Explorer 中,右键单击 HelloDesigner 项目并指向 Set as Startup Project

4.            在 Solution Explorer 中,右键单击 HelloDesigner 项目并指向 Add references。从 .Net 选项卡中选择以下程序集

◦              System.Activities.Presentation

◦              System.Activities.Core.Presentation

◦              System.Activities

5.            从 Projects 选项卡中,添加以下程序集:

◦              HelloWorkflow.Activities

◦              HelloWorkflow.Activities.Designers

6.            打开 MainWindow.xaml 并按如下所示修改

XAML (C#)

<Window x:Class="HelloDesigner.MainWindow"

 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

 Title="MainWindow" Height="600" Width="1000">

    <Grid>

        <Grid.RowDefinitions>

            <RowDefinition Height="4*" />

            <RowDefinition Height="*" />

        </Grid.RowDefinitions>

        <Grid Name="grid1">

            <Grid.ColumnDefinitions>

                <ColumnDefinition Width="Auto" />

                <ColumnDefinition Width="4*" />

                <ColumnDefinition Width="Auto" />

            </Grid.ColumnDefinitions>

        </Grid>

        <TextBox Grid.Row="1" Name="textXAML"   

                 VerticalScrollBarVisibility="Visible" />

    </Grid>

</Window>

XAML (VB)

<Window x:Class="MainWindow"

 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

 Title="MainWindow" Height="600" Width="1000">

    <Grid>

        <Grid.RowDefinitions>

            <RowDefinition Height="4*" />

            <RowDefinition Height="*" />

        </Grid.RowDefinitions>

        <Grid Name="grid1">

            <Grid.ColumnDefinitions>

                <ColumnDefinition Width="Auto" />

                <ColumnDefinition Width="4*" />

                <ColumnDefinition Width="Auto" />

            </Grid.ColumnDefinitions>

        </Grid>

        <TextBox Grid.Row="1" Name="textXAML"

 VerticalScrollBarVisibility="Visible" />

    </Grid>

</Window>

7.            打开 MainWindow.xaml.cs (C#) 或 MainWindow.xaml.vb,为此,右键单击 MainWindow.xaml 文件并选择 View Code (F7)。

8.            添加以下命名空间指令:

(代码片段 - WF 简介实验– MainWindow 命名空间指令 CSharp)

C#

using System.Activities.Presentation;

using System.Activities.Statements;

using System.Activities.Presentation.Toolbox;

using System.Activities.Core.Presentation;

using System.Activities.Presentation.Metadata;

using System.ComponentModel;

using HelloWorkflow.Activities;

using HelloWorkflow.Activities.Designers;

(代码片段 - WF 简介实验– MainWindow 命名空间指令 VB)

Visual Basic

Imports System.Activities.Presentation

Imports System.Activities.Statements

Imports System.Activities.Presentation.Toolbox

Imports System.Activities.Core.Presentation

Imports System.Activities.Presentation.Metadata

Imports System.ComponentModel

Imports HelloWorkflow.Activities

Imports HelloWorkflow.Activities.Designers

9.            添加一个类型为 WorkflowDesigner 的字段:

C#

WorkflowDesigner workflowDesigner = new WorkflowDesigner();

Visual Basic

Private workflowDesigner As WorkflowDesigner = New WorkflowDesigner()

10.          新建一个名为 RegisterMetadata 的函数如下。

(代码片段 - WF 简介实验–RegisterMetadata CSharp)

C#

private void RegisterMetadata()

{

    DesignerMetadata metaData = new DesignerMetadata();

    metaData.Register();

    AttributeTableBuilder builder = new AttributeTableBuilder();

    MetadataStore.AddAttributeTable(builder.CreateTable());

}

(代码片段 - WF 简介实验–RegisterMetadata VB)

Visual Basic

Public Sub RegisterMetadata()

    Dim metaData As DesignerMetadata = New DesignerMetadata()

    metaData.Register()

    Dim builder As AttributeTableBuilder = New AttributeTableBuilder()

    MetadataStore.AddAttributeTable(builder.CreateTable())

End Sub

注意:该函数支持设计器元数据存储器。

当托管设计器时,您可以控制工具箱。您可以选择显示哪些控件、要显示的类型,甚至控件的名称。  

11.          添加一个名为 CreateToolboxControl 的函数,并使用 4 个项填充工具箱。

(代码片段 - WF 简介实验–CreateToolboxControl CSharp)

C#

private ToolboxControl CreateToolboxControl()

{

    //Create the ToolBoxControl

    ToolboxControl ctrl = new ToolboxControl();

    //Create a collection of category items

    ToolboxCategory category = new ToolboxCategory("Hello Workflow");

    //Creating toolboxItems

    ToolboxItemWrapper tool0 = new ToolboxItemWrapper(

        "System.Activities.Statements.Assign",

        "System.Activities, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"

        , null,

        "Assign");

    ToolboxItemWrapper tool1 = new ToolboxItemWrapper(

        "System.Activities.Statements.Sequence",

        "System.Activities, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",

        null,

        "Sequence");

    ToolboxItemWrapper tool2 = new ToolboxItemWrapper(

        "System.Activities.Statements.TryCatch",

        "System.Activities, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",

        null,

        "Try It"); // Can use a different name

    ToolboxItemWrapper tool3 = new ToolboxItemWrapper(

        "HelloWorkflow.Activities.PrePostSequence",

        "HelloWorkflow.Activities",

        null,

        "PrePostSequence");

    //Adding the toolboxItems to the category.

    category.Add(tool0);

    category.Add(tool1);

    category.Add(tool2);

    category.Add(tool3);

    //Adding the category to the ToolBox control.

    ctrl.Categories.Add(category);

    return ctrl;

}

(代码片段 - WF 简介实验–CreateToolboxControl VB)

Visual Basic

Private Function CreateToolboxControl() As ToolboxControl

    'Create the ToolBoxControl

    Dim ctrl As ToolboxControl = New ToolboxControl()

    'Create a collection of category items

    Dim category As ToolboxCategory = New ToolboxCategory("Hello Workflow")

    'Creating toolboxItems

    Dim tool0 As ToolboxItemWrapper = New ToolboxItemWrapper(

        "System.Activities.Statements.Assign",

        "System.Activities, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",

        Nothing,

        "Assign")

    Dim tool1 As ToolboxItemWrapper = New ToolboxItemWrapper(

        "System.Activities.Statements.Sequence",

        "System.Activities, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",

        Nothing,

        "Sequence")

    Dim tool2 As ToolboxItemWrapper = New ToolboxItemWrapper(

        "System.Activities.Statements.TryCatch",

        "System.Activities, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",

        Nothing,

        "Try It") ' Can use a different name

    Dim tool3 As ToolboxItemWrapper = New ToolboxItemWrapper(

        "HelloWorkflow.Activities.PrePostSequence",

        "HelloWorkflow.Activities",

        Nothing,

        "PrePostSequence")

    'Adding the toolboxItems to the category.

    category.Add(tool0)

    category.Add(tool1)

    category.Add(tool2)

    category.Add(tool3)

    'Adding the category to the ToolBox control.

    ctrl.Categories.Add(category)

    Return ctrl

End Function

12.          添加一个名为 AddDesigner 的函数,该函数将向您的窗口添加一个设计器。

(代码片段 - WF 简介实验– AddDesigner CSharp)

C#

private void AddDesigner()

{

    //Create an instance of WorkflowDesigner class

    this.workflowDesigner = new WorkflowDesigner();

    //Place the WorkflowDesigner in the middle column of the grid

    Grid.SetColumn(this.workflowDesigner.View, 1);

    // Flush the workflow when the model changes

    workflowDesigner.ModelChanged += (s, e) =>

    {

        workflowDesigner.Flush();

        textXAML.Text = workflowDesigner.Text;

    };

    //Load a new Sequence as default.

    this.workflowDesigner.Load(new Sequence());

    //Add the WorkflowDesigner to the grid

    grid1.Children.Add(this.workflowDesigner.View);

    // Add the Property Inspector

    Grid.SetColumn(workflowDesigner.PropertyInspectorView, 2);

    grid1.Children.Add(workflowDesigner.PropertyInspectorView);

    // Add the toolbox

    ToolboxControl tc = CreateToolboxControl();

    Grid.SetColumn(tc, 0);

    grid1.Children.Add(tc);

}

(代码片段 - WF 简介实验– AddDesigner VB)

Visual Basic

Private Sub AddDesigner()

    'Create an instance of WorkflowDesigner class

    workflowDesigner = New WorkflowDesigner()

    'Place the WorkflowDesigner in the middle column of the grid

    Grid.SetColumn(workflowDesigner.View, 1)

    ' Setup the Model Changed event handler

    AddHandler workflowDesigner.ModelChanged,

        Function(sender As Object, e As System.EventArgs)

            ' Flush the workflow when the model changes

            workflowDesigner.Flush()

            textXAML.Text = workflowDesigner.Text

            Return Nothing

        End Function

    'Load a new Sequence as default.

    workflowDesigner.Load(New Sequence())

    'Add the WorkflowDesigner to the grid

    grid1.Children.Add(workflowDesigner.View)

    ' Add the Property Inspector

    Grid.SetColumn(workflowDesigner.PropertyInspectorView, 2)

    grid1.Children.Add(workflowDesigner.PropertyInspectorView)

    ' Add the toolbox

    Dim tc As ToolboxControl = CreateToolboxControl()

    Grid.SetColumn(tc, 0)

    grid1.Children.Add(tc)

End Sub

13.          修改 MainWindow 的构造函数,调用刚添加的函数:

(代码片段 - WF 简介实验– MainWindows 构造函数 CSharp)

C#

public MainWindow()

{

    InitializeComponent();

    RegisterMetadata();

    AddDesigner();

}

(代码片段 - WF 简介实验– MainWindows 构造函数 VB)

Visual Basic

Public Sub New()

    ' This call is required by the designer.

    InitializeComponent();

    ' Add any initialization after the InitializeComponent() call.

    RegisterMetadata()

    AddDesigner()

End Sub

后续步骤

练习 10:验证

               

练习 10:验证

要验证是否正确执行了所有步骤,运行 HelloDesigner 应用程序并修改工作流

1.            确保将 HelloDesigner 设置为了启动项目。

2.            按 F5 在调试模式下启动应用程序。

3.            当设计窗口出现时,将 PrePostSequence 放在设计器界面上。

4.            您应该看到以下内容:

 

图 61

托管 WorkflowDesigner 的 HelloDesigner 应用程序

后续步骤

总结

               

总结

在此动手实验中,通过创建一个简单的 Windows Workflow 应用程序,您学习了 Windows Workflow Foundation 4 中基本的工作流创作过程。在本实验中,您学习了如何使用新的 Visual Studio 2010 工作流设计器创建工作流,还学习了替代方法——使用 C# 或 VB 代码创建工作流。您研究了输入和输出参数的使用,学习了如何使用 If 活动向工作流添加 if/else 逻辑,最后,您学习了如何使用 Try/Catch、Catch<T> 和 Throw 等特定于异常处理的活动来处理工作流中的错误情况。当然,Windows Workflow 4 的知识还有很多。建议您查看流程图和工作流服务实验,了解更多有关构建支持工作流的应用程序的信息。

   反馈

您对本实验有何看法?您对新的 Windows Workflow 4 有何看法?您的反馈非常重要;它可以帮助我们为您构建最佳的产品。请花一点时间提供反馈。将您的评论发送到 wfwcfhol@microsoft.com