65.9K
CodeProject 正在变化。 阅读更多。
Home

WF4 用于消息调解的自定义活动

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.97/5 (24投票s)

2009年11月10日

CPOL

24分钟阅读

viewsIcon

113534

downloadIcon

2030

本文描述了 XAML 工作流的自定义消息中介活动的设计、实现和使用。

注意:本文已更新至 WF4.5 版本。

目录

特点

  • 声明式消息中介
  • 基于契约模型
  • 创建、转换和检查无类型消息
  • 重托管工作流设计器用户控件
  • 在自托管进程中交互式设计和运行
  • XSLT 技术
  • .NET 4 技术

引言

在我之前的文章《使用 WF4 WorkflowInvoker》中,我提到了即将到来的 .NET 4 技术中的“XAML 堆栈”策略,以及为什么有必要将 WF 模型从 WF3/3.5 更改。WF 范式中最重要的变化之一是工作流模型与通信模型的封装。这些 WCF 和 WF 模型通过由 Endpoint 元素表示的 XAML 堆栈集成。换句话说,Endpoint 表示服务与其消费者之间的逻辑连接层。这种逻辑连接可以用契约模型描述;更多详细信息请参阅我的文章《可管理服务的契约模型》。

Endpoint 背后的工作流服务由有类型或无类型的消息驱动。工作流服务不需要知道消息将如何传输等等。它的职责是根据业务工作流处理消息。消息通过活动流经服务,在活动中可以消费、修改或创建另一个消息。消息的处理过程称为消息中介,活动是中介原语。消息可以根据本地资源或通过另一个服务远程中介。中介原语可以更改消息的格式、内容或目标。中介流可以很简单,只有一个中介原语,也可以是按顺序排列的多个原语。

例如,消息日志记录器表示一个中介原语,用于在中介流期间捕获存储中的业务流程状态。在此原语中,流经的消息被中介为正确的格式,添加更多信息并作为消息日志存储在存储中。另一个很好的例子是消息路由器,其中消息根据内容(连接或业务)进行中介,以选择性地路由消息。

基本上,我们可以有三种消息中介原语模型,请参见下图

如上图所示,第一个原语将输入消息中介为消息输出,如果发生异常,将创建新消息错误。第二个原语将消费消息输入并更新本地中介状态,除了异常消息外没有输出消息。最后一个原语将根据本地中介状态创建新消息输出和消息错误

下图显示了 WF4 中介原语 Assign 的一个非常简单的示例

如您所见,输出消息需要一个字符串元素 Name,而不是输入的字符串元素,例如 FirstNLastN (FirstName/LastName)。因此,消息必须由原语进行中介。这是一个非常简单的中介,可以通过表达式工具箱中的 Assign 活动完成。得益于新的 WF 模型,表达式文本的使用有助于简化消息中介和原语的实现。

请注意,上述示例需要一个包含程序集中 CLR 类型的有类型消息,因此对于任何类型更改,我们都需要重新编译程序集的新版本。例如,如果上述示例的新版本需要使用中间名,则输入消息的架构需要更新以适应新类型。另一方面,Assign 原语可以直观地、声明式地在 XAML 堆栈中修改表达式文本。

好的,这是一种处理消息中介的方法。另一种方法是使用声明式消息中介。这种方法基于无类型消息和 XSLT 技术。

下图显示了基于 XSLT 技术创建消息输出的自定义消息中介

在上面的示例中,自定义 CreateMessage 中介原语将根据 XSLT 模板创建输出消息。在设计时,设计器将根据契约模型(WSDL、模式等)手动或自动创建参数化 XSLT 模板(参见上面的中介器文本框)资源,并将其存储到 XAML 堆栈中。在运行时,XSLT 编译器将此 XSLT 模板转换为带有参数实时值的消息输出。契约模型中的任何更改都只需要修改此 XSLT 模板,例如 XAML 堆栈中的嵌入资源。

XLT 模板可以设计用于任何 WCF 支持的消息版本,包括 None。使用此消息原语,我们可以声明式地将消息发送到契约模型描述的任何端点。请注意,契约模型可以从存储库或物理端点发现。

本文重点介绍声明式消息中介。我将向您展示如何使用 WF4 技术实现和使用自定义中介原语。此库可以导入到 WF4 工具箱中。作为奖励,我为我们的测试目的在 Windows 窗体上包含了一个重托管的工作流设计器。

我假设您对当前的 WCF 和 WF 技术有一定的实践经验。好的,让我们开始吧。

概念与设计

声明式消息中介的概念和设计基于处理无类型消息。Microsoft 即将推出的 WF4 技术没有内置的无类型消息中介原语。无类型消息只能发送、接收和查询。没有工具支持直接使用契约模型。它需要向项目中添加一些服务引用,这将在项目程序集中创建所有 CLR 类型。

请注意,处理无类型消息需要知道其元数据,这些元数据可以从存储库、文件系统或现有物理端点获取。下图显示了消息中介原语的概念

如上图所示,中介原语分为两部分:设计时运行时设计时负责根据契约元数据为特定消息版本创建 XSLT 模板。契约元数据可以从 URL 资源获取,成功接收后将更新契约和操作组合框。通过选择特定的契约和操作,我们可以生成参数化的 XSLT 消息模板并将其存储在 XML 记事本面板中进行自定义。最后,设计过程将此 XSLT 资源嵌入到 XAML 堆栈中,包括设计信息(例如:URL、版本等)。

原语的第二部分,即运行时,非常简单;XSLT 处理器将加载此 XSLT 资源、运行时值集合并调用 XSLT 转换以生成输出消息。

正如我之前提到的,契约元数据可以从存储库、文件系统(例如:项目)或现有物理端点加载。这是一个很棒的功能,允许我们以松散耦合的方式依赖于契约优先模型或存储在存储库中的元数据。在契约模型更改的情况下,我们需要返回到设计时过程并修改我们的 XSLT 消息模板。请注意,XML 模板可以通过 InArgument<XElement> 属性在自定义活动(原语)中访问。

消息中介原语

在本段中,我将描述一组用于消息中介的自定义活动(原语),它们可以加载到工作流设计器中并用于服务编排。下图显示了工作流设计器工具箱的屏幕截图

上图显示了一个包含 7 个自定义活动的展开节点。让我们逐一描述它们的特性

1. CreateMessage

此原语的特点是创建一个新消息,将参数化 XSLT 模板转换为输出格式。

中介器具有用于显示 XSLT 模板的读/写表达式文本框 (etb)。中介器 etb 可以手动更新,通过内置编辑器或从剪贴板粘贴内容。输出可以格式化为特定的 WCF MessageVersion,包括 None 版本(无 SOAP 信封)。如前所述,XSLT 资源可以参数化。此映射在 Parameters 集合中完成,其中每个参数都可以通过表达式文本拥有自己的 Assign 原语。请注意,本文仅支持手动映射 XSLT 模板和工作流之间的参数。

CreateMessage 原语是一个非常强大的消息中介器,允许动态创建代理,从元数据生成无类型消息或根据模式动态创建类型。

2. XPathMessageInspector

XPathMessageInspector 是用于检查消息内容的消息中介原语。

此原语的特点是将无类型消息的选定内容映射到强类型参数。此原语不仅可以选择单个类型,还可以选择复杂类型并将其成员映射到输出参数。

以下屏幕截图显示了映射 XPath 结果的示例

请注意,_result 是 XPath 结果的别名。它应该是 XElement 类型。其他名称是 _result 复杂类型的成员。输出的名称定义为 XName 值,例如本地名称和命名空间。输出表示为中介状态,可以直接在其他原语的表达式文本中使用,例如作为 CreateMessage 的参数。

XPath 属性是 etb 类型,与之前的 Mediator 一样,可以通过编辑器、手动或从剪贴板粘贴创建。要创建正确的 XPath 值以了解消息模式,因此此原语内置了编辑器以简化此任务。

按下 XPath 按钮,XPath 编辑器将启动其窗体,允许获取消息元数据,在消息内容中导航或键入 XPath 公式以验证其在消息中的位置等。请注意,命名空间集合可以在此设计时创建。最后,xpath 公式、命名空间集合和设计时信息存储在 XAML 堆栈中以供运行时处理。

3. TransformMessage

TransformMessage 中介原语是一个通用原语,用于根据 XSLT 中介器将输入消息转换为输出消息。

其功能允许参数化 XSLT 中介器并将 XPath 查询映射到输出参数。对于性能问题,有一个复选框选项可以创建缓冲消息副本或成为消息的最后一个消费者。

中介器是 etb 类型属性,因此我们可以手动、从编辑器或从剪贴板粘贴创建 XSLT 资源。请注意,此原语只有简单的 XML 记事本编辑器,不支持像以前的原语那样的工具。

目前,编辑器正在建设中,拥有此功能是一个真正的挑战,它将根据图形设计器中的输入/输出元数据生成参数化 XSLT 资源。

以下屏幕截图显示了用户控件的初稿,其中左右消息已根据其元数据创建。它们之间(白色窗格)是设计器-转换器的工作区,用于放置函数、参数或直接映射线。此工作区中的每次更改都将在底部窗格中重新生成 XSLT 资源。最后,XSLT 资源将存储在 XAML 堆栈中,包括用于设计时的附加信息,以在编辑器中重新创建状态。

如前所述,此功能不包含在本文中,因此我建议使用一些第三方 XML 工具(例如 Altova)来创建 XSLT 资源并将其剪切并粘贴到此原语中。当然,对于小的更改或 XSLT 资源,我们可以使用内置的 XML 编辑器。

4. ShowMessage

ShowMessage 是一种中介原语,用于将输入消息转换为在所选目标(例如 Trace、Console 或 MessageBox)上显示的格式。可以选择传输完整消息或仅传输消息头。以下屏幕截图显示此原语作为自定义活动

5. SendMessage

SendMessage 原语是使用远程资源进行消息中介的复杂原语。此序列按以下简单原语的顺序排列

  1. CreateMessage - 创建无类型消息
  2. ShowMessage 用于诊断和日志记录
  3. 使用通用契约通过 Endpoint (WF4 内置活动) 发送无类型消息
  4. ReceiveReply 用于接收对已发送请求的回复消息 (WF4 内置活动)
  5. ShowMessage 用于诊断和日志记录
  6. XPathMessageInspector 用于将输出映射到回复消息

SendMessage 原语具有属性网格中显示的输入/输出参数,如下图所示

上述参数的功能已在之前的原语中详细描述。Endpoint 属性是 WF4 复杂类型,用于声明 Send 活动的绑定和地址。

请注意,SendMessage 原语表示一个元数据驱动的代理,用于任何由 SOAP 消息驱动的服务。契约模型中的任何更改都只需要更改 XAML 堆栈。

6. FireMessage

FireMessage 原语是一个复杂的原语,用于发送中介消息而无需等待其响应。此序列按以下简单原语的顺序排列

  1. CreateMessage - 创建无类型消息
  2. ShowMessage 用于诊断和日志记录
  3. 使用通用契约通过 Endpoint (WF4 内置活动) 发送无类型消息

FireMessage 原语的输入参数如下图所示在属性网格中

如您所见,没有输出参数。此原语的用途在于通知、日志记录等场景,即消息被发送并遗忘。

7. Assign2<T>

WF4 模型内置了 Assign<T> 活动,用于将单个值分配给变量。Assign2<T> 功能能够为相同的复杂类型分配多个参数。以下屏幕截图是分配给 EventMessage 类型的示例

请注意,Assign2<T> 原语允许将参数分配给匿名类型。以下示例显示了此功能

如我之前所说,WF4 Beta2 不支持 VB 编译器的 strict OFF 选项,因此无法输入诸如 var.idvar.name 之类的表达式。如果能在发布版本中实现会很好。

好的,消息中介原语的描述到此结束。现在,让我们继续在实际示例中使用它们,例如,通过邮政编码获取城市天气(公共服务 http://ws.cdyne.com/WeatherWS/Weather.asmx)。

用法

在本文中,我提供了一个在 Windows 窗体上重新托管工作流设计器的解决方案。得益于 WF4 技术,此任务比之前的 WF3 模型更容易完成——更多详细信息请参阅我的文章《可管理服务》。这次,我创建了一个用户控件面板,用于托管 WorkflowDesigner、ToolBox、Properties 和 XAML 资源,这是一个提取并嵌入的 XmlNotepad2007 用户控件。请注意,在 Windows 用户控件上托管所有这些控件的原因是为了在 MMC3 上直接托管。无论如何,下图显示了重新托管的工作流设计器,它可能是一个非常有用的测试工具。

在开始之前,我建议启动 WindowsFormDesignerTester.exe 程序并开始使用它。如您所见,窗体左侧是工作流面板和 XAML 面板之间的水平拆分器。此拆分器可双击以最小化 XAML 面板。XAML 面板上有 3 个按钮。

RefreshLoad 按钮用于同步工作流与 XAML 文本。Run 按钮允许在自托管控制台程序上运行我们的 XAML 堆栈。

还有一件事,WF4 技术的一个很棒的功能——右侧的工具箱面板。其中有 WF4 内置活动、自定义消息中介活动(原语)库和特殊文件夹 DynamicWorkflows。通过选择自定义活动 DynamicWorkflowSelector,我们可以选择文件系统中的任何 XAML 资源,以便将其嵌入到工作流放置器中。

好的,现在您已经了解了我们的测试器,我们可以开始一步一步地构建我们的示例,而无需使用 Visual Studio 2010。每次我们都可以按下 Run 按钮来查看运行时结果。此示例主要分为三个阶段,例如

  1. 创建请求消息
  2. 创建通用代理
  3. 分析响应

步骤 A1

我们的示例(从公共服务 http://ws.cdyne.com/WeatherWS/Weather.asmx 通过邮政编码获取城市天气)我们开始创建变量 messagemyZipCode

步骤 A2。

从工具箱中拖放以下活动,并使用 message 变量填充 Output 属性

如您所见,在此步骤中,我们将为 http://ws.cdyne.com/WeatherWS/Weather.asmx Web 服务创建和中介消息。为了在控制台上查看消息内容,我们在工作流中添加了 ShowMessage 活动。

步骤 A3。

在这一步中,我将演示 CreateMessage 原语中内置工具的强大功能。点击 Mediator 按钮,将启动以下编辑器。

在 URL 文本框中输入 http://ws.cdyne.com/WeatherWS/Weather.asmx,然后点击 Get 按钮。这是魔术动作,此端点正在请求服务元数据。分析导入的元数据后,我们可以在组合框中看到所有契约。对于我们的示例,请求以下选择

对于 Soap11、WeatherSoap、GetCityWeatherByZip 和 Request 等选择,我们将在 XML 记事本中看到 XSLT 转换的结果,其中特定模式被转换为参数化的 soap 消息 XSLT 模板。我们需要进行 3 处更改来自定义此 XSLT 模板,例如参数 zipcodeaction

更改后,切换到 Xml 选项卡,其内容应与以下屏幕截图所示相同

还有另外两个按钮,Test 和 XSD。这些按钮提供运行时消息的预览以及有关模式的信息。下图是预览消息的示例

完成此步骤后,我们可以按下“确定”按钮以接受此中介。

步骤 A4。

此步骤向您展示了工作流设计器中消息中介的结果。如前所述,Meditor 是一个 etb 属性,其内容也可以手动编辑。

步骤 A5。

在这一步中,我们将把工作流变量“插入”到中介器中。按下“参数”按钮,将显示以下编辑器

创建参数 zipcode(请注意,此名称必须与 XSLT 参数匹配),并将其映射到变量 myZipCode。参数的值在表达式文本中,因此我们可以使用任何 VB 表达式进行显式或隐式计算。

步骤 A6。

此步骤仅用于审查 XAML 堆栈。现在,是时候查看 CreateMessage 消息中介原语的主体了。请注意,SourceUri 表示设计时中介的状态。

步骤 A7。

好的,示例的第一阶段就这些了。在这一步中,我们可以按下 Run 按钮来查看工作流处理过程

步骤 B1。

此阶段的目标是使用通用契约(无类型消息)创建代理,并将我们的请求消息发送到物理端点。因此,从工具箱中拖放 SendAndReceiveReplyFactorySendMessage 活动到我们的工作流中,请参见以下屏幕截图

将显示名称重命名为 Proxy 并在 OperationName 中键入 ProcessMessage。之后,双击 Send.Content 并设置其属性为无类型消息,如下图所示。

这一步就到此为止,现在我们需要添加更多变量并为 Send 活动填充属性。

步骤 B2。

在这一步中,我们将在工作流中添加更多变量,例如 Web 服务的物理地址和响应——请参见下面的屏幕截图

一旦我们有了响应消息的变量,我们就可以回到工作流并双击 ReceiveReplyForSend.Content 属性以完成其内容,如下图所示

步骤 B3。

在这一步中,我们将最终确定发送活动的参数值。在 Action 属性中放置 '*' 非常重要,因为我们的代理适用于通用契约,例如请求/响应无类型消息 (System.ServiceModel.Channels.Message)。请注意,相关句柄 __handle1 是为此代理自动创建的。

此阶段就到此为止,我们可以继续看下一步运行时会发生什么。

步骤 B4。

在此步骤中,单击“运行”按钮,我们将在自托管控制台上运行我们的工作流。结果应与下图所示相同

这真是太酷了,我们的中介消息已发送到端点,并且我们收到了回复消息。您可以看到我的邮政编码 = 92705 的回复(天气)。

好的,让我们进入最后阶段。其目标是中介响应消息。

步骤 C1。

与前几个阶段一样,我们应该从向工作流空间添加更多变量开始。请添加以下变量

请注意,结果是 XElement 类型,以便我们能够将其成员映射到强类型。

步骤 C2。

在此步骤中,我们将从工具箱中拖放以下活动

第一个自定义活动是一个消息中介原语,用于映射输出值,其他活动用于在控制台上显示表达式文本。在下一步之前,在 Message 属性中键入 response

步骤 C3。

此步骤是关于使用神奇的 XPath 编辑器生成 XPath 公式(蓝线)。点击 XPath 按钮,我们可以开始使用此编辑器。由于我们要中介来自邮政编码 Web 服务的响应(回复)消息,因此我们需要此消息的响应元数据。

输入此端点的 URL 地址 (http://ws.cdyne.com/WeatherWS/Weather.asmx),点击 Get 按钮,等待几秒钟获取此元数据。然后选择 Soap11、WeatherSoap、GetCityWeatherByZIP 和 Response。您应该得到与下图所示相同的结果

XPath 选择显示在只读文本框中,每次您高亮显示某个元素时。双击此文本框,将此 XPath 公式移动到蓝色文本框的输出缓冲区。同时,验证逻辑将显示此 XPath 在消息中的状态。

另一种选择是反向操作。您可以在蓝色文本框中直接输入 XPath 公式,然后查看其验证和在消息中的位置。

一旦我们有了正确的 XPath 公式,然后点击 OK 按钮,我们就可以将 XPath、命名空间和关于设计时源的附加信息持久化到 XAML 堆栈中。

步骤 C4。

好的,我们离完成这个例子已经很近了。我们需要将 Outputs 参数,例如城市、温度和结果,映射到中介消息原语

请注意,Name 是 XName 类型,因此 _result 的每个成员都需要命名空间。

就这样,我们终于可以进行完整的运行时测试了。

步骤 C5。

这是显示我们结果(例如“Santa Ana, 88”和 GetCityWeatherByZIPResponse 对象)的最后一步。您可以按 Enter 键重复此测试,或者您可以返回设计并更改中介等,或者您可以选择另一个 URL 来创建您的示例等。享受无类型消息中介器带来的乐趣。

摘要

此示例演示了在声明式 XAML 工作流中使用无类型消息。我希望您体验了由元数据驱动的无类型消息中介的强大功能——契约模型。契约模型可以集中存储在存储库中,并用于服务中介。

好的,如果您想了解使用 WF4 技术的一些有趣实现设计,那么接下来的段落就是为您准备的。

实现

消息中介的实现分为 MessageMediationActivityLibrary 解决方案下的以下项目

基本上,有用于运行时、设计时和测试的项目。从该解决方案中只取前两个程序集以在您的解决方案中使用该库。如您所见,中介库分为两部分,即运行时和设计时。为了构建您的自定义活动,我建议将运行时和设计时进程的程序集完全隔离,这样我们可以减少程序集的分布式大小。另一方面,设计器程序集可以比运行时程序集具有更复杂的部分,并且可以增量开发等等。

请注意,Tester 的项目用于通用用途,并且不依赖于自定义活动。只需将您的程序集插入到 \bin Tester 的文件夹中,例如 WindowsFormsDesignerTesterWorkflowConsoleXamlTester。请注意,设计器程序集不需要在控制台测试器中,因为它是运行时测试器。

在接下来的代码片段中,我将只向您展示几个实现部分。在这个解决方案中讨论每个实现部分都很困难,因此我只选择有趣的几个:

Assign2<T> 活动

在此活动中,execute 方法负责根据 Parameters 集合更新对象 To 的属性。以下代码片段显示了此逻辑

protected override void Execute(CodeActivityContext context)
{
  var valueOfTo = context.GetValue<T>(this.To);
  if (valueOfTo == null)
    throw new ArgumentException("...");

  this.Parameters.Keys.ToList().ForEach(delegate(string name)
  {
    Type type = this.Parameters[name].ArgumentType;
    var val = this.Parameters[name].Get(context);
    var pi = valueOfTo.GetType().GetProperties()
     .FirstOrDefault(p => p.Name == name);
    
    if(pi == null)
      throw new ArgumentException("..."));
    
    pi.SetValue(valueOfTo, val, null);
  });

  context.SetValue<T>(this.To, valueOfTo);
}

上述逻辑的第一步是获取对象 To 的运行时值,然后从 Parameters 填充此实例。最后一步是设置运行时 To 对象的值。请注意,To 对象必须是 InOutArgument<T>

以下代码片段显示了收集所有运行时参数(包括集合中的参数)的逻辑。覆盖方法 CacheMetadata 是执行此任务的正确位置

protected override void CacheMetadata(CodeActivityMetadata metadata)
{
  Collection<RuntimeArgument> arguments = new Collection(<RuntimeArgument>);
  RuntimeArgument argumentTo = 
    new RuntimeArgument("To", typeof(T), ArgumentDirection.InOut, true);
  metadata.Bind(this.To, argumentTo);
  arguments.Add(argumentTo);

  this.Parameters.Keys.ToList().ForEach(delegate(string name)
  {
    Type type = this.Parameters[name].ArgumentType;

    if (this.To.ArgumentType is object)
    {
      Trace.WriteLine("The type of the assign2 is object"); 
    }
    else
    {
      PropertyInfo pi = typeof(T).GetProperty(name);
      if (pi == null)
        throw new ArgumentException("...");
      if (pi.PropertyType != type)
        throw new ArgumentException("...");
  }

  RuntimeArgument argumentProperty = 
    new RuntimeArgument("ra_" + name, type, ArgumentDirection.In);
    
  metadata.Bind(this.Parameters[name], argumentProperty);
  arguments.Add(argumentProperty);
  }); 

  metadata.SetArgumentsCollection(arguments);
}

CreateMessageDesigner

CreateMessage 中介器在可滚动多行文本框中显示 ExpressionTextBox,可以直接编辑而无需使用任何编辑器。得益于 Beta2 版本,引入了展开/折叠功能,使工作流布局更具可读性。在我们的自定义活动中,也实现了此功能。以下屏幕截图显示了 CreateMessage 自定义活动的设计器。如您所见,设计显示了一个折叠的模板。

另一个模板,例如 Expanded,是一个用于设计时的完整模板 - 请参阅步骤 A4。有一个按钮可以启动其编辑器。此设计器代码的有趣部分是从动态资源模板获取 etb 实例。以下代码片段显示了按钮处理程序

private void buttonMediator_Click(object sender, RoutedEventArgs e)
{
  try
  {    
    // get the etb from dynamicly datatemplate
    ContentControl cc = e.Source as ContentControl;
    var sp = VisualTreeUtils.GetNamedChild<StackPanel>(cc.Parent, "stackPanel", 5);
    ExpressionTextBox expMediator = 
      VisualTreeUtils.GetNamedChild<ExpressionTextBox>(sp, "controlRoot", 0);
    
    if (expMediator == null)
        throw new Exception("...");

    var sourceUri = ModelItem.Properties.FirstOrDefault(p=>p.Name=="SourceUri");
    string xmltext = expMediator.Expression == null ? 
      string.Empty : 
      (string)expMediator.Expression.Properties["ExpressionText"].ComputedValue;

    CreateMessageForm dialog = new CreateMessageForm(xmltext, sourceUri==null ? 
      string.Empty : (string)sourceUri.ComputedValue);
      
    dialog.ShowDialog();
    if (dialog.DialogResult == System.Windows.Forms.DialogResult.OK)
    {
       //...
    }
  }
  catch (Exception ex)
  {
    System.Windows.MessageBox.Show(ex.Message);
  } 
}

如您所见,我们可以通过按下按钮获取按钮内容控件。此控件与我们的 expMediator 控件具有相同的父级。遍历父级的子级,我们可以找到我们的 etb 控件。请注意,Beta2 有一些 bug;因此,此步骤已分为两步。第一步是找到 stackPanel,然后是我们的 etb 控件。一旦我们有了 etb 控件实例,我们就可以获取或更新其表达式属性。

好的,让我们看看运行时过程的一部分,其中 CreateMessage 负责生成输出消息。

XSLT 转换使用 System.Xml.Xsl.XslCompiledTransform 类非常简单,所有任务都集中在将资源正确传递给这个强大的类。

以下代码片段显示了 CreteMessage.Execute 方法中此逻辑的主要步骤

#region Step 3: Load metadata
try
{
  string xsltText = this.Mediator.Get(context).ToString());
  using (MemoryStream msMetadata = new MemoryStream(UTF8Encoding.UTF8.GetBytes(xsltText)))
  {
    XmlDictionaryReader rdMetadata = 
      XmlDictionaryReader.CreateTextReader(msMetadata, XmlDictionaryReaderQuotas.Max);
      
    xsltprocessor.Load(rdMetadata);
  }
}
catch (Exception ex)
{
    throw new Exception("...");
}
#endregion

#region Step 4: Transform empty Input and create MessageOutput

try
{
  using (MemoryStream msMessageOutput = new MemoryStream())
  using (XmlDictionaryWriter wrMessageOutput = 
    XmlDictionaryWriter.CreateTextWriter(msMessageOutput, Encoding.UTF8, true))
  using (XmlDictionaryReader rdEmptyInput = 
    XmlDictionaryReader.CreateTextReader(UTF8Encoding.UTF8.GetBytes(
    "<root />"), XmlDictionaryReaderQuotas.Max))
  {
    xsltprocessor.Transform(rdEmptyInput, xsltArgList, wrMessageOutput);
   
    msMessageOutput.Position = 0;
    XmlDictionaryReader rdMessageOutput = XmlDictionaryReader.CreateTextReader(
      msMessageOutput.ToArray(), XmlDictionaryReaderQuotas.Max);
    Message outputMessage = System.ServiceModel.Channels.Message.CreateMessage(
      rdMessageOutput, int.MaxValue, LibHelper.MessageVersion(this.MessageVersion));
    
    this.Message.Set(context, outputMessage);                
  }
}
catch (Exception ex)
{
    throw new Exception("...");
}
#endregion

上述步骤 3 显示了将 Mediator 表达式加载到 XSLT 处理器实例中的实现。以下步骤 4 根据加载的 XSLT 模板创建输出消息。请注意,Transform 方法需要输入资源,因此已传递虚拟(空)输入。这很好,因为我们不想转换 A -> B 格式,此中介是基于加载的 XSLT 模板创建新消息 B。

自定义活动就这些了,让我们看看重新托管的工作流设计器的一些有趣部分。设计师如何以松散耦合的方式将自定义活动添加到工具箱中?这种机制适用于添加到 bin 文件夹中的任何自定义活动程序集。

以下代码片段显示了“应用程序执行之前”和“应用程序关闭时”

[STAThread]
static void Main()
{
  Application.EnableVisualStyles();
  Application.SetCompatibleTextRenderingDefault(false);

  string asmLocation = typeof(WorkflowDesignerUserControl).Assembly.Location;
  string filepathname = Path.ChangeExtension(asmLocation, "xaml");

  Form1 form = new Form1();
  
  LoadToolboxItems(form.Designer);
  
  LoadXamlFile(form.Designer, filepathname);

  form.FormClosing += delegate(object sender, FormClosingEventArgs e)
  {
    SaveXamlFile(form.Designer, filepathname);
  };

  Application.Run(form);
}

如您所见,在重新托管的工作流设计器的启动序列中有一个 LoadToolBoxItems 调用。以下代码片段显示了实现的有趣片段,其中查询 bin 文件夹中的每个程序集以查找类型 ActivityIActivityTemplateFactory。调用设计器将项目添加到工具箱中,以便每个此类型的集合使用。

private static void LoadToolboxItems(WorkflowDesignerUserControl designer)
{
  // ...
  
  // custom activities
  var types = from t in asm.GetExportedTypes()
            where typeof(Activity).IsAssignableFrom(t)
            select t.FullName;

  if (types != null && types.Count() > 0)
  {
    string category = asm.FullName.Split(',')[0] + "_" + asm.FullName.Split(',')[1];
    types.ToList<string>(().ForEach(typename => designer.AddToolboxItem(category, typename, asm.FullName));
  }

  // dynamic workflows
  var factories = from t in asm.GetExportedTypes()
                where typeof(IActivityTemplateFactory).IsAssignableFrom(t)
                select t.FullName;

  if (factories != null && factories.Count() > 0)
  {
    string category = "DynamicWorkflows";
    factories.ToList<string>(().ForEach(typename => designer.AddToolboxItem(category, typename, asm.FullName));
  }
  
  // ...
}

请注意,IActivityTemplateFactory 是 Beta2 的一个很棒的新功能,允许将由 XAML 堆栈定义的可预制复合活动导入到主工作流中。

动态自定义活动的实现非常简单,如下面的代码片段所示。这是 DynamicWorkflowSelector 自定义活动的完整实现,它使用 OpenFileDialog 来选择特定的 XAML 资源,以便将其导入到主位置。

public class DynamicWorkflowSelector : IActivityTemplateFactory
{
  public Activity Create(DependencyObject target)
  {
    DynamicActivity activity = null;
    try
    {
        OpenFileDialog dialog = new OpenFileDialog()
        {
           Title = "Select a XAML workflow",
           InitialDirectory = AppDomain.CurrentDomain.BaseDirectory,
           Multiselect = false,
           AddExtension = true,
           SupportMultiDottedExtensions = true,                    
           Filter = "Xaml files(*.xaml)|*.xaml"
        };
        if (dialog.ShowDialog() == DialogResult.OK)
        {
           using (FileStream stream = File.OpenRead(dialog.FileName))
           {
             activity = (DynamicActivity)ActivityXamlServices.Load(stream);
           } 
        }
    }
    catch (Exception ex)
    {
        System.Windows.Forms.MessageBox.Show("...");
    }
    
    return activity == null ? null : activity.Implementation();
  }
}

在上面的示例中,XAML 资源的来源是文件系统,但它可以是任何其他东西,例如 Web 服务、存储库等。

以上就是关于 WF4 中使用自定义活动进行消息中介的全部内容。

结论

总之,本文描述了即将推出的 WF4 技术中用于消息中介的自定义活动。无类型消息使用基于 XSLT 技术的消息原语进行处理。基本上,设计时中介原语使用复杂的工具支持来生成运行时 XSLT 资源。本文介绍了基于 WSDL 元数据的一些工具支持的概念设计。希望您喜欢它。

© . All rights reserved.