Matt Milner,Pluralsight
2009 年 11 月
更新时间:2010 年 4 月
概述
正如软件开发人员所知道的,编写应用程序可能很有挑战性,我们不断寻找工具和框架来简化该过程,帮助我们专注于我们试图解决的业务挑战。 我们已从使用计算机语言编写代码(例如汇编程序)迁移到更高级别的语言(如 C# 和 Visual Basic),以简化开发、消除内存管理等较低级别的问题,以及提高开发人员的工作效率。 对于Microsoft开发人员,迁移到 .NET 允许公共语言运行时(CLR)分配内存、清理不需要的对象并处理低级别构造(如指针)。
应用程序的复杂性大部分都存在于后台的逻辑和处理中。 异步或并行执行、通常协调响应用户请求或服务请求的任务等问题可快速导致应用程序开发人员回退到句柄、回调、同步等低级别编码中。作为开发人员,我们需要为应用程序内部声明性编程模型提供与 Windows Presentation Foundation(WPF)中用户界面相同的功能和灵活性。 Windows Workflow Foundation (WF)提供了用于生成应用程序和服务逻辑的声明性框架,并为开发人员提供了用于处理异步、并行任务和其他复杂处理的高级语言。
让运行时能够管理内存和对象,使我们更多地关注编写代码的重要业务方面。 同样,拥有一个可以管理协调异步工作的复杂性的运行时提供了一组可提高开发人员工作效率的功能。 WF 是用于声明工作流(业务逻辑)、活动以帮助定义逻辑和控制流的一组工具,以及用于执行生成的应用程序定义的运行时。 简言之,WF 是使用更高级别的语言来编写应用程序,目的是使开发人员更高效、更易于管理的应用程序,以及更快地实现更改。 WF 运行时不仅为你执行工作流,它还提供在编写应用程序逻辑时非常重要的服务和功能,此类持久性状态、书签和恢复业务逻辑,所有这些都会导致线程和进程敏捷性,从而纵向扩展和横向扩展业务流程。
有关使用 WF 生成应用程序可以提高工作效率的更多概念性信息,建议阅读 David Chappell 的“工作流方式”,该信息位于“其他资源”部分。
WF4 中的新增功能
在 Microsoft® .NET Framework 版本 4 中,Windows Workflow Foundation 引入了与作为 .NET 3.0 和 3.5 一部分提供的早期技术版本相比的巨大变化。 事实上,团队重新审视了编程模型、运行时和工具的核心,并重新构建了每个编程模型,以提高性能和工作效率,以及解决使用以前版本的客户参与中获取的重要反馈。 必须进行重大更改,以便采用 WF 的开发人员提供最佳体验,并使 WF 能够继续成为可在应用程序中构建的强大基础组件。 我将在这里介绍高级别的变化,在整个论文中,每个主题将得到更深入的处理。
在继续之前,请务必了解向后兼容性也是此版本中的关键目标。 新的框架组件主要位于 System.Activities.* 程序集中,而向后兼容的框架组件则位于 System.Workflow.* 程序集中。 System.Workflow.* 程序集是 .NET Framework 4 的一部分,提供完整的向后兼容性,因此你可以将应用程序迁移到 .NET 4,且不会对工作流代码进行更改。 在本白皮书中,我将使用名称 WF4 来引用 System.Activities.* 程序集和 WF3 中找到的新组件,以引用 System.Workflow.* 程序集中找到的组件。
设计师
最明显的改进领域之一是在工作流设计器中。 可用性和性能是 VS 2010 版本团队的关键目标。 设计器现在支持使用更大的工作流,而性能下降,设计器都基于 Windows Presentation Foundation(WPF),充分利用可以使用声明性 UI 框架构建的丰富用户体验。 活动开发人员将使用 XAML 定义其活动外观,并在视觉设计环境中与用户交互的方式。 此外,在自己的应用程序中重新托管工作流设计器,使非开发人员能够查看和与工作流交互,现在要容易得多。
数据流
在 WF3 中,工作流中的数据流不透明。 WF4 提供了一个清晰、简洁的数据流模型,并在使用参数和变量时确定范围。 这些概念对所有开发人员都熟悉,可以简化数据存储的定义,以及数据流进出工作流和活动。 数据流模型还使给定活动的预期输入和输出更加明显,并改进了运行时的性能,因为数据更易于管理。
流程图
添加了名为 Flowchart 的新控制流活动,使开发人员能够使用流程图模型定义工作流。 流程图更类似于许多分析师和开发人员在创建解决方案或设计业务流程时经历的概念和思想流程。 因此,提供一个活动是有意义的,以便轻松建模已经完成的概念思维和规划。 流程图使概念得以实现,例如返回以前的步骤和基于单个条件拆分逻辑或 Switch/Case 逻辑。
编程模型
WF 编程模型已经过改造,使其更简单、更可靠。 活动是编程模型中的核心基类型,表示工作流和活动。 此外,你不再需要创建 WorkflowRuntime 来调用工作流,只需创建一个实例并执行它,从而简化单元测试和应用程序方案,而你不想在设置特定环境时遇到问题。 最后,工作流编程模型成为活动的完全声明性组合,无需旁写代码,从而简化工作流创作。
Windows Communication Foundation (WCF) 集成
WF 的优点当然适用于创建服务和使用或协调服务交互。 大力加强 WCF 与 WF 之间的集成。 新的消息传递活动、消息关联和改进的托管支持以及完全声明性服务定义是改进的主要方面。
工作流入门
了解 WF 的最佳方式是开始使用 WF 并应用概念。 我将介绍有关工作流基础的几个核心概念,然后逐步讲解如何创建一些简单的工作流来说明这些概念彼此之间的关系。
工作流结构
活动是 WF 的构建基块,所有活动最终都派生自 Activity。 术语说明 - 活动是 WF 中的一个工作单元。 活动可以组合成较大的活动。 当活动用作顶级入口点时,它称为“工作流”,就像 Main 只是另一个表示 CLR 程序的顶级入口点的函数一样。 例如,图 1 显示了在代码中生成的简单工作流。
序列 s = 新序列
{
活动 = {
new WriteLine {Text = “Hello”},
new Sequence {
活动 =
{
new WriteLine {Text = “Workflow”},
new WriteLine {Text = “World”}
}
}
}
};
图 1:简单的工作流
请注意,图 1 中,Sequence 活动用作根活动来定义工作流的根控制流样式。 任何活动都可以用作根活动或工作流并执行,即使是简单的 WriteLine。 通过将 Sequence 上的 Activities 属性设置为其他活动的集合,我定义了工作流结构。 此外,子活动可以有子活动,创建构成整个工作流定义的活动的树。
工作流模板和工作流设计器
WF4 附带许多活动,Visual Studio 2010 包含用于定义活动的模板。 用于根控制流的两个最常见活动是 Sequence 和 Flowchart。 尽管任何活动都可以作为工作流执行,但这两者提供了用于定义业务逻辑的最常见设计模式。 图 2 显示了一个顺序工作流模型示例,该模型定义了正在接收、保存的订单的处理,然后将通知发送到其他服务。
图 2:顺序工作流设计
WF4 中引入了流程图工作流类型,以解决现有用户的常见请求,例如能够返回到工作流中的先前步骤,因为它更类似于分析师和开发人员在定义业务逻辑时所做的概念设计。 例如,请考虑涉及用户输入应用程序的方案。 为了响应用户提供的数据,程序应继续处理或返回到上一步以再次提示输入。 使用顺序工作流时,这涉及到类似于图 3 中所示的内容,其中 DoWhile 活动用于继续处理,直到满足某些条件。

图 3:决策分支顺序
图 3 中的设计工作,但作为查看工作流的开发人员或分析师,模型并不表示所描述的原始逻辑或要求。 图 4 中的流程图工作流为图 3 中使用的顺序模型提供了类似的技术结果,但工作流的设计更符合最初定义的思维和要求。

图 4:流程图工作流
工作流中的数据流
大多数人在考虑工作流时首先想到的是业务流程或应用程序的流。 但是,与流一样关键,即驱动进程的数据以及在执行工作流期间收集和存储的信息。 在 WF4 中,数据的存储和管理是设计考虑的主要领域。
有三个主要概念可以理解数据:变量、参数和表达式。 每个变量的简单定义是用于存储数据、参数用于传递数据,表达式用于操作数据。
变量 - 存储数据
工作流中的变量非常类似于你用于命令性语言的变量:它们描述了要存储的数据的命名位置,并且遵循某些范围规则。 若要创建变量,首先确定变量需要提供的范围。 就像在类或方法级别可用的代码中有变量一样,可以在不同的范围内定义工作流变量。 请考虑图 5 中的工作流。 在此示例中,可以在工作流的根级别或收集源数据序列活动定义的范围定义变量。

图 5:作用域为活动 的变量
参数 - 传递数据
参数在活动上定义,并定义传入和传出活动的数据流。 可以将参数视为活动,就像在命令性代码中对方法使用参数一样。 参数可以是 In、Out 或 In/Out,并且具有名称和类型。 生成工作流时,可以对根活动定义参数,该活动允许在调用工作流时将数据传递到工作流中。 使用参数窗口在工作流上定义参数,就像使用变量窗口一样。
将活动添加到工作流时,需要为活动配置参数,这主要是通过引用作用域内变量或使用表达式来完成的,接下来我将讨论这些变量。 事实上,Argument 基类包含一个 Expression 属性,该属性是一个返回参数类型的值的活动,因此所有这些选项都相关且依赖于 Activity。
使用工作流设计器编辑参数时,可以在属性网格中键入表示文本值的表达式,或使用变量名称引用作用域内变量,如图 6 所示,其中 emailResult 和 emailAddress 是工作流中定义的变量。

图 6:配置活动参数
表达式 - 对数据进行操作
表达式是在工作流中可用于对数据进行操作的活动。 表达式可以在使用 Activity 的位置使用,并且对返回值感兴趣,这意味着可以在设置参数时使用表达式,或者定义 While 或 If 活动等活动的条件。 请记住,WF4 中的大多数内容派生自 Activity 和表达式都不一样,它们是 Activity<TResult 的派生体>,这意味着它们返回特定类型的值。 此外,WF4 还包含几个用于引用变量和参数以及 Visual Basic 表达式的常见表达式。 由于此专用表达式层,用于定义参数的表达式可以包括代码、文本值和变量引用。 表 1 提供使用工作流设计器定义参数时可以使用的表达式类型的小采样。 在代码中创建表达式时,还有几个选项,包括使用 lambda 表达式。
| 表达 | 表达式类型 |
|---|---|
“hello world” |
文本字符串值 |
10 |
文本 Int32 值 |
System.String.Concat(“hello”, “, ”world“) |
命令性方法调用 |
“hello” & “world” |
Visual Basic 表达式 |
argInputString |
参数引用 (参数名称) |
varResult |
变量引用(变量名称) |
“hello: ” & argInputString |
混合的文本和参数/变量 |
表 1:示例表达式
生成第一个工作流
现在,我已经介绍了有关活动和数据流的核心概念,接下来可以使用这些概念创建工作流。 我将从简单的 hello world 工作流开始,专注于概念,而不是 WF 的真正价值主张。 首先,在 Visual Studio 2010 中创建新的单元测试项目。 若要使用 WF 的核心,请添加对 System.Activities 程序集的引用,并在测试类文件中添加 System.Activities、System.Activities.Statements 和 System.IO using 语句。 然后添加一个测试方法,如图 7 所示,创建基本工作流并执行它。
[TestMethod]
public void TestHelloWorldStatic()
{
StringWriter 编写器 = 新的 StringWriter();
Console.SetOut(编写器);
序列 wf = 新序列
{
活动 = {
new WriteLine {Text = “Hello”},
new WriteLine {Text = “World”}
}
};
WorkflowInvoker.Invoke(wf);
Assert.IsTrue(String.Compare(
“hello\r\nWorld\r\n”,
作家。GetStringBuilder()。ToString()= 0,
“写入的字符串不正确”;
}
图 7:在代码 中创建 hello world
WriteLine 活动的 Text 属性是 InArgument<字符串>,在此示例中,我向该属性传递了一个文本值。
下一步是更新此工作流以使用变量并将这些变量传递给活动参数。 图 8 显示了更新为使用变量的新测试。
[TestMethod]
public void TestHelloWorldVariables()
{
StringWriter 编写器 = 新的 StringWriter();
Console.SetOut(编写器);
序列 wf = 新序列
{
变量 = {
新变量<字符串>{Default = “Hello”, Name = “greeting”},
new Variable<string> { Default = “Bill”, Name = “name” } },
活动 = {
new WriteLine { Text = new VisualBasicValue<string>(“greeting”),
new WriteLine { Text = new VisualBasicValue<string>()
“name + ”Gates\“]}
}
};
WorkflowInvoker.Invoke(wf);
}
图 8:包含变量的代码工作流
在这种情况下,变量定义为变量类型<字符串>,并给定默认值。 变量在 Sequence 活动中声明,然后从两个活动的 Text 参数中引用。 或者,可以使用表达式初始化变量,如图 9 所示,其中 VisualBasicValue<TResult> 类用于传入表示表达式的字符串。 在第一种情况下,表达式引用变量的名称,在第二种情况下,该变量与文本值连接。 文本表达式中使用的语法是 Visual Basic,即使在 C# 中编写代码时也是如此。
活动 = {
new WriteLine { Text = new VisualBasicValue<string>(“greeting”),
TextWriter = 编写器 },
new WriteLine { Text = new VisualBasicValue<string>(“name + \”Gates\“”),
TextWriter = 编写器 }
}
图 9:使用表达式定义参数
虽然代码示例有助于说明重要要点,并且通常对开发人员感到舒适,但大多数人将使用设计器来创建工作流。 在设计器中,将活动拖到设计图面上,并使用更新但熟悉的属性网格来设置参数。 此外,工作流设计器还包含底部的可展开区域,用于编辑工作流和变量的参数。 若要在设计器中创建类似的工作流,请将新项目添加到解决方案,选择活动库模板,然后添加新活动。
首次显示工作流设计器时,它不包含任何活动。 定义活动的第一步是选择根活动。 在本示例中,通过将流程图活动从工具箱中的流程图类别拖动,将流程图活动添加到设计器。 在流程图设计器中,从工具箱中拖动两个 WriteLine 活动,并将其一个添加到流程图下方。 现在,你必须将活动连接在一起,以便流程图知道要遵循的路径。 为此,首先将鼠标悬停在流程图顶部的绿色“start”圆圈上以查看抓取手柄,然后单击并拖动第一个 WriteLine 并将其拖放到活动顶部显示的拖动手柄上。 执行相同的操作,将第一个 WriteLine 连接到第二个 WriteLine。 设计图面应类似于图 10。

图 10:Hello 流程图布局
结构到位后,需要配置一些变量。 通过单击设计图面,然后单击设计器底部附近的“变量”按钮,可以编辑流程图的变量集合。 将两个名为“greeting”和“name”的变量分别添加到列表,并将默认值分别设置为“Hello”和“Bill”-请务必在设置值时包含引号,因为这是一个表达式,因此需要引用文本字符串。 图 11 显示了使用两个变量及其默认值配置的变量窗口。

图 11:工作流设计器中定义的变量
若要在 WriteLine 活动中使用这些变量,请在第一个活动的 Text 参数的属性网格中输入“greeting”(不含引号),并在第二个 WriteLine 上的 Text 参数输入“name”(再次没有引号)。 在运行时,计算 Text 参数时,变量中的值将解析并供 WriteLine 活动使用。
在测试项目中,添加对包含工作流的项目的引用。 然后,可以添加一个测试方法,如图 12 所示以调用此工作流并测试输出。 在图 12 中,可以通过实例化已创建的类的实例来查看正在创建的工作流。 在这种情况下,工作流在设计器中定义并编译为派生自 Activity 的类,然后可以调用该类。
[TestMethod]
public void TestHelloFlowChart()
{
StringWriter tWriter = new StringWriter();
Console.SetOut(tWriter);
Workflows.HelloFlow wf = new Workflows.HelloFlow();
WorkflowInvoker.Invoke(wf);
省略断言
}
图 12:测试流程图
到目前为止,我一直在工作流中使用变量,并使用这些变量向 WriteLine 活动的参数提供值。 工作流还可以定义参数,这样就可以在调用工作流时将数据传入工作流,并在工作流完成时接收输出。 若要更新上一个示例中的流程图,请删除“name”变量(将其选中并按 Delete 键),并改为创建字符串类型的“name”参数。 这样做的方式大致相同,只不过使用“参数”按钮查看参数编辑器。 请注意,参数还可以有方向,不需要提供默认值,因为该值将在运行时传递到活动。 由于对变量的参数使用相同的名称,因此 WriteLine 活动 Text 参数仍然有效。 现在,在运行时,这些参数将计算并解析工作流上的“name”参数的值,并使用该值。 添加具有 Out 方向和名称“fullGreeting”的字符串类型的附加参数;这会返回到调用代码。

图 13:定义工作流 的参数
在流程图中,添加一个 Assign 活动并将其连接到最后一个 WriteLine 活动。 对于 To 参数,输入“fullGreeting”(无引号),对于 Value 参数,请输入“greeting & name”(不含引号)。 这将为包含 name 参数的问候变量的串联赋给 fullGreeting 输出参数。
现在在单元测试中,更新代码以在调用工作流时提供参数。 参数作为字典<字符串传递给工作流,对象> 其中键是参数的名称。 只需更改调用以调用工作流即可执行此操作,如图 14 所示。 请注意,输出参数也包含在 Dictionary<字符串、对象> 集合中,该集合键位于参数名称上。
IDictionary<字符串,对象> 结果 = WorkflowInvoker.Invoke(wf,
new Dictionary<string,object> {
{“name”, “Bill” } }
);
string outValue = results[“fullGreeting”]。ToString();
图 14:将参数传递给工作流
现在,你已经了解了如何组合活动、变量和参数的基础知识,我将指导你了解框架中包含的活动,以便更有趣的工作流专注于业务逻辑,而不是低级别概念。
工作流活动调色板教程
使用任何编程语言时,你期望有用于定义应用程序逻辑的核心构造。 使用更高级别的开发框架(如 WF)时,这种期望不会改变。 就像在 .NET 语言(如 If/Else、Switch 和 While)中拥有语句一样,在声明性工作流中定义逻辑时,还需要这些相同的功能。 这些功能以框架附带的基本活动库的形式提供。 在本部分中,我将快速了解随框架一起提供的活动,以便了解现时提供的功能。
活动基元和收集活动
移动到声明性编程模型时,很容易开始想知道如何在编写代码时执行第二性质的常见对象操作任务。 对于发现自己使用对象和需要设置属性、调用命令或管理项集合的任务,有一组专门设计的活动与这些任务在一起。
| 活动 | 描述 |
|---|---|
分配 |
将值分配给位置 - 启用设置变量。 |
延迟 |
延迟指定时间的执行路径。 |
InvokeMethod |
在 .NET 对象上调用方法或 .NET 类型的静态方法,可以选择使用 T 的返回类型。 |
WriteLine |
将指定的文本写入文本编写器 – 默认为 Console.Out |
AddToCollection<T> |
将项添加到类型化集合。 |
RemoveFromCollection<T> |
从类型化集合中删除项。 |
ClearCollection<T> |
从集合中删除所有项。 |
ExistsInCollection<T> |
返回一个布尔值,该值指示集合中是否存在指定的项。 |
控制流活动
定义业务逻辑或业务流程时,控制执行流至关重要。 控制流活动包括基本知识,例如序列,在需要按顺序执行步骤时提供通用容器,以及 If 和 Switch 活动等常见分支逻辑。 控制流活动还包括基于数据(ForEach)和条件(While)的循环逻辑。 简化复杂编程最重要的任务是并行活动,这一切都使多个异步活动能够同时完成工作。
| 活动 | 描述 |
|---|---|
序列 |
用于执行系列中的活动 |
While/DoWhile |
当条件(表达式)为 true 时执行子活动 |
ForEach<T> |
循环访问可枚举集合,并为集合中的每个项执行一次子活动,等待子项在开始下一次迭代之前完成。 提供对以命名参数形式驱动迭代的单个项的类型化访问权限。 |
如果 |
根据条件(表达式)的结果执行两个子活动之一。 |
切换 T<> |
计算表达式,并使用匹配键计划子活动。 |
平行 |
同时计划所有子活动,但也提供了一个完成条件,使活动能够取消任何未完成的子活动(如果满足某些条件)。 |
ParallelForEach<T> |
循环访问可枚举集合,并为集合中的每个项执行一次子活动,同时计划所有实例。 与 ForEach<T>一样,此活动以命名参数的形式提供对当前数据项的访问权限。 |
摘 |
计划所有子 PickBranch 活动,并取消除第一个触发器完成的所有活动。 PickBranch 活动同时具有触发器和操作;每个活动都是一个活动。 触发器活动完成后,Pick 将取消其所有其他子活动。 |
下面的两个示例演示了其中几个用于说明如何将这些活动组合在一起。 第一个示例图 15 包括使用 ParallelForEach<T> 使用 URL 列表并异步获取指定地址的 RSS 源。 返回源后,ForEach<T> 用于循环访问源项并处理它们。
请注意,代码声明并定义一个 VisualBasicSettings 实例,该实例引用了 System.ServiceModel.Syndication 类型。 然后,在声明 VisualBasicValue<T> 实例引用该命名空间中的变量类型以启用这些表达式的类型解析时,将使用此设置对象。 另请注意,ParallelForEach 活动的正文采用有关创建自定义活动的部分中提到的 ActivityAction。 这些操作使用 DelegateInArgument 和 DelegateOutArgument 的方式与活动使用 InArgument 和 OutArgument 的方式大致相同。

图 15:控制流活动
第二个示例图 16 是等待超时响应的常见模式。 例如,等待经理批准请求,并在响应尚未到达分配时间时发送提醒。 DoWhile 活动会导致重复等待响应,而 Pick 活动用于同时执行 ManagerResponse 活动和触发器的延迟活动。 当延迟首先完成时,将发送提醒,当 ManagerResponse 活动首先完成时,标志将设置为中断 DoWhile 循环。

图 16:选取和 DoWhile 活动
图 16 中的示例还演示了如何在工作流中使用书签。 书签由活动创建,用于标记工作流中的某个位置,以便以后可以从该时间点恢复处理。 延迟活动具有一个书签,该书签在经过特定时间后恢复。 ManagerResponse 活动是一个自定义活动,在数据到达后等待输入并恢复工作流。 消息传送活动(不久讨论)是用于书签执行的主要活动。 当工作流未主动处理工作时,当它只是等待书签恢复时,它被视为空闲,并且可以持久保存到持久存储。 有关创建自定义活动的部分中将更详细地讨论书签。
迁移
对于使用 WF3 的开发人员,互操作活动可以在重用现有资产方面发挥重要作用。 在 System.Workflow.Runtime 程序集中找到的活动包装现有活动类型,并将活动上的属性显示为 WF4 模型中的参数。 由于属性是参数,因此可以使用表达式来定义值。 图 17 显示了调用 WF3 活动的互操作活动的配置。 输入参数是在工作流定义中使用对作用域内变量的引用定义的。 WF3 中生成的活动具有属性而不是参数,因此每个属性都提供相应的输入和输出参数,使你能够区分发送到活动中的数据,以及希望在活动执行后检索的数据。 在新的 WF4 项目中,在工具箱中找不到此活动,因为目标框架设置为 .NET Framework 4 客户端配置文件。 将项目属性中的目标框架更改为 .NET Framework 4,活动将显示在工具箱中。

图 17:互操作活动配置
流程图
设计流程图工作流时,有几个构造可用于管理流程图中的执行流。 这些构造本身提供简单的步骤、基于单个条件或 switch 语句的简单决策点。 流程图的真正功能是能够将这些节点类型连接到所需的流。
| 构造/活动 | 描述 |
|---|---|
流程图 |
一系列流步骤的容器,每个流步骤可以是任何活动或以下构造之一,但要执行,必须在流程图中连接该容器。 |
FlowDecision |
根据条件提供分支逻辑。 |
FlowSwitch<T> |
基于表达式的值启用多个分支。 |
FlowStep |
表示流程图中的一个步骤,能够连接到其他步骤。 此类型不显示在工具箱中,因为它由设计器隐式添加。 此活动包装工作流中的其他活动,并为工作流中的下一步提供导航语义。 |
请务必注意,虽然流程图模型有特定活动,但工作流中可以使用任何其他活动。 同样,可以将流程图活动添加到另一个活动,以提供该工作流中的流程图的执行和设计语义。 这意味着你可以有一个序列,其中包含多个活动和中间的流程图。
消息传送活动
WF4 的主要重点之一是 WF 与 WCF 之间的更紧密集成。 在工作流方面,这意味着对消息传递操作(例如发送和接收消息)进行建模的活动。 在消息传递的框架中,实际上包含多个不同的活动,每个活动的功能和用途略有不同。
| 活动 | 描述 |
|---|---|
发送/接收 |
用于发送或接收消息的消息活动的一种方式。 这些相同的活动由请求/响应样式交互组成。 |
ReceiveAndSendReply |
为接收消息并发送回复的服务操作建模。 |
SendAndReceiveReply |
调用服务操作并接收响应。 |
InitializeCorrelation |
允许在工作流逻辑中显式初始化相关值,而不是从消息中提取值。 |
CorrelationScope |
定义一个执行范围,其中可访问相关句柄来接收和发送活动,从而简化由多个消息传送活动共享的句柄的配置。 |
TransactedReceiveScope |
允许工作流逻辑包含在使用 Receive 活动流入 WCF 操作的同一事务中。 |
若要从工作流中调用服务操作,需要遵循向工作流项目添加服务引用的熟悉步骤。 然后,Visual Studio 中的项目系统将使用服务中的元数据,并为协定中找到的每个服务操作创建自定义活动。 可以将这视为在 C# 或 Visual Basic 项目中创建的 WCF 代理的工作流等效项。 例如,使用图 18 中显示的服务协定搜索酒店预订的现有服务;添加服务引用会生成图 19 中显示的自定义活动。
[ServiceContract]
公共接口 IHotelService
{
[OperationContract]
列出<HotelSearchResult> SearchHotels(
HotelSearchRequest requestDetails;
}
图 18:服务协定

图 19:自定义 WCF 活动
有关生成公开为 WCF 服务的工作流的详细信息,请参阅本文后面的“工作流服务”部分。
事务和错误处理
编写可靠的系统可能很困难,并且需要做好处理错误和管理以在应用程序中保持一致的状态。 对于工作流,使用包含 ActivityAction 或 Activity 属性的活动对管理异常处理和事务的范围建模,使开发人员能够对错误处理逻辑进行建模。 除了这些常见的一致性模式之外,WF4 还包括一些活动,这些活动允许你通过补偿和确认对长时间运行的分布式协调进行建模。
| 活动 | 描述 |
|---|---|
CancellationScope |
用于允许工作流开发人员在取消工作主体时做出反应。 |
TransactionScope |
通过在事务下执行作用域中的所有子活动,启用类似于在代码中使用事务范围的语义。 |
TryCatch/Catch<T> |
用于为异常处理建模和捕获类型化异常。 |
扔 |
可用于从活动引发异常。 |
重新引发 |
用于重新引发异常,通常是使用 TryCatch 活动捕获的异常。 |
CompensableActivity |
定义执行子活动的逻辑,这些子活动可能需要在成功后进行补偿。 提供补偿逻辑、确认逻辑和取消处理的占位符。 |
补偿 |
调用可补偿活动的补偿处理逻辑。 |
确认 |
调用可补偿活动的确认逻辑。 |
TryCatch 活动提供了一种熟悉的方法,用于限定一组工作范围来捕获可能发生的任何异常,Catch<T> 活动为异常处理逻辑提供容器。 可以看到图 20 中如何使用此活动的示例,其中每个类型异常块由 Catch<T> 活动定义,通过每个 catch 左侧看到的命名参数提供对异常的访问。 如果需要,可以使用 Rethrow 活动重新引发捕获的异常,或使用 Throw 活动引发你自己的新异常。

图 20:TryCatch 活动
事务有助于确保短期工作中的一致性,TransactionScope 活动提供了可以使用 TransactionScope 类在 .NET 代码中获取的相同范围。
最后,如果需要保持一致性,但不能跨资源使用原子事务,可以使用补偿。 通过补偿,可以定义一组工作,如果完成,可以有一组活动来补偿所做的更改。 作为一个简单的示例,请考虑图 21 中发送电子邮件的补偿活动。 如果活动完成后发生异常,则可以调用补偿逻辑,使系统恢复一致状态:在本例中,后续电子邮件可收回先前的邮件。

图 21:补偿活动
创建和执行工作流
与任何编程语言一样,可以使用工作流执行两项基本操作:定义它们和执行它们。 在这两种情况下,WF 都提供了多个选项,使你可以灵活控制。
用于设计工作流的选项
设计或定义工作流时,有两个主要选项:代码或 XAML。 XAML 提供真正的声明性体验,并允许在 XML 标记中定义工作流的整个定义,引用使用 .NET 生成的活动和类型。 大多数开发人员可能会使用工作流设计器生成工作流,这将导致声明性 XAML 工作流定义。 但是,由于 XAML 只是 XML,因此可以使用任何工具来创建它,这也是生成应用程序的强大模型的原因之一。 例如,图 22 中显示的 XAML 是在简单文本编辑器中创建的,可以编译,也可以直接用于执行所定义的工作流的实例。
<p:Activity x:Class=“Workflows.HelloSeq” xmlns=“https://schemas.microsoft.com/netfx/2009/xaml/activities/design"xmlns:p=“https://schemas.microsoft.com/netfx/2009/xaml/activities"xmlns:x=“https://schemas.microsoft.com/winfx/2006/xaml">
<x:Members>
<x:Property Name=“greeting” Type=“p:InArgument(x:String)” />
<x:Property Name=“name” Type=“p:InArgument(x:String)” />
</x:Members>
<p:Sequence>
<p:WriteLine>[问候 &“from workflow”]</p:WriteLine>
<p:WriteLine>[name]</p:WriteLine>
</p:Sequence>
</p:Activity>
图 22:XAML 中定义的工作流
此同一 XAML 由设计器生成,可由自定义工具生成,因此更易于管理。 此外,也可以使用代码生成工作流,如前所述。 这涉及到通过使用框架和自定义活动中的各种活动创建活动层次结构。 你已看到一些已在代码中完全定义的工作流示例。 与 WF3 不同,现在可以在代码中创建工作流并将其轻松序列化为 XAML;在建模和管理工作流定义方面提供更大的灵活性。
正如我所展示的,若要执行工作流,只需要一个活动,可以是一个内置代码的实例,也可以是一个从 XAML 创建的实例。 每个建模技术的最终结果是派生自 Activity 的类,也可以是可以反序列化或编译为活动的 XML 表示形式。
用于执行工作流的选项
若要执行工作流,需要定义工作流的活动。 可通过两种典型方法获取可以执行的活动:在代码中创建活动或在 XAML 文件中读取,并将内容反序列化为活动。 第一个选项很简单,我已经展示了几个示例。 若要加载 XAML 文件,应使用提供静态 Load 方法的 ActivityXamlServices 类。 只需传入 Stream 或 XamlReader 对象,即可返回 XAML 中表示的活动。
有了活动后,最简单的执行方法是使用 WorkflowInvoker 类,就像之前在单元测试中一样。 此类的 Invoke 方法具有 Activity 类型的参数,并返回一个 IDictionary<字符串、对象>。 如果需要将参数传递到工作流中,请先在工作流上定义这些参数,然后将值与 Activity 一起作为名称/值对的字典传递到 Invoke 方法中。 同样,在工作流上定义的任何 Out 或 In/Out 参数都将作为执行方法的结果返回。 图 23 提供了从 XAML 文件加载工作流、将参数传递到工作流和检索生成的输出参数的示例。
活动 mathWF;
using (Stream mathXaml = File.OpenRead(“Math.xaml”))
{
mathWF = ActivityXamlServices.Load(mathXaml);
}
var outputs = WorkflowInvoker.Invoke(mathWF,
new Dictionary<string, object> {
{ “operand1”, 5 },
{ “operand2”, 10 },
{ “operation”, “add” } };
Assert.AreEqual<int>(15, (int)outputs[“result”], “错误返回的结果”;
图 23:使用 in 和 out 参数调用“松散 XAML”工作流
请注意,在此示例中,活动是从 XAML 文件加载的,它仍然可以接受并返回参数。 工作流是使用 Visual Studio 中的设计器开发的,以便于,但可以在自定义设计器中开发,并存储在任何位置。 XAML 可以从数据库、SharePoint 库或其他一些存储加载,然后再交给运行时以供执行。
使用 WorkflowInvoker 提供了运行短期工作流的最简单机制。 它实质上使工作流就像应用程序中的方法调用一样,使你能够更轻松地利用 WF 的所有优势,而无需执行大量工作来托管 WF 本身。 这在单元测试活动和工作流时特别有用,因为它减少了执行测试组件所需的测试设置。
另一个常见的托管类是 WorkflowApplication,它为在运行时执行的工作流提供安全句柄,使你能够更轻松地管理长时间运行的工作流。 使用 WorkflowApplication,仍可以像使用 WorkflowInvoker 一样将参数传递到工作流中,但使用 Run 方法实际启动工作流运行。 此时,工作流开始在另一个线程上执行,控件将返回到调用代码。
由于工作流现在以异步方式运行,因此在托管代码中,你可能想知道工作流何时完成,或者它是否引发异常等。对于这些类型的通知,WorkflowApplication 类具有一组类型为 Action<T> 的属性,这些属性可用于添加代码以响应工作流执行的某些条件,包括:中止、未经处理的异常、已完成、空闲和卸载。 使用 WorkflowApplication 执行工作流时,可以使用类似于图 24 中所示的代码来处理事件。
WorkflowApplication wf = new WorkflowApplication(new Flowchart1()):
wf.Completed = delegate(WorkflowApplicationCompletedEventArgs e)
{
Console.WriteLine(“Workflow {0} complete”, e.InstanceId;
};
wf.Aborted = delegate(WorkflowApplicationAbortedEventArgs e)
{
Console.WriteLine(例如原因):
};
wf.OnUnhandledException =
delegate(WorkflowApplicationUnhandledExceptionEventArgs e)
{
Console.WriteLine(e.UnhandledException.ToString()):
return UnhandledExceptionAction.Terminate;
};
wf.Run();
图 24:WorkflowApplication 操作
在此示例中,主机代码(一个简单的控制台应用程序)的目标是在工作流完成或发生错误时通过控制台通知用户。 在实际系统中,主机将对这些事件感兴趣,并可能以不同的方式对这些事件做出反应,以向管理员提供有关故障的信息或更好地管理实例。
工作流扩展
自 WF3 以来,WF 的核心功能之一是,它足够轻量级,可以托管在任何 .NET 应用程序域中。 由于运行时可以在不同的域中执行,并且可能需要自定义的执行语义,因此运行时行为的方方面面需要从运行时外部化。 这就是工作流扩展发挥作用的地方。 工作流扩展使你能够以开发人员的身份编写主机代码(如果是这样选择),通过用自定义代码扩展它来向运行时添加行为。
WF 运行时知道的两种扩展类型是持久性和跟踪扩展。 持久性扩展提供了将工作流状态保存到持久存储区以及在需要时检索该状态的核心功能。 框架附带的持久性扩展包括对 Microsoft SQL Server 的支持,但可以编写扩展以支持其他数据库系统或持久存储。
坚持
若要使用持久性扩展,必须先设置数据库来保存状态,可以使用 %windir%\Microsoft.NET\Framework\v4.0.30319\sql\<语言>(例如 c:\windows\microsoft.net\framework\v4.0.30319\sql\en\) 中找到的 SQL 脚本来完成。 创建数据库后,执行两个脚本来创建数据库内的结构(SqlWorkflowInstanceStoreSchema.sql)和存储过程(SqlWorkflowInstanceStoreLogic.sql)。
设置数据库后,将 SqlWorkflowInstanceStore 与 WorkflowApplication 类一起使用。 首先创建存储并对其进行配置,然后将其提供给 WorkflowApplication,如图 25 所示。
WorkflowApplication 应用程序 = 新的 WorkflowApplication(活动);
InstanceStore instanceStore = 新的 SqlWorkflowInstanceStore(
@"Data Source=.\\SQLEXPRESS;Integrated Security=True";
InstanceView 视图 = instanceStore.Execute(
instanceStore.CreateInstanceHandle(), new CreateWorkflowOwnerCommand(),
TimeSpan.FromSeconds(30)):
instanceStore.DefaultInstanceOwner = 视图。InstanceOwner;
应用。InstanceStore = instanceStore;
图 25:添加 SQL 持久性提供程序
工作流可以通过两种方式持久化。 第一个是通过直接在工作流中使用 Persist 活动。 当此活动执行时,它会导致工作流状态保存到数据库,从而保存工作流的当前状态。 这样,工作流作者就可以控制保存工作流的当前状态。 第二个选项使宿主应用程序能够在工作流实例上发生各种事件时保留工作流状态;最有可能在工作流处于空闲状态时。 通过在 WorkflowApplication 上注册 PersistableIdle 操作,主机代码可以响应事件,以指示实例是否应持久化、卸载或两者均未执行。 图 26 显示了一个主机应用程序,在工作流处于空闲状态并导致它保持时收到通知。
wf.PersistableIdle = (waie) => PersistableIdleAction.Persist;
图 26:在工作流空闲时卸载工作流
工作流空闲并持久保存后,WF 运行时可以从内存中卸载它,为需要处理的其他工作流释放资源。 可以通过从 PersistableIdle 操作返回相应的枚举,选择让工作流实例卸载。 卸载工作流后,在某个时候,主机需要将其从持久性存储中加载回以恢复它。 为此,必须使用实例存储和实例标识符加载工作流实例。 这又会导致要求实例存储加载状态。 加载状态后,可以使用 WorkflowApplication 上的 Run 方法,也可以恢复书签,如图 27 所示。 有关书签的详细信息,请参阅自定义活动部分。
WorkflowApplication 应用程序 = 新的 WorkflowApplication(活动);
应用。InstanceStore = instanceStore;
应用。Load(id);
应用。ResumeBookmark(readLineBookmark,输入);
图 27:恢复持久化工作流
WF4 中的一项更改是工作流状态的持久化方式,并严重依赖于参数和变量的新数据管理技术。 仅保留作用域变量和参数值以及书签信息等运行时数据,而不是序列化整个活动树并维护工作流生存期的状态。 请注意,在图 27 中,在重新加载持久化实例时,除了使用实例存储外,还传递定义工作流的活动。 从本质上讲,数据库中的状态将应用于提供的活动。 此方法为版本控制提供了更大的灵活性,并且会导致更好的性能,因为状态的内存占用量较小。
跟踪
持久性使主机能够支持长时间运行的进程、跨主机对实例进行负载均衡和其他容错行为。 但是,工作流实例完成后,数据库中的状态通常会被删除,因为它不再有用。 在生产环境中,了解工作流当前执行的操作及其所执行的操作对于管理工作流和深入了解业务流程至关重要。 能够跟踪应用程序中发生的情况是使用 WF 运行时的令人信服的功能之一。
跟踪由两个主要组件组成:跟踪参与者和跟踪配置文件。 跟踪配置文件定义希望运行时跟踪的事件和数据。配置文件可以包括三种主要类型的查询:
- ActivityStateQuery – 用于标识活动状态(例如已关闭)和变量或参数以提取数据
- WorkflowInstanceQuery - 用于标识工作流事件
- CustomTrackingQuery - 用于标识用于跟踪数据的显式调用,通常在自定义活动中
例如,图 28 显示了正在创建的 TrackingProfile,其中包括 ActivityStateQuery 和 WorkflowInstanceQuery。 请注意,查询指示何时收集信息,以及要收集的数据。 对于 ActivityStateQuery,我包含了一个变量列表,这些变量应提取其值并将其添加到跟踪的数据中。
TrackingProfile 配置文件 = 新的 TrackingProfile
{
Name = “SimpleProfile”,
查询 = {
new WorkflowInstanceQuery {
States = { “*” }
},
new ActivityStateQuery {
ActivityName = “WriteLine”,
States={ “*” },
变量 = {“Text” }
}
}
};
图 28:创建跟踪配置文件
跟踪参与者是可以添加到运行时的扩展,负责在发出跟踪记录时进行处理。 TrackingParticipant 基类定义一个属性,用于提供 TrackingProfile 和用于处理跟踪的 Track 方法。 图 29 显示了一个有限的自定义跟踪参与者,用于将数据写入控制台。 若要使用跟踪参与者,必须使用跟踪配置文件进行初始化,然后将其添加到工作流实例上的扩展集合中。
public 类 ConsoleTrackingParticipant : TrackingParticipant
{
protected override void Track(TrackingRecord record, TimeSpan timeout)
{
ActivityStateRecord aRecord = 记录为 ActivityStateRecord;
if (aRecord != null)
{
Console.WriteLine(“{0} 输入状态 {1}”,
aRecord.Activity.Name,aRecord.State;
foreach (aRecord.Arguments 中的 var 项)
{
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine(“变量:{0} 具有值: {1}”,
项目。键,项。值;
Console.ResetColor();
}
}
}
}
图 29:控制台跟踪参与者
WF 附带了 EtwTrackingParticipant (ETW = 适用于 Windows 的企业跟踪),可将其添加到运行时,以实现数据的高性能跟踪。 ETW 是 Windows 中的本机组件的跟踪系统,由 OS 中的许多组件和服务使用,包括驱动程序和其他内核级代码。 写入 ETW 的数据可以使用自定义代码或使用即将推出的 Windows Server AppFabric 等工具。 对于从 WF3 迁移的用户,这是一个更改,因为框架中不会有 SQL 跟踪参与者传送。 但是,Windows Server AppFabric 将与 ETW 使用者一起提供,这些使用者将收集 ETW 数据并将其存储到 SQL 数据库。 同样,可以构建 ETW 使用者,以你喜欢的任何格式存储数据,或者编写自己的跟踪参与者,无论哪种方式对你的环境都更有意义。
创建自定义活动
虽然基本活动库包含用于与服务、对象和集合交互的丰富活动调色板,但它不提供用于与子系统(如数据库、电子邮件服务器或自定义域对象和系统)交互的活动。 WF4 入门的一部分是确定生成工作流时可能需要或想要的核心活动。 最大的好处是,当你构建核心工作单元时,你可以将它们合并到开发人员可以在工作流中使用的更粗粒度的活动。
活动类层次结构
虽然活动是活动,但并非所有活动都具有相同的要求或需求。 因此,与从活动直接派生的所有活动相比,有一个活动基类的层次结构,如图 30 所示,可以在生成自定义活动时从中进行选择。

图 30:活动类层次结构
对于大多数自定义活动,将从 AsyncCodeActivity、CodeActivity 或 NativeActivity(或泛型变体之一)派生,或者以声明方式为活动建模。 概括而言,这四个基类可以如下所述:
- 活动 – 用于通过编写其他活动来为活动建模,通常使用 XAML 定义。
- CodeActivity – 需要编写一些代码来完成工作时简化的基类。
- AsyncCodeActivity - 在活动异步执行某些工作时使用。
- NativeActivity – 当活动需要访问运行时内部时,例如计划其他活动或创建书签。
在以下部分中,我将生成多个活动,了解如何使用这些基类。 一般情况下,在考虑要使用的基类时,应从活动基类开始,看看是否可以使用声明性逻辑生成活动,如下一部分所示。 接下来,如果确定必须编写一些 .NET 代码来完成任务,并且希望活动异步执行,CodeActivity 将提供简化的模型。 最后,如果要编写在框架中找到的控制流活动(例如 While,Switch,If),则需要从 NativeActivity 基类派生,以便管理子活动。
使用活动设计器撰写活动
创建新的活动库项目或向 WF 项目添加新项并选择活动模板时,你将获得的内容是一个 XAML 文件,其中包含一个空的 Activity 元素。 在设计器中,这会将自身呈现为设计图面,你可以在其中创建活动的正文。 若要开始使用将具有多个步骤的活动,通常有助于将序列活动作为正文拖动进来,然后将实际活动逻辑填充为正文表示单个子活动。
可以非常像对具有参数的组件使用方法一样看待活动。 在活动本身上,可以定义参数及其方向性,以定义活动的接口。 需要在构成正文的活动(如前面提到的根序列)中定义要在活动中使用的变量。 例如,可以生成一个 NotifyManager 活动,该活动由两个更简单的活动组成:GetManager 和 SendMail。
首先,在 Visual Studio 2010 中创建新的 ActivityLibrary 项目,并将 Activity1.xaml 文件重命名为 NotifyManager.xaml。 接下来,从工具箱中拖动序列活动,并将其添加到设计器。 设置序列后,可以使用子活动填充该序列,如图 31 所示。 (请注意,此示例中使用的活动是引用项目中的自定义活动,在框架中不可用)。

图 31:通知管理器活动
此活动需要获取员工名称和消息正文的参数。 GetManager 活动查找员工的经理,并将电子邮件作为 OutArgument<字符串>提供。 最后,SendMail 活动向管理器发送消息。 下一步是通过展开设计器底部的 Arguments 窗口并输入两个所需输入参数的信息来定义活动的自变量。
若要撰写这些项,需要能够将 NotifyManager 活动中指定的输入参数传递给单个子活动。 对于 GetManager 活动,需要员工名称,该名称是活动的参数。 可以使用 GetManager 活动上 employeeName 参数的属性对话框中的参数名称,如图 31 所示。 SendMail 活动需要经理的电子邮件地址和邮件。 对于消息,可以输入包含消息的参数的名称。 但是,对于电子邮件地址,需要一种方法将 GetManager 活动中的参数传递到 SendMail 活动的 in 参数。 为此,需要一个变量。
突出显示 Sequence 活动后,可以使用“变量”窗口定义名为“mgrEmail”的变量。 现在,可以为 GetManager 活动上的 ManagerEmail out 参数和 SendMail 活动的 To 参数输入该变量名称。 执行 GetManager 活动时,输出数据将存储在该变量中,当 SendMail 活动执行时,它将从该变量读取数据作为参数。
刚刚描述的方法是用于定义活动主体的纯声明性模型。 在某些情况下,你可能更喜欢在代码中指定活动的正文。 例如,你的活动可能包括一组属性集合,这些属性又会驱动一组子活动;驱动创建一组 Assign 活动的命名值将是首选使用代码的一种情况。 在这些情况下,可以编写派生自活动的类,并在实现属性中编写代码以创建活动(以及任何子元素)来表示活动的功能。 在这两种情况下,最终结果都是相同的:逻辑是通过组合其他活动定义的,只有定义正文的机制是不同的。 图 32 显示了代码中定义的相同 NotifyManager 活动。
public class NotifyManager : Activity
{
public InArgument<string> EmployeeName { get; set; }
public InArgument<string> Message { get; set; }
protected override Func<Activity> Implementation
{
获取
{
return () =>
{
变量<字符串> mgrEmail =
新变量<字符串> { Name = “mgrEmail” };
序列 s = 新序列
{
变量 = { mgrEmail },
活动 = {
new GetManager {
EmployeeName =
新的 VisualBasicValue<字符串>(“EmployeeName”),
Result = mgrEmail,
},
new SendMail {
ToAddress = mgrEmail,
MailBody = 新的 VisualBasicValue<字符串>(“Message”),
From = “test@contoso.com”,
主题 = “自动电子邮件”
}
}
};
返回 s;
};
}
set { base.实现 = 值;}
}
}
图 32:使用代码 的活动组合
请注意,基类是 Activity,实现属性是 Func<Activity> 返回单个活动。 此代码与之前在创建工作流时显示的代码非常相似,这不应令人吃惊,因为两种情况下的目标都是创建活动。 此外,可以使用变量设置代码方法参数,也可以使用表达式将一个参数连接到另一个参数,如在两个子活动上使用 EmployeeName 和 Message 参数所示。
编写自定义活动类
如果确定无法通过编写其他活动来完成活动逻辑,则可以编写基于代码的活动。 在代码中编写活动时,请从相应的类派生,定义参数,然后重写 Execute 方法。 当活动完成其工作的时间时,运行时将调用 Execute 方法。
若要生成在上一个示例中使用的 SendMail 活动,首先需要选择基类型。 虽然可以使用 Activity 基类和编写 TryCatch<T> 和 InvokeMethod 活动来创建 SendMail 活动的功能,但对于大多数开发人员来说,在 CodeActivity 和 NativeActivity 基类之间进行选择并编写执行逻辑的代码更为自然。 由于此活动不需要创建书签或计划其他活动,因此可以从 CodeActivity 基类派生。 此外,由于此活动将返回单个输出参数,因此它应派生自 CodeActivity<TResult>,它提供 OutArgument<TResult>。 第一步是为电子邮件属性定义多个输入参数。 然后重写 Execute 方法来实现活动的功能。 图 33 显示了已完成的活动类。
public class SendMail : CodeActivity<SmtpStatusCode>
{
public InArgument<string> To { get; set; }
public InArgument<string> From { get; set; }
public InArgument<string> Subject { get; set; }
public InArgument<string> Body { get; set; }
protected override SmtpStatusCode Execute(
CodeActivityContext 上下文)
{
尝试
{
SmtpClient 客户端 = 新的 SmtpClient();
客户。发送(
From.Get(context),ToAddress.Get(context),
Subject.Get(context)、MailBody.Get(context);
}
catch (SmtpException smtpEx)
{
返回 smtpEx.StatusCode;
}
返回 SmtpStatusCode.Ok;
}
}
图 33:SendMail 自定义活动
关于参数的使用,需要注意几个事项。 我已使用 InArgument<T>类型声明了多个标准 .NET 属性。 这是基于代码的活动定义其输入和输出参数的方式。 但是,在代码中,我不能简单地引用这些属性来获取参数的值,我需要使用参数作为句柄来使用提供的 ActivityContext 检索值;在本例中为 CodeActivityContext。 使用参数类的 Get 方法检索值,使用 Set 方法为参数赋值。
活动完成 Execute 方法后,运行时将检测到此情况,并假定该活动已完成并关闭该活动。 对于异步或长时间运行的工作,有一些方法可以在即将发布的部分中解释此行为,但在这种情况下,一旦发送电子邮件,工作就会完成。
现在,让我们使用 NativeActivity 基类检查活动来管理子活动。 我将生成一个迭代器活动,以给定次数执行某些子活动。 首先,我需要一个参数来保存要执行的迭代数、要执行的 Activity 的属性,以及用于保存当前迭代数的变量。 Execute 方法调用 BeginIteration 方法以启动初始迭代和后续迭代的方式相同。 请注意,在图 34 中,使用 Get 和 Set 方法操作变量的方式与参数相同。
public class Iterator : NativeActivity
{
public Activity Body { get; set; }
public InArgument<int> RequestedIterations { get; set; }
public Variable<int> CurrentIteration { get; set; }
public Iterator()
{
CurrentIteration = new Variable<int> { Default = 0 };
}
protected override void Execute(NativeActivityContext context)
{
BeginIteration(context);
}
private void BeginIteration(NativeActivityContext 上下文)
{
if (RequestedIterations.Get(context) > CurrentIteration.Get(context)
{
上下文。ScheduleActivity(正文,
new CompletionCallback(ChildComplete),
new FaultCallback(ChildFaulted):
}
}
}
图 34:迭代器本机活动
如果仔细查看 BeginIteration 方法,你会注意到 ScheduleActivity 方法调用。 这是一个活动如何与运行时交互来计划另一个执行活动,这是因为你需要从 NativeActivity 派生的此功能。 另请注意,在 ActivityExecutionContext 上调用 ScheduleActivity 方法时,会提供两个回调方法。 由于你不知道将哪个活动用作正文,或者需要多长时间才能完成,并且由于 WF 是一个非常异步的编程环境,因此回调用于在子活动完成时通知活动,从而允许编写代码来做出响应。
第二个回调是一个 FaultCallback,如果子活动发生引发异常,将调用该回调。 在此回调中,你会收到异常,并且能够通过 ActivityFaultContext 向运行时指示错误已处理,从而阻止其向上和退出活动。 图 35 显示了迭代器活动的回调方法,其中同时计划下一次迭代和 FaultCallback 处理错误,以允许执行继续执行下一次迭代。
private void ChildComplete(NativeActivityContext 上下文,
ActivityInstance 实例)
{
CurrentIteration.Set(context, CurrentIteration.Get(context) + 1;
BeginIteration(context);
}
private void ChildFaulted(NativeActivityFaultContext 上下文,异常 ex,
ActivityInstance 实例)
{
CurrentIteration.Set(context, CurrentIteration.Get(context) + 1;
上下文。HandleFault();
}
图 35:活动回调
当活动需要执行可能长时间运行的工作时,理想情况下,工作可以移交给另一个线程,以允许工作流线程用于继续处理其他活动,或者用于处理其他工作流。 但是,只需启动线程并将控制权返回到运行时可能会导致问题,因为运行时不监视创建的线程。 如果运行时确定工作流处于空闲状态,则工作流可能会卸载、释放回调方法,或者运行时可以假定你的活动已完成,并移动到工作流中的下一个活动。 若要在活动中支持异步编程,请派生自 AsyncCodeActivity 或 AsyncCodeActivity<T>。
图 36 显示了加载 RSS 源的异步活动的示例。 对于异步活动,执行方法的此签名不同,因为它返回 IAsyncResult。 在 execute 方法中编写代码以开始异步操作。 重写 EndExecute 方法,以在异步操作完成并返回执行结果时处理回调。
public sealed 类 GetFeed:AsyncCodeActivity<SyndicationFeed>
{
public InArgument<Uri> FeedUrl { get; set; }
protected override IAsyncResult BeginExecute(
AsyncCodeActivityContext 上下文、AsyncCallback 回调、
对象状态)
{
var req = (HttpWebRequest)HttpWebRequest.Create(
FeedUrl.Get(context);
req.方法 = “GET”;
上下文。UserState = req;
返回 req。BeginGetResponse(新的 AsyncCallback(回调),状态;
}
protected override SyndicationFeed EndExecute(
AsyncCodeActivityContext 上下文,IAsyncResult 结果)
{
HttpWebRequest req = context。UserState 作为 HttpWebRequest;
WebResponse wr = req。EndGetResponse(result);
SyndicationFeed localFeed = SyndicationFeed.Load(
XmlReader.Create(wr.GetResponseStream();
返回 localFeed;
}
}
图 36:创建异步活动
其他活动概念
现在,你已了解了使用基类、参数和变量创建活动的基础知识,以及管理执行;我将简要介绍可在活动开发中使用的一些更高级的功能。
书签使活动作者能够在工作流执行中创建恢复点。 创建书签后,可以继续从其离开位置继续工作流处理。 书签作为状态的一部分保存,因此与异步活动不同,创建书签后,工作流实例无法持久保存和卸载。 当主机想要恢复工作流时,它将加载工作流实例并调用 ResumeBookmark 方法以从其离开位置恢复实例。 恢复书签时,数据也可以传递到活动中。 图 37 显示了一个 ReadLine 活动,该活动创建一个书签来接收输入,并注册在数据到达时要调用的回调方法。 运行时知道活动何时具有未完成的书签,在恢复书签之前不会关闭该活动。 ResumeBookmark 方法可用于 WorkflowApplication 类,将数据发送到命名书签并发出 BookmarkCallback 信号。
public class ReadLine : NativeActivity<字符串>
{
public OutArgument<string> InputText { get; set; }
protected override void Execute(NativeActivityContext context)
{
上下文。CreateBookmark(“ReadLine”,
new BookmarkCallback(BookmarkResumed);
}
private void BookmarkResumed(NativeActivityContext 上下文,
书签 bk,对象状态)
{
Result.Set(context, state);
}
}
图 37:创建书签
活动作者的另一个强大功能是 ActivityAction 的概念。 ActivityAction 是命令性代码中 Action 类的工作流等效项:描述通用委托;但对于一些人来说,模板的想法可能更容易理解。 请考虑 ForEach<T> 活动,以便循环访问一组数据并为每个数据项计划子活动。 你需要一些方法来允许活动的使用者定义正文,并能够使用类型为 T 的数据项。本质上,你需要一些可以接受 T 类型的项目并对其执行操作的活动,ActivityAction<T> 用于启用此方案,并提供对活动的参数和定义进行处理该项目的引用。 可以在自定义活动中使用 ActivityAction,使活动的使用者能够在适当的位置添加自己的步骤。 这样,便可以创建排序的工作流或活动模板,使用者可在其中填写相关部件来自定义其使用执行。 当活动需要调用的委托返回值时,还可以使用 ActivityFunc<TResult> 和相关替代项。
最后,可以通过检查单个设置、限制允许的子活动等来验证活动,以确保正确配置活动。对于需要特定参数的常见需求,可以将 RequiredArgument 属性添加到活动中的属性声明。 为了进行更相关的验证,请在活动的构造函数中创建一个约束,并将其添加到活动类上显示的约束集合。 约束是写入以检查目标活动并确保有效性的活动。 图 38 显示了迭代器活动的构造函数,该构造函数验证是否已设置 RequestedIterations 属性。 最后,重写 CacheMetadata 方法,可以在 ActivityMetdata 参数上调用 AddValidationError 方法,为活动添加验证错误。
var act = new DelegateInArgument<Iterator> { Name = “constraintArg” };
var vctx = 新的 DelegateInArgument<ValidationContext>():
约束<迭代器> 缺点 = 新的约束<迭代器>
{
Body = new ActivityAction<Iterator, ValidationContext>
{
Argument1 = act,
Argument2 = vctx,
Handler = new AssertValidation
{
消息 = “必须提供迭代”,
PropertyName = “RequestedIterations”,
断言 = 新的 InArgument<bool>()
(e) => 行为。Get(e)。RequestedIterations != null)
}
}
};
基础。Constraints.Add(cons);
图 38:约束
活动设计器
WF 的优点之一是,它允许以声明方式对应用程序逻辑进行编程,但大多数人不想手动编写 XAML,这就是为什么 WF 中的设计器体验如此重要。 生成自定义活动时,你可能还需要创建一个设计器,以便为活动的使用者提供显示和视觉交互体验。 WF 的设计器基于 Windows Presentation Foundation,这意味着你拥有所有样式、触发器、数据绑定和其他用于为设计器生成丰富 UI 的强大工具。 此外,WF 提供了一些用户控件,你可以在设计器中使用这些控件来简化显示单个子活动或活动集合的任务。 四个主要控件包括:
- ActivityDesigner – 活动设计器中使用的根 WPF 控件
- WorkflowItemPresenter – 用于显示单个活动
- WorkflowItemsPresenter – 用于显示子活动的集合
- ExpressionTextBox - 用于就地编辑表达式(如参数)
WF4 包含一个 ActivityDesignerLibrary 项目模板和 ActivityDesigner 项模板,可用于最初创建项目。 初始化设计器后,可以通过布局如何显示子活动的数据和视觉表示形式,开始使用上述元素来自定义活动的外观。 在设计器中,你有权访问作为实际活动的抽象的 ModelItem,并显示活动的所有属性。
WorkflowItemPresenter 控件可用于显示属于活动的单个活动。 例如,若要提供将活动拖到前面显示的迭代器活动并显示 Iteractor 活动中包含的活动的功能,可以使用图 39 中显示的 XAML,将 WorkflowItemPresenter 绑定到 ModelItem.Body。 图 40 显示设计器在工作流设计图面上使用。
<sap:ActivityDesigner x:Class=“CustomActivities.Presentation.IteratorDesigner”
xmlns=“https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x=“https://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“>
<网格>
<BorderBrush=“MidnightBlue” BorderThickness=“3” CornerRadius=“10”>
<sap:WorkflowItemPresenter MinHeight=“50”
Item=“{Binding Path=ModelItem.Body, Mode=TwoWay}”
HintText=“在此处添加正文”/>
</Border>
</Grid>
</sap:ActivityDesigner>
图 39:活动设计器中的 WorkflowItemPresenter

图 40:迭代器设计器
WorkflowItemsPresenter 提供类似的功能来显示多个项,例如序列中的子项。 可以定义一个模板和方向,以便显示项的方式,以及用于在活动(如连接器、箭头或更有趣的内容)之间添加视觉元素的空格键模板。 图 41 显示了自定义序列活动的简单设计器,该设计器显示每个活动之间的水平线的子活动。
<sap:ActivityDesigner x:Class=“CustomActivities.Presentation.CustomSequenceDesigner”
xmlns=“https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x=“https://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>
<BorderBrush=“Goldenrod” BorderThickness=“3”>
<sap:WorkflowItemsPresenter HintText=“删除此处的活动”
Items=“{Binding Path=ModelItem.ChildActivities}”>
<sap:WorkflowItemsPresenter.SpacerTemplate>
<DataTemplate>
<矩形高度=“3” Width=“40”
Fill=“MidnightBlue” Margin=“5” />
</DataTemplate>
</sap:WorkflowItemsPresenter.SpacerTemplate>
<sap:WorkflowItemsPresenter.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation=“Vertical”/>
</ItemsPanelTemplate>
</sap:WorkflowItemsPresenter.ItemsPanel>
</sap:WorkflowItemsPresenter>
</Border>
</StackPanel>
</Grid>
</sap:ActivityDesigner>
图 41:使用 WorkflowItemsPresenter 的自定义序列设计器

图 42:序列设计器
最后,ExpressionTextBox 控件除了属性网格外,还可以在设计器中就地编辑活动参数。 在 Visual Studio 2010 中,这包括对正在输入的表达式的 Intellisense 支持。 图 43 显示了之前创建的 GetManager 活动的设计器,从而在设计器中启用 EmployeeName 和 ManagerEmail 参数的编辑。 实际设计器如图 44 所示。
<sap:ActivityDesigner x:Class=“CustomActivities.Presentation.GetManagerDesigner”
xmlns=“https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x=“https://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”
xmlns:sapc=“clr-namespace:System.Activities.Presentation.Converters;
assembly=System.Activities.Presentation“>
<sap:ActivityDesigner.Resources>
<sapc:ArgumentToExpressionConverter
x:Key=“ArgumentToExpressionConverter”
x:Uid=“swdv:ArgumentToExpressionConverter_1” />
</sap:ActivityDesigner.Resources>
<网格>
<Grid.ColumnDefinitions>
<ColumnDefinition Width=“50” />
<ColumnDefinition Width=“*” />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock VerticalAlignment=“Center”
HorizontalAlignment=“Center”>Employee:</TextBlock>
<sapv:ExpressionTextBox Grid.Column=“1”
Expression=“{Binding Path=ModelItem.EmployeeName, Mode=TwoWay,
Converter={StaticResource ArgumentToExpressionConverter},
ConverterParameter=In}“ OwnerActivity=”{Binding Path=ModelItem}”
MinLines=“1” MaxLines=“1” MinWidth=“50”
HintText=“<员工姓名>”/>
<TextBlock Grid.Row=“1” VerticalAlignment=“Center”
HorizontalAlignment=“Center”>Mgr Email:</TextBlock>
<sapv:ExpressionTextBox Grid.Column=“2” Grid.Row=“1”
UseLocationExpression=“True”
Expression=“{Binding Path=ModelItem.ManagerEmail, Mode=TwoWay,
Converter={StaticResource ArgumentToExpressionConverter},
ConverterParameter=Out}“ OwnerActivity=”{Binding Path=ModelItem}”
MinLines=“1” MaxLines=“1” MinWidth=“50” />
</Grid>
</sap:ActivityDesigner>
图 43:使用 ExpressionTextBox 编辑设计器中的参数

图 44:GetManager 活动设计器
此处提供的示例设计器可以简单突出显示特定功能,但 WPF 的全部功能可供你使用,使活动更易于配置,更自然地供使用者使用。 创建设计器后,可通过两种方法将其与活动相关联:将属性应用于活动类或实现 IRegisterMetadata 接口。 若要让活动定义驱动设计器的选择,只需将 DesignerAttribute 应用于活动类并提供设计器的类型;这与 WF3 中的工作方式完全相同。 对于更松散耦合的实现,可以实现 IRegisterMetadata 接口,并定义要应用于活动类和属性的属性。 图 45 显示了一个示例实现,用于为两个自定义活动应用设计器。 Visual Studio 将发现你的实现并自动调用它,而接下来讨论的自定义设计器主机将显式调用该方法。
public class Metadata : IRegisterMetadata
{
public void Register()
{
AttributeTableBuilder builder = new AttributeTableBuilder();
建筑工人。AddCustomAttributes(typeof(SendMail), new Attribute[] {
new DesignerAttribute(typeof(SendMailDesigner)]};
建筑工人。AddCustomAttributes(typeof(GetManager),新 Attribute[]{
new DesignerAttribute(typeof(GetManagerDesigner)]}:
MetadataStore.AddAttributeTable(builder.CreateTable();
}
}
图 45:为活动注册设计器
重新托管工作流设计器
过去,开发人员通常希望为用户提供创建或编辑工作流的能力。 在早期版本的 Windows Workflow Foundation 中,这是可能的,但不是一项微不足道的任务。 迁移到 WPF 后,新设计器重新托管是开发团队的主要用例。 可以在自定义 WPF 应用程序中托管设计器,如图 46 中所示,其中包含几行 XAML 和几行代码。

图 46:重新托管的工作流设计器
窗口的 XAML 图 47 使用网格来布局三列,然后在每个单元格中放置边框控件。 在第一个单元格中,工具箱以声明方式创建,但也可在代码中创建。 对于设计器视图本身和属性网格,其余单元格中都有两个空边框控件。 这些控件将添加到代码中。
<Window x:Class=“WorkflowEditor.MainWindow”
xmlns=“https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x=“https://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys=“clr-namespace:System;assembly=mscorlib”
xmlns:sapt=“clr-namespace:System.Activities.Presentation.Toolbox;
assembly=System.Activities.Presentation”
Title=“工作流编辑器” Height=“500” Width=“700” >
<Window.Resources>
<sys:String x:Key=“AssemblyName”>System.Activities,Version=4.0.0.0,
Culture=neutral, PublicKeyToken=31bf3856ad364e35</sys:String>
<sys:String x:Key=“MyAssemblyName”>CustomActivities</sys:String>
</Window.Resources>
<Grid x:Name=“DesignerGrid”>
<Grid.ColumnDefinitions>
<ColumnDefinition Width=“2*” />
<ColumnDefinition Width=“7*” />
<ColumnDefinition Width=“3*” />
</Grid.ColumnDefinitions>
<边框>
<sapt:ToolboxControl>
<sapt:ToolboxControl.Categories>
<sapt:ToolboxCategory CategoryName=“Basic”>
<sapt:ToolboxItemWrapper
AssemblyName=“{StaticResource AssemblyName}” >
<sapt:ToolboxItemWrapper.ToolName>
System.Activities.Statements.Sequence
</sapt:ToolboxItemWrapper.ToolName>
</sapt:ToolboxItemWrapper>
<sapt:ToolboxItemWrapper
AssemblyName=“{StaticResource MyAssemblyName}”>
<sapt:ToolboxItemWrapper.ToolName>
CustomActivities.SendMail
</sapt:ToolboxItemWrapper.ToolName>
</sapt:ToolboxItemWrapper>
</sapt:ToolboxCategory>
</sapt:ToolboxControl.Categories>
</sapt:ToolboxControl>
</Border>
<边框名称=“DesignerBorder” Grid.Column=“1”
BorderBrush=“Salmon” BorderThickness=“3” />
<Border x:Name=“PropertyGridExpander” Grid.Column=“2” />
</Grid>
</Window>
图 47:设计器重新托管 XAML
用于初始化设计器和属性网格的代码如图 48 所示。 OnInitialized 方法中的第一步是为工作流设计器中使用的所有活动注册设计器。 DesignerMetadata 类为附带框架的活动注册关联的设计器,元数据类为自定义活动注册设计器。 接下来,创建 WorkflowDesigner 类,并使用 View 和 PropertyInspectorView 属性访问呈现所需的 UIElement 对象。 设计器使用空序列初始化,但你也可以添加菜单并从各种源(包括文件系统或数据库)加载工作流 XAML。
public MainWindow()
{
InitializeComponent();
}
protected override void OnInitialized(EventArgs e) {
基础。OnInitialized(e):
RegisterDesigners():
PlaceDesignerAndPropGrid();
}
private void RegisterDesigners() {
new DesignerMetadata()。Register();
新的 CustomActivities.Presentation.Metadata()。Register();
}
private void PlaceDesignerAndPropGrid() {
WorkflowDesigner 设计器 = 新的 WorkflowDesigner();
设计师。Load(new System.Activities.Statements.Sequence():
DesignerBorder.Child = designer。视图;
PropertyGridExpander.Child = 设计器。PropertyInspectorView;
}
图 48:设计器重新托管代码
有了这一点代码和标记,就可以从工具箱中编辑工作流、拖放活动、在工作流中配置变量以及操作活动参数。 还可以通过在设计器上调用 Flush 来获取 XAML 的文本,然后引用 WorkflowDesigner 的 Text 属性。 你可以做的更多,入门比以前容易得多。
工作流服务
如前所述,此版本的 Windows Workflow Foundation 中的一大重点是与 Windows Communication Foundation 的更紧密集成。 活动演练中的示例演示了如何从工作流调用服务,在本部分中,我将讨论如何将工作流公开为 WCF 服务。
首先,创建新项目并选择 WCF 工作流服务项目。 模板完成后,你将找到具有 web.config 文件和具有 XAMLX 扩展名的文件的项目。 XAMLX 文件是声明性服务或工作流服务,与其他 WCF 模板一样,提供一个正常运行的服务实现,该实现接收请求并返回响应。 对于此示例,我将更改合同以创建一个操作来接收费用报表,并返回已收到该报表的指示器。 首先,将 ExpenseReport 类添加到项目,如图 49 中显示的类。
[DataContract]
public class ExpenseReport
{
[DataMember]
public DateTime FirstDateOfTravel { get; set; }
[DataMember]
public double TotalAmount { get; set; }
[DataMember]
public string EmployeeName { get; set; }
}
图 49:支出报表合同类型
返回工作流设计器,将变量中的“data”变量的类型更改为刚刚定义的 ExpenseReport 类型(可能需要生成项目,以便类型显示在类型选取器对话框中)。 通过单击“内容”按钮并将对话框中的类型更改为 ExpenseReport 类型以匹配来更新“接收”活动。 更新活动属性以定义新的 OperationName 和 ServiceContractName,如图 50 所示。

图 50:配置接收位置
现在,SendReply 活动可以将值设置为 True 以返回回执指示器。 显然,在更复杂的示例中,可以使用工作流的完整功能将信息保存到数据库、对报表进行验证等,然后再确定响应。 使用 WCFTestClient 应用程序浏览到服务会显示活动配置如何定义公开的服务协定,如图 51 所示。

图 51:从工作流服务生成的协定
创建工作流服务后,需要托管它,就像使用任何 WCF 服务一样。 前面的示例使用了用于托管的最简单选项:IIS。 若要在自己的进程中托管工作流服务,需要使用 System.ServiceModel.Activities 程序集中找到的 WorkflowServiceHost 类。 请注意,System.WorkflowServices 程序集中存在同名的另一个类,该程序集用于托管使用 WF3 活动生成的工作流服务。 在最简单的自承载情况下,可以使用如图 52 所示的代码来托管服务并添加终结点。
WorkflowServiceHost 主机 = new
WorkflowServiceHost(XamlServices.Load(“ExpenseReportService.xamlx”),”
new Uri(“https://localhost:9897/Services/Expense"));
主机。AddDefaultEndpoints();
主机。Description.Behaviors.Add(
new ServiceMetadataBehavior { HttpGetEnabled = true };
主机。Open();
Console.WriteLine(“主机已打开”):
Console.ReadLine();
图 52:自承载工作流服务
如果要利用 Windows 中的托管托管托管选项,可以将 XAMLX 文件复制到目录中,并在 web.config 文件中指定行为、绑定、终结点等所需的配置信息,从而在 IIS 中托管服务。 事实上,如前所述,使用声明性工作流服务项目模板之一在 Visual Studio 中创建新项目时,你将获得一个具有 web.config 文件和 XAMLX 中定义的工作流服务的项目,可供部署。
使用 WorkflowServiceHost 类托管工作流服务时,仍需能够配置和控制工作流实例。 若要控制服务,有一个名为 WorkflowControlEndpoint 的新标准终结点。 配套类 WorkflowControlClient 提供预生成的客户端代理。 可以在服务上公开 WorkFlowControlEndpoint,并使用 WorkflowControlClient 创建和运行工作流的新实例,或控制正在运行的工作流。 使用此同一模型来管理本地计算机上的服务,也可以使用它来管理远程计算机上的服务。 图 53 显示了在服务上公开工作流控制终结点的更新示例。
WorkflowServiceHost 主机 = new
WorkflowServiceHost(“ExpenseReportService.xamlx”,
new Uri(“https://localhost:9897/Services/Expense"));
主机。AddDefaultEndpoints();
WorkflowControlEndpoint wce = new WorkflowControlEndpoint(
new NetNamedPipeBinding(),
new EndpointAddress(“net.pipe://localhost/Expense/WCE”);
主机。AddServiceEndpoint(wce);
主机。Open();
图 53:工作流控制终结点
由于未直接创建 WorkflowInstance,因此有两个选项可用于向运行时添加扩展。 第一个是可以向 WorkflowServiceHost 添加一些扩展,它们将正确初始化工作流实例。 第二个是使用配置。 例如,跟踪系统可以在应用程序配置文件中完全定义。 定义配置文件中的跟踪参与者和跟踪配置文件,并使用行为将特定参与者应用到服务,如图 54 所示。
<system.serviceModel>
<跟踪>
<参与者>
<add name=“EtwTrackingParticipant”
type=“System.Activities.Tracking.EtwTrackingParticipant, System.Activities, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35”
profileName=“HealthMonitoring_Tracking_Profile”/>
</参与者>
<配置文件>
<trackingProfile name=“HealthMonitoring_Tracking_Profile”>
<工作流 activityDefinitionId=“*”>
<workflowInstanceQuery>
<状态>
<状态名称=“Started”/>
<状态名称=“Completed”/>
</states>
</workflowInstanceQuery>
</workflow>
</trackingProfile>
</profiles>
</tracking>
. . .
> <行为
<serviceBehaviors>
<行为名称=“SampleTrackingSample.SampleWFBehavior”>
<etwTracking profileName=“ HealthMonitoring_Tracking_Profile” />
</behavior>
</serviceBehaviors>
</behaviors>
. . .
图 54:配置服务跟踪
WCF 服务的托管和配置有许多新的改进,可以在工作流中利用这些服务,例如“.svc-less”服务,或者不需要文件扩展名、默认绑定和默认终结点的服务来命名几个终结点。 有关 WCF4 中的这些功能和其他功能的详细信息,请参阅“其他资源”部分中的 WCF 的配套文件。
除了托管改进之外,Windows Workflow Foundation 还利用 WCF 中的其他功能;一些直接和其他人间接。 例如,消息关联现在是构建服务的关键功能,它允许根据消息中的数据(如订单号或员工 ID)将消息与给定工作流实例相关联。可以对请求和响应的各种消息传送活动配置关联,并支持在消息中的多个值上关联。 同样,有关这些新功能的更多详细信息,请参阅 WCF4 的配套白皮书。
结论
附带 .NET Framework 4 的新 Windows Workflow Foundation 技术在性能和开发人员工作效率方面具有重大改进,实现了业务逻辑声明性应用程序开发的目标。 该框架提供了简化复杂工作短单元的工具,以及通过跟踪生成具有相关消息、持久状态和丰富应用程序可见性的复杂长时间运行的服务。
关于作者
Matt 是 Pluralsight 的技术人员,他专注于连接的系统技术(WCF、WF、BizTalk、AppFabric 和 Azure 服务平台)。 Matt 也是一位独立顾问,专门从事Microsoft .NET 应用程序设计和开发。 作为一名作家,马特已经为包括MSDN杂志在内的几本期刊和杂志做出了贡献,他目前为基金会专栏创作了工作流内容。 马特经常通过在当地、地区和国际会议(如 Tech Ed)发表演讲来分享他对技术的热爱。 Microsoft承认马特是围绕互联系统技术的社区贡献的 MVP。 通过他的博客联系马特:http://www.pluralsight.com/community/blogs/matt/default.aspx。
其他资源
- 大卫·查佩尔
工作流方式 - Aaron Skonnard 在 .NET 4 中介绍 WCF 的开发人员简介
- 从 .NET 3.5 中的 WF 迁移到 .Net 4 中的 WF 的指南。
- Windows Server AppFabric