开发自定义活动
Windows Workflow Foundation 中可扩展性的要点是创建自定义活动,因为这使您可以扩展用于生成工作流模型的构造块集。
让我们研究一下活动的内部体系结构,方法是开发一个自定义活动来发送电子邮件。Windows Workflow Foundation 为自定义活动提供一个现成的 Visual Studio 2005 模板。它的名称为 Workflow Activity Library。该模板创建一个可任意重命名的 C# 文件 — 例如,可将其重命名为 SendMailActivity。活动是从父类继承的普通类。可从任何现有活动(无论它是内置的活动,还是您自己创建或从第三方供应商购买的活动)派生您的活动。显然,父类向新的组件中添加了预定义的行为。要完全从头开始生成活动,请让其从 Activity 派生。下面的代码示例显示新类的主干。
public partial class SendMailActivity : System.Workflow.ComponentModel.Activity { public SendMailActivity() { InitializeComponent(); } protected override Status Execute(ActivityExecutionContext context) { : } }
正如您可以猜到的那样,Execute 方法是该组件的核心 — 即完成该组件的核心任务的位置。
在开发之后,活动就被放到工具箱中,以供拖放操作将其拖放到新的工作流应用程序中。尽管属性列表不是必需的,但不带属性的活动几乎没有任何用处。要添加属性,您需要在设计器中选择正在开发的活动,然后单击 Properties 窗格上的 Activity Properties 项(参见图 13)。

图 13. 向自定义活动中添加属性
向活动中添加属性与向工作流中添加参数并无太大的不同。必须做的工作就是为每个需要的属性配置名称和属性。图 14 显示如何向 SendMail 活动中添加 To 属性。

图 14. 添加到 SendMail 活动中的 To 属性
为了完成所有工作,我们添加了其他属性(如 From、Subject、Body 和 Host),以便用户可以完整地配置要发送的电子邮件。当您添加属性时,向导会修改包含活动的逻辑在内的 C# 代码隐藏文件。
最后一个步骤是使 Execute 方法变得充实一些,以指示它在执行该活动时发送电子邮件。
protected override Status Execute(ActivityExecutionContext context) { MailAddress toAddress = new MailAddress(To); MailAddress fromAddress = new MailAddress(From); MailAddressCollection addresses = new MailAddressCollection(); addresses.Add(toAddress); MailMessage msg = new MailMessage(fromAddress, toAddress); msg.Subject = Subject; msg.Body = Body; SmtpClient mail = new SmtpClient(Host); mail.Send(msg); return Status.Closed; }
如果在工作流解决方案的内部开发活动项目,则工作流文档将自动查找工具箱中列出的新活动,如图 15 所示。否则,必须通过右键单击工具箱来添加它。

图 15. SendMail 活动显示在工具箱中
图 16 说明 SendMail 活动确实有效。

图 16. 正在工作的 SendMail 活动
计划更现实的工作流
让我们看一下如何组合表 2 中列出的一些活动,从而解决一项更为现实的任务。假设有这样一个业务应用程序,其中,订单在完成之前可能要经历多个状态。在典型的方案中,有一些根据当前状态指示订单中可能发生某些事件的规则。例如,可以处理或更新未完成的订单,但不能将其取消或发送。
当事件发生时,状态机工作流将转换订单的状态。例如,当订单未完成并且 BeingProcessed 事件发生时,状态机工作流会将订单转换到正确的状态。图 17 显示示例订单状态机工作流的关系图。

图 17. 管理订单的状态机的示例架构
让我们首先创建一个状态机工作流。您将使用 State 活动对订单的可能状态进行建模。然后,通过使用 EventDriven 活动指定可以从每个状态发生的事件。通过自定义服务产生的外部事件将转换订单的状态。要执行转换,需要使用 SetState 活动。创作工作流后,需要使用 Windows 窗体宿主应用程序来检验它。
工作流通过专门为此目的建立的服务与外部世界通信。该服务会引发工作流内的事件驱动活动将挂钩到的事件。同样,该服务公开了供该工作流调用的公共方法并向主机发送数据。方法和事件在接口中定义。该接口也称为数据交换服务。每当工作流与外部组件交互(进行输入和输出)时,您都需要该服务。
数据交换服务是常规的 .NET 类库,它最起码包含一个接口定义以及一个实现该接口的类。该接口是为您希望表示的任务定制的。在此情况下,状态机表示订单的生存期,该接口包含五个事件。
[DataExchangeService] public interface IOrderService { event EventHandler OrderCreated; event EventHandler OrderShipped; event EventHandler OrderUpdated; event EventHandler OrderProcessed; event EventHandler OrderCanceled; }
[DataExchangeService] 属性将 IOrderService 标记为数据交换服务接口,以便工作流运行库知道它将用来与工作流实例交换数据。在此情况下,宿主将向一串 EventDriven 活动引发事件,从而向工作流实例发送数据。如果需要,可通过 InvokeMethod 活动从工作流实例内部调用 IOrderService 接口中的方法。
该接口中的事件声明使用泛型,这是 .NET Framework 2.0 中的一项非常热门的新功能。EventHandler 类是一个委托,它表示用于处理事件的函数的原型。在 .NET Framework 1.x 中,EventHandler 按如下方式定义。
void EventHandler(object sender, EventArgs e)
要使该事件传递自定义数据结构(例如,OrderEventArgs),必须创建一个新的委托并使用它来替代 EventHandler。下面是一个示例。
delegate void OrderEventHandler(object sender, OrderEventArgs e)
该模式在 .NET Framework 2.0 中仍然有效。然而,.NET Framework 2.0 中泛型的出现使您无需显式定义(和实例化)新的委托类即可获得相同的结果。您将使用 EventHandler 委托的泛型版本,其中,事件数据的类型是参数。
事件传递 OrderEventArgs 类型的客户端数据,该类型是一个从 Windows Workflow Foundation WorkflowMessageEventArgs 类派生的自定义类,后者在同一个程序集中按如下方式定义。
[Serializable] public class OrderEventArgs : WorkflowMessageEventArgs { private string _orderId; public OrderEventArgs(Guid instanceId, string orderId) : base(instanceId) { _orderId = orderId; } public string OrderId { get { return _orderId; } set { _orderId = value; } } }
下一步,需要定义一个实现该接口的类。该类所引发的公共方法与接口中引发的事件一样多。
public class OrderService : IOrderService { public OrderService() { } public void RaiseOrderCreatedEvent(string orderId, Guid instanceId) { if (OrderCreated != null) OrderCreated(null, new OrderEventArgs(instanceId, orderId)); } public void RaiseOrderShippedEvent(string orderId, Guid instanceId) { if (OrderShipped != null) OrderShipped(null, new OrderEventArgs(instanceId, orderId)); } public void RaiseOrderUpdatedEvent(string orderId, Guid instanceId) { if (OrderUpdated != null) OrderUpdated(null, new OrderEventArgs(instanceId, orderId)); } public void RaiseOrderProcessedEvent(string orderId, Guid instanceId) { if (OrderProcessed != null) OrderProcessed(null, new OrderEventArgs(instanceId, orderId)); } public void RaiseOrderCanceledEvent(string orderId, Guid instanceId) { if (OrderCanceled != null) OrderCanceled(null, new OrderEventArgs(instanceId, orderId)); } public event EventHandler OrderCreated; public event EventHandler OrderShipped; public event EventHandler OrderUpdated; public event EventHandler OrderProcessed; public event EventHandler OrderCanceled; }
现在,需要用订单服务编译该程序集,并重新切换到状态机工作流项目。在该工作流项目中,首先需要添加对新创建程序集的引用。接下来,需要添加四个 State 活动并且按如下方式命名它们: WaitingForOrderState、OrderOpenState、OrderProcessedState、OrderCompletedState。
表 3 表示工作流的状态关系图。每个状态都有一些能够导致向另一个状态进行转换的事件。
表 3. 订单的示例状态机 | ||
状态 | 受支持的事件 | 转换到 |
WaitingForOrderState |
OrderCreated |
OrderOpenState |
OrderOpenState |
OrderUpdated |
OrderOpenState |
OrderProcessed |
OrderProcessedState | |
OrderProcessedState |
OrderUpdated |
OrderOpenState |
OrderCanceled |
Terminate 活动 | |
OrderShipped |
OrderCompletedState | |
OrderCompletedState |
要实现该关系图,需要向每个 State 活动中添加与该表中受支持事件相同数目的 EventDriven 块。例如,名为 WaitingForOrderState 的 State 活动将包含单个 EventDriven 活动(该活动的名称可以是任意的,例如 OrderCreatedEvent)。如图 18 所示,EventDriven 活动嵌入一个 EventSink 活动和一个 SetState 活动,以便捕获外部事件并转换到新的状态。

图 18. OrderCreatedEvent EventDriven 活动的内部视图
在 EventSink 活动的 Properties 窗格上,可选择自己喜欢的数据交换服务(在此情况下为 IOrderService 接口)以及要预订的事件名称。如果单击 EventSink 活动 Properties 窗格上的 InterfaceType 项,则 Visual Studio 2005 将提供该项目可用的数据交换服务列表。选择服务后,EventName 属性反映由该服务所公开事件的列表。可选择自己感兴趣的事件并继续。对于 OrderCreatedEvent 活动,可选择 OrderCreated 事件。
SetState 活动将状态机转换到由其 TargetState 属性指示的新状态。图 18 中的 SetState 活动设置为 OrderOpenState。
可对表 3 中的所有状态和事件接收器重复执行上述操作。最后,您的工作流应该如图 19 所示。

图 19. 最终完成的订单状态机
最后一个步骤涉及到生成 Windows 窗体应用程序以测试该工作流。用户界面包含一个用于跟踪所有未完成订单的列表视图,以及用于创建新订单的文本框和按钮。其他按钮将用来更新、处理和终止该订单。
状态机工作流在 Form_Load 事件中初始化。状态机工作流的初始化要比顺序工作流复杂一些,尤其是当您希望能够跟踪状态更改的时候。下面的代码示例显示如何初始化工作流运行库。
private void StartWorkflowRuntime() { // Create a new Workflow Runtime for this application _runtime = new WorkflowRuntime(); // Register event handlers for the WorkflowRuntime object _runtime.WorkflowTerminated += new EventHandler(WorkflowRuntime_WorkflowTerminated); _runtime.WorkflowCompleted += new EventHandler(WorkflowRuntime_WorkflowCompleted); // Create a new instance of the StateMachineTrackingService class _stateMachineTrackingService = new StateMachineTrackingService(_runtime); // Start the workflow runtime _runtime.StartRuntime(); // Add a new instance of the OrderService to the runtime _orderService = new OrderService(); _runtime.AddService(_orderService); }
StateMachineTrackingService 在运行库之上工作,并且用跟踪工作流中状态更改的功能来扩展它。数据交换服务的实例还被添加到运行库中。
当用户单击以创建新订单时,将执行下面的代码。
private Guid StartOrderWorkflow(string orderID) { // Create a new GUID for the WorkflowInstanceId Guid instanceID = Guid.NewGuid(); // Load the OrderWorkflows assembly Assembly asm = Assembly.Load("OrderWorkflows"); // Get a type reference to the OrderWorkflows.Workflow1 class Type workflowType = asm.GetType("OrderWorkflows.Workflow1"); // Start a new instance of the state machine with state tracking support StateMachineInstance stateMachine = _stateMachineTrackingService.RegisterInstance(workflowType, instanceID); stateMachine.StateChanged += new EventHandler(StateMachine_StateChanged); stateMachine.StartWorkflow(); _stateMachineInstances.Add(instanceID.ToString(), stateMachine); // Return the workflow GUID return instanceID; }
首先,代码实例化工作流实例并注册状态更改的事件处理程序。请注意,使用 .NET Reflection 来获得类型信息并不是绝对需要的,但这可以大大提高灵活性。普通的旧运算符 typeof 也可以很好地将工作流实例的类型传递给工作流运行库。
图 20 显示正在工作的示例应用程序。按钮是基于所选工作流实例的状态而启用的。

图 20. Windows 窗体应用程序中承载的状态机工作流
当用户单击给定按钮时,通信接口上的相应事件将被引发并被工作流的事件接收器捕获。例如,对处于挂起状态的工作流实例而言,其 Order Processed 按钮的单击事件将按如下方式处理。
private void btnOrderEvent_Click(object sender, EventArgs e) { // Get the name of the clicked button string buttonName = ((Button)sender).Name; // Get the GUID of the selected order Guid instanceID = GetSelectedWorkflowInstanceID(); // Get the ID of the selected order string orderID = GetSelectedOrderID(); // Disable buttons before proceeding DisableButtons(); // Determines what to do based on the name of the clicked button switch(buttonName) { // Raise an OrderShipped event using the Order Local Service case "btnOrderShipped": _orderService.RaiseOrderShippedEvent(orderID, instanceID); break; // Raise an OrderUpdated event using the Order Local Service case "btnOrderUpdated": _orderService.RaiseOrderUpdatedEvent(orderID, instanceID); break; // Raise an OrderCanceled event using the Order Local Service case "btnOrderCanceled": _orderService.RaiseOrderCanceledEvent(orderID, instanceID); break; // Raise an OrderProcessed event using the Order Local Service case "btnOrderProcessed": _orderService.RaiseOrderProcessedEvent(orderID, instanceID); break; } }
该工作流中引发的事件由图 21 中所示的 EventDriven 活动捕获。

图 21. 状态机用于处理 Order Processed 事件的 EventDriven 块
EventSink 活动捕获该事件,并且通过转换到 SetState 活动所设置的状态来处理它。工作流中的状态更改由附加的状态跟踪服务检测,并通过 StateChanged 事件报告给宿主,如上述代码清单所示。
您可以在 http://msdn.microsoft.com/workflow 找到本文中讨论的全部示例的完整源代码,以及更多的工作流内容。
小结
Windows Workflow Foundation 旨在成为新的和现有的 Microsoft 产品的工作流框架,它向所有需要为 .NET 平台创建工作流驱动应用程序的开发人员提供了 WinFX 的强大功能和 Visual Studio 2005 的易用性。
Windows Workflow Foundation 为工作台带来的主要好处是统一的工作流模型和一组能够取代很多专用库的工具。在这方面,Windows Workflow Foundation 对于目前工作流产品的供应商也具有重要意义,因为采用 Windows Workflow Foundation 则意味着他们不必再维护其低级别的代码,并且可以集中力量去完成更高级别的任务。
Windows Workflow Foundation 是一种面向多种特定应用程序和需要的工作流技术。Windows Workflow Foundation 因而成为一种广泛的框架,它是为提高每个级别的可扩展性而设计的。这种形式的可扩展性的最佳示例是自定义活动和可插接的运行库服务。自定义活动使您可扩展可用来创作工作流的构造块集。可更改持久性存储和跟踪等运行库服务以适应应用程序的环境,并可使应用程序将数据持久存储到 Microsoft SQL Server 或其他供应商的数据库中。
Windows Workflow Foundation 的 Visual Studio 2005 扩展将允许对工作流进行可视化建模和直接代码访问。
还可以在其他设计环境中承载可视化设计器,从而使设计器提供商可以将可视化建模功能嵌入到其自己的环境中,并且提供应用程序用户所熟悉的用户体验。
本文仅仅讨论了所有 Windows Workflow Foundation 技术和功能中的一些粗浅知识,提供了有关其工作方式、内部原理的概述和一些有代表性的示例代码。