当新版本的 Visual Studio
® 发布时,开发人员通常总是先想了解自己喜爱的编程工具中又新增了哪些功能。尽管 Windows
® Workflow Foundation (WF) 和 Windows Communication Foundation (WCF) 已于 2006 年 11 月作为 Microsoft
® .NET Framework 3.0 版的一部分发布过,但大家开始了解这些技术所提供的强大功能却是从 Visual Studio 2008 和 .NET Framework 3.5 开始的。
遗憾的是,在 .NET Framework 3.0 中没有十分出色的模型,能将 Windows WF 声明性编程模型和 WCF 简洁且丰富的通信模型组合在一起。.NET Framework 3.5 中 Windows WF 的引人注目之处在于它能真正与 WCF 实现集成。此集成表明工作流现在能够更方便地使用和协调服务,并且可以通过服务端点提供。
集成要求
当考虑 Windows WF 与 WCF 的集成时,有几个需要特别注意的关键点。任何工作流的主要组件都是允许应用程序逻辑进行建模的活动。在 .NET Framework 3.5 中,由两种新活动(发送和接收)为在工作流中使用服务和实现服务端点提供通用模型。我将简要介绍一下如何使用这些活动。
除了能够构建通信模型,集成还需要一种能够托管 WCF 和 Windows WF 运行时的机制。因为每种技术都有其自身的运行时、配置和扩展性模型,所以我们需要一种能够在单独进程中托管这两种技术的方法。新框架中提供的工具以 WCF 中的扩展点为基础,它允许由 WCF 运行时托管工作流运行时。
集成所需的最后一个项目是真正的关键:如何将 WCF 消息封送到工作流实例中,并将这些操作映射为工作流中的活动。同样,这种功能依赖于 WCF 扩展性模型,并且它将负责安全性、工作流实例管理以及将消息主体作为数据封送到活动等事宜。
新程序集 (System.WorkflowServices.dll) 中有为 Windows WF 和 WCF 提供集成的新类。这个新程序集为 .NET 3.5 专有,现有的 3.0 程序集修复了程序错误,其余保持不变。通过使用 WCF 和 Windows WF 现有的扩展性模型,可以在不修改现有组件的情况下提供集成。我将介绍集成这些技术时会用到的核心类,并解释如何将它们组合在一起。
除这些新程序集外,Visual Studio 2008 还提供一些新的项目和组件模板,专门用于构建将作为服务发布的工作流。在新项目对话框中,您可以在 WCF 类别下发现“顺序工作流服务库”和“状态机工作流服务库”项目。您还会发现 Windows WF 顺序服务和 Windows WF 状态机服务项的新项目模板及其 XAML 配对项。每种新模板都增加了工作流、定义服务约定的接口以及用于为服务配置端点的 app.config 文件。
使用发送和接收活动为通信建模
新的接收活动用于为工作流中的点建模,作为 WCF 服务约定的一部分,您的进程将在该点接收数据。这意味着您工作流中的每种接收活动都需要用相关服务约定的适当信息进行配置,并绑定到该约定中的某项操作。为了完成活动和操作的绑定,活动必须有 ServiceOperationInfo 属性。
可以通过以下两种方式中的一种声明约定:使用 WCF 服务约定属性或直接在工作流内声明。后者通常称为“工作流优先”设计,前者则称为“约定优先”设计。在工作流中直接定义约定时,约定信息嵌入工作流定义内部,而不是像使用 WCF 定义约定那样表达为 .NET 接口。
当编辑 ServiceOperationInfo 属性时,您可以从出现的对话框中选择操作,在此对话框中还必须确定将使用现有的服务约定还是定义一个新的服务约定。该对话框如
图 1 所示,右上角处有两个按钮,您可以使用这两个按钮导入服务约定或添加新的约定。如果选择导入约定,则所有标记 OperationContract 属性的操作将导入此对话框,您可以从中选择您的活动所要实现的操作。在对话框的底部有三个选项卡用于显示操作的参数、属性和权限。当导入约定时,参数和属性直接从服务约定中获得,一般不会更改。
![](http://i.msdn.microsoft.com/cc164251.fig01%28zh-cn%29.gif)
图 1
ServiceOperationInfo 配置对话框 (单击该图像获得较大视图)
创建服务约定期间,您可以在此对话框中配置每项操作的属性,以便恰当地设计服务约定。您还可以指定操作的参数和返回类型,并通过设置 IsOneWay 属性来确定消息交换模式。此外,您还可以指定应用于消息的保护级别,这样将可以对其进行签名和/或加密。
导入或定义完约定并选定接收活动的操作后,还需要为基本的接收活动配置一些项目。第一个是 CanCreateInstance 属性,它告知运行时是否用此操作启动工作流。每个将作为服务公开的工作流都必须具备至少一个 CanCreateInstance 属性设置为 true 的接收活动;否则将无法通过服务调用启动工作流。
接收活动作为复合活动实现,这意味着它可以拥有子活动。基本上,接收活动的工作方式类似于顺序活动,它按顺序执行其所有子活动直至完成。
图 2 显示了使用子活动实现操作的接收活动。这些子活动组成了该服务操作的实现。执行完毕后,接收活动将返回绑定到 return value 属性的返回值,或者抛出绑定到 fault 属性的异常。
![](http://i.msdn.microsoft.com/cc164251.fig02%28zh-cn%29.gif)
图 2
接收活动 实现操作
在接收活动中执行操作时,需要牢记两个关键点:您应该设置执行过程中的 return value 或 fault 属性,并且您所做的任何工作必须在服务调用返回之前完成。设置输出或异常非常重要,如错过此步骤,在测试初期就可以发现。有时更容易忽略的是工作的持续时间,它将影响您的服务性能。因为您处在工作流设计人员的立场,所以很容易忘记您正编写的是服务操作代码,而客户端应用程序在等待响应。如果您的操作是单向操作,那不需要太担心,而且也不需要设置输出或异常属性,因为客户端无法收到这些消息。
发送活动使用工作流内部的服务,您可以使用工作流协调服务或利用服务中的逻辑和数据。在 WCF 中,客户端创建配置有端点信息的通道以便调用服务操作。发送活动使用相同的 ServiceOperationInfo 对话框方便您导入服务约定和选择所要调用的操作。发送活动设计人员随后动态创建您所选操作的参数和返回值属性,从而使您能够在调用服务之前在工作流中绑定或设置这些值。
要配置客户端点,您需要使用两个属性:ChannelToken 和 CustomAddress。ChannelToken 的 EndpointName 属性应该配置为客户端点的 WCF 名称,该名称可以从托管工作流的进程配置文件中获得。该端点将包含运行时所需的地址和绑定信息,运行时将使用这些信息来创建客户端通道,并在运行时调用操作。另外,您还可以使用 CustomAddress 属性在运行时更改地址(端点地址通常变化最频繁),或者从工作流上下文中数据中获取该值。
发送活动的其他主要功能包括预处理和后处理事件,以及在范围级别缓存 WCF 通道。这些事件为您的代码运行提供挂接点,并在进行处理前准备发送活动及其参数,或在对工作流中下一个活动实施控制之前处理操作调用的结果。如果将要在工作流内多次调用相同的服务,则缓存可以提高性能。客户端通道的创建和安全及其他协议的协商过程可能非常耗时,如果能够使用缓存则可以减少这些步骤所花费的时间。您可以使用发送活动的 ChannelToken 属性配置缓存的范围,并为令牌中的 OwnerActivityName 属性选择父项合成活动。要为另一次发送活动重用此通道,可以在其他活动中将 ChannelToken 属性设置为所用的通道。
将工作流托管为服务
对于通过 WCF 服务接收数据的工作流,必须像其他 WCF 服务一样托管此类工作流。托管服务将创建 WCF 运行时,它将负责接收网络上的消息、处理这些消息并将消息传递给服务。如果服务作为工作流实现,则消息负载必须发送到接收活动正等待数据的工作流实例。有关数据如何与工作流通信的更多信息,请参见我以前撰写的有关工作流通信的专栏 (msdn.microsoft.com/msdnmag/issues/07/09/Foundations)。
为了托管工作流服务,您需要使用从 System.ServiceModel 的 ServiceHostBase 类中继承而来的新类 WorkflowServiceHost。请注意,ServiceHostBase 类提供托管 WCF 服务的基础逻辑,并负责建立 WCF 运行时,其中包含通道、行为等。如开发人员希望自定义 WCF 运行时,可选择创建自定义服务托管。工作流服务使用的托管模型在极大程度上依赖于对 WCF 运行时模型的扩展。有关扩展 WCF 的更多信息,请参见《MSDN
® 杂志》2007 年 12 月刊载的“服务站”专栏 (msdn.microsoft.com/msdnmag/issues/07/12/ServiceStation)。
下面的代码显示了在 .NET 应用程序中使用 WorkflowServiceHost 类托管简单工作流类型的示例:
WorkflowServiceHost host = new WorkflowServiceHost(typeof(Workflows.SimpleWorkflow)); host.Open();
在本示例中,我已经将工作流的类型信息传递给了 WorkflowServiceHost 的构造函数。这就是为标准 WCF 服务构造 ServiceHost 的方法。
定义工作流时,您可以在新类中定义结构,或者使用可扩展应用程序标记语言 (XAML) 定义您的工作流。WorkflowServiceHost 提供几种重载构造函数(请参见
图 3)。您不仅可以提供 XAML 文件路径,还可以提供流。这使您能够更灵活地选择存储工作流定义的位置。另外,这些重载构造函数还可接受包含工作流规则的 XML 文件路径或流。在这些重载构造函数中,您会发现两种技术的结合,一种是 WorkflowRuntime 的 CreateWorkflow 方法,另一种是 ServiceHost 类的构造函数,它们共同组合到 WorkflowServiceHost 中。
![](http://i.msdn.microsoft.com/Global/Images/clear.gif)
public WorkflowServiceHost(Stream workflowDefinition, params Uri[] baseAddress); public WorkflowServiceHost(string workflowDefinitionPath, params Uri[] baseAddress); public WorkflowServiceHost(Type workflowType, params Uri[] baseAddress); public WorkflowServiceHost(Stream workflowDefinition, Stream ruleDefinition, params Uri[] baseAddress); public WorkflowServiceHost(string workflowDefinitionPath, string ruleDefinitionPath, params Uri[] baseAddress); public WorkflowServiceHost(Stream workflowDefinition, Stream ruleDefinition, ITypeProvider typeProvider, params Uri[] baseAddress); public WorkflowServiceHost(string workflowDefinitionPath, string ruleDefinitionPath, ITypeProvider typeProvider, params Uri[] baseAddress);
创建 WorkflowServiceHost 之后,您可以像配置其他服务那样使用代码或配置文件配置 WCF 运行时。这样您就可以向运行时添加端点或行为,或者修改服务描述。当仅使用 XAML 编辑工作流时,用于标识配置文件中服务的名称取决于您使用的标记。例如,如果使用 x:Name 属性,则值将成为服务的名称。但是,如果省略名称属性,则会使用第一个活动的名称,而此名称在工作流中通常不是唯一的名称。
因为 WCF 托管模型用于服务,所以可以通过 WCF 宿主访问 WorkflowRuntime。.NET Framework 3.5 的一个新功能是 WorkflowRuntimeBehavior,如果您不显式地将其添加到 WCF 描述中,它将由 WorkflowServiceHost 自动进行添加。通过此行为,您可以在代码或配置文件中配置 WorkflowRuntime 对象。在配置文件中,可以使用包含此新元素的服务行为配置定义希望添加到运行时的服务。然后,您可以按照通常配置 Windows WF 运行时的方式配置服务。
图 4 显示配置了运行时的配置文件示例。
![](http://i.msdn.microsoft.com/Global/Images/clear.gif)
<behaviors> <serviceBehaviors> <behavior name="wfService"> <serviceMetadata httpGetEnabled ="true"/> <workflowRuntime cachedInstanceExpiration="00:10:00" validateOnCreate="false"> <services> <add type="System.Workflow.Runtime.Hosting.SqlWorkflowPersistenceService ..." ConnectionString="server=.;database=wfpersist;integrated security=SSPI" UnloadOnIdle="false"/> </services> </workflowRuntime> </behavior> </serviceBehaviors> </behaviors>
标准工作流配置部分尚未提供的一个配置项是 cachedInstanceExpiration 属性,该属性可以配置运行时将处于空闲状态的工作流保存在内存中的时间。工作流进入空闲状态时,它提供的控制比 SQL 持久性服务工作流的基本功能要多。
如果需要在宿主代码中访问工作流运行时,必须首先访问此行为,然后从此行为的属性中获取运行时。要访问行为,可以导航至 WorkflowServiceHost 中的服务描述,然后按类型找出 WorkflowRuntimeBehavior。该行为具有访问运行时的强类型属性,您可以与其交互以添加服务、注册事件处理程序,或编写其他与工作流运行时相关的特定宿主代码。
图 5 提供了访问运行时以添加服务并为托管相关事件注册处理程序的示例。
![](http://i.msdn.microsoft.com/Global/Images/clear.gif)
WorkflowServiceHost host = new WorkflowServiceHost(typeof(Workflows.SimpleWorkflow)); WorkflowRuntimeBehavior wfbehavior = host.Description.Behaviors.Find<WorkflowRuntimeBehavior>(); WorkflowRuntime wfruntime = wfbehavior.WorkflowRuntime; wfruntime.WorkflowTerminated += delegate(object sender, WorkflowTerminatedEventArgs wte) { Console.WriteLine(wte.Exception.Message); }; host.Open();
当在代码中修改了 WCF 和 Windows WF 运行时中的对象后,只需简单调用 WorkflowServiceHost 的 Open 即可构造并配置这两种运行时,并注册所有服务端点以侦听数据。直接使用 WorkflowServiceHost 称为自托管,与之相对的是使用提供的托管进程,如 IIS 或 Windows Process Activation Service (WAS),稍后我们将讨论这两种托管方式。
因为 WorkflowRuntime 由 WCF 行为管理,所以每个 WorkflowServiceHost 以及宿主中的每项服务都有一个 WorkflowRuntime。(而在 Windows WF 中通常仅有一个托管许多不同类型工作流的 WorkflowRuntime。)您还可以为所选的运行时服务创建多个实例,并且每个实例都可独立运行。例如,如果在相同的应用程序中托管三项服务,并且每项服务都配置使用 SQL 持久性服务,则您将得到持久性服务的三个运行实例,每个实例都将定期轮询数据库查找到期的计时器。如果使用相同的连接字符串配置这些服务,那它们将作为三个不同的宿主进程运行,这样可能会出现所有权持续时间和锁定的问题。最后请注意,即便使用相同的行为配置这些服务,这种情况依然存在。因为它只是配置,每个 WorkflowServiceHost 都将创建该行为的新实例,而实例又会创建新的 WorkflowRuntime。
使用 IIS 或 Windows 激活服务托管
在 IIS 或 WAS 中托管服务与以前使用的 Web 服务技术有很多相似之处,如 .NET Framework 2.0 中的 ASMX Web 服务,但它能够提供更多扩展选项。IIS 中托管的典型 WCF 服务端点使用扩展名为 .svc 的文件作为由客户端调用的可寻址资源。该文件仅指明用于实现服务的服务类型,而 .svc 扩展名将映射到 WCF 处理程序,以便它能够正确地将消息传递给该服务。
该模型中一个关键扩展点在于可以通过创建自定义工厂获得对 ServiceHost 创建进程的控制。通常,默认 IIS 托管可处理大量现成的情况,并为 .svc 文件中命名的服务创建一个 ServiceHost 实例。但是,通过自定义服务宿主,您可以充分利用 IIS 中的托管功能,而不必完全放弃对宿主的控制。
要托管工作流服务,您需要创建 .svc 文件,并指定工作流类型或包含工作流定义的 XAML 文件的路径,并且您还需要为 Factory 属性指定一个新类型 WorkflowServiceHostFactory。以下是 .svc 文件中定义的基本工作流服务:
<%@ ServiceHost Language="C#" Service=" SimpleWorkflow.xoml" Factory="System.ServiceModel.Activation.WorkflowServiceHostFactory" %>
请注意,此示例中通过 XAML 文件路径定义服务。WorkflowServiceHostFactory 负责加载并使用 XAML 文件,如果存在规则文件,则与规则文件一起使用以便为工作流创建服务宿主,并在 WCF 运行时请求时将其返回。规则文件必须与 XAML 文件同名,但以 .rules 作为扩展名。这提供了一种非常强大的模型,您可以将一些文本文件(XAML、.rules 和 .svc 文件)放在虚拟目录中,并且只需要将端点配置添加到配置文件中。
使用上下文管理长时间运行的服务
当服务需要长时间运行且与客户端有多种交互时,使用工作流实现服务将特别有效。例如,假设您需要一个服务来管理开支报告批准流程。此服务如作为工作流实现,至少需要两个交互点,包括员工的初始提交和经理批准或拒绝。在这种情况下,您需要能够以某种方式在整个过程中管理工作流并将消息路由至正确的工作流实例。换句话说,您需要一种能够管理消息上下文的方法。
在 .NET Framework 3.5 中,上下文支持由称为 ContextBindingElement 的新绑定元素控制。该元素使开发人员能够创建上下文感知的绑定,并有助于在向正确的工作流实例传递消息的过程中实施管理。该框架还包括三种用于最常见情形的预构建绑定:WSHttpContextBinding、BasicHttpContextBinding 和 NetTcpContextBinding。每种绑定都与功能和配置中其非上下文感知的配对项相对应,但上下文感知绑定会向运行时栈添加上下文通道。此上下文通道可在交互操作的唯一会话标识符在通道栈中传输时对其进行管理。
图 6 显示了如何使用内含的上下文通道管理上下文。当使用客户端通道与服务进行交互时,上下文信息将在第一次信息交换时发回客户端。此信息至少包含为处理请求而创建的工作流实例 ID。此后客户端通道将用于调用服务,上下文信息将发回到用于将消息传送到已运行工作流实例的服务。
![](http://i.msdn.microsoft.com/cc164251.fig06%28zh-cn%29.gif)
图 6
上下文管理 (单击该图像获得较大视图)
除了将消息传送给正确的实例外,服务之间的一些交互也变得更为复杂。例如,工作流可能会使用发送活动来发起与三个不同服务实例的通信,并随后等待来自三个实例的接收响应。这种情况下,工作流将有三个等待相同消息类型到达的接收活动实例。因此,需要有一种机制不仅能将传入消息传递给正确的工作流,而且还可以传递到工作流中正确的接收活动。上下文机制不仅支持传递实例 ID,而且还可以包括工作流等待相同操作的多个消息时所需的会话标识符。
图 7 提供了这种情况的示例。
![](http://i.msdn.microsoft.com/cc164251.fig07%28zh-cn%29.gif)
图 7
会话管理 (单击该图像获得较大视图)
上下文采用 name-values 对集合的方式进行管理,并且可以通过 SOAP 标头或 HTTP cookie 传输。上下文通道可以帮助您处理许多底层的情况,但作为工作流编写者,您有责任管理工作流内的上下文。在前面的例子中,我调出三个服务实例,每个服务实例都需要一段代码,用于在向我的工作流服务发送响应之前设置上下文。这意味着,在某些方面,调用工作流必须在不使用内置通道功能的情况下发送上下文信息,并且被调用的服务必须在稍后调回工作流时使用该上下文信息。发送活动通常包含可以在调用服务前设置的上下文属性,该属性将使活动在调用服务前设置通道上下文。
如果所有通信都是由调用工作流发起,则不需要将上下文传递到被调用工作流,因为被调用工作流只需将响应发送给接收的请求即可。虽然这种实现方式主要针对内部网络应用程序并有一些局限性,但它确实为可扩展性提供了框架,我将在以后的期刊中介绍这些内容。