Windows Workflow Foundation (WF)

http://msdn.microsoft.com/zh-cn/magazine/dd252947.aspx
Windows Workflow Foundation (WF) 是 Microsoft .NET Framework 3.0 的一部分,自其发布以来,我花费了大量时间研究此技术,使用它实现系统并将相关经验传授给其他人。通过这些经验,我大致总结出一些最佳实践(也有称不上是最佳的),在现实生活中使用 WF 实现软件解决方案。
我曾经遇到过 WF 出现貌似识别危机的问题。与我交谈的许多开发人员,都在一定程度上了解 WF 是怎么回事:条件逻辑、流控制、原子操作等等。
“是的,我想我是了解的。您将一些形状拖动到设计图面上。这些形状代表在某种“流程图”序列中执行的操作。这种方法真是太巧妙了!”之后可能会略表异议“但是,我能用它来做什么呢?”或“但是,我在 C# 或 Visual Basic 中就已经可以执行这样的操作了!”
从理论上说,这些异议都是合理的。虽然从表面上看,WF 就是在 Visual Studio 中的拖放设计器体验,其功能与其他程序的功能看起来也并无大异,但这种外表之下确实存在优点。
要了解 WF 的价值所在,知道一些历史背景将会很有帮助。自 .NET Framework 问世之初,它的主要目标之一就是提高构建 Windows 软件程序的抽象级别。过去,Windows 编程人员需要掌握 COM、HRESULT、智能指针、MTA、消息泵、thunk 层等很多非特定于域的细了内容,任务非常艰巨。
幸好,.NET Framework 的出现已成功解放了编程人员,使他们不必再掌握其中大部分细节内容了。实际上,我们大部分开发人员认为,打开 Visual Studio 并立即开始编写直接映射到手头问题的代码再正常不过了。令人高兴的是,.NET 在运行时会考虑以下问题:内存管理、实施安全策略、代码依赖关系运行时解析和大量的其他详细问题。所以,如今的许多开发人员比以往更有效率。
在通往更高的编程抽象级别的历程中,WF 代表逻辑的下一个步骤。WF 程序在特殊运行时环境中执行,其本身在 .NET CLR 上运行。WF 运行时环境对开发人员编写在该环境中运行的程序施加了一些限制。但是反过来,WF 提供了一组功能强大的、灵活且可扩展的运行时服务,例如支持长时间运行代码,代码执行操作可以持续几天甚至几个月;支持暂停/恢复/取消运行程序;支持审核和跟踪程序执行,甚至支持修改正在运行的程序逻辑!
WF 的核心是其声明性编程模型。也就是说,通常,在 WF 中您可以在 Visual Studio 中拖放 WF 设计器,来描述您希望程序完成的任务。但是,您不能精确指定如何完成此任务,反而是 WF 运行时使用您的程序说明(即控制流和活动的层次图)作为执行计划逻辑的蓝图。

工作流编程模型
在 Visual Studio 中使用工作流设计器 5 分钟,您就会明白 WF 是一种组件技术。WF 中的一个组件就是一个活动。更具体地讲,WF 中的组件是 Activity 基类的任何子类。
活动是 WF 中的操作组合和执行单位。某些活动(这些活动对 CompositeActivity 进行子分类,而 CompositeActivity 本身对 Activity 进行子分类)具有子活动,作为父活动的执行序列的一部分执行。实际上,WF 中的工作流就是这些活动的流程图,从单个根活动开始,然后向任意深度和复杂性扩展。图形中每个活动的原始逻辑,结合此逻辑控制的数据,最终确定活动执行顺序和整体工作流行为。
我喜欢把 WF 活动看成是 Lego 块。您可以将不同颜色、大小和功能的单个 Lego 块(单独每一块的价值很小)合并,构建一个大的整体,其价值超过所有块价值的总和。与此相似,在 WF 中,您将执行离散函数的原子活动合并,以创建高阶逻辑集,然后再将这些逻辑集合并来实现大型业务流程等等。
通常,组件编程(尤其是 WF)常见的一种模式更青睐于组合,而不是继承,这样才可以重复使用。长时间使用 WF 构建日益复杂的工作流,您将会更加认为您正在从小型、自治活动组合程序,而不是创建基于使用 C# 或 Visual Basic 编写的复杂系统的深类层次。
您仍能够在 WF 中将类继承用作重复使用技术。从 SendEmailActivity 派生以创建对象,假设创建的是 SendEncryptedEmailActivity,没有任何问题。在这种情况下,您正在更改执行的操作的基本特征,在此继承技术自然就派上用场。但是,如果需要根据条件仅在特定环境下发送电子邮件,或者在发送电子邮件前后执行其他任务,则最好选择使 SendEmailActivity 成为另一个 CompositeActivity 子类(例如,SequenceActivity 或 IfElseActivity)的子项,然后在 SendEmailActivity 外定义其他逻辑。
图 1 显示了封装有条件的电子邮件操作的复合活动。在该图中,Send­EmailActivity 仅负责发送电子邮件,从而提高了它在其他工作流中重复使用的机会。它还允许您重复使用 WF(支持条件分支和顺序活动执行)中现有的结构,而不需要从头开始编写新代码。
图 1  具有条件逻辑的自定义组合活动

带副作用的编程
在编程中,对副作用的定义为:因执行程序的某一部分导致的对共享状态的明显更改。副作用的示例包括更改对象属性的值,或更改数据库表中的一个或多个值等等。在软件中完成实际工作一直用到副作用。
在进行纯函数式编程时,避免共享状态,并且主要通过方法返回值来进行数据流动和操作,与这种编程方法相比,工作流中一切有趣的内容会都带来某种副作用。在工作流程序中,没有返回值等内容,但是,您可以比较执行前和执行后状态来查看更改。您通过使各个活动内外组合,而不是通过调用方法来指示控制流。在设计活动类(作为属性)时活动输入和输出是隐式的,而不是通过方法参数或返回值显式定义的。数据从一个活动到另一个活动的流动,是通过内置支持活动数据绑定完成的。通过与您的自定义 WF 活动逻辑中的配置的服务进行交互,您也可以选择处理共享状态。
WF 活动的基本执行顺序是初始化活动状态、执行活动、检查合成的活动状态是否发生更改,然后对所有其他活动重复上述操作。那么,这恰恰就是活动执行的副作用(“PurchaseOrderTotal 属性的新值是什么?”或“哪些数据库值刚刚进行了更改?”),告知您刚刚发生的情况以及接下来要发生的情况。您可能会使用不适用于其他下游活动的方法使活动处理状态(然而,请注意,此处的风险不会高于 C# 之类的任何命令式语言中所固有的风险),但不必担心数据复制出现问题。

服务,服务,服务
作为一种具有一般目的的编程环境,WF 旨在与外部子系统和代码集成。完成此任务的最直接的方法是,创建自定义工作流活动,来调用数据库、Web 服务或第三方库。在 WF 术语中,这样的外部系统称为服务。
WF 服务的原理很简单:您的工作流中的活动所需的、但又不包含在活动定义中的任何库或功能元素,在 WF 运行时注册为服务:
// create an instance of your service type...
  LoggingService logSvc = new LoggingService();

  // register it with the WorkflowRuntime instance...
  workflowRuntime.AddService( logSvc );

  WorkflowInstance instance = 
    workflowRuntime.CreateWorkflow( typeof( Workflow1 ) );

  instance.Start();
在执行期间,活动可要求通过 ActivityExecutionContext(活动从不与主机运行时直接交互)访问所需的服务,并使用配置的服务执行任务,同时使 WF 运行时和主机管理服务生存期:
protected override ActivityExecutionStatus Execute( 
    ActivityExecutionContext aec ) {
  // ActivityExecutionContext provides access to services...
  LoggingService logSvc = aec.GetService();

  Debug.Assert( logSvc != null );
  logSvc.LogEvent( EvtType, Message ); 
  return ActivityExecutionStatus.Closed;
}
典型服务示例包括记录组件、数据访问层和外部子系统的代理,当然还可能有更多的其他服务。
WF 服务模型的强大功能源于其简单性。简单地说,即任何 .NET 对象都可以配置为服务。请注意:某些其他行为,如服务生存期管理,适用于专为 WorkflowRuntimeService 划分子类的服务。此外,我经常发现通过接口(而不是通过实际键入)公开服务很有用;这将使区分独立于依赖服务的活动的服务实现更加轻松。
因此,此时关键的最佳实践就是从完成此任务所需的构成片段减少自定义 WF 活动的核心逻辑。此外,您应该重构具有状态的服务逻辑,在工作流中多个活动之间共享(例如,记录);在执行期间将引用沿活动图形向下传递给记录组件是件繁琐的事情。这对于在执行周期期间的某个点进行序列化的工作流来说将更加复杂。
接下来,我将介绍其他一些与服务有关的建议。Windows WF 附带一个基础结构(称为本地服务),用于调用活动中的 .NET 类实例并处理活动内部的 .NET 事件。您可以通过 CallExternalMethod 和 HandleExternalEvent 活动与这些本地服务进行交互。他们实质上是位于基本 WF 服务基础结构顶部的附加层,并可以提供与更适合 WF 主机环境(例如 SharePoint)的声明性形式的原始服务相同的好处。但是,对于更多通用的主机,例如 Windows Presentation Foundation (WPF) 或 ASP.NET,此附加层使用起来可能比同等的原始服务配置更加繁琐。请记住,在使用本地服务构建解决方案之前预先考虑承载要求。
此处使用的关键原则是利用 WF 服务模型的功能和灵活性设计分解合理的、可重复使用的工作流活动。

分段执行
使 WF 非常适合通用编程任务的一个功能是它支持异步、无阻塞任务。现代以 Web 服务和以数据库为中心的应用程序的本质是适应异步调用,即发出服务请求后,释放出与该请求调用关联的本地资源以处理其他任务。稍后,完成调度的任务后,执行可在其退出的位置继续进行。ASP.NET 异步页面是此项技术的一个示例。
当 WF 运行时首次执行活动时,即调用其 Execute 方法。活动执行 Execute 中的所有任务后,Execute 将返回 ActivityExecutionStatus.Completed。当 Execute 执行了尽可能多的任务,正在等待某些外部促进因素(例如,对 Web 服务或数据库调用或发送到特定地址的电子邮件的响应)以继续执行操作时,返回 ActivityExecutionStatus.Executing。
图 2 显示了活动的 Execute 方法,用于查询外部服务以获取给定客户 ID 的信用评分。由于信用评分可能需要花费几分钟甚至几小时的时间才能完成,因此工作流将异步调用服务,然后闲置等待结果。在闲置之前,此活动将注册委托以在促进因素到来时由运行时调用。此委托称为延续(字面意思是“指向程序的其余部分的指针”),允许活动创建或定义继续活动进程所需的任何逻辑。单一工作流的生存期可能由几个这样的闲置-继续状态转换构成。
protected override ActivityExecutionStatus Execute( 
  ActivityExecutionContext aec ) {

  if ( string.IsNullOrEmpty( SSN ) ) {
    throw new InvalidOperationException( "SSN property value is invalid." );
  }

  ICreditScoring creditSvc = aec.GetService();
  Debug.Assert( creditSvc != null );

  WorkflowQueuingService queueSvc = aec.GetService();
  Debug.Assert( queueSvc != null );

  Guid queueName = Guid.NewGuid();
  WorkflowQueue queue = queueSvc.CreateWorkflowQueue( queueName, false );
  queue.QueueItemAvailable += CreditScoreComputed;
  creditSvc.ComputeScoreAsync( SSN, queueName );

  return ActivityExecutionStatus.Executing;
}
与工作流实例相关联的工作流队列上的项目(任何 .NET 对象)到达后,运行时会接收到外部促进因素的通知,来唤醒闲置工作流。当异步调用 Web 服务时,等待响应的代码将获取对队列的引用,并将相关数据(推测是最终从 Web 服务接收的任何内容)推入队列。运行时监视队列并调用注册的委托;委托逻辑可获取加入队列的项目并执行任何必要的处理。
此方法的强大功能源于工作流在空闲期间执行的操作。从其 Execute 方法返回 ActivityExecutionStatus.Executing 的活动很显然在某段时期内没有执行任何任务(因此,被视为空闲状态)。可能需要花费几秒钟、几个小时、几天甚至几个月的时间来等待某些外部促进因素。在这段时间,工作流不需要继续保留在内存中消耗计算机资源,而是释放这些资源使其更充分得到利用,因此 WF 运行时支持自动从内存卸载这样的工作流,然后将它们序列化到持久存储,以使用 SqlWorkflowPersistenceService 等持久服务。当外部促进因素到达时,WF 运行时将重新加载工作流并继续正常执行。

单元测试仍是您的好帮手
单元测试始终与致力于 WF 的编程人员的生活息息相关。实际上,WF 的显式组合特性本身适用于编写一组全面的测试,以测试您的解决方案的各个片段是否与其他片段脱离。我将在此处为您提供一些较高级别的信息,但是您一定要访问 2008 年 11 月这一期的《MSDN 杂志》中由 Matt Milner 撰写的 Foundations 专栏文章 (“单元测试工作流和活动”),以深入了解单元测试工作流。
对于使用 WF 有一种常见误解,即您只能执行整个工作流(也就是说,进行子分类后的 SequentialWorkflowActivity 实例)。实际上,您可以将任意活动实例作为独立的工作流执行,以便轻松地使用通用活动测试工具执行活动,并验证活动是孤立存在的还是更大的整体中的小子集。 图 3 展示的就是这样的测试工具。
protected Dictionary ExecuteActivity( 
  Type activityType,  
  IEnumerable 
  services, 
  params object[] inputs ) {

  Dictionary outputs = null;
  Exception ex = null;

  using ( WorkflowRuntime workflowRuntime = new WorkflowRuntime() ) {
    AutoResetEvent waitHandle = new AutoResetEvent( false );

    workflowRuntime.WorkflowCompleted += 
      delegate( object sender, WorkflowCompletedEventArgs e ) {

      outputs = e.OutputParameters;
      waitHandle.Set();
    };

    workflowRuntime.WorkflowTerminated += 
      delegate( object sender, WorkflowTerminatedEventArgs e ) {

      ex = e.Exception;
      waitHandle.Set();
    };

    foreach ( object svc in services ) {
      workflowRuntime.AddService( svc );
    }

    Dictionary parms = new Dictionary();

    for ( int i = 0; i < inputs.Length; i += 2 ) {
      Debug.Assert( inputs[ i ] is string );

      parms.Add( (string) inputs[ i ], inputs[ i + 1 ] );
    }

    WorkflowInstance instance = 
      workflowRuntime.CreateWorkflow( activityType, parms );

    instance.Start();

    waitHandle.WaitOne();

    if ( ex != null ) {
      Assert.True( false, ex.Message );
      return null;
    }
    Else {
      return outputs;
    }
  }
}
 
        
       
      
     
    
   
“data in/execute code/make assertions against data”(输入数据/执行代码/根据数据得出结论)这种面向状态的基础测试模式,非常适合于使用通用测试工具对 WF 进行测试。WF 运行时支持自动填充根活动属性值(使用以属性名称作为键的通用 Dictionary)。它还允许您在活动执行完成后,检查根活动属性值。即使您的活动最终将嵌入工作流层次结构内部几个层级,并且活动依靠数据绑定初始化其属性值,您也可以测试此活动是否孤立存在并模拟该活动的全部预期的错误情形。
WF 也非常适合使用更高级的单元测试技术进行测试,如对象模拟,该技术着重于将测试的代码从其依赖关系中隔离,并验证对这些依赖关系的期望值(例如,“执行此测试的代码时,我预期为此依赖项调用 Foo() 方法的次数恰为 3 次”)。下面是一个示例:
// mock the IBanking interface...
var bankingMock = new Mock();

// specify that you expect AddCustomer to 
// be called with the customer instance...
bankingMock.Expect( bank => bank.AddCustomer( customer ) );

// execute WF harness, configuring the mock as a service...
ExecuteActivity( typeof( AddCustomerActivity ), 
  new List() { bankingMock.Object }, "Customer", customer );
 
     
    
   
如果您熟悉单元测试和对象模拟,您可能已经猜测到 WF 服务是在以 WF 活动为目标的测试中进行模拟的最佳选择。WF 服务本质上是依赖关系,因此不应包括在此类单元测试范围内。

运行时内的运行时
WF 不只是基于组件的可扩展编程环境。它还包含一个执行引擎,构建在 CLR 之上。此引擎提供了许多对工作流程序非常有用的服务,例如,工作计划、在空闲状态时自动序列化/反序列化,以及通过数据绑定将数据从父活动传送到子活动等等。但是您必须谨慎遵守 WF 规则;未试验过的工作流实现在此环境中执行起来可能很差,或者根本无法执行。
也许,要了解 WF 运行时,最重要的一点是了解它对其宿主代码没有任何特定线程要求。相反,它经过专门设计,以便与任何现有线程模型(单线程或多线程)集成。
通过根据 WF 运行时配置的服务计划工作流执行。DefaultWorkflowSchedulerService 在 .NET ThreadPool 上执行工作流,同时 ManualWorkflowSchedulerService 允许您精确指定您要使用的线程(这对亲自将工作分派给 ASP.NET 之类的 ThreadPool 的 WF 主机非常有用)。尽管在实际中很少需要这样做,但您也可以选择提供您自己的自定义计划实现。
请注意,不论线程模型如何,单个工作流在任何给定时间都绝不会在多个线程上执行。这是 WF 运行时执行模型中需要特别注意的设计选择。如果您需要在一个工作流中调用多线程代码,最好在自定义 WF 服务中执行该操作。
与传统的基于堆栈的编程环境(例如 C# 或 Visual Basic)不同,工作流根据其调用代码异步执行。也就是说,当工作流完成执行时,控制不会自动返回到工作流的调用点。相反,各个工作流实例将成为您的程序中单独的逻辑分支。
如果不了解 WF 运行时,管理在 WF 中运行的工作流状态会引出潜在问题。传统的 POCO (Plain Old CLR Objects) 编程使用类成员字段和属性进行数据存储。您使用相同的方法构建 WF 活动,但当将分段式工作流配置为在空闲状态持续时,可能通过多个物理活动图形实例(如 图 4 所示)并跨多个应用程序甚至多台计算机实现单一逻辑工作流生存期。
图 4  分段式工作流的逻辑与物理工作流生存期
尽管 WF 支持在持续期间自动序列化活动图形和状态,但一定要注意,这仅适用于一部分数据类型(不包含数组,对泛型的支持亦有限)。通过序列化代理项可以手动编写自定义序列化。您也可以覆盖 Activity.OnActivityExecutionContextLoad 和 Activity.OnActivityExecutionContextUnload,以根据各个实例的活动和工作流对象图形的拆卸进行自定义处理。
请记住,WF 自身是一个运行时。基本了解 WF 线程、计划和序列化概念,可以避免常出现的失误。

两极式思维不可取
首次采用某种新技术后,便处处应用,有时甚至根本不考虑其他办法(即谚语“拿锤在手,看什么都是钉子”所讽刺的狭隘思维方式),是一种常见的反面模式。我们有时会犯这种错误。虽然使用 WF 时也难免犯该谚语所形容的这种错误,但 WF 的本质是低级别探测技术,包含许多集成点和可扩展性挂钩,允许您在许多不同的问题域中应用该技术。
WF 在高度多线程化的环境(例如 ASP.NET),或使用 Windows 窗体或 WPF 构建的桌面客户端应用程序中运行得也相当好。在这些方案中,WF 的常见用途包括实现 UI 驱动的逻辑(即使用工作流,而不是命令性代码实现用户界面事件处理逻辑);面向任务的异步后台工作(通常在单一线程上调用,例如,提取和处理 Web 搜索结果或处理磁盘上的大文件);页流(工作流逻辑指示 ASP.NET 或智能客户端的 UI 导航)。
图 5 展示  Microsoft 示例 PageFlow 应用程序的设计图面。工作流中的每个顶层活动定义要显示的视图(Web 页面或应用程序窗体),以及每个视图的任何自定义处理。允许的视图之间的转换也在此进行描述。对比使用这样的清晰图形方式,和使用几十行 C# 或 Visual Basic 代码表达相同的信息,您会发现 WF 具有明显的优势。
图 5  管理用户界面导航步骤的工作流(单击图像可查看大图)
WF 和 Windows Communication Foundation (WCF) 是面向服务的应用程序中天然的技术组合。工作流实际上是一种服务,您调用它以执行特定功能,而 WCF 设计用于公开服务以供使用。为了方便您使用,.NET Framework 3.5 提供了 SendActivity 和 ReceiveActivity 活动,使您可以直接集成 WF 和 WCF。您可以使用 ReceiveActivity 以声明的方式通过 WCF 地址/绑定/协定形式公开 WF 工作流。此工作流为服务实现,在接收到请求时由 WCF 集成探测进行调用。
您也可以通过这种方式公开长时间运行的工作流,在这种工作流中,单一工作流实例可能会公开多个服务操作,并经历多个执行/空闲序列。WCF 绑定类型(BasicHttpContextBinding、WsHttpContextBinding 或 NetTcpContextBinding)用于将从客户端发送的信息与特定的运行中的工作流实例相关联。
最后,您可能会猜想,SendActivity 是否允许您轻松配置从工作流到外部服务的出战 WCF 客户端连接。
作为 .NET 集成技术,WF 程序可与 .NET 代码使用的任意库或技术交互。实现 WF 中的应用程序逻辑时,您不必抛弃现有的技术投入;此技术的真正价值在于可以更好、更有效的使用现有技术资产。
WF 自身与其说是一种方法,倒不如说是一种集成技术。WF 是使用工作流产品的 Microsoft 产品中所用的一项基础技术。这些产品包括 SharePoint、Dynamics 产品以及即将发布的 Office Communications Server 和 Office Project。这种最常见用法允许更轻松地在多项技术和您的 WF 应用程序之间进行集成。希望其大有作为。

即插即用
为了尽可能地方便集成,可扩展性是 WF 的基本原则。核心基础结构的主要内容是可配置 — 甚至是可以使用自定义实现替换。虽然 WF 附带有许多用于控制流、事务性语义和服务通信等的内置活动,但您的 WF 程序不需要使用其中任一活动,且这些活动都不使用普通 WF 编程社区无法使用的特殊功能。与 WF 自带的活动相比,您自己的自定义活动(或第三方供应商提供的活动)提供功能的能力差不多。
映射到特定域行为的面向操作的活动是自定义实现的理所当然的选择。抵押放款公司发放贷款所使用的工作流,可能包含请求大量保险报价、计算财产税和获取申请人信用分数等自定义活动。这些活动非常有用并且很有必要,但更有趣的情况是自定义组合活动,其重点强调自定义控制流,而不是实现特定域功能。WF 提供了几个标准的控制流组合,例如 IfElse­Activity 和 WhileActivity。但是使用 WF,您可以实现更特殊的情况,例如根据某些可配置的优先级执行组合子活动,或可能使用依赖关系网络确定执行顺序(请参见 图 6)。
[Designer( typeof( DependencyNetworkActivityDesigner ) )]
[ActivityValidator( typeof( DependencyNetworkActivityValidator ) )]
public partial class DependencyNetworkActivity : SequenceActivity {

  DependencyProperty.RegisterAttached( "RunAfter",
    typeof( string ), typeof( DependencyNetworkActivity ),
    new PropertyMetadata( DependencyPropertyOptions.Metadata ) );

  public static object GetRunAfter( object depObj ) {
    return ( (DependencyObject) depObj ).GetValue( RunAfterProperty );
  }

  public static void SetRunAfter( object depObj, object value ) {
    ( (DependencyObject) depObj ).SetValue( RunAfterProperty, value );
  }

  public DependencyNetworkActivity() {
    InitializeComponent();
  }

  private IList _orderedList = null;
  private int _currentIndex = 0;

  protected override ActivityExecutionStatus Execute( ActivityExecutionContext aec ) {
    if ( this.EnabledActivities.Count == 0 ) {
      return ActivityExecutionStatus.Closed;
    }
    else {
      _orderedList = DependencyNetworkActivityValidator.ComputeOrderedList( this );

      Debug.Assert( _orderedList != null );
      Debug.Assert( _orderedList.Count == this.EnabledActivities.Count );

      ScheduleNextChild( aec );

      return ActivityExecutionStatus.Executing;
    }
  }

  private void ScheduleNextChild( ActivityExecutionContext aec ) {
    Debug.Assert( aec != null );

    Debug.Assert( _orderedList != null );
    Debug.Assert( _currentIndex < _orderedList.Count );

    Activity child = _orderedList[ _currentIndex++ ];

    Debug.Assert( child != null );

    child.Closed += ChildDone;

    aec.ExecuteActivity( child );
  }

  private void ChildDone( object sender, ActivityExecutionStatusChangedEventArgs e ) {
    ActivityExecutionContext aec = ( sender as ActivityExecutionContext ); 
    e.Activity.Closed -= ChildDone;

    if ( _currentIndex < _orderedList.Count ) {
      ScheduleNextChild( aec );
    }
    else {
      aec.CloseActivity();
    }
  }
}
此外,WF 提供的不同的运行时服务几乎都具有可扩展性或可替换性。例如,您可以在其他存储媒体之上实现您自己的工作流持久层,或者可能实现工作流计划程序,该程序根据自定义线程池实现而非内置 .NET ThreadPool 执行。这样的自定义服务的配置方式与其他所有 WF 服务配置方式完全相同,因此该模型具有一致性,也很简单。
最后,在面对不断发展的技术和应用程序体系结构时,WF 的目标是保持灵活性,而不是因固守僵化的要求导致构建的技术不能广泛应用。
其他 WF 资源

工作流提示和技巧

工作流服务(Windows Workflow Foundation 和 Windows Communication Foundation)

构建工作流以捕获数据和创建文档

在 WF 中加载工作流模型

域建模和程序设计
使用 WF 构建软件的一个难度较大的问题,是将您的问题域的元素映射到工作流元素。主要问题是 WF 以控制流为中心的编程模型,这种模型不同于传统的命令式编程或面向对象的编程,工作流的原子元素(活动)不代表您问题域中的实体,而是代表操作或处理步骤。这是一点很细微但又非常重要的区别,它使重要的任务(域建模和程序设计)更具有挑战性,特别是对于 WF 初学者而言。
了解 WF 编程模型机制:活动、服务、分段执行,以及其他内容是一回事,但将功能性说明或大量用例转化为一个或多个有效的、考虑周详而运行可靠的工作流完全是另一回事。哪些域概念可映射到活动、服务以及整个工作流?哪些数据应在工作流中流动?如何流动?在什么位置可并行完成工作,以及在什么位置必须将工作流设计为闲置,以等待用户输入?
说实话,在本文有限的篇幅内无法将此类问题解释清楚。但是我在此可以为您提供几个我认为不错的一般指导原则。在讨论建模和设计时,可采用以下建议,但这些建议并不是对该主题的完整说明,只是个开端而已,也不要认为这是取得理想 WF 设计的唯一途径,不是这样的。
首先,根据适合您的项目的前期设计考虑,像通常那样使用用例、使用者和域元素开始建模阶段。使用适合您的任何方法;在此不需要特别考虑是否适合于 WF。
接下来,将您的使用者(Customer、BankManager 等)和您的域元素(PurchaseOrders、InsurancePolicies、AcmeWidgets)建模为对象。与平时一样,认真考虑由每个项封装的数据。根据需要,使用属性公开数据。为使用者方法建立存根,但是现在不要实现它们。
此时,您需要做出决定。您预想的设计是否以 WF 为中心,使得所有重要行为都表示为一个或多个工作流中的不同活动?或者,您是否希望以更有针对性的方式使用 WF,即在 WF 中实现关键方面,而将其余部分保留为更传统的 .NET 代码形式?不要认为这是一个简单的用“是”或“不是”就能回答的问题;而应认真考虑您究竟希望在多大范围内使用 WF。WF 的优点 — 模块性、可见性和功能集 — 在某些区域(复杂的、不断更改的业务逻辑)为您提供的优势要多于其他区域(用户界面布局或数据访问层)。
现在考虑由各个域元素定义的操作。在传统的面向对象的编程中,这些操作将映射到具体类型的公共方法。在 WF 中,这样的操作(例如“从银行帐户提取现金”或“向购物车添加商品”)通常映射到在一个或多个定义的对象上执行操作的单独的自定义活动。整体范围内使用 WF 越多,作为 WF 活动实现的操作就越多,作为传统的类方法实现的操作就越少。
用例将定义使用者为达到特定目标而执行的一组有序操作,以及出现错误的不测情况。在 C# 或 Visual Basic 中,这将通过使用 Strategy 或 Template Method 模式展现,从而导致方法不愿参加使用者类型定义。使用 WF,我倾向将用例映射到自定义组合活动,这些活动由域元素操作活动和定义用例中的条件逻辑的其他活动组成。 图 7 展示了自定义组合活动 AddCustomerWithAuditActivity,该活动不包含基于代码的逻辑;所有步骤都是作为一系列自定义活动调用实现的。
图 7  不包含基于代码的逻辑的自定义复合活动
由于您的用例现在定义为自定义组合活动,因此您可以将大型业务流程(“执行月末协调”或“下一个财务年预算项目”)定义为工作流,合并这些自定义组合。使用者行为可以定义为类方法,这些方法接受所有必需的输入并启动相应的工作流,根据需要将数据传入工作流并获取结果。根据需要,可通过数据绑定或服务调用传递数据。
WF 中的域建模和代码设计与命令式 .NET 编程中的等效任务有一些相似之处,但也存在一些重要差异。可以预见到学习曲线和某些重构机会是您的第一个 WF 项目的无可避免的副产品。

其他建议
我提几点重要建议,从而帮助您更好地了解它们:
首先,尽可能地减少使用 CodeActivity。这对于一次性“粘合”代码以确保数据在各活动间的正确流动非常有用,但在别的方面却应由特定于手头任务的自定义活动替换。
当将工作流与其他子系统集成时,最好通过界面间接公开 WF 功能。这将促进松散耦合,使您的工作流代码更易于使用和测试。这也将更易于随时间变化 WF 逻辑实现。
不要忽视实现您的自定义活动的验证程序。WF 活动组合模型支持可选的验证逻辑,以验证允许的和不允许的活动配置。您可通过为 ActivityValidator 划分子类,并设置目标活动类型的属性来实现此工作。请参阅随附代码下载部分,以获得验证程序实现示例。
了解样板 Visual Studio 向导生成的工作流宿主代码对于临时调用和测试工作流非常有用,但仅此而已。最有趣和最有用的工作流可能从不通过命令行执行!
为获得最具灵活性的运行时,您应考虑使用依赖关系注入容器。请查看  StructureMap 或  Spring.NET 以了解 WF 自定义服务配置。
如果您的工作流使用 PolicyActivity 执行业务规则,请考虑通过为 RuleAction 划分子类,使用特定于域的关键字扩展可用语法。这将有利于提高编写的规则的表现方式,从而更有利于理解。
最后但不容忽视的一点是,切记要设计您的工作流主机环境,以使它可以持续接收来自外部系统的与空闲工作流绑定的服务响应。您的工作流可能处于空闲状态,但如果您的工作流主机没有运行,则将不会处理外部系统响应,并且您的空闲工作流也不会唤醒!
对于像 WF 这样应用广泛的技术而言,由于使用的环境等级不一样,任何最佳实践或关键原则的列表都可能是不完整的、或只在不同程度上适用。此处提供的指南是我以及我的同事和学生们的经验所得,虽然内容很广泛,但可能不全面或不适用。请根据您的问题域和限制,决定如何使用我的建议最好地解决您的问题,但也许我的建议不适用您的问题。

你可能感兴趣的:(WF)