使用 WF4 WorkflowInvoker





5.00/5 (16投票s)
本文介绍了一个自定义服务操作调用器的设计、实现和使用,用于调用 xaml 工作流。它基于即将发布的 Microsoft .NET 4 技术。
注意:本文使用 .NET Framework 4 Beta 1 版本编写。
目录
特点
- 松耦合设计模式
- WF4 工作流的自定义操作调用器
- 声明式业务封装,实现产品灵活性
- Xaml 方法 vs CLR 方法
- 增量式(逐个操作)元数据驱动应用程序
- 逻辑中心化的工作流(xaml 方法)存储库
- IIS/WAS,自托管等。
- Xaml 资源位置灵活
- WCF 和 WF4 技术
引言
Microsoft 最近发布了 Visual Studio 2010 和 .Net Framework 4 Beta 1。与当前的 .NET 3.0 和 3.5 版本等技术相比,.NET 4 Beta 1 中的 Windows Workflow Foundation (WF4) 引入了大量的更改。我将稍作解释,说明这些更改为何必要。
WF 首次在 PDC2005 上作为独立的 Windows 基础技术推出。它基于自己的编程模型、托管和与宿主应用程序之间的私有通信层。这种概念使得 WF 技术可以与其他技术并存。
因此,当时我们接触到了用于通信和工作流驱动应用程序的伟大新技术。坏消息是,它们无法无缝协同工作。它们在逻辑上和物理上相互隔离。它们的运行时模型、故障排除、监控等都不同。有关更多详细信息,请参阅我在 codeproject 上发布的有关如何集成这些技术的文章。
每个模型(WCF 和 WF)都基于自己的元数据和配置,并且需要更多的粘合代码。下一版本,例如 .Net 3.5,引入了用于集成 WCF 和 WF 编程模型的通用运行时模型。它引入了 `WorkflowServices` 等新范例。
`WorkflowServices` 使得可以声明式地“绕过终结点”。换句话说,基于元数据(xoml),消息和服务可以以松耦合的方式进行中介和编排。在我看来,`WorkflowService` 代表了一种高质量的服务——一种可管理的服务,它可以逻辑上集中在存储库中,并在运行时物理上分散,例如部署在云端。有关此策略的更多详细信息,请参阅我的文章 Manageable Service。
故事的结局是,今天我们拥有了集成 WCF + WF 到一个运行时编程模型中的生产技术 (.Net 3.5),但每个模型仍然拥有自己的元数据,如配置、xoml 和规则。对于跨不同托管环境的运行时投影,没有通用的元数据模型(堆栈)。这将在 .Net Framework 4 中得到解决,其首个公开 Beta 1 已发布。
所有这些挑战都源于 Microsoft 的模型驱动开发策略,其中“XAML 堆栈
”在运行时扮演着重要角色。我们将只管理一个逻辑堆栈来声明 WPF、WCF、WF 等组件及其之间的绑定。此功能在 PDC2008 上进行了演示,当时我们看到了一个嵌入了通信和工作流的 Silverlight 应用程序中包含的微型“xaml 堆栈”的示例。
我希望您能和我一样看到,WF4 编程模型为何需要进行重大更改。使用 WF4 将大大简化开发过程,并提高声明式编程的生产力。请访问此 链接 获取更多详细信息。
WF4 的一项“核心功能”是 `WorkflowInvoker`,无需了解托管、通信、事件等。本文介绍了一种服务操作合同的扩展,用于将服务操作的命令式实现解耦为声明式实现。这种方法允许将服务操作逐个移动到“工作流世界
”中,将操作集中在中介存储库中,并根据需求进行管理。这种方法为应用程序提供了灵活性,可以通过声明式方式进行更改,而无需编写和部署新的程序集。这是声明在单个操作范围内,并进入元数据模型驱动解决方案的“小小一步”。
我假设您对当前 WCF 和 WF 技术有一些实践经验。好了,我们开始吧。
概念
与命令式处理服务操作相比,声明式处理服务操作的概念非常直接,基于 WCF Framework 的可扩展性。下图显示了 WCF Framework 中调用服务方法的相应位置:
您可以看到,`OperationInvoker` 负责使用反射机制在服务上调用 CLR 方法。此类派生自 `System.ServiceModel.Dispatcher.IOperationInvoker`,它代表了一个可扩展点,用于在通道堆栈的消息流的末端自定义 WCF Framework。您可以在 `Google` 上找到许多自定义操作调用器的示例,例如缓存、验证等。
现在,我将添加另一个自定义操作调用器,以声明式方式处理服务操作,换句话说,自定义操作用于调用 XAML 方法。下图显示了这一概念:
通过比较上述两张图片,您可以看到,消息的传输是完全透明的,无需了解服务方法的实现。在服务中调用 xaml 方法的使用非常简单,如下面的代码片段所示。
上面的代码片段是命名空间 Test 中的服务 Ping 的示例。此服务有三种不同的访问和调用方式的方法。第一个是 `Echo` 方法,它是操作合同(WCF 模型)下的常规 clr 方法;第二个也是 WCF 合同下的,但其实现是通过 xaml 堆栈声明式实现的(我们可以称之为 `XAML Method`);第三个方法是私有的常规 clr 方法。
通过为服务操作添加 `[OperationWorkflowInvokerAttribute]` 属性,WCF Framework 将此操作视为 xaml 方法。默认情况下,该方法声明在 `Test.Ping.Echo2.xaml` 文件中,该文件位于程序集文件夹中,但也可以位于不同名称下的特定文件夹中。本文的解决方案仅使用文件系统存储 xaml 资源,但通过稍作修改(添加 xaml 加载器),可以将 xaml 资源存储在存储库中。请注意,该方法的 clr 实现将永远不会被调用,因此可以在方法体中插入 `NotImplementationException`。
System.Activities.WorkflowInvoker
`System.Activities.WorkflowInvoker` 是围绕通用 `WorkflowInstance` 的一个包装类,用于简化在自托管工作流运行时中同步初始化和运行工作流实例。从宿主应用程序的角度来看,它被抽象为“工作流方法”,签名如下:
换句话说,`WorkflowInvoker` 说:“用工作流 (WorkflowElement
) 描述你想要什么,给我一些输入,我会同步处理并返回输出”。
此外,我们还可以要求 `WorkflowInvoker` 在时间限制内提供结果,这只是对此方法进行外部控制。
请注意,我们无法在 `WorkflowInvoker` 中控制工作流处理,例如持久化、取消、中止等,因此工作流编排应在设计时考虑这一点,并且仅用于短暂的工作流。
正如我之前提到的,WF4 的范式已从 WF3/3.5 发生变化。其中一个变化是 Activity 具有类似于 clr 方法的签名和正文。存在公共参数,定义了数据如何在活动之间流入和流出。活动的正文代表在运行时上下文中调用的执行逻辑块。
基于以上,我们知道将 `WorkflowInvoker` 整合到 `OperationInvoker` 需要两个字典,一个用于输入参数,另一个用于输出参数,以及一个 `WorkflowElement` 对象,代表根工作流图。
让我们在 MMC 中查看我自定义的重新托管的 WF4 设计器中的这些组件,以处理一个非常简单的工作流 - Echo 回显。
根工作流 `Activity` 声明了两个参数,如 `text` 和 `_retval`。请注意,`_retval` 是返回值的硬编码名称。执行正文只有一个子活动,即 `Assign`,其职责是将参数 `text` 的值分配给参数 `_retval`。此工作流的 xaml 堆栈非常简单,如上图所示。
这是设计时,我们使用 WF4 设计器以声明方式创建操作方法,该设计器生成用于运行时投影的 xaml 堆栈。
下面的代码片段显示了使用编码方式实现上述示例的等效代码。
[OperationContract] string Echo(string text) { return text; }
当您将此简单的 Echo 方法的实现与编码和 xaml 声明进行比较时,可能会提出以下问题:为什么要为一行代码学习这种复杂性?使用 xaml 方法有什么优势?等等。
这些都是有效的问题。但是,从另一方面来看,还有更多问题,例如:我们是否需要更改方法正文?业务逻辑的复杂性如何?我们能否将业务逻辑解耦为可重用且经过良好测试的小型活动?我们能否使用第三方活动等?
好了,让我们回到设计时创建的 xaml 资源。该资源是部署包的一部分。当流程开始时,我们需要从存储库(本文中使用的是文件系统)加载此资源,并将其投影到 WorkflowElement 图对象中。这只需要一次工作,可以在 `ApplyDispatchBehavior` 方法中完成,在服务操作行为扩展过程中,请参阅下一节实现。
实现
用于 xaml 方法的自定义操作调用器的实现基于 WCF Framework 的可扩展性,分为两个类。第一个派生自 `System.Attribute` 和 `System.ServiceModel.Description.IOperationBehavior`,其职责是注入我们的自定义操作调用器。下面的代码片段显示了一个带有伪正文的方法。
public void ApplyDispatchBehavior(OperationDescription description, DispatchOperation dispatch) { // load Xaml from repository (FileSystem, etc.) // create WorkflowElement (Dynamic Activity) // validate inputs // validate outputs // validate return dispatch.Invoker = new OperationInterceptorInvoker() { OperationDescription = description, InnerOperationInvoker = dispatch.Invoker, Activity = activity }; }
您可以看到,在创建 xaml 资源的活动时,会进行一些处理。首先,我们必须从存储库(本实现中是文件系统,但可以是任何存储)获取该资源,然后从 xaml 资源创建 `DynamicActivity`,验证参数和返回值。
下面的代码片段显示了从 byte[] 创建 `DynamicActivity` 的代码片段,其中已加载 xaml 资源。
#region create WorkflowElement WorkflowElement activity = null; using (MemoryStream ms = new MemoryStream(xamlbytes)) { activity = WorkflowXamlServices.Load(ms); } if (activity == null || activity.GetArguments() == null) throw new InvalidOperationException("The Activity is not root ..."); #endregion
下面的代码片段显示了如何验证用于请求/响应消息交换模式的返回值。
#region validate return if (description.IsOneWay == false) { MethodInfo mi = null; if (description.SyncMethod != null && description.SyncMethod.ReturnType != typeof(void)) { mi = description.SyncMethod; } else if (description.EndMethod != null && description.EndMethod.ReturnType != typeof(void)) { mi = description.EndMethod; } if (mi != null && activity.GetArguments().FirstOrDefault(e=>e.Direction == ArgumentDirection.Out && e.Type==mi.ReturnType && e.Name=="_retval") == null) { throw new ArgumentException("Missing output argument in the xaml", "_retval"); } } #endregion
一旦我们在没有异常的情况下通过了第一个配置步骤(在宿主打开之前),我们可以假定 xaml 方法可以正确调用,并且输入、输出和返回值具有匹配的类型。请注意,我们的实现设计用于缓存 `DynamicActivity`,因此我们无法动态更改 xaml 方法。
WCF Framework 将根据调用模式调用我们的自定义操作调用器。下面的代码片段显示了调用方法的同步模式。
object IOperationInvoker.Invoke(object instance, object[] inputs, out object[] outputs) { outputs = new object[0]; // method info MethodInfo mi = OperationDescription.SyncMethod; // dictionary of inputs var inArguments = this.InArguments(mi, inputs); // invoke xaml method var outArguments = WorkflowInvoker.Invoke(Activity, inArguments, Timeout); // dictionary of outputs object retVal = this.OutArguments(mi, out outputs, outArguments); // done return retVal; }
如您所见,上述方法非常直接。感谢 WF4 的“核心功能”处理 xaml 方法的所有魔力。我们只需要将参数打包到 Dictionary 对象中,如下面的代码片段所示,用于输出和返回值。
private object OutArguments(MethodInfo mi, out object[] outputs, IDictionary<string, object> outArguments) { outputs = new object[0]; if (OperationDescription.IsOneWay || mi.ReturnType == typeof(void) || outArguments.ContainsKey("_retval") == false) { return null; } string[] outParamName = (from pi in mi.GetParameters() where pi.IsOut select pi.Name).ToArray(); outputs = new object[outParamName.Count()]; for (int ii = 0; ii < outParamName.Count(); ii++) { outputs[ii] = outArguments.FirstOrDefault(e=>e.Key==outParamName[ii]).Value; } return outArguments["_retval"]; }
参数 XamlPath
`OperationWorfklowInvokerAttribute` 实现了一个字符串类型的参数 `XamlPath`,用于显式指定 xaml 文件位置的路径。此值取决于托管环境。在 IIS 托管的情况下,站点是基本位置路径。下面的代码片段展示了此参数的一些示例。
namespace WcfService1 { public class Service1 : IService1 { //[OperationWorkflowInvoker( XamlPath="App_Data/")] //[OperationWorkflowInvoker( XamlPath="App_Data/GetData.xaml")] [OperationWorkflowInvoker] public string GetData(int value) { return null; } } }
请注意,文件的默认名称基于命名空间、合同名称、方法名称和扩展名 xaml,因此对于上面的示例,其名称为 `WcfService1.IService1.GetData.xaml`。
第二个属性示例显示了其在站点内位置和名称的更改。
以上是关于实现的全部内容,让我们来看一下这个切换服务操作到“工作流世界”的神奇属性的使用和测试。
用法
使用自定义操作调用 WorkflowInvoker 非常简单,需要以下步骤:
步骤 1. 安装 VS 2010 和 .NET Framework 4(目前为 Beta 1 版本)。
步骤 2. 从本文下载 `OperationWorkflowInvoker.dll` 程序集并将其添加到您的项目中。
步骤 3. 为服务操作添加 `OperationWorkflowInvokerAttribute` 属性,如以下测试示例所示。
步骤 4:使用 VS 2010 创建 xaml 方法,以下是我们测试示例的屏幕截图。
Xaml 方法“SayHello”
- 此方法实现了一个回显功能,将输入信息传递回调用者。根是一个 Sequence 活动。
Xaml 方法“DivisionOfIntegers”
- 此方法实现了整数除法功能。为了演示目的,根活动由 `Try/Catches/Finally` 活动表示。您可以看到,在 `Try` 中有一个名为 `Calculator` 的 Sequence 活动。
为了测试目的,本文包含了一个小型测试器。启动控制台程序 `WorkflowConsoleTester.exe`,然后尝试使用 `SayHello` 和 `DivisionOfInteger` 等 xaml 方法。测试器具有硬编码的地址:https:///Service.svc
对于客户端,我们可以使用通用的 Microsoft WCF Test Client,如以下屏幕截图所示。
下一个测试可以针对托管在 IIS/WAS 上的服务进行。本文包含一个项目 `WcfService1`,该项目来自模板 Web 库。此模板项目已预先编写了一个具有简单合同的服务。我通过注入 `OperationWorkflowInvoker` 来修改了此代码,如以下代码片段所示。
这个最后的测试可以演示自定义操作调用器调用 xaml 堆栈中声明的业务逻辑的能力。只有选定的操作由工作流处理,而不会影响其他操作,在我们的示例中是 clr 方法 `GetDataUsingDataContract`。
本文就此结束,但还有一件事,让我们在下一段中描述它。
附录
WF4 WorkflowInvoker 是用于同步调用工作流活动的“神奇”类。它是管道设计的理想组件,用于封装业务逻辑以进行声明式更改。
在我之前的文章《Workflow Adapter/Connector Pair》中(三年前),我发布了基于 WF 3.0 技术的 WorkflowInvoker 的实现。我描述了 Remoting 的管道设计和实现。管道概念基于创建自定义客户端和服务器通道接收器。通过 WF4 WorkflowInvoker 实现此自定义接收器,可以在不影响代码的情况下替换远程对象,只需在配置文件中的 remoting 配置部分稍作更改即可。
最初,我计划将 Remoting 自定义服务器接收器的实现作为调用 WF4 Workflow 的适配器,但后来我改变了主意,决定利用这段时间撰写下一篇关于托管在 MMC 上的 WF4 设计器的文章(请参阅下面的屏幕截图),因此希望它很快就能在 codeproject 上发布。
结论
总之,本文描述了 WF4 WorkflowInvoker 在 WCF Framework 中的管道用途。此自定义操作调用器允许您的应用程序迈出进入工作流世界的第一步。该解决方案利用 Microsoft xaml 堆栈进行运行时投影,为您提供了极大的应用程序灵活性,而无需构建自己的基础设施。希望您喜欢它。
注意,本文使用 .NET 4 Beta 1 编写,将在 Beta 2 公开可用时进行更新。
参考文献
A Developer’s Introduction to Windows Communication Foundation (WCF) .NET 4 Beta 1
A Developer's Introduction to Windows Workflow Foundation (WF4) in .NET 4 Beta 1