基础知识

版本控制工作流

Matthew Milner

代码下载可从 MSDN 代码库
浏览代码联机

内容

问题
.NET 版本控制
使用 XOML 工作流定义的版本控制
活动版本控制
版本控制工作流服务
动态更新

在任何应用程序,没有的一点您可以确定的: 更改发生。我发现的努力开发人员最常见问题之一是如何处理版本控制工作流和相关的类。在本月的专栏中,我将讨论与工作流版本控制在核心问题并提供我的建议更改工作流定义、 活动和工作流服务。

fig01.gif

图 1 的可能保留的简单工作流

如果生成目前在.NET 应用程序,说出 Windows Presentation Foundation (WPF) 或 ASP.NET 应用程序,您可能认为有关版本控制,或不可能。在想要部署到 ASP.NET 应用程序的更新,通常必须进程来只是复制全部或部分包括页、 二进制文件和配置这些组件。在某些情况下,您可能必须与此部署有关的问题,如确保没有请求当前正在执行,但此过程是通常易于管理。原因部署工作流因此硬盘的应用程序的新版本?

工作流可以长时间运行业务流程或业务逻辑的模型。此外,Windows Workflow Foundation (WF) 提供持久性服务,允许一个业务流程的单个实例的状态保存到持久存储如 Microsoft SQL Server 数据库。保存状态的序列化的.NET 对象 ; 组成,并其中位于该问题。

请考虑某些.NET 类型用作数据的工作流,说出一个 Order 类。与作为然后保留到数据库,包含序列化的顺序对象的状态的一个参数传递的顺序对象启动工作流。现在更改 Order 对象类,并重新生成您的应用程序库。时,保存工作流需要恢复由于延迟已过期,或向工作流中引发一些事件,Order 对象需要进行反序列化。遗憾的是,因为该类型已被更改,在反序列化失败,引发异常。

因为标准.NET 二进制序列化失败,并且其他问题发生的 WF 实现序列化的原因是,某些更改将导致反序列化问题。无论该的原因最终结果是相同: 当您尝试加载引用已更改的类型的工作流时异常。这些类型可能是,类或您创建的接口,或者可能是您编写的自定义活动。

若要提供具体的示例,请考虑 图 1 中的工作流。它只是写入一些数据控制台使用一个自定义 WriteLine 活动、 延迟 (它还允许其保留) 和写入到控制台的更多数据。

现在,如果我启动此工作流实例,并让它运行空闲的点,延迟,它将会保留在数据库中。我可以关闭运行库,并在我的版本 1.1 的工作流开始工作。我只是将工作流定义写入到该的控制台的更多数据,如 图 2 中所示的末尾添加一个活动。

启动而不启动另一个工作流实例的运行时,将导致持久性服务轮询数据库查找过期的计时器。找到现有实例则持久性服务将尝试加载对象,并将收到一个异常。通过注册 WorkflowRuntime 上 ServicesExceptionNotHandled 事件的事件处理程序,我可以检测此错误。我将看到是的持久性服务将引发一个事件消息异常"下标为数组的界限之外"。

fig02.gif

图 2 修改工作流定义

问题以下是我已更改我的工作流类型的定义,它不再匹配定义工作流已保存时已经存在。当持久性服务尝试反序列化工作流时,因为新的类型定义中包含字段不存在该实例被序列化时收到异常。读取新类型字段并尝试从序列化的数据加载这些值在反序列化代码。因为实例已序列化时,某些字段不存在,尝试访问该数据时,将引发异常。

.NET 版本控制

要变通解决版本控制问题的一个方法是利用内置在.NET Framework 的版本控制系统。因为 1.0 版.NET Framework 中,程序集的版本控制一直被支持但只有这样才启用的并行程序集的多个版本的支持。以是短每个程序集可以有附加的版本号,并且重程序集被签名使用密钥时, 可以将程序集的多个版本部署到全局程序集缓存 (GAC)。有关详细信息的程序集版本和强命名程序集上请参阅在在程序集上的 MSDN 文档.

这可以在工作流和活动内置的 1.0 版本的项目、 签名它们,并将其部署到 GAC 中。现在,启动工作流它仍然存在时,版本信息属于序列化的状态。如果您更改工作流,很重要更改程序集的版本并且所有其他程序集工作流取决如果那些已更改,过。是例如如果您必须从上一个示例工作流,并且 WriteLine 活动和工作流定义进行更改然后将要增加每个项目的版本号并将更新部署到 GAC 中。

到目前为止您将版本 1.0 和 1.1 版的工作流部署到 GAC。在版本 1.0 的工作流从持久性存储加载,运行库将能够解决 1.0 版的类型定义时和使用的类型信息反序列化工作流。因为运行库能够太查找定义为版本 1.1 类型,工作已也将持久化任何版本 1.1 工作流。实质上是,只要程序集包含该类型工作流及其相关的类可以被解析,和匹配原始定义,工作流将正确地反序列化,并可以继续处理。您可以看到此过程如何工作在 图 3 从在的数据库加载不同的版本,其类型正确解析在 GAC 中找到的时间。

fig03.gif

图 3 并排.NET 版本控制

要注意的一个要点是 1.0 版的工作流将继续工作,并且处理,但它们仍然基于工作流的 1.0 版定义。这意味着您在工作流 1.1 版中引入的任何更改不会存在工作流定义。换句话说,任何活动添加、 删除,或否则更改不会出现在工作流定义,,您将不看到版本 1.0 工作流中的这些更改的影响。稍后,我将讨论称为能够更改现有的进程内,1.0 工作流的动态更新功能。

只需通过并行版本工作流和相关的程序集并不意味着操作滚以及很好地,但是。宿主应用程序可能是旨在对一个的特定和可能是最新的该工作流的版本以便它可以与之交互。这可能导致问题,如试图与之交互持久化对相关的类的 1.0 版生成的 1.0 版工作流时生成工作流的版本 1.1 的主机将遇到问题。

此问题经常出现为开发人员的一个区域是 ExternalDataExchangeService 与 HandleExternalEvent 和 CallExternalMethod 活动结合使用本地通信时。考虑它们用于版本控制,执行正确的操作的许多人请以便 1.0 和 1.1 版本可执行上述步骤。但是,当主机尝试将数据发送到通过一个事件的工作流,不再使用的接口类型匹配。主机使用版本 1.1 的接口的工作流实例是构建版本 1.0。

要充分理解为何问题,撤回如何通信真正工作在 WF 中。(有关更多工作流通讯请参阅在2007 年 9 月期的基础.) HandleExternalEvent 活动创建队列以接收数据,并该队列的名称中包含该接口的类型信息。当数据从主机发送时, 必须将发送到正确的队列。ExternalDataExchangeService (EDS) 使用类型信息和事件名称创建内容它认为是适当的队列名称。遗憾的是,因为已创建队列基于接口的 1.0 版并 EDS 创建基于版本 1.1 的队列名称,两个 IComparable 对象将不匹配,而且该队列将找不。

若要完全解决此问题,宿主应用程序需要引发 1.0 接口上的事件。两个的可能方法,来完成此包括反射和继承。第一个选项包括加载本地服务的原始版本,并将它添加到此种 EDS 后您引发事件的接口上, 再次使用反射中使用反射。

第二个选项包括生成新的程序集从中您创建您的接口的 1.1 版本和使其从该版本 1.0 接口派生使您可以不更改基类型时向派生类型中添加事件。这种方式,如果引发版本 1.0 在界面上定义的事件时,将创建正确的队列名称,并将消息发送即使您调用通过派生类型的操作。关键是接口的 1.0 版必须保持部署和不变,在新的接口和服务必须从版本 1.0 的接口派生。图 4 显示了这种方法。

fig04.gif

图 4 派生为本地通信的接口

注意只有派生服务和接口都添加到 EDS。这使主机与单个服务进行交互,而引发基于多个接口的事件。目标是保留版本 1.0 接口接口解决,并使用,因此 EDS 创建匹配所在的工作流将创建的队列名称。同时请注意这涉及创建整个新项目而不是只创建通信库的新版本。untenable 是考虑使用此方法的长期时想到的单词。

通常,我的建议解决通信是非常因此避免本地通信活动,并使用创建基于如删除大量工作流和主机之间类型依赖性的字符串之类的简单名称的队列的自定义活动。其他建议是使用提供接收请求和它们路由到正确版本的工作流,附加功能,如我将稍后讨论的工作流服务。

使用 XOML 工作流定义的版本控制

首先于谈到版本控制使轻松的生命周期是不编译它们使用 XOML 基于工作流。此真正声明性的模型提供除了版本控制的许多好处。请参阅摘自 2008 发行的 MSDN Magazine 的详细信息 ("加载 WF 中的工作流模型").我将重点好处用于版本控制此处。

生成通过一个代码方法的工作流时, 要定义新类型。是例如如果您创建了新的代码使用 Visual Studio 项目或项模板的工作流定义从 WF 集中的类型派生的新类。它前面描述,因为当通过添加或删除活动中更改工作流,您将更改该类型的问题,主要原因。不会发生这种情况在生成 XOML 工作流时。

不进行编译的 XOML 工作流是只描述了现有类型的集合的 XML 文档。这似乎一个细微的区别,但是描述而不是创建新类型的现有类型会使大的差异。

下面是一个 XOML 工作流定义:

<SequentialWorkflowActivity x:Name="Workflow2" 
    xmlns:ns0="clr-namespace:SimpleWorkflows" 
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/workflow">
  <ns0:WriteLineActivity x:Name="Hello" OutputText="Hello" />
  <DelayActivity TimeoutDuration="00:00:03" x:Name="SmallDelay" />
  <ns0:WriteLineActivity x:Name="World" OutputText="World" />
</SequentialWorkflowActivity>

您可以看到根元素是在 System.Workflow.activities 程序集定义的 SequentialWorkflowActivity。此类才版本为 3.0,和 nonchanging。如果您构建为您的根活动使用此类型的工作流,而该工作流是序列化且保留,您可以能确保,工作流是加载和反序列化时,运行库将能够找到该 SequentialWorkflowActivity 3.0 版在 GAC 中会。

通过添加或删除活动,呢更改工作流定义?XOML 工作流定义中, 您正在只更改某些不会更改任何类型的 XML。如果为某些其他类型更改工作流定义中的根元素,您正在不更改现有的工作流实例所依赖的类型。实际上,工作流创建从在 XOML 后, 该 XOML 将不能引用再次由该实例的生存期内运行库。

使用 XOML 基于工作流意味着您执行没有到版本工作流因为未定义类型。因为没有强名称和版本您的工作流您还没有强名称并且版本其他类型,如本地通信接口和业务实体。还会更更便于进行如类型,不多的工作应用程序中添加成员或方法之类的不间断更改。

使用 XOML 工作流缺点是必须小到不能访问而不是封装在您的活动以便最终编写更多的活动的代码。但由于活动是只是类,这不是主要的窗口阻止程序,使您可以捕获方法,用于提供事件处理逻辑或检查 XOML 工作流中的活动的条件。

此外,因为 SequentialWorkflowActivity 类没有使用您的业务案例针对它定义的任何属性,将无法将参数传递给 CreateWorkflow 方法。幸运的是,您可以创建您自己的根活动键入从的一种在包含的工作流类型派生并添加有,您所需的属性或工作流开头这样就可以启动工作流后发送初始数据中一个接收活动或自定义活动。

最后,基于 XAML 的完全声明性工作流正向查找,是传入 WF 4.0 主建模选项。在对包括使用什么当前被调用的 ActivityAction 替换事件在了功能进行很多改进。实质上是,这些将提供一种模型使用活动的委托,并让该活动的方法您需要执行这些活动而不是调用一个委托。

活动版本控制

工作流处于活动,因此它遵循的许多讨论的问题为止与工作流类型应用于活动类型。实际上,只是因为如果您添加活动,工作流程类型将导致问题如果您添加到活动的属性类型和执行该活动程序集的版本不,则将在反序列化工作流时的异常。遗憾的是,活动需要是类,并且必须是已编译的类型,因此与工作流没有简单 XOML 解决方案。

简单回答的活动就是您应该版本使用.NET 版本控制以任何方式活动的界面更改的技术。这使得现有工作流和新引用正确的版本,并正确执行的工作流。请记住,但是,如果您正在修复不会更改该接口的活动中的方法中的一个缺陷,您可以使此修复程序和部署程序集具有相同的版本号。该活动加载和执行现有的或新工作流,正确的代码将运行。

长应答需要深入了解.NET Framework 超出了本文的范围中的序列化机制。但是的两个密钥方法可以共享的可能提供的投资少量最大好处。

首先,如果您要向您的活动中添加属性或字段,可以标记具有非系列化属性以避免索引界限异常时生成所针对早期版本的您的活动的工作流反序列化字段。

第二个,您可以重写 OnActivityExecutionContextLoad 方法初始化反序列时不自动化的任何状态。当从保留状态重新创建活动或创建新的执行上下文,获取调用此方法。这两种技术一起可帮助您对活动进行更改,而无需您强命名它们并更改版本号。

版本控制工作流服务

本地通信以及其他机会 decoupling 来自工作流,工作流服务提供类似的挑战。由于工作流程服务生成使用服务合同,服务或数据的任何版本非常工作流将需要重新生成对合同的新版本的方法。当邮件到达服务终结点时,它将接收由使用具有特定版本的合同的服务。如果合同的相同的版本生成的邮件是工作流,然后操作工作和。但是,如果工作流时生成所针对早期版本的合同,然后保存,然后将消息无法发送。提供对两个版本合同的支持,您需要部署服务的两个版本。

虽然遗憾您不能完全利用的固有的 Windows Communication Foundation (WCF) 会使该服务以处理来自客户端对旧版本生成的消息的新版本的版本控制功能,但 WCF 将不会提供一个机制管理这种情况。WCF 程度客户端和服务只正在交换消息并.NET 类型执行不跨线。我将讲述有关在版本控制问题已被仅仅是在.NET 级别且可以隐藏从客户端使用消息路由器。

与工作流服务好处是则可以发布两种服务并排,具有不同的终结点地址,尚未发布一个外观,使这两种服务可以访问在相同的地址。当您部署您的服务 1.0 版时,请使用此方法,,您可以部署其传递 WCF 路由器,只需将邮件中并将它们传递到该服务之后。准备部署服务的版本 1.1 时,可以更新路由信息,以便邮件获取发送 Version 1.0 服务或 1.1 版服务的根据邮件或已收到的上下文。图 5 显示了与路由器和与其交互的客户端服务的两个版本的此路由器概念。

fig06.gif

图 5 使用版本控制服务的路由器

在关键确保解决方案具有路由器将运行的是服务的将其部署您的 1.0 版。然后当移动到下一版本,将路由器不一些新要添加到部署和测试过程。.NET Framework 3.5 SDK 提供的示例路由器,并您也可以按照一个 有关使用 WF 规则作出路由决定的动手体验文章.

动态更新

有关如何最管理更改工作流的定义和处理运行的并行的新的和旧版本的所有讨论之后, 通常没有有关如何处理这些旧的工作流的一个问题。由于原因,对您的工作流进行更改。可能它若要修复逻辑中的一个缺陷或者也许您有需要遵循的规章遵从法规。任何其原因通常是允许所有现有的工作流完成它们已定义不 tenable 的结果。在这的种情况下,您可以考虑利用 WF 中的一种强大的功能: 动态更新。

以是短动态更新允许您更改运行工作流实例。这些更改可由组成添加或删除工作流结构中的活动。这是可以执行所有,但它可以完全更改您的工作流的定义。是例如在状态机中可以添加与在侦听的事件和定义的转换之类的整个状态。您可以通过删除和 re-adding SetState 活动更改状态转换。在顺序工作流中,您可以添加或删除步骤为您业务流程更改。

若要更改工作流,您首先需要创建一个基于工作流的 WorkflowChanges 对象。此时,在 WorkflowChanges 对象提供可以处理通过其 TransientWorkflow 属性的工作流结构的克隆。通过添加和删除活动,对克隆进行更改。完成您的更改时您调用 ApplyChanges 方法在 WorkflowInstance 类上传递以表示您要将应用于实际的实例,的更改 WorkflowChanges。

以下是示例前面所示的工作流的包含两个 WriteLine 活动分隔一个延迟活动与其他 WriteLine 活动添加到序列的末尾。此代码运行当工作流变成空闲时,或在延迟开始执行。

WorkflowChanges changes = 
  new WorkflowChanges(instance.GetWorkflowDefinition());
CompositeActivity root = changes.TransientWorkflow;
WriteLineActivity wl = new WriteLineActivity {
  Name = "newWriteLine",
  OutputText = "dynamically added"
};
root.Activities.Add(wl);
instance.ApplyWorkflowChanges(changes);

此简单的示例说明是可能,但更高级的方案可以生成许多的其他更改应用到正在运行的工作流。 这使您能够版本 1.0 的工作流已启动,并且该实例进行更改,以便它更适用的喜欢 version1.1 工作流。

将您的问题和提出的意见发送至 mmnet30@Microsoft.com.

Matt Milner 是人员的在其中他关注统技术 (WCF、 Windows WF、 BizTalk、"都柏林和 Azure 服务平台) Pluralsight,技术的成员。 Matt 也是一个独立的顾问,擅长 Microsoft.NET 应用程序设计和开发中。 通过在本地、 地区,和国际会议,如 Tech Ed 来说,Matt 定期共享他爱的技术。 Microsoft 已识别为作为 MVP 的 Matt 他社区围绕连接系统技术的贡献。 通过在他的博客与 Matt pluralsight.com/community/blogs/Matt.